Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion api/envoy/admin/v3/server_info.proto
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ message ServerInfo {
config.core.v3.Node node = 7;
}

// [#next-free-field: 38]
// [#next-free-field: 39]
message CommandLineOptions {
option (udpa.annotations.versioning).previous_message_type =
"envoy.admin.v2alpha.CommandLineOptions";
Expand Down Expand Up @@ -189,4 +189,7 @@ message CommandLineOptions {

// See :option:`--enable-core-dump` for details.
bool enable_core_dump = 37;

// See :option:`--stats-tag` for details.
repeated string stats_tag = 38;
}
6 changes: 6 additions & 0 deletions docs/root/operations/cli.rst
Original file line number Diff line number Diff line change
Expand Up @@ -359,3 +359,9 @@ following are the command line options that Envoy supports.
It enables core dumps by invoking `prctl <https://man7.org/linux/man-pages/man2/prctl.2.html>`_ using the
PR_SET_DUMPABLE option. This is useful for container environments when using capabilities, given that when
Envoy has more capabilities than its base environment core dumping will be disabled by the kernel.

.. option:: --stats-tag

*(optional)* This flag provides a universal tag for all stats generated by Envoy. The format is ``tag:value``. Only
alphanumeric values are allowed for tag names. For tag values all characters are permitted except for '.' (dot).
This flag can be repeated multiple times to set multiple universal tags. Multiple values for the same tag name are not allowed.
7 changes: 7 additions & 0 deletions envoy/server/options.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
#include "envoy/common/pure.h"
#include "envoy/config/bootstrap/v3/bootstrap.pb.h"
#include "envoy/network/address.h"
#include "envoy/stats/tag.h"

#include "absl/types/optional.h"
#include "spdlog/spdlog.h"
Expand Down Expand Up @@ -259,6 +260,12 @@ class Options {
* @return the mode of socket file.
*/
virtual mode_t socketMode() const PURE;

/**
* @return the stats tags provided by the cli. Tags may contain duplicates. It is the
* responsibility of the caller to handle the duplicates.
*/
virtual const Stats::TagVector& statsTags() const PURE;
};

} // namespace Server
Expand Down
5 changes: 3 additions & 2 deletions source/common/config/utility.cc
Original file line number Diff line number Diff line change
Expand Up @@ -215,8 +215,9 @@ Utility::parseRateLimitSettings(const envoy::config::core::v3::ApiConfigSource&
}

Stats::TagProducerPtr
Utility::createTagProducer(const envoy::config::bootstrap::v3::Bootstrap& bootstrap) {
return std::make_unique<Stats::TagProducerImpl>(bootstrap.stats_config());
Utility::createTagProducer(const envoy::config::bootstrap::v3::Bootstrap& bootstrap,
const Stats::TagVector& cli_tags) {
return std::make_unique<Stats::TagProducerImpl>(bootstrap.stats_config(), cli_tags);
}

Stats::StatsMatcherPtr
Expand Down
4 changes: 3 additions & 1 deletion source/common/config/utility.h
Original file line number Diff line number Diff line change
Expand Up @@ -420,10 +420,12 @@ class Utility {
* Create TagProducer instance. Check all tag names for conflicts to avoid
* unexpected tag name overwriting.
* @param bootstrap bootstrap proto.
* @param cli_tags tags that are provided by the cli
* @throws EnvoyException when the conflict of tag names is found.
*/
static Stats::TagProducerPtr
createTagProducer(const envoy::config::bootstrap::v3::Bootstrap& bootstrap);
createTagProducer(const envoy::config::bootstrap::v3::Bootstrap& bootstrap,
const Stats::TagVector& cli_tags);

/**
* Create StatsMatcher instance.
Expand Down
37 changes: 26 additions & 11 deletions source/common/config/well_known_names.cc
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ namespace Config {

namespace {

const absl::string_view TAG_VALUE_REGEX = R"([^\.]+)";

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

constexpr absl::string_view TAG_VALUE_REGEX = R"([^\.]+)";

and s/NAME_REGEX/TAG_VALUE_REGEX throughout?

// To allow for more readable regular expressions to be declared below, and to
// reduce duplication, define a few common pattern substitutions for regex
// segments.
Expand All @@ -18,14 +20,23 @@ std::string expandRegex(const std::string& regex) {
// underscores.
{"<CIPHER>", R"([\w-]+)"},
// A generic name can contain any character except dots.
{"<NAME>", R"([^\.]+)"},
{"<TAG_VALUE>", TAG_VALUE_REGEX},
// Route names may contain dots in addition to alphanumerics and
// dashes with underscores.
{"<ROUTE_CONFIG_NAME>", R"([\w-\.]+)"}});
}

const Regex::CompiledGoogleReMatcher& validTagValueRegex() {
CONSTRUCT_ON_FIRST_USE(Regex::CompiledGoogleReMatcher, absl::StrCat("^", TAG_VALUE_REGEX, "$"),
false);
}

} // namespace

bool doesTagNameValueMatchInvalidCharRegex(absl::string_view name) {
return validTagValueRegex().match(name);
}

TagNameValues::TagNameValues() {
// Note: the default regexes are defined below in the order that they will typically be matched
// (see the TagExtractor class definition for an explanation of the iterative matching process).
Expand Down Expand Up @@ -53,22 +64,25 @@ TagNameValues::TagNameValues() {
addRe2(RESPONSE_CODE_CLASS, R"(_rq_((\d))xx$)", "_rq_");

// http.[<stat_prefix>.]dynamodb.table.[<table_name>.]capacity.[<operation_name>.](__partition_id=<last_seven_characters_from_partition_id>)
addRe2(DYNAMO_PARTITION_ID,
R"(^http\.<NAME>\.dynamodb\.table\.<NAME>\.capacity\.<NAME>(\.__partition_id=(\w{7}))$)",
".dynamodb.table.");
addRe2(
DYNAMO_PARTITION_ID,
R"(^http\.<TAG_VALUE>\.dynamodb\.table\.<TAG_VALUE>\.capacity\.<TAG_VALUE>(\.__partition_id=(\w{7}))$)",
".dynamodb.table.");

// http.[<stat_prefix>.]dynamodb.operation.(<operation_name>.)* or
// http.[<stat_prefix>.]dynamodb.table.[<table_name>.]capacity.(<operation_name>.)[<partition_id>]
addRe2(DYNAMO_OPERATION,
R"(^http\.<NAME>\.dynamodb.(?:operation|table\.<NAME>\.capacity)(\.(<NAME>))(?:\.|$))",
".dynamodb.");
addRe2(
DYNAMO_OPERATION,
R"(^http\.<TAG_VALUE>\.dynamodb.(?:operation|table\.<TAG_VALUE>\.capacity)(\.(<TAG_VALUE>))(?:\.|$))",
".dynamodb.");

// mongo.[<stat_prefix>.]collection.[<collection>.]callsite.(<callsite>.)query.*
addTokenized(MONGO_CALLSITE, "mongo.*.collection.*.callsite.$.query.**");

// http.[<stat_prefix>.]dynamodb.table.(<table_name>.)* or
// http.[<stat_prefix>.]dynamodb.error.(<table_name>.)*
addRe2(DYNAMO_TABLE, R"(^http\.<NAME>\.dynamodb.(?:table|error)\.((<NAME>)\.))", ".dynamodb.");
addRe2(DYNAMO_TABLE, R"(^http\.<TAG_VALUE>\.dynamodb.(?:table|error)\.((<TAG_VALUE>)\.))",
".dynamodb.");

// mongo.[<stat_prefix>.]collection.(<collection>.)query.*
addTokenized(MONGO_COLLECTION, "mongo.*.collection.$.**.query.*");
Expand All @@ -92,7 +106,8 @@ TagNameValues::TagNameValues() {
addRe2(SSL_CIPHER, R"(^listener\..*?\.ssl\.cipher(\.(<CIPHER>))$)");

// cluster.[<cluster_name>.]ssl.ciphers.(<cipher>)
addRe2(SSL_CIPHER_SUITE, R"(^cluster\.<NAME>\.ssl\.ciphers(\.(<CIPHER>))$)", ".ssl.ciphers.");
addRe2(SSL_CIPHER_SUITE, R"(^cluster\.<TAG_VALUE>\.ssl\.ciphers(\.(<CIPHER>))$)",
".ssl.ciphers.");

// cluster.[<route_target_cluster>.]grpc.(<grpc_service>.)*
addTokenized(GRPC_BRIDGE_SERVICE, "cluster.*.grpc.$.**");
Expand All @@ -115,7 +130,7 @@ TagNameValues::TagNameValues() {
// listener.[<address>.]http.(<stat_prefix>.)*
// The <address> part can be anything here (.*?) for the sake of a simpler
// internal state of the regex which performs better.
addRe2(HTTP_CONN_MANAGER_PREFIX, R"(^listener\..*?\.http\.((<NAME>)\.))", ".http.");
addRe2(HTTP_CONN_MANAGER_PREFIX, R"(^listener\..*?\.http\.((<TAG_VALUE>)\.))", ".http.");

// http.(<stat_prefix>.)*
addTokenized(HTTP_CONN_MANAGER_PREFIX, "http.$.**");
Expand All @@ -132,7 +147,7 @@ TagNameValues::TagNameValues() {
// http.[<stat_prefix>.]rds.(<route_config_name>.)<base_stat>
// Note: <route_config_name> can contain dots thus we have to maintain full
// match.
addRe2(RDS_ROUTE_CONFIG, R"(^http\.<NAME>\.rds\.((<ROUTE_CONFIG_NAME>)\.)\w+?$)", ".rds.");
addRe2(RDS_ROUTE_CONFIG, R"(^http\.<TAG_VALUE>\.rds\.((<ROUTE_CONFIG_NAME>)\.)\w+?$)", ".rds.");

// listener_manager.(worker_<id>.)*
addRe2(WORKER_ID, R"(^listener_manager\.((worker_\d+)\.))", "listener_manager.worker_");
Expand Down
2 changes: 2 additions & 0 deletions source/common/config/well_known_names.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
namespace Envoy {
namespace Config {

bool doesTagNameValueMatchInvalidCharRegex(absl::string_view name);

/**
* Well-known address resolver names.
*/
Expand Down
1 change: 1 addition & 0 deletions source/common/stats/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ envoy_cc_library(
":symbol_table_lib",
"//envoy/stats:stats_interface",
"//envoy/stats:symbol_table_interface",
"//source/common/config:well_known_names",
],
)

Expand Down
13 changes: 12 additions & 1 deletion source/common/stats/tag_producer_impl.cc
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,22 @@
namespace Envoy {
namespace Stats {

TagProducerImpl::TagProducerImpl(const envoy::config::metrics::v3::StatsConfig& config) {
TagProducerImpl::TagProducerImpl(const envoy::config::metrics::v3::StatsConfig& config)
: TagProducerImpl(config, {}) {}

TagProducerImpl::TagProducerImpl(const envoy::config::metrics::v3::StatsConfig& config,
const Stats::TagVector& cli_tags) {
// To check name conflict.
reserveResources(config);
absl::node_hash_set<std::string> names = addDefaultExtractors(config);

for (const auto& cli_tag : cli_tags) {
if (!names.emplace(cli_tag.name_).second) {
throw EnvoyException(fmt::format("Tag name '{}' specified twice.", cli_tag.name_));
}
default_tags_.emplace_back(cli_tag);
}

for (const auto& tag_specifier : config.stats_tags()) {
const std::string& name = tag_specifier.tag_name();
if (!names.emplace(name).second) {
Expand Down
4 changes: 4 additions & 0 deletions source/common/stats/tag_producer_impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,11 @@ namespace Stats {
*/
class TagProducerImpl : public TagProducer {
public:
TagProducerImpl(const envoy::config::metrics::v3::StatsConfig& config,
const Stats::TagVector& cli_tags);

TagProducerImpl(const envoy::config::metrics::v3::StatsConfig& config);

TagProducerImpl() = default;

/**
Expand Down
17 changes: 17 additions & 0 deletions source/common/stats/tag_utility.cc
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
#include "source/common/stats/tag_utility.h"

#include <regex>

#include "source/common/config/well_known_names.h"
#include "source/common/stats/symbol_table_impl.h"

namespace Envoy {
Expand Down Expand Up @@ -48,6 +51,20 @@ SymbolTable::StoragePtr TagStatNameJoiner::joinNameAndTags(StatName name,

return symbol_table.join(stat_names);
}

bool isTagValueValid(absl::string_view name) {
return Config::doesTagNameValueMatchInvalidCharRegex(name);
}

bool isTagNameValid(absl::string_view value) {
for (const auto& token : value) {
if (!absl::ascii_isalnum(token)) {
return false;
}
}
return true;
}

} // namespace TagUtility
} // namespace Stats
} // namespace Envoy
5 changes: 5 additions & 0 deletions source/common/stats/tag_utility.h
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,11 @@ class TagStatNameJoiner {
SymbolTable::StoragePtr joinNameAndTags(StatName name, const StatNameTagVector& stat_name_tags,
SymbolTable& symbol_table);
};

bool isTagNameValid(absl::string_view name);

bool isTagValueValid(absl::string_view value);

} // namespace TagUtility
} // namespace Stats
} // namespace Envoy
1 change: 1 addition & 0 deletions source/server/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -388,6 +388,7 @@ envoy_cc_library(
"//source/common/common:macros",
"//source/common/protobuf:utility_lib",
"//source/common/stats:stats_lib",
"//source/common/stats:tag_utility_lib",
"//source/common/version:version_lib",
],
)
Expand Down
2 changes: 1 addition & 1 deletion source/server/config_validation/server.cc
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ void ValidationInstance::initialize(const Options& options,
InstanceUtil::loadBootstrapConfig(bootstrap_, options,
messageValidationContext().staticValidationVisitor(), *api_);

Config::Utility::createTagProducer(bootstrap_);
Config::Utility::createTagProducer(bootstrap_, options_.statsTags());
if (!bootstrap_.node().user_agent_build_version().has_version()) {
*bootstrap_.mutable_node()->mutable_user_agent_build_version() = VersionInfo::buildVersion();
}
Expand Down
40 changes: 40 additions & 0 deletions source/server/options_impl.cc
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
#include "source/common/common/logger.h"
#include "source/common/common/macros.h"
#include "source/common/protobuf/utility.h"
#include "source/common/stats/tag_utility.h"
#include "source/common/version/version.h"
#include "source/server/options_impl_platform.h"

Expand Down Expand Up @@ -151,6 +152,14 @@ OptionsImpl::OptionsImpl(std::vector<std::string> args,
"600", "string", cmd);
TCLAP::SwitchArg enable_core_dump("", "enable-core-dump", "Enable core dumps", cmd, false);

TCLAP::MultiArg<std::string> stats_tag(
"", "stats-tag",
"This flag provides a universal tag for all stats generated by Envoy. The format is "
"``tag:value``. Only alphanumeric values are allowed for tag names. For tag values all "
"characters are permitted except for '.' (dot). This flag can be repeated multiple times to "
"set multiple universal tags. Multiple values for the same tag name are not allowed.",
false, "string", cmd);

cmd.setExceptionHandling(false);
TRY_ASSERT_MAIN_THREAD {
cmd.parse(args);
Expand Down Expand Up @@ -282,6 +291,34 @@ OptionsImpl::OptionsImpl(std::vector<std::string> args,
if (!disable_extensions.getValue().empty()) {
disabled_extensions_ = absl::StrSplit(disable_extensions.getValue(), ',');
}

if (!stats_tag.getValue().empty()) {
for (const auto& cli_tag_pair : stats_tag.getValue()) {

std::vector<absl::string_view> cli_tag_pair_tokens =
absl::StrSplit(cli_tag_pair, absl::MaxSplits(':', 1));
if (cli_tag_pair_tokens.size() != 2) {
throw MalformedArgvException(
fmt::format("error: misformatted stats-tag '{}'", cli_tag_pair));
}

auto name = cli_tag_pair_tokens[0];
if (!Stats::TagUtility::isTagNameValid(name)) {
throw MalformedArgvException(
fmt::format("error: misformatted stats-tag '{}' contains invalid char in '{}'",
cli_tag_pair, name));
}

auto value = cli_tag_pair_tokens[1];
if (!Stats::TagUtility::isTagValueValid(value)) {
throw MalformedArgvException(
fmt::format("error: misformatted stats-tag '{}' contains invalid char in '{}'",
cli_tag_pair, value));
}

stats_tags_.emplace_back(Stats::Tag{std::string(name), std::string(value)});
}
}
}

spdlog::level::level_enum OptionsImpl::parseAndValidateLogLevel(absl::string_view log_level) {
Expand Down Expand Up @@ -396,6 +433,9 @@ Server::CommandLineOptionsPtr OptionsImpl::toCommandLineOptions() const {
}
command_line_options->set_socket_path(socketPath());
command_line_options->set_socket_mode(socketMode());
for (const auto& tag : statsTags()) {
command_line_options->add_stats_tag(fmt::format("{}:{}", tag.name_, tag.value_));
}
return command_line_options;
}

Expand Down
4 changes: 4 additions & 0 deletions source/server/options_impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,8 @@ class OptionsImpl : public Server::Options, protected Logger::Loggable<Logger::I

void setSocketMode(mode_t socket_mode) { socket_mode_ = socket_mode; }

void setStatsTags(const Stats::TagVector& stats_tags) { stats_tags_ = stats_tags; }

// Server::Options
uint64_t baseId() const override { return base_id_; }
bool useDynamicBaseId() const override { return use_dynamic_base_id_; }
Expand Down Expand Up @@ -146,6 +148,7 @@ class OptionsImpl : public Server::Options, protected Logger::Loggable<Logger::I
bool signalHandlingEnabled() const override { return signal_handling_enabled_; }
bool mutexTracingEnabled() const override { return mutex_tracing_enabled_; }
bool coreDumpEnabled() const override { return core_dump_enabled_; }
const Stats::TagVector& statsTags() const override { return stats_tags_; }
Server::CommandLineOptionsPtr toCommandLineOptions() const override;
void parseComponentLogLevels(const std::string& component_log_levels);
bool cpusetThreadsEnabled() const override { return cpuset_threads_; }
Expand Down Expand Up @@ -207,6 +210,7 @@ class OptionsImpl : public Server::Options, protected Logger::Loggable<Logger::I
bool core_dump_enabled_{false};
bool cpuset_threads_{false};
std::vector<std::string> disabled_extensions_;
Stats::TagVector stats_tags_;
uint32_t count_{0};

// Initialization added here to avoid integration_admin_test failure caused by uninitialized
Expand Down
2 changes: 1 addition & 1 deletion source/server/server.cc
Original file line number Diff line number Diff line change
Expand Up @@ -395,7 +395,7 @@ void InstanceImpl::initialize(Network::Address::InstanceConstSharedPtr local_add

// Needs to happen as early as possible in the instantiation to preempt the objects that require
// stats.
stats_store_.setTagProducer(Config::Utility::createTagProducer(bootstrap_));
stats_store_.setTagProducer(Config::Utility::createTagProducer(bootstrap_, options_.statsTags()));
stats_store_.setStatsMatcher(
Config::Utility::createStatsMatcher(bootstrap_, stats_store_.symbolTable()));
stats_store_.setHistogramSettings(Config::Utility::createHistogramSettings(bootstrap_));
Expand Down
Loading