From 886cb98f99d1f8c79e43ee50b6d4d5714e9118c7 Mon Sep 17 00:00:00 2001 From: Damien Mehala Date: Fri, 2 Feb 2024 17:06:31 +0100 Subject: [PATCH 1/6] Rework configuration - All fields are now optional. This allows us to distinguish default configuration from user configuration. --- benchmark/benchmark.cpp | 11 +- examples/hasher/hasher.cpp | 4 +- examples/http-server/proxy/proxy.cpp | 4 +- examples/http-server/server/server.cpp | 4 +- src/datadog/datadog_agent_config.cpp | 36 +- src/datadog/datadog_agent_config.h | 12 +- src/datadog/span_sampler_config.cpp | 189 +------- src/datadog/span_sampler_config.h | 7 +- src/datadog/trace_sampler_config.cpp | 116 +---- src/datadog/trace_sampler_config.h | 2 +- src/datadog/tracer_config.cpp | 640 ++++++++++++++++++++----- src/datadog/tracer_config.h | 90 +++- test/test_curl.cpp | 2 +- test/test_datadog_agent.cpp | 2 +- test/test_remote_config.cpp | 8 +- test/test_smoke.cpp | 2 +- test/test_span.cpp | 34 +- test/test_span_sampler.cpp | 8 +- test/test_trace_sampler.cpp | 10 +- test/test_trace_segment.cpp | 36 +- test/test_tracer.cpp | 99 ++-- test/test_tracer_config.cpp | 49 +- 22 files changed, 760 insertions(+), 605 deletions(-) diff --git a/benchmark/benchmark.cpp b/benchmark/benchmark.cpp index 73f27d00..12d494e5 100644 --- a/benchmark/benchmark.cpp +++ b/benchmark/benchmark.cpp @@ -1,12 +1,11 @@ #include - #include -#include #include #include #include #include +#include #include #include "hasher.h" @@ -35,9 +34,7 @@ struct SerializingCollector : public dd::Collector { } nlohmann::json config_json() const override { - return nlohmann::json::object({ - {"type", "SerializingCollector"} - }); + return nlohmann::json::object({{"type", "SerializingCollector"}}); } }; @@ -47,7 +44,7 @@ struct SerializingCollector : public dd::Collector { void BM_TraceTinyCCSource(benchmark::State& state) { for (auto _ : state) { dd::TracerConfig config; - config.defaults.service = "benchmark"; + config.service = "benchmark"; config.logger = std::make_shared(); config.collector = std::make_shared(); const auto valid_config = dd::finalize_config(config); @@ -58,6 +55,6 @@ void BM_TraceTinyCCSource(benchmark::State& state) { } BENCHMARK(BM_TraceTinyCCSource); -} // namespace +} // namespace BENCHMARK_MAIN(); diff --git a/examples/hasher/hasher.cpp b/examples/hasher/hasher.cpp index b39ea1e0..8b96b68f 100644 --- a/examples/hasher/hasher.cpp +++ b/examples/hasher/hasher.cpp @@ -132,8 +132,8 @@ int sha256_traced(Digest &digest, const fs::path &path, int main() { dd::TracerConfig config; - config.defaults.service = "dd-trace-cpp-example"; - config.defaults.environment = "dev"; + config.service = "dd-trace-cpp-example"; + config.environment = "dev"; auto validated = dd::finalize_config(config); if (auto *error = validated.if_error()) { diff --git a/examples/http-server/proxy/proxy.cpp b/examples/http-server/proxy/proxy.cpp index 500b5f18..451c4031 100644 --- a/examples/http-server/proxy/proxy.cpp +++ b/examples/http-server/proxy/proxy.cpp @@ -25,8 +25,8 @@ void hard_stop(int /*signal*/) { std::exit(0); } int main() { // Set up the Datadog tracer. See `src/datadog/tracer_config.h`. dd::TracerConfig config; - config.defaults.service = "dd-trace-cpp-http-server-example-proxy"; - config.defaults.service_type = "proxy"; + config.service = "dd-trace-cpp-http-server-example-proxy"; + config.service_type = "proxy"; // `finalize_config` validates `config` and applies any settings from // environment variables, such as `DD_AGENT_HOST`. diff --git a/examples/http-server/server/server.cpp b/examples/http-server/server/server.cpp index 9fa030c9..684abba0 100644 --- a/examples/http-server/server/server.cpp +++ b/examples/http-server/server/server.cpp @@ -109,8 +109,8 @@ void on_post_notes(const httplib::Request& request, httplib::Response& response) int main() { // Set up the Datadog tracer. See `src/datadog/tracer_config.h`. dd::TracerConfig config; - config.defaults.service = "dd-trace-cpp-http-server-example-server"; - config.defaults.service_type = "server"; + config.service = "dd-trace-cpp-http-server-example-server"; + config.service_type = "server"; // `finalize_config` validates `config` and applies any settings from // environment variables, such as `DD_AGENT_HOST`. diff --git a/src/datadog/datadog_agent_config.cpp b/src/datadog/datadog_agent_config.cpp index fd98d81d..dccb2a7b 100644 --- a/src/datadog/datadog_agent_config.cpp +++ b/src/datadog/datadog_agent_config.cpp @@ -5,7 +5,6 @@ #include #include "default_http_client.h" -#include "environment.h" #include "parse_util.h" #include "threaded_event_scheduler.h" @@ -116,7 +115,7 @@ Expected finalize_config( "milliseconds."}; } result.flush_interval = - std::chrono::milliseconds(config.flush_interval_milliseconds); + std::chrono::milliseconds(*config.flush_interval_milliseconds); if (config.request_timeout_milliseconds <= 0) { return Error{Error::DATADOG_AGENT_INVALID_REQUEST_TIMEOUT, @@ -125,7 +124,7 @@ Expected finalize_config( } result.request_timeout = - std::chrono::milliseconds(config.request_timeout_milliseconds); + std::chrono::milliseconds(*config.request_timeout_milliseconds); if (config.shutdown_timeout_milliseconds <= 0) { return Error{Error::DATADOG_AGENT_INVALID_SHUTDOWN_TIMEOUT, @@ -134,21 +133,10 @@ Expected finalize_config( } result.shutdown_timeout = - std::chrono::milliseconds(config.shutdown_timeout_milliseconds); + std::chrono::milliseconds(*config.shutdown_timeout_milliseconds); int rc_poll_interval_seconds = - config.remote_configuration_poll_interval_seconds; - - if (auto raw_rc_poll_interval_value = - lookup(environment::DD_REMOTE_CONFIG_POLL_INTERVAL_SECONDS)) { - auto res = parse_int(*raw_rc_poll_interval_value, 10); - if (auto error = res.if_error()) { - return error->with_prefix( - "DatadogAgent: Remote Configuration poll interval error "); - } - - rc_poll_interval_seconds = *res; - } + *config.remote_configuration_poll_interval_seconds; if (rc_poll_interval_seconds <= 0) { return Error{Error::DATADOG_AGENT_INVALID_REMOTE_CONFIG_POLL_INTERVAL, @@ -159,21 +147,7 @@ Expected finalize_config( result.remote_configuration_poll_interval = std::chrono::seconds(rc_poll_interval_seconds); - auto env_host = lookup(environment::DD_AGENT_HOST); - auto env_port = lookup(environment::DD_TRACE_AGENT_PORT); - - std::string configured_url = config.url; - if (auto url_env = lookup(environment::DD_TRACE_AGENT_URL)) { - assign(configured_url, *url_env); - } else if (env_host || env_port) { - configured_url.clear(); - configured_url += "http://"; - append(configured_url, env_host.value_or("localhost")); - configured_url += ':'; - append(configured_url, env_port.value_or("8126")); - } - - auto url = config.parse(configured_url); + auto url = config.parse(*config.url); if (auto* error = url.if_error()) { return std::move(*error); } diff --git a/src/datadog/datadog_agent_config.h b/src/datadog/datadog_agent_config.h index 59d9f10b..a95bd454 100644 --- a/src/datadog/datadog_agent_config.h +++ b/src/datadog/datadog_agent_config.h @@ -33,7 +33,7 @@ struct DatadogAgentConfig { // optional: a `Curl` instance will be used if `http_client` is left null. // If this library was built without libcurl, then `http_client` is required // not to be null. - std::shared_ptr http_client; + std::shared_ptr http_client = nullptr; // The `EventScheduler` used to periodically submit batches of traces to the // Datadog Agent. If `event_scheduler` is null, then a // `ThreadedEventScheduler` instance will be used instead. @@ -47,16 +47,16 @@ struct DatadogAgentConfig { // - unix:// // // The port defaults to 8126 if it is not specified. - std::string url = "http://localhost:8126"; + Optional url; // How often, in milliseconds, to send batches of traces to the Datadog Agent. - int flush_interval_milliseconds = 2000; + Optional flush_interval_milliseconds; // Maximum amount of time an HTTP request is allowed to run. - int request_timeout_milliseconds = 2000; + Optional request_timeout_milliseconds; // Maximum amount of time the process is allowed to wait before shutting down. - int shutdown_timeout_milliseconds = 2000; + Optional shutdown_timeout_milliseconds; // How often, in seconds, to query the Datadog Agent for remote configuration // updates. - int remote_configuration_poll_interval_seconds = 5; + Optional remote_configuration_poll_interval_seconds; static Expected parse(StringView); }; diff --git a/src/datadog/span_sampler_config.cpp b/src/datadog/span_sampler_config.cpp index 40fe2dc6..862221b4 100644 --- a/src/datadog/span_sampler_config.cpp +++ b/src/datadog/span_sampler_config.cpp @@ -1,211 +1,24 @@ #include "span_sampler_config.h" #include -#include #include #include #include "environment.h" #include "expected.h" #include "json.hpp" -#include "logger.h" namespace datadog { namespace tracing { -namespace { - -// `env_var` is the name of the environment variable from which `rules_raw` was -// obtained. It's used for error messages. -Expected> parse_rules(StringView rules_raw, - StringView env_var) { - std::vector rules; - nlohmann::json json_rules; - - try { - json_rules = nlohmann::json::parse(rules_raw); - } catch (const nlohmann::json::parse_error &error) { - std::string message; - message += "Unable to parse JSON from "; - append(message, env_var); - message += " value "; - append(message, rules_raw); - message += ": "; - message += error.what(); - return Error{Error::SPAN_SAMPLING_RULES_INVALID_JSON, std::move(message)}; - } - - std::string type = json_rules.type_name(); - if (type != "array") { - std::string message; - message += "Trace sampling rules must be an array, but JSON in "; - append(message, env_var); - message += " has type \""; - message += type; - message += "\": "; - append(message, rules_raw); - return Error{Error::SPAN_SAMPLING_RULES_WRONG_TYPE, std::move(message)}; - } - - const std::unordered_set allowed_properties{ - "service", "name", "resource", "tags", "sample_rate", "max_per_second"}; - - for (const auto &json_rule : json_rules) { - auto matcher = SpanMatcher::from_json(json_rule); - if (auto *error = matcher.if_error()) { - std::string prefix; - prefix += "Unable to create a rule from "; - append(prefix, env_var); - prefix += " JSON "; - append(prefix, rules_raw); - prefix += ": "; - return error->with_prefix(prefix); - } - - SpanSamplerConfig::Rule rule{*matcher}; - - auto sample_rate = json_rule.find("sample_rate"); - if (sample_rate != json_rule.end()) { - type = sample_rate->type_name(); - if (type != "number") { - std::string message; - message += "Unable to parse a rule from "; - append(message, env_var); - message += " JSON "; - append(message, rules_raw); - message += ". The \"sample_rate\" property of the rule "; - message += json_rule.dump(); - message += " is not a number, but instead has type \""; - message += type; - message += "\"."; - return Error{Error::SPAN_SAMPLING_RULES_SAMPLE_RATE_WRONG_TYPE, - std::move(message)}; - } - rule.sample_rate = *sample_rate; - } - - auto max_per_second = json_rule.find("max_per_second"); - if (max_per_second != json_rule.end()) { - type = max_per_second->type_name(); - if (type != "number") { - std::string message; - message += "Unable to parse a rule from "; - append(message, env_var); - message += " JSON "; - append(message, rules_raw); - message += ". The \"max_per_second\" property of the rule "; - message += json_rule.dump(); - message += " is not a number, but instead has type \""; - message += type; - message += "\"."; - return Error{Error::SPAN_SAMPLING_RULES_MAX_PER_SECOND_WRONG_TYPE, - std::move(message)}; - } - rule.max_per_second = *max_per_second; - } - - // Look for unexpected properties. - for (const auto &[key, value] : json_rule.items()) { - if (allowed_properties.count(key)) { - continue; - } - std::string message; - message += "Unexpected property \""; - message += key; - message += "\" having value "; - message += value.dump(); - message += " in trace sampling rule "; - message += json_rule.dump(); - message += ". Error occurred while parsing from "; - append(message, env_var); - message += ": "; - append(message, rules_raw); - return Error{Error::SPAN_SAMPLING_RULES_UNKNOWN_PROPERTY, - std::move(message)}; - } - - rules.emplace_back(std::move(rule)); - } - - return rules; -} - -} // namespace SpanSamplerConfig::Rule::Rule(const SpanMatcher &base) : SpanMatcher(base) {} Expected finalize_config( - const SpanSamplerConfig &config, Logger &logger) { + const SpanSamplerConfig &config) { FinalizedSpanSamplerConfig result; std::vector rules = config.rules; - auto rules_env = lookup(environment::DD_SPAN_SAMPLING_RULES); - if (rules_env) { - auto maybe_rules = - parse_rules(*rules_env, name(environment::DD_SPAN_SAMPLING_RULES)); - if (auto *error = maybe_rules.if_error()) { - return std::move(*error); - } - rules = std::move(*maybe_rules); - } - - if (auto file_env = lookup(environment::DD_SPAN_SAMPLING_RULES_FILE)) { - if (rules_env) { - const auto rules_file_name = - name(environment::DD_SPAN_SAMPLING_RULES_FILE); - const auto rules_name = name(environment::DD_SPAN_SAMPLING_RULES); - std::string message; - append(message, rules_file_name); - message += " is overridden by "; - append(message, rules_name); - message += ". Since both are set, "; - append(message, rules_name); - message += " takes precedence, and "; - append(message, rules_file_name); - message += " will be ignored."; - logger.log_error(message); - } else { - const auto span_rules_file = std::string(*file_env); - - const auto file_error = [&](const char *operation) { - std::string message; - message += "Unable to "; - message += operation; - message += " file \""; - message += span_rules_file; - message += "\" specified as value of environment variable "; - append(message, name(environment::DD_SPAN_SAMPLING_RULES_FILE)); - - return Error{Error::SPAN_SAMPLING_RULES_FILE_IO, std::move(message)}; - }; - - std::ifstream file(span_rules_file); - if (!file) { - return file_error("open"); - } - - std::ostringstream rules_stream; - rules_stream << file.rdbuf(); - if (!file) { - return file_error("read"); - } - - auto maybe_rules = parse_rules( - rules_stream.str(), name(environment::DD_SPAN_SAMPLING_RULES_FILE)); - if (auto *error = maybe_rules.if_error()) { - std::string prefix; - prefix += "With "; - append(prefix, name(environment::DD_SPAN_SAMPLING_RULES_FILE)); - prefix += '='; - append(prefix, *file_env); - prefix += ": "; - return error->with_prefix(prefix); - } - - rules = std::move(*maybe_rules); - } - } - for (const auto &rule : rules) { auto maybe_rate = Rate::from(rule.sample_rate); if (auto *error = maybe_rate.if_error()) { diff --git a/src/datadog/span_sampler_config.h b/src/datadog/span_sampler_config.h index 0eaa81e1..65c5147b 100644 --- a/src/datadog/span_sampler_config.h +++ b/src/datadog/span_sampler_config.h @@ -18,8 +18,6 @@ namespace datadog { namespace tracing { -class Logger; - struct SpanSamplerConfig { struct Rule : public SpanMatcher { double sample_rate = 1.0; @@ -37,7 +35,7 @@ struct SpanSamplerConfig { class FinalizedSpanSamplerConfig { friend Expected finalize_config( - const SpanSamplerConfig&, Logger&); + const SpanSamplerConfig&); friend class FinalizedTracerConfig; FinalizedSpanSamplerConfig() = default; @@ -51,8 +49,7 @@ class FinalizedSpanSamplerConfig { std::vector rules; }; -Expected finalize_config(const SpanSamplerConfig&, - Logger&); +Expected finalize_config(const SpanSamplerConfig&); nlohmann::json to_json(const FinalizedSpanSamplerConfig::Rule&); diff --git a/src/datadog/trace_sampler_config.cpp b/src/datadog/trace_sampler_config.cpp index 10813a64..1969a74b 100644 --- a/src/datadog/trace_sampler_config.cpp +++ b/src/datadog/trace_sampler_config.cpp @@ -18,96 +18,6 @@ Expected finalize_config( std::vector rules = config.rules; - if (auto rules_env = lookup(environment::DD_TRACE_SAMPLING_RULES)) { - rules.clear(); - nlohmann::json json_rules; - try { - json_rules = nlohmann::json::parse(*rules_env); - } catch (const nlohmann::json::parse_error &error) { - std::string message; - message += "Unable to parse JSON from "; - append(message, name(environment::DD_TRACE_SAMPLING_RULES)); - message += " value "; - append(message, *rules_env); - message += ": "; - message += error.what(); - return Error{Error::TRACE_SAMPLING_RULES_INVALID_JSON, - std::move(message)}; - } - - std::string type = json_rules.type_name(); - if (type != "array") { - std::string message; - message += "Trace sampling rules must be an array, but "; - append(message, name(environment::DD_TRACE_SAMPLING_RULES)); - message += " has JSON type \""; - message += type; - message += "\": "; - append(message, *rules_env); - return Error{Error::TRACE_SAMPLING_RULES_WRONG_TYPE, std::move(message)}; - } - - const std::unordered_set allowed_properties{ - "service", "name", "resource", "tags", "sample_rate"}; - - for (const auto &json_rule : json_rules) { - auto matcher = SpanMatcher::from_json(json_rule); - if (auto *error = matcher.if_error()) { - std::string prefix; - prefix += "Unable to create a rule from "; - append(prefix, name(environment::DD_TRACE_SAMPLING_RULES)); - prefix += " value "; - append(prefix, *rules_env); - prefix += ": "; - return error->with_prefix(prefix); - } - - TraceSamplerConfig::Rule rule{*matcher}; - - auto sample_rate = json_rule.find("sample_rate"); - if (sample_rate != json_rule.end()) { - type = sample_rate->type_name(); - if (type != "number") { - std::string message; - message += "Unable to parse a rule from "; - append(message, name(environment::DD_TRACE_SAMPLING_RULES)); - message += " value "; - append(message, *rules_env); - message += ". The \"sample_rate\" property of the rule "; - message += json_rule.dump(); - message += " is not a number, but instead has type \""; - message += type; - message += "\"."; - return Error{Error::TRACE_SAMPLING_RULES_SAMPLE_RATE_WRONG_TYPE, - std::move(message)}; - } - rule.sample_rate = *sample_rate; - } - - // Look for unexpected properties. - for (const auto &[key, value] : json_rule.items()) { - if (allowed_properties.count(key)) { - continue; - } - std::string message; - message += "Unexpected property \""; - message += key; - message += "\" having value "; - message += value.dump(); - message += " in trace sampling rule "; - message += json_rule.dump(); - message += ". Error occurred while parsing "; - append(message, name(environment::DD_TRACE_SAMPLING_RULES)); - message += ": "; - append(message, *rules_env); - return Error{Error::TRACE_SAMPLING_RULES_UNKNOWN_PROPERTY, - std::move(message)}; - } - - rules.emplace_back(std::move(rule)); - } - } - for (const auto &rule : rules) { auto maybe_rate = Rate::from(rule.sample_rate); if (auto *error = maybe_rate.if_error()) { @@ -127,17 +37,6 @@ Expected finalize_config( } auto sample_rate = config.sample_rate; - if (auto sample_rate_env = lookup(environment::DD_TRACE_SAMPLE_RATE)) { - auto maybe_sample_rate = parse_double(*sample_rate_env); - if (auto *error = maybe_sample_rate.if_error()) { - std::string prefix; - prefix += "While parsing "; - append(prefix, name(environment::DD_TRACE_SAMPLE_RATE)); - prefix += ": "; - return error->with_prefix(prefix); - } - sample_rate = *maybe_sample_rate; - } // If `sample_rate` was specified, then it translates to a "catch-all" rule // appended to the end of `rules`. First, though, we have to make sure the @@ -154,18 +53,7 @@ Expected finalize_config( result.rules.push_back(std::move(catch_all)); } - auto max_per_second = config.max_per_second; - if (auto limit_env = lookup(environment::DD_TRACE_RATE_LIMIT)) { - auto maybe_max_per_second = parse_double(*limit_env); - if (auto *error = maybe_max_per_second.if_error()) { - std::string prefix; - prefix += "While parsing "; - append(prefix, name(environment::DD_TRACE_RATE_LIMIT)); - prefix += ": "; - return error->with_prefix(prefix); - } - max_per_second = *maybe_max_per_second; - } + auto max_per_second = config.max_per_second.value_or(200); const auto allowed_types = {FP_NORMAL, FP_SUBNORMAL}; if (!(max_per_second > 0) || @@ -175,7 +63,7 @@ Expected finalize_config( message += "Trace sampling max_per_second must be greater than zero, but the " "following value was given: "; - message += std::to_string(config.max_per_second); + message += std::to_string(*config.max_per_second); return Error{Error::MAX_PER_SECOND_OUT_OF_RANGE, std::move(message)}; } result.max_per_second = max_per_second; diff --git a/src/datadog/trace_sampler_config.h b/src/datadog/trace_sampler_config.h index 80b35e9c..5fab1845 100644 --- a/src/datadog/trace_sampler_config.h +++ b/src/datadog/trace_sampler_config.h @@ -28,7 +28,7 @@ struct TraceSamplerConfig { Optional sample_rate; std::vector rules; - double max_per_second = 200; + Optional max_per_second; }; class FinalizedTraceSamplerConfig { diff --git a/src/datadog/tracer_config.cpp b/src/datadog/tracer_config.cpp index b7a726dc..793892bf 100644 --- a/src/datadog/tracer_config.cpp +++ b/src/datadog/tracer_config.cpp @@ -4,8 +4,10 @@ #include #include #include +#include #include #include +#include #include #include "cerr_logger.h" @@ -161,17 +163,381 @@ std::string json_quoted(StringView text) { return nlohmann::json(std::move(unquoted)).dump(); } -Expected finalize_propagation_styles(FinalizedTracerConfig &result, - const TracerConfig &config, - Logger &logger) { - namespace env = environment; +// `env_var` is the name of the environment variable from which `rules_raw` was +// obtained. It's used for error messages. +Expected> parse_rules(StringView rules_raw, + StringView env_var) { + std::vector rules; + nlohmann::json json_rules; + + try { + json_rules = nlohmann::json::parse(rules_raw); + } catch (const nlohmann::json::parse_error &error) { + std::string message; + message += "Unable to parse JSON from "; + append(message, env_var); + message += " value "; + append(message, rules_raw); + message += ": "; + message += error.what(); + return Error{Error::SPAN_SAMPLING_RULES_INVALID_JSON, std::move(message)}; + } + + std::string type = json_rules.type_name(); + if (type != "array") { + std::string message; + message += "Trace sampling rules must be an array, but JSON in "; + append(message, env_var); + message += " has type \""; + message += type; + message += "\": "; + append(message, rules_raw); + return Error{Error::SPAN_SAMPLING_RULES_WRONG_TYPE, std::move(message)}; + } + + const std::unordered_set allowed_properties{ + "service", "name", "resource", "tags", "sample_rate", "max_per_second"}; + + for (const auto &json_rule : json_rules) { + auto matcher = SpanMatcher::from_json(json_rule); + if (auto *error = matcher.if_error()) { + std::string prefix; + prefix += "Unable to create a rule from "; + append(prefix, env_var); + prefix += " JSON "; + append(prefix, rules_raw); + prefix += ": "; + return error->with_prefix(prefix); + } + + SpanSamplerConfig::Rule rule{*matcher}; + + auto sample_rate = json_rule.find("sample_rate"); + if (sample_rate != json_rule.end()) { + type = sample_rate->type_name(); + if (type != "number") { + std::string message; + message += "Unable to parse a rule from "; + append(message, env_var); + message += " JSON "; + append(message, rules_raw); + message += ". The \"sample_rate\" property of the rule "; + message += json_rule.dump(); + message += " is not a number, but instead has type \""; + message += type; + message += "\"."; + return Error{Error::SPAN_SAMPLING_RULES_SAMPLE_RATE_WRONG_TYPE, + std::move(message)}; + } + rule.sample_rate = *sample_rate; + } + + auto max_per_second = json_rule.find("max_per_second"); + if (max_per_second != json_rule.end()) { + type = max_per_second->type_name(); + if (type != "number") { + std::string message; + message += "Unable to parse a rule from "; + append(message, env_var); + message += " JSON "; + append(message, rules_raw); + message += ". The \"max_per_second\" property of the rule "; + message += json_rule.dump(); + message += " is not a number, but instead has type \""; + message += type; + message += "\"."; + return Error{Error::SPAN_SAMPLING_RULES_MAX_PER_SECOND_WRONG_TYPE, + std::move(message)}; + } + rule.max_per_second = *max_per_second; + } + + // Look for unexpected properties. + for (const auto &[key, value] : json_rule.items()) { + if (allowed_properties.count(key)) { + continue; + } + std::string message; + message += "Unexpected property \""; + message += key; + message += "\" having value "; + message += value.dump(); + message += " in trace sampling rule "; + message += json_rule.dump(); + message += ". Error occurred while parsing from "; + append(message, env_var); + message += ": "; + append(message, rules_raw); + return Error{Error::SPAN_SAMPLING_RULES_UNKNOWN_PROPERTY, + std::move(message)}; + } + + rules.emplace_back(std::move(rule)); + } + + return rules; +} + +Expected load_env_config(Logger &logger) { + TracerConfig env_cfg; + + if (auto service_env = lookup(environment::DD_SERVICE)) { + env_cfg.service = std::string{*service_env}; + } + + if (auto environment_env = lookup(environment::DD_ENV)) { + env_cfg.environment = std::string{*environment_env}; + } + if (auto version_env = lookup(environment::DD_VERSION)) { + env_cfg.version = std::string{*version_env}; + } + + if (auto tags_env = lookup(environment::DD_TAGS)) { + auto tags = parse_tags(*tags_env); + if (auto *error = tags.if_error()) { + std::string prefix; + prefix += "Unable to parse "; + append(prefix, name(environment::DD_TAGS)); + prefix += " environment variable: "; + return error->with_prefix(prefix); + } + env_cfg.tags = std::move(*tags); + } + + if (auto startup_env = lookup(environment::DD_TRACE_STARTUP_LOGS)) { + env_cfg.log_on_startup = !falsy(*startup_env); + } + if (auto enabled_env = lookup(environment::DD_TRACE_ENABLED)) { + env_cfg.report_traces = !falsy(*enabled_env); + } + if (auto enabled_env = + lookup(environment::DD_INSTRUMENTATION_TELEMETRY_ENABLED)) { + env_cfg.report_telemetry = !falsy(*enabled_env); + } + if (auto trace_delegate_sampling_env = + lookup(environment::DD_TRACE_DELEGATE_SAMPLING)) { + env_cfg.delegate_trace_sampling = !falsy(*trace_delegate_sampling_env); + } + if (auto enabled_env = + lookup(environment::DD_TRACE_128_BIT_TRACEID_GENERATION_ENABLED)) { + env_cfg.trace_id_128_bit = !falsy(*enabled_env); + } + + if (auto rules_env = lookup(environment::DD_TRACE_SAMPLING_RULES)) { + nlohmann::json json_rules; + try { + json_rules = nlohmann::json::parse(*rules_env); + } catch (const nlohmann::json::parse_error &error) { + std::string message; + message += "Unable to parse JSON from "; + append(message, name(environment::DD_TRACE_SAMPLING_RULES)); + message += " value "; + append(message, *rules_env); + message += ": "; + message += error.what(); + return Error{Error::TRACE_SAMPLING_RULES_INVALID_JSON, + std::move(message)}; + } + + std::string type = json_rules.type_name(); + if (type != "array") { + std::string message; + message += "Trace sampling rules must be an array, but "; + append(message, name(environment::DD_TRACE_SAMPLING_RULES)); + message += " has JSON type \""; + message += type; + message += "\": "; + append(message, *rules_env); + return Error{Error::TRACE_SAMPLING_RULES_WRONG_TYPE, std::move(message)}; + } + + const std::unordered_set allowed_properties{ + "service", "name", "resource", "tags", "sample_rate"}; + + for (const auto &json_rule : json_rules) { + auto matcher = SpanMatcher::from_json(json_rule); + if (auto *error = matcher.if_error()) { + std::string prefix; + prefix += "Unable to create a rule from "; + append(prefix, name(environment::DD_TRACE_SAMPLING_RULES)); + prefix += " value "; + append(prefix, *rules_env); + prefix += ": "; + return error->with_prefix(prefix); + } + + TraceSamplerConfig::Rule rule{*matcher}; + + auto sample_rate = json_rule.find("sample_rate"); + if (sample_rate != json_rule.end()) { + type = sample_rate->type_name(); + if (type != "number") { + std::string message; + message += "Unable to parse a rule from "; + append(message, name(environment::DD_TRACE_SAMPLING_RULES)); + message += " value "; + append(message, *rules_env); + message += ". The \"sample_rate\" property of the rule "; + message += json_rule.dump(); + message += " is not a number, but instead has type \""; + message += type; + message += "\"."; + return Error{Error::TRACE_SAMPLING_RULES_SAMPLE_RATE_WRONG_TYPE, + std::move(message)}; + } + rule.sample_rate = *sample_rate; + } + + // Look for unexpected properties. + for (const auto &[key, value] : json_rule.items()) { + if (allowed_properties.count(key)) { + continue; + } + std::string message; + message += "Unexpected property \""; + message += key; + message += "\" having value "; + message += value.dump(); + message += " in trace sampling rule "; + message += json_rule.dump(); + message += ". Error occurred while parsing "; + append(message, name(environment::DD_TRACE_SAMPLING_RULES)); + message += ": "; + append(message, *rules_env); + return Error{Error::TRACE_SAMPLING_RULES_UNKNOWN_PROPERTY, + std::move(message)}; + } + + env_cfg.trace_sampler.rules.emplace_back(std::move(rule)); + } + } + + if (auto sample_rate_env = lookup(environment::DD_TRACE_SAMPLE_RATE)) { + auto maybe_sample_rate = parse_double(*sample_rate_env); + if (auto *error = maybe_sample_rate.if_error()) { + std::string prefix; + prefix += "While parsing "; + append(prefix, name(environment::DD_TRACE_SAMPLE_RATE)); + prefix += ": "; + return error->with_prefix(prefix); + } + env_cfg.trace_sampler.sample_rate = *maybe_sample_rate; + } + + if (auto limit_env = lookup(environment::DD_TRACE_RATE_LIMIT)) { + auto maybe_max_per_second = parse_double(*limit_env); + if (auto *error = maybe_max_per_second.if_error()) { + std::string prefix; + prefix += "While parsing "; + append(prefix, name(environment::DD_TRACE_RATE_LIMIT)); + prefix += ": "; + return error->with_prefix(prefix); + } + env_cfg.trace_sampler.max_per_second = *maybe_max_per_second; + } + + // SpanSampler + auto rules_env = lookup(environment::DD_SPAN_SAMPLING_RULES); + if (rules_env) { + auto maybe_rules = + parse_rules(*rules_env, name(environment::DD_SPAN_SAMPLING_RULES)); + if (auto *error = maybe_rules.if_error()) { + return std::move(*error); + } + env_cfg.span_sampler.rules = std::move(*maybe_rules); + } + + if (auto file_env = lookup(environment::DD_SPAN_SAMPLING_RULES_FILE)) { + if (rules_env) { + const auto rules_file_name = + name(environment::DD_SPAN_SAMPLING_RULES_FILE); + const auto rules_name = name(environment::DD_SPAN_SAMPLING_RULES); + std::string message; + append(message, rules_file_name); + message += " is overridden by "; + append(message, rules_name); + message += ". Since both are set, "; + append(message, rules_name); + message += " takes precedence, and "; + append(message, rules_file_name); + message += " will be ignored."; + logger.log_error(message); + } else { + const auto span_rules_file = std::string(*file_env); + + const auto file_error = [&](const char *operation) { + std::string message; + message += "Unable to "; + message += operation; + message += " file \""; + message += span_rules_file; + message += "\" specified as value of environment variable "; + append(message, name(environment::DD_SPAN_SAMPLING_RULES_FILE)); + + return Error{Error::SPAN_SAMPLING_RULES_FILE_IO, std::move(message)}; + }; + + std::ifstream file(span_rules_file); + if (!file) { + return file_error("open"); + } + + std::ostringstream rules_stream; + rules_stream << file.rdbuf(); + if (!file) { + return file_error("read"); + } + + auto maybe_rules = parse_rules( + rules_stream.str(), name(environment::DD_SPAN_SAMPLING_RULES_FILE)); + if (auto *error = maybe_rules.if_error()) { + std::string prefix; + prefix += "With "; + append(prefix, name(environment::DD_SPAN_SAMPLING_RULES_FILE)); + prefix += '='; + append(prefix, *file_env); + prefix += ": "; + return error->with_prefix(prefix); + } + + env_cfg.span_sampler.rules = std::move(*maybe_rules); + } + } + + // DatadogAgentConfig + if (auto raw_rc_poll_interval_value = + lookup(environment::DD_REMOTE_CONFIG_POLL_INTERVAL_SECONDS)) { + auto res = parse_int(*raw_rc_poll_interval_value, 10); + if (auto error = res.if_error()) { + return error->with_prefix( + "DatadogAgent: Remote Configuration poll interval error "); + } + + env_cfg.agent.remote_configuration_poll_interval_seconds = *res; + } + + auto env_host = lookup(environment::DD_AGENT_HOST); + auto env_port = lookup(environment::DD_TRACE_AGENT_PORT); + + if (auto url_env = lookup(environment::DD_TRACE_AGENT_URL)) { + env_cfg.agent.url = std::string{*url_env}; + } else if (env_host || env_port) { + std::string configured_url = "http://"; + append(configured_url, env_host.value_or("localhost")); + configured_url += ':'; + append(configured_url, env_port.value_or("8126")); + + env_cfg.agent.url = std::move(configured_url); + } + + // PropagationStyle // Print a warning if a questionable combination of environment variables is // defined. - const auto ts = env::DD_TRACE_PROPAGATION_STYLE; - const auto tse = env::DD_TRACE_PROPAGATION_STYLE_EXTRACT; - const auto se = env::DD_PROPAGATION_STYLE_EXTRACT; - const auto tsi = env::DD_TRACE_PROPAGATION_STYLE_INJECT; - const auto si = env::DD_PROPAGATION_STYLE_INJECT; + const auto ts = environment::DD_TRACE_PROPAGATION_STYLE; + const auto tse = environment::DD_TRACE_PROPAGATION_STYLE_EXTRACT; + const auto se = environment::DD_PROPAGATION_STYLE_EXTRACT; + const auto tsi = environment::DD_TRACE_PROPAGATION_STYLE_INJECT; + const auto si = environment::DD_PROPAGATION_STYLE_INJECT; // clang-format off /* ts tse se tsi si @@ -187,7 +553,7 @@ Expected finalize_propagation_styles(FinalizedTracerConfig &result, si | x x x x x */ // In each pair, the first would be overridden by the second. - const std::pair questionable_combinations[] = { + const std::pair questionable_combinations[] = { {ts, tse}, {ts, se}, {ts, tsi}, {ts, si}, {se, tse}, /* ok */ /* ok */ @@ -225,154 +591,214 @@ Expected finalize_propagation_styles(FinalizedTracerConfig &result, if (!value_override) { continue; } + // TODO: log const auto var_name = name(var); const auto var_name_override = name(var_override); + logger.log_error(Error{ Error::MULTIPLE_PROPAGATION_STYLE_ENVIRONMENT_VARIABLES, warn_message(var_name, *value, var_name_override, *value_override)}); } - // Parse the propagation styles from the configuration and/or from the - // environment. - // Exceptions make this section simpler. try { - const auto global_styles = styles_from_env(env::DD_TRACE_PROPAGATION_STYLE); - result.extraction_styles = - value_or(styles_from_env(env::DD_TRACE_PROPAGATION_STYLE_EXTRACT), - styles_from_env(env::DD_PROPAGATION_STYLE_EXTRACT), - global_styles, config.extraction_styles); - result.injection_styles = - value_or(styles_from_env(env::DD_TRACE_PROPAGATION_STYLE_INJECT), - styles_from_env(env::DD_PROPAGATION_STYLE_INJECT), - global_styles, config.injection_styles); + const auto global_styles = + styles_from_env(environment::DD_TRACE_PROPAGATION_STYLE); + + if (auto propagation_value = + styles_from_env(environment::DD_TRACE_PROPAGATION_STYLE_EXTRACT)) { + env_cfg.extraction_styles = std::move(*propagation_value); + } else if (auto propagation_value = + styles_from_env(environment::DD_PROPAGATION_STYLE_EXTRACT)) { + env_cfg.extraction_styles = std::move(*propagation_value); + } else { + env_cfg.extraction_styles = global_styles; + } + + if (auto injection_styles = + styles_from_env(environment::DD_TRACE_PROPAGATION_STYLE_INJECT)) { + env_cfg.injection_styles = std::move(*injection_styles); + } else if (auto injection_styles = + styles_from_env(environment::DD_PROPAGATION_STYLE_INJECT)) { + env_cfg.injection_styles = std::move(*injection_styles); + } else { + env_cfg.injection_styles = global_styles; + } } catch (Error &error) { return std::move(error); } - if (result.extraction_styles.empty()) { - return Error{Error::MISSING_SPAN_EXTRACTION_STYLE, - "At least one extraction style must be specified."}; - } - if (result.injection_styles.empty()) { - return Error{Error::MISSING_SPAN_INJECTION_STYLE, - "At least one injection style must be specified."}; - } - - return {}; + return env_cfg; } } // namespace +static const TracerDefaultConfig k_default_cfg; + Expected finalize_config(const TracerConfig &config) { - return finalize_config(config, default_clock); + return finalize_config(config, k_default_cfg, default_clock); } -Expected finalize_config(const TracerConfig &config, +Expected finalize_config(const TracerConfig &user_config, const Clock &clock) { - FinalizedTracerConfig result; + return finalize_config(user_config, k_default_cfg, clock); +} - result.clock = clock; - result.defaults = config.defaults; +Expected finalize_config( + const TracerConfig &config, const TracerDefaultConfig &default_config) { + return finalize_config(config, default_config, default_clock); +} - if (auto service_env = lookup(environment::DD_SERVICE)) { - assign(result.defaults.service, *service_env); +Expected finalize_config( + const TracerConfig &user_config, const TracerDefaultConfig &default_config, + const Clock &clock) { + FinalizedTracerConfig final_cfg; + + if (user_config.logger) { + final_cfg.logger = user_config.logger; + } else { + final_cfg.logger = std::make_shared(); } - if (result.defaults.service.empty()) { - return Error{Error::SERVICE_NAME_REQUIRED, "Service name is required."}; + + auto env = load_env_config(*final_cfg.logger); + if (auto error = env.if_error()) { + return *error; } - if (auto environment_env = lookup(environment::DD_ENV)) { - assign(result.defaults.environment, *environment_env); + final_cfg.clock = clock; + final_cfg.defaults.service = value_or(env->service, user_config.service, ""); + final_cfg.defaults.service_type = value_or( + env->service_type, user_config.service_type, default_config.service_type); + final_cfg.defaults.environment = + value_or(env->environment, user_config.environment, ""); + final_cfg.defaults.version = value_or(env->version, user_config.version, ""); + final_cfg.defaults.name = value_or(env->name, user_config.name, ""); + + std::unordered_map default_tags; + final_cfg.defaults.tags = value_or(env->tags, user_config.tags, default_tags); + + final_cfg.extraction_styles = + value_or(env->extraction_styles, user_config.extraction_styles, + default_config.extraction_styles); + final_cfg.injection_styles = + value_or(env->injection_styles, user_config.injection_styles, + default_config.injection_styles); + + final_cfg.log_on_startup = + value_or(env->log_on_startup, user_config.log_on_startup, + default_config.log_on_startup); + final_cfg.report_traces = + value_or(env->report_traces, user_config.report_traces, + default_config.report_traces); + final_cfg.report_telemetry = + value_or(env->report_telemetry, user_config.report_telemetry, + default_config.report_telemetry); + final_cfg.report_hostname = + value_or(env->report_hostname, user_config.report_hostname, + default_config.report_hostname); + final_cfg.delegate_trace_sampling = value_or( + env->delegate_trace_sampling, user_config.delegate_trace_sampling, + default_config.delegate_trace_sampling); + final_cfg.tags_header_size = + value_or(env->tags_header_size, user_config.tags_header_size, + default_config.max_tags_header_size); + final_cfg.trace_id_128_bit = + value_or(env->trace_id_128_bit, user_config.trace_id_128_bit, + default_config.generate_128bit_trace_ids); + final_cfg.integration_name = + value_or(env->integration_name, user_config.integration_name, ""); + final_cfg.integration_version = + value_or(env->integration_version, user_config.integration_version, ""); + + if (user_config.runtime_id) { + final_cfg.runtime_id = user_config.runtime_id; } - if (auto version_env = lookup(environment::DD_VERSION)) { - assign(result.defaults.version, *version_env); + + DatadogAgentConfig merged_agent_config; + merged_agent_config.url = + value_or(env->agent.url, user_config.agent.url, default_config.agent_url); + merged_agent_config.flush_interval_milliseconds = + value_or(env->agent.flush_interval_milliseconds, + user_config.agent.flush_interval_milliseconds, + default_config.flush_interval_milliseconds); + merged_agent_config.shutdown_timeout_milliseconds = + value_or(env->agent.shutdown_timeout_milliseconds, + user_config.agent.shutdown_timeout_milliseconds, + default_config.shutdown_timeout_milliseconds); + merged_agent_config.request_timeout_milliseconds = + value_or(env->agent.request_timeout_milliseconds, + user_config.agent.request_timeout_milliseconds, + default_config.request_timeout_milliseconds); + merged_agent_config.remote_configuration_poll_interval_seconds = + value_or(env->agent.remote_configuration_poll_interval_seconds, + user_config.agent.remote_configuration_poll_interval_seconds, + default_config.remote_configuration_poll_interval_seconds); + if (user_config.agent.http_client) { + merged_agent_config.http_client = user_config.agent.http_client; + } + if (user_config.agent.event_scheduler) { + merged_agent_config.event_scheduler = user_config.agent.event_scheduler; } - if (auto tags_env = lookup(environment::DD_TAGS)) { - auto tags = parse_tags(*tags_env); - if (auto *error = tags.if_error()) { - std::string prefix; - prefix += "Unable to parse "; - append(prefix, name(environment::DD_TAGS)); - prefix += " environment variable: "; - return error->with_prefix(prefix); - } - result.defaults.tags = std::move(*tags); + TraceSamplerConfig ts; + if (env->trace_sampler.sample_rate) { + ts.sample_rate = env->trace_sampler.sample_rate; + } else if (user_config.trace_sampler.sample_rate) { + ts.sample_rate = user_config.trace_sampler.sample_rate; } - if (config.logger) { - result.logger = config.logger; + if (!env->trace_sampler.rules.empty()) { + ts.rules = env->trace_sampler.rules; } else { - result.logger = std::make_shared(); + ts.rules = user_config.trace_sampler.rules; } - result.log_on_startup = config.log_on_startup; - if (auto startup_env = lookup(environment::DD_TRACE_STARTUP_LOGS)) { - result.log_on_startup = !falsy(*startup_env); + ts.max_per_second = value_or(env->trace_sampler.max_per_second, + user_config.trace_sampler.max_per_second, 200); + + SpanSamplerConfig ss; + if (!env->span_sampler.rules.empty()) { + ss.rules = env->span_sampler.rules; + } else { + ss.rules = user_config.span_sampler.rules; } - result.report_traces = config.report_traces; - if (auto enabled_env = lookup(environment::DD_TRACE_ENABLED)) { - result.report_traces = !falsy(*enabled_env); + if (final_cfg.defaults.service.empty()) { + return Error{Error::SERVICE_NAME_REQUIRED, "Service name is required."}; } - if (!config.collector) { - auto finalized = finalize_config(config.agent, result.logger, clock); + if (final_cfg.extraction_styles.empty()) { + return Error{Error::MISSING_SPAN_EXTRACTION_STYLE, + "At least one extraction style must be specified."}; + } + if (final_cfg.injection_styles.empty()) { + return Error{Error::MISSING_SPAN_INJECTION_STYLE, + "At least one injection style must be specified."}; + } + + if (!user_config.collector) { + auto finalized = + finalize_config(merged_agent_config, final_cfg.logger, clock); if (auto *error = finalized.if_error()) { return std::move(*error); } - result.collector = *finalized; + final_cfg.collector = *finalized; } else { - result.collector = config.collector; - } - - bool report_telemetry = config.report_telemetry; - if (auto enabled_env = - lookup(environment::DD_INSTRUMENTATION_TELEMETRY_ENABLED)) { - report_telemetry = !falsy(*enabled_env); - } - result.report_telemetry = report_telemetry; - - result.delegate_trace_sampling = config.delegate_trace_sampling; - if (auto trace_delegate_sampling_env = - lookup(environment::DD_TRACE_DELEGATE_SAMPLING)) { - result.delegate_trace_sampling = !falsy(*trace_delegate_sampling_env); + final_cfg.collector = user_config.collector; } - if (auto trace_sampler_config = finalize_config(config.trace_sampler)) { - result.trace_sampler = std::move(*trace_sampler_config); + if (auto trace_sampler_config = finalize_config(ts)) { + final_cfg.trace_sampler = std::move(*trace_sampler_config); } else { return std::move(trace_sampler_config.error()); } - if (auto span_sampler_config = - finalize_config(config.span_sampler, *result.logger)) { - result.span_sampler = std::move(*span_sampler_config); + if (auto span_sampler_config = finalize_config(ss)) { + final_cfg.span_sampler = std::move(*span_sampler_config); } else { return std::move(span_sampler_config.error()); } - auto maybe_error = - finalize_propagation_styles(result, config, *result.logger); - if (!maybe_error) { - return maybe_error.error(); - } - - result.report_hostname = config.report_hostname; - result.tags_header_size = config.tags_header_size; - - if (auto enabled_env = - lookup(environment::DD_TRACE_128_BIT_TRACEID_GENERATION_ENABLED)) { - result.trace_id_128_bit = !falsy(*enabled_env); - } else { - result.trace_id_128_bit = config.trace_id_128_bit; - } - - result.runtime_id = config.runtime_id; - result.integration_name = config.integration_name; - result.integration_version = config.integration_version; - - return result; + return final_cfg; } } // namespace tracing diff --git a/src/datadog/tracer_config.h b/src/datadog/tracer_config.h index c9ed4dda..0425c071 100644 --- a/src/datadog/tracer_config.h +++ b/src/datadog/tracer_config.h @@ -28,10 +28,33 @@ class SpanSampler; class TraceSampler; struct TracerConfig { - // `defaults` are properties that spans created by the tracer will have unless - // overridden at the time of their creation. See `span_defaults.h`. Note - // that `defaults.service` is required to have a nonempty value. - SpanDefaults defaults; + // Set the service name. + // + // Overriden by the `DD_SERVICE` environment variables. + Optional service; + + // Set the type of service. + Optional service_type; + + // Set the application environment. + // + // Overriden by the `DD_ENV` environment variable. + // Example: `prod`, `pre-prod` or `staging`. + Optional environment; + + // Set the application version. + // + // Overriden by the `DD_VERSION` environment variable. + // Example values: `1.2.3`, `6c44da20`, `2020.02.13`. + Optional version; + + // Set the default name for spans. + Optional name; + + // Set global tags to be attached to every span. + // + // Overriden by the `DD_TAGS` environment variable. + Optional> tags; // `agent` configures a `DatadogAgent` collector instance. See // `datadog_agent_config.h`. Note that `agent` is ignored if `collector` is @@ -42,26 +65,26 @@ struct TracerConfig { // traces to Datadog. If `collector` is null, then a `DatadogAgent` instance // will be created using the `agent` configuration. Note that `collector` is // ignored if `report_traces` is `false`. - std::shared_ptr collector = nullptr; + std::shared_ptr collector; // `report_traces` indicates whether traces generated by the tracer will be // sent to a collector (`true`) or discarded on completion (`false`). If // `report_traces` is `false`, then both `agent` and `collector` are ignored. // `report_traces` is overridden by the `DD_TRACE_ENABLED` environment // variable. - bool report_traces = true; + Optional report_traces; // `report_telemetry` indicates whether telemetry about the tracer will be // sent to a collector (`true`) or discarded on completion (`false`). If // `report_telemetry` is `false`, then this feature is disabled. // `report_telemetry` is overridden by the // `DD_INSTRUMENTATION_TELEMETRY_ENABLED` environment variable. - bool report_telemetry = true; + Optional report_telemetry; // `delegate_trace_sampling` indicates whether the tracer will consult a child // service for a trace sampling decision, and prefer the resulting decision // over its own, if appropriate. - bool delegate_trace_sampling = false; + Optional delegate_trace_sampling; // `trace_sampler` configures trace sampling. Trace sampling determines which // traces are sent to Datadog. See `trace_sampler_config.h`. @@ -77,8 +100,7 @@ struct TracerConfig { // All styles indicated by `injection_styles` are used for injection. // `injection_styles` is overridden by the `DD_TRACE_PROPAGATION_STYLE_INJECT` // and `DD_TRACE_PROPAGATION_STYLE` environment variables. - std::vector injection_styles = {PropagationStyle::DATADOG, - PropagationStyle::W3C}; + Optional> injection_styles; // `extraction_styles` indicates with which tracing systems trace propagation // will be compatible when extracting (receiving) trace context. @@ -88,35 +110,34 @@ struct TracerConfig { // `extraction_styles` is overridden by the // `DD_TRACE_PROPAGATION_STYLE_EXTRACT` and `DD_TRACE_PROPAGATION_STYLE` // environment variables. - std::vector extraction_styles = {PropagationStyle::DATADOG, - PropagationStyle::W3C}; + Optional> extraction_styles; // `report_hostname` indicates whether the tracer will include the result of // `gethostname` with traces sent to the collector. - bool report_hostname = false; + Optional report_hostname; // `tags_header_size` is the maximum allowed size, in bytes, of the serialized // value of the "X-Datadog-Tags" header used when injecting trace context for // propagation. If the serialized value of the header would exceed // `tags_header_size`, the header will be omitted instead. - std::size_t tags_header_size = 512; + Optional tags_header_size; // `logger` specifies how the tracer will issue diagnostic messages. If // `logger` is null, then it defaults to a logger that inserts into // `std::cerr`. - std::shared_ptr logger = nullptr; + std::shared_ptr logger; // `log_on_startup` indicates whether the tracer will log a banner of // configuration information once initialized. // `log_on_startup` is overridden by the `DD_TRACE_STARTUP_LOGS` environment // variable. - bool log_on_startup = true; + Optional log_on_startup; // `trace_id_128_bit` indicates whether the tracer will generate 128-bit trace // IDs. If true, the tracer will generate 128-bit trace IDs. If false, the // tracer will generate 64-bit trace IDs. `trace_id_128_bit` is overridden by // the `DD_TRACE_128_BIT_TRACEID_GENERATION_ENABLED` environment variable. - bool trace_id_128_bit = true; + Optional trace_id_128_bit; // `runtime_id` denotes the current run of the application in which the tracer // is embedded. If `runtime_id` is not specified, then it defaults to a @@ -127,19 +148,41 @@ struct TracerConfig { // `integration_name` is the name of the product integrating this library. // Example: "nginx", "envoy" or "istio". - std::string integration_name; + Optional integration_name; // `integration_version` is the version of the product integrating this // library. // Example: "1.2.3", "6c44da20", "2020.02.13" - std::string integration_version; + Optional integration_version; +}; + +struct TracerDefaultConfig final { + bool log_on_startup = true; + bool report_traces = true; + bool report_hostname = false; + bool report_telemetry = true; + bool delegate_trace_sampling = false; + bool generate_128bit_trace_ids = true; + std::string service_type = "web"; + std::size_t max_tags_header_size = 512; + std::vector injection_styles = {PropagationStyle::DATADOG, + PropagationStyle::W3C}; + std::vector extraction_styles = {PropagationStyle::DATADOG, + PropagationStyle::W3C}; + + std::string agent_url = "http://localhost:8126"; + int flush_interval_milliseconds = 2000; + int request_timeout_milliseconds = 2000; + int shutdown_timeout_milliseconds = 2000; + int remote_configuration_poll_interval_seconds = 5; }; // `FinalizedTracerConfig` contains `Tracer` implementation details derived from // a valid `TracerConfig` and accompanying environment. // `FinalizedTracerConfig` must be obtained by calling `finalize_config`. -class FinalizedTracerConfig { +class FinalizedTracerConfig final { friend Expected finalize_config( - const TracerConfig& config, const Clock& clock); + const TracerConfig& config, const TracerDefaultConfig& default_config, + const Clock& clock); FinalizedTracerConfig() = default; public: @@ -178,6 +221,11 @@ class FinalizedTracerConfig { Expected finalize_config(const TracerConfig& config); Expected finalize_config(const TracerConfig& config, const Clock& clock); +Expected finalize_config( + const TracerConfig& config, const TracerDefaultConfig& default_config); +Expected finalize_config( + const TracerConfig& config, const TracerDefaultConfig& default_config, + const Clock& clock); } // namespace tracing } // namespace datadog diff --git a/test/test_curl.cpp b/test/test_curl.cpp index 1c9873e4..e19a1db0 100644 --- a/test/test_curl.cpp +++ b/test/test_curl.cpp @@ -146,7 +146,7 @@ TEST_CASE("parse response headers and body", "[curl]") { // It's still good to test that everything works with this mock // `CurlLibrary` in place, though. TracerConfig config; - config.defaults.service = "testsvc"; + config.service = "testsvc"; config.logger = logger; config.agent.http_client = client; // The http client is a mock that only expects a single request, so diff --git a/test/test_datadog_agent.cpp b/test/test_datadog_agent.cpp index 02590bf0..d45bae12 100644 --- a/test/test_datadog_agent.cpp +++ b/test/test_datadog_agent.cpp @@ -14,7 +14,7 @@ using namespace datadog::tracing; TEST_CASE("CollectorResponse") { TracerConfig config; - config.defaults.service = "testsvc"; + config.service = "testsvc"; const auto logger = std::make_shared(std::cerr, MockLogger::ERRORS_ONLY); const auto event_scheduler = std::make_shared(); diff --git a/test/test_remote_config.cpp b/test/test_remote_config.cpp index 0e7ff156..1a8fde74 100644 --- a/test/test_remote_config.cpp +++ b/test/test_remote_config.cpp @@ -26,8 +26,8 @@ REMOTE_CONFIG_TEST("first payload") { }; TracerConfig config; - config.defaults.service = "testsvc"; - config.defaults.environment = "test"; + config.service = "testsvc"; + config.environment = "test"; const auto config_manager = std::make_shared(*finalize_config(config)); @@ -60,8 +60,8 @@ REMOTE_CONFIG_TEST("response processing") { }; TracerConfig config; - config.defaults.service = "testsvc"; - config.defaults.environment = "test"; + config.service = "testsvc"; + config.environment = "test"; config.trace_sampler.sample_rate = 1.0; config.report_traces = true; const auto config_manager = diff --git a/test/test_smoke.cpp b/test/test_smoke.cpp index fc94807e..c71fb7bf 100644 --- a/test/test_smoke.cpp +++ b/test/test_smoke.cpp @@ -10,7 +10,7 @@ using namespace datadog::tracing; TEST_CASE("smoke") { TracerConfig config; - config.defaults.service = "testsvc"; + config.service = "testsvc"; config.logger = std::make_shared(); auto maybe_config = finalize_config(config); diff --git a/test/test_span.cpp b/test/test_span.cpp index 671cea10..dd8868a7 100644 --- a/test/test_span.cpp +++ b/test/test_span.cpp @@ -31,7 +31,7 @@ using namespace datadog::tracing; TEST_CASE("set_tag") { TracerConfig config; - config.defaults.service = "testsvc"; + config.service = "testsvc"; const auto collector = std::make_shared(); config.collector = collector; config.logger = std::make_shared(); @@ -108,7 +108,7 @@ TEST_CASE("set_tag") { TEST_CASE("lookup_tag") { TracerConfig config; - config.defaults.service = "testsvc"; + config.service = "testsvc"; config.collector = std::make_shared(); config.logger = std::make_shared(); @@ -153,7 +153,7 @@ TEST_CASE("lookup_tag") { TEST_CASE("remove_tag") { TracerConfig config; - config.defaults.service = "testsvc"; + config.service = "testsvc"; config.collector = std::make_shared(); config.logger = std::make_shared(); @@ -182,7 +182,7 @@ TEST_CASE("remove_tag") { TEST_CASE("span duration") { TracerConfig config; - config.defaults.service = "testsvc"; + config.service = "testsvc"; auto collector = std::make_shared(); config.collector = collector; config.logger = std::make_shared(); @@ -266,7 +266,7 @@ TEST_CASE(".error() and .set_error*()") { false, nullopt, nullopt, nullopt}})); TracerConfig config; - config.defaults.service = "testsvc"; + config.service = "testsvc"; auto collector = std::make_shared(); config.collector = collector; config.logger = std::make_shared(); @@ -311,7 +311,7 @@ TEST_CASE("property setters and getters") { // corresponding getter method and in the resulting span data sent to the // collector. TracerConfig config; - config.defaults.service = "testsvc"; + config.service = "testsvc"; auto collector = std::make_shared(); config.collector = collector; config.logger = std::make_shared(); @@ -365,7 +365,7 @@ TEST_CASE("property setters and getters") { // the interface of `Span`, so the test is here. TEST_CASE("injection") { TracerConfig config; - config.defaults.service = "testsvc"; + config.service = "testsvc"; config.collector = std::make_shared(); config.logger = std::make_shared(); config.injection_styles = {PropagationStyle::DATADOG, PropagationStyle::B3}; @@ -448,8 +448,8 @@ TEST_CASE("injection") { TEST_CASE("injection can be disabled using the \"none\" style") { TracerConfig config; - config.defaults.service = "testsvc"; - config.defaults.name = "spanny"; + config.service = "testsvc"; + config.name = "spanny"; config.collector = std::make_shared(); config.logger = std::make_shared(); config.injection_styles = {PropagationStyle::NONE}; @@ -467,7 +467,7 @@ TEST_CASE("injection can be disabled using the \"none\" style") { TEST_CASE("injecting W3C traceparent header") { TracerConfig config; - config.defaults.service = "testsvc"; + config.service = "testsvc"; config.collector = std::make_shared(); config.logger = std::make_shared(); config.injection_styles = {PropagationStyle::W3C}; @@ -572,7 +572,7 @@ TEST_CASE("injecting W3C tracestate header") { // - at the extra fields (extracted from W3C) TracerConfig config; - config.defaults.service = "testsvc"; + config.service = "testsvc"; // The order of the extraction styles doesn't matter for this test, because // it'll either be one or the other in the test cases. config.extraction_styles = {PropagationStyle::DATADOG, PropagationStyle::W3C}; @@ -703,13 +703,13 @@ TEST_CASE("injecting W3C tracestate header") { TEST_CASE("128-bit trace ID injection") { TracerConfig config; - config.defaults.service = "testsvc"; + config.service = "testsvc"; config.logger = std::make_shared(); config.trace_id_128_bit = true; - config.injection_styles.clear(); - config.injection_styles.push_back(PropagationStyle::W3C); - config.injection_styles.push_back(PropagationStyle::DATADOG); - config.injection_styles.push_back(PropagationStyle::B3); + + std::vector injection_styles{ + PropagationStyle::W3C, PropagationStyle::DATADOG, PropagationStyle::B3}; + config.injection_styles = injection_styles; const auto finalized = finalize_config(config); REQUIRE(finalized); @@ -756,7 +756,7 @@ TEST_CASE("128-bit trace ID injection") { TEST_CASE("sampling delegation injection") { TracerConfig config; - config.defaults.service = "testsvc"; + config.service = "testsvc"; config.logger = std::make_shared(); config.collector = std::make_shared(); diff --git a/test/test_span_sampler.cpp b/test/test_span_sampler.cpp index 3944309b..298ee72b 100644 --- a/test/test_span_sampler.cpp +++ b/test/test_span_sampler.cpp @@ -152,7 +152,7 @@ TEST_CASE("span rules matching") { })); TracerConfig config; - config.defaults.service = "testsvc"; + config.service = "testsvc"; const auto collector = std::make_shared(); config.collector = collector; config.logger = std::make_shared(); @@ -208,7 +208,7 @@ TEST_CASE("span rules matching") { TEST_CASE("span rules only on trace drop") { TracerConfig config; - config.defaults.service = "testsvc"; + config.service = "testsvc"; const auto collector = std::make_shared(); config.collector = collector; config.logger = std::make_shared(); @@ -244,7 +244,7 @@ TEST_CASE("span rules only on trace drop") { TEST_CASE("span rule sample rate") { TracerConfig config; - config.defaults.service = "testsvc"; + config.service = "testsvc"; const auto collector = std::make_shared(); config.collector = collector; config.logger = std::make_shared(); @@ -283,7 +283,7 @@ TEST_CASE("span rule sample rate") { TEST_CASE("span rule limiter") { TracerConfig config; - config.defaults.service = "testsvc"; + config.service = "testsvc"; const auto collector = std::make_shared(); config.collector = collector; config.logger = std::make_shared(); diff --git a/test/test_trace_sampler.cpp b/test/test_trace_sampler.cpp index 68a117cf..10704cf9 100644 --- a/test/test_trace_sampler.cpp +++ b/test/test_trace_sampler.cpp @@ -63,7 +63,7 @@ TEST_CASE("trace sampling rule sample rate") { const std::size_t num_iterations = 10'000; TracerConfig config; - config.defaults.service = "testsvc"; + config.service = "testsvc"; config.trace_sampler.sample_rate = test_case.sample_rate; // Plenty of head room so that the limiter doesn't throttle us. config.trace_sampler.max_per_second = num_iterations * 2; @@ -120,7 +120,7 @@ TEST_CASE("trace sampling rate limiter") { CAPTURE(test_case.expected_kept_count); TracerConfig config; - config.defaults.service = "testsvc"; + config.service = "testsvc"; config.trace_sampler.sample_rate = 1.0; config.trace_sampler.max_per_second = test_case.max_per_second; const auto collector = std::make_shared(); @@ -176,8 +176,8 @@ TEST_CASE("priority sampling") { 1.0}})); TracerConfig config; - config.defaults.service = "testsvc"; - config.defaults.environment = "dev"; + config.service = "testsvc"; + config.environment = "dev"; // plenty of head room config.trace_sampler.max_per_second = 2 * num_iterations; const auto collector = @@ -206,7 +206,7 @@ TEST_CASE("priority sampling") { TEST_CASE("sampling rules") { TracerConfig config; - config.defaults.service = "testsvc"; + config.service = "testsvc"; const auto collector = std::make_shared(); config.collector = collector; config.logger = std::make_shared(); diff --git a/test/test_trace_segment.cpp b/test/test_trace_segment.cpp index 6d54ea80..50c949da 100644 --- a/test/test_trace_segment.cpp +++ b/test/test_trace_segment.cpp @@ -29,21 +29,22 @@ Rate assert_rate(double rate) { TEST_CASE("TraceSegment accessors") { TracerConfig config; - config.defaults.service = "testsvc"; + config.service = "testsvc"; const auto collector = std::make_shared(); config.collector = collector; config.logger = std::make_shared(); SECTION("hostname") { - config.report_hostname = GENERATE(true, false); + const bool report_hostname = GENERATE(true, false); + config.report_hostname = report_hostname; auto finalized = finalize_config(config); REQUIRE(finalized); Tracer tracer{*finalized}; auto span = tracer.create_span(); auto hostname = span.trace_segment().hostname(); - if (config.report_hostname) { + if (report_hostname) { REQUIRE(hostname); } else { REQUIRE(!hostname); @@ -51,18 +52,27 @@ TEST_CASE("TraceSegment accessors") { } SECTION("defaults") { - config.defaults.name = "wobble"; - config.defaults.service_type = "fake"; - config.defaults.version = "v0"; - config.defaults.environment = "test"; - config.defaults.tags = {{"hello", "world"}, {"foo", "bar"}}; + const std::unordered_map tags{{"hello", "world"}, + {"foo", "bar"}}; + + config.name = "wobble"; + config.service_type = "fake"; + config.version = "v0"; + config.environment = "test"; + config.tags = tags; auto finalized = finalize_config(config); REQUIRE(finalized); Tracer tracer{*finalized}; auto span = tracer.create_span(); - REQUIRE(span.trace_segment().defaults() == config.defaults); + const auto span_default = span.trace_segment().defaults(); + CHECK(span_default.service == "testsvc"); + CHECK(span_default.name == "wobble"); + CHECK(span_default.service_type == "fake"); + CHECK(span_default.version == "v0"); + CHECK(span_default.environment == "test"); + CHECK(span_default.tags == tags); } SECTION("origin") { @@ -133,7 +143,7 @@ TEST_CASE("TraceSegment accessors") { TEST_CASE("When Collector::send fails, TraceSegment logs the error.") { TracerConfig config; - config.defaults.service = "testsvc"; + config.service = "testsvc"; const auto collector = std::make_shared(); config.collector = collector; const auto logger = std::make_shared(); @@ -154,7 +164,7 @@ TEST_CASE("When Collector::send fails, TraceSegment logs the error.") { TEST_CASE("TraceSegment finalization of spans") { TracerConfig config; - config.defaults.service = "testsvc"; + config.service = "testsvc"; const auto collector = std::make_shared(); config.collector = collector; config.logger = std::make_shared(); @@ -455,8 +465,8 @@ TEST_CASE("independent of Tracer") { // // Primarily, the test checks that the code doesn't crash in this scenario. TracerConfig config; - config.defaults.service = "testsvc"; - config.defaults.name = "do.thing"; + config.service = "testsvc"; + config.name = "do.thing"; config.logger = std::make_shared(); auto maybe_tracer = finalize_config(config); diff --git a/test/test_tracer.cpp b/test/test_tracer.cpp index a5ab0bbf..565a3c77 100644 --- a/test/test_tracer.cpp +++ b/test/test_tracer.cpp @@ -53,13 +53,13 @@ using namespace datadog::tracing; // tracer. TEST_CASE("tracer span defaults") { TracerConfig config; - config.defaults.service = "foosvc"; - config.defaults.service_type = "crawler"; - config.defaults.environment = "swamp"; - config.defaults.version = "first"; - config.defaults.name = "test.thing"; - config.defaults.tags = {{"some.thing", "thing value"}, - {"another.thing", "another value"}}; + config.service = "foosvc"; + config.service_type = "crawler"; + config.environment = "swamp"; + config.version = "first"; + config.name = "test.thing"; + config.tags = {{"some.thing", "thing value"}, + {"another.thing", "another value"}}; const auto collector = std::make_shared(); config.collector = collector; @@ -83,12 +83,12 @@ TEST_CASE("tracer span defaults") { overrides.tags = {{"different.thing", "different"}, {"another.thing", "different value"}}; - REQUIRE(overrides.service != config.defaults.service); - REQUIRE(overrides.service_type != config.defaults.service_type); - REQUIRE(overrides.environment != config.defaults.environment); - REQUIRE(overrides.version != config.defaults.version); - REQUIRE(overrides.name != config.defaults.name); - REQUIRE(overrides.tags != config.defaults.tags); + REQUIRE(overrides.service != config.service); + REQUIRE(overrides.service_type != config.service_type); + REQUIRE(overrides.environment != config.environment); + REQUIRE(overrides.version != config.version); + REQUIRE(overrides.name != config.name); + REQUIRE(overrides.tags != config.tags); // Some of the sections below create a span from extracted trace context. const std::unordered_map headers{ @@ -111,12 +111,12 @@ TEST_CASE("tracer span defaults") { REQUIRE(root_ptr); const auto& root = *root_ptr; - REQUIRE(root.service == config.defaults.service); - REQUIRE(root.service_type == config.defaults.service_type); - REQUIRE(root.environment() == config.defaults.environment); - REQUIRE(root.version() == config.defaults.version); - REQUIRE(root.name == config.defaults.name); - REQUIRE_THAT(root.tags, ContainsSubset(config.defaults.tags)); + REQUIRE(root.service == config.service); + REQUIRE(root.service_type == config.service_type); + REQUIRE(root.environment() == config.environment); + REQUIRE(root.version() == config.version); + REQUIRE(root.name == config.name); + REQUIRE_THAT(root.tags, ContainsSubset(*config.tags)); } SECTION("can be overridden in a root span") { @@ -159,12 +159,12 @@ TEST_CASE("tracer span defaults") { REQUIRE(span_ptr); const auto& span = *span_ptr; - REQUIRE(span.service == config.defaults.service); - REQUIRE(span.service_type == config.defaults.service_type); - REQUIRE(span.environment() == config.defaults.environment); - REQUIRE(span.version() == config.defaults.version); - REQUIRE(span.name == config.defaults.name); - REQUIRE_THAT(span.tags, ContainsSubset(config.defaults.tags)); + REQUIRE(span.service == config.service); + REQUIRE(span.service_type == config.service_type); + REQUIRE(span.environment() == config.environment); + REQUIRE(span.version() == config.version); + REQUIRE(span.name == config.name); + REQUIRE_THAT(span.tags, ContainsSubset(*config.tags)); } SECTION("can be overridden in an extracted span") { @@ -210,12 +210,12 @@ TEST_CASE("tracer span defaults") { REQUIRE(child_ptr); const auto& child = *child_ptr; - REQUIRE(child.service == config.defaults.service); - REQUIRE(child.service_type == config.defaults.service_type); - REQUIRE(child.environment() == config.defaults.environment); - REQUIRE(child.version() == config.defaults.version); - REQUIRE(child.name == config.defaults.name); - REQUIRE_THAT(child.tags, ContainsSubset(config.defaults.tags)); + REQUIRE(child.service == config.service); + REQUIRE(child.service_type == config.service_type); + REQUIRE(child.environment() == config.environment); + REQUIRE(child.version() == config.version); + REQUIRE(child.name == config.name); + REQUIRE_THAT(child.tags, ContainsSubset(*config.tags)); } SECTION("can be overridden in a child span") { @@ -248,7 +248,7 @@ TEST_CASE("tracer span defaults") { TEST_CASE("span extraction") { TracerConfig config; - config.defaults.service = "testsvc"; + config.service = "testsvc"; const auto collector = std::make_shared(); config.collector = collector; config.logger = std::make_shared(); @@ -1023,7 +1023,7 @@ TEST_CASE("span extraction") { span->inject(writer); CAPTURE(writer.items); - if (config.delegate_trace_sampling) { + if (*config.delegate_trace_sampling) { // If sampling delegation is enabled, then expect the delegation header to // have been injected. auto found = writer.items.find("x-datadog-delegate-trace-sampling"); @@ -1040,7 +1040,7 @@ TEST_CASE("span extraction") { TEST_CASE("report hostname") { TracerConfig config; - config.defaults.service = "testsvc"; + config.service = "testsvc"; config.collector = std::make_shared(); config.logger = std::make_shared(); @@ -1071,16 +1071,15 @@ TEST_CASE("128-bit trace IDs") { }; TracerConfig config; - config.defaults.service = "testsvc"; + config.service = "testsvc"; config.trace_id_128_bit = true; const auto collector = std::make_shared(); config.collector = collector; const auto logger = std::make_shared(); config.logger = logger; - config.extraction_styles.clear(); - config.extraction_styles.push_back(PropagationStyle::W3C); - config.extraction_styles.push_back(PropagationStyle::DATADOG); - config.extraction_styles.push_back(PropagationStyle::B3); + std::vector extraction_styles{ + PropagationStyle::W3C, PropagationStyle::DATADOG, PropagationStyle::B3}; + config.extraction_styles = extraction_styles; const auto finalized = finalize_config(config, clock); REQUIRE(finalized); Tracer tracer{*finalized}; @@ -1191,14 +1190,14 @@ TEST_CASE( CAPTURE(test_case.name); TracerConfig config; - config.defaults.service = "testsvc"; + config.service = "testsvc"; config.trace_id_128_bit = true; const auto collector = std::make_shared(); config.collector = collector; const auto logger = std::make_shared(); config.logger = logger; - config.extraction_styles.clear(); - config.extraction_styles.push_back(PropagationStyle::W3C); + std::vector extraction_styles{PropagationStyle::W3C}; + config.extraction_styles = extraction_styles; const auto finalized = finalize_config(config); REQUIRE(finalized); Tracer tracer{*finalized}; @@ -1258,19 +1257,19 @@ TEST_CASE("_dd.is_sampling_decider") { TracerConfig config1; config1.collector = collector; config1.logger = logger; - config1.defaults.service = "service1"; + config1.service = "service1"; config1.delegate_trace_sampling = true; TracerConfig config2; config2.collector = collector; config2.logger = logger; - config2.defaults.service = "service2"; + config2.service = "service2"; config2.delegate_trace_sampling = true; TracerConfig config3; config3.collector = collector; config3.logger = logger; - config3.defaults.service = "service3"; + config3.service = "service3"; config3.delegate_trace_sampling = service3_delegation_enabled; config3.trace_sampler.sample_rate = 1; // keep all traces CAPTURE(config3.delegate_trace_sampling); @@ -1493,7 +1492,7 @@ TEST_CASE("sampling delegation is not an override") { config1.collector = collector; config1.logger = logger; config1.extraction_styles = config1.injection_styles = styles; - config1.defaults.service = "service1"; + config1.service = "service1"; config1.delegate_trace_sampling = service1_delegate; config1.trace_sampler.sample_rate = 1.0; // as a default // `service1_sampling_priority` will be dealt with when service1 injects trace @@ -1503,14 +1502,14 @@ TEST_CASE("sampling delegation is not an override") { config2.collector = collector; config2.logger = logger; config2.extraction_styles = config1.injection_styles = styles; - config2.defaults.service = "service2"; + config2.service = "service2"; config2.delegate_trace_sampling = true; TracerConfig config3; config3.collector = collector; config3.logger = logger; config3.extraction_styles = config1.injection_styles = styles; - config3.defaults.service = "service3"; + config3.service = "service3"; config3.trace_sampler.sample_rate = service3_sample_rate; auto valid_config = finalize_config(config1); @@ -1685,7 +1684,7 @@ TEST_CASE("heterogeneous extraction") { CAPTURE(test_case.expected_injected_headers); TracerConfig config; - config.defaults.service = "testsvc"; + config.service = "testsvc"; config.extraction_styles = test_case.extraction_styles; config.injection_styles = test_case.injection_styles; config.logger = std::make_shared(); @@ -1707,7 +1706,7 @@ TEST_CASE("heterogeneous extraction") { TEST_CASE("move semantics") { // Verify that `Tracer` can be moved. TracerConfig config; - config.defaults.service = "testsvc"; + config.service = "testsvc"; config.logger = std::make_shared(); config.collector = std::make_shared(); diff --git a/test/test_tracer_config.cpp b/test/test_tracer_config.cpp index 86f8c747..3d87f43e 100644 --- a/test/test_tracer_config.cpp +++ b/test/test_tracer_config.cpp @@ -152,7 +152,7 @@ TEST_CASE("TracerConfig::defaults") { REQUIRE(finalized.error().code == Error::SERVICE_NAME_REQUIRED); } SECTION("nonempty") { - config.defaults.service = "testsvc"; + config.service = "testsvc"; auto finalized = finalize_config(config); REQUIRE(finalized); } @@ -160,7 +160,7 @@ TEST_CASE("TracerConfig::defaults") { SECTION("DD_SERVICE overrides service") { const EnvGuard guard{"DD_SERVICE", "foosvc"}; - config.defaults.service = "testsvc"; + config.service = "testsvc"; auto finalized = finalize_config(config); REQUIRE(finalized); REQUIRE(finalized->defaults.service == "foosvc"); @@ -168,8 +168,8 @@ TEST_CASE("TracerConfig::defaults") { SECTION("DD_ENV overrides environment") { const EnvGuard guard{"DD_ENV", "prod"}; - config.defaults.environment = "dev"; - config.defaults.service = "required"; + config.environment = "dev"; + config.service = "required"; auto finalized = finalize_config(config); REQUIRE(finalized); REQUIRE(finalized->defaults.environment == "prod"); @@ -177,8 +177,8 @@ TEST_CASE("TracerConfig::defaults") { SECTION("DD_VERSION overrides version") { const EnvGuard guard{"DD_VERSION", "v2"}; - config.defaults.version = "v1"; - config.defaults.service = "required"; + config.version = "v1"; + config.service = "required"; auto finalized = finalize_config(config); REQUIRE(finalized); REQUIRE(finalized->defaults.version == "v2"); @@ -186,8 +186,8 @@ TEST_CASE("TracerConfig::defaults") { SECTION("DD_TRACE_DELEGATE_SAMPLING") { SECTION("is disabled by default") { - config.defaults.version = "v1"; - config.defaults.service = "required"; + config.version = "v1"; + config.service = "required"; auto finalized = finalize_config(config); REQUIRE(finalized); REQUIRE(finalized->delegate_trace_sampling == false); @@ -195,8 +195,8 @@ TEST_CASE("TracerConfig::defaults") { SECTION("setting is overridden by environment variable") { const EnvGuard guard{"DD_TRACE_DELEGATE_SAMPLING", "1"}; - config.defaults.version = "v1"; - config.defaults.service = "required"; + config.version = "v1"; + config.service = "required"; auto finalized = finalize_config(config); REQUIRE(finalized); REQUIRE(finalized->delegate_trace_sampling == true); @@ -231,8 +231,8 @@ TEST_CASE("TracerConfig::defaults") { })); // This will be overriden by the DD_TAGS environment variable. - config.defaults.tags = {{"foo", "bar"}}; - config.defaults.service = "required"; + config.tags = std::unordered_map{{"foo", "bar"}}; + config.service = "required"; CAPTURE(test_case.name); const EnvGuard guard{"DD_TAGS", test_case.dd_tags}; @@ -240,7 +240,6 @@ TEST_CASE("TracerConfig::defaults") { if (test_case.expected_error) { REQUIRE(!finalized); REQUIRE(finalized.error().code == *test_case.expected_error); - } else { REQUIRE(finalized); REQUIRE(finalized->defaults.tags == test_case.expected_tags); @@ -250,7 +249,7 @@ TEST_CASE("TracerConfig::defaults") { TEST_CASE("TracerConfig::log_on_startup") { TracerConfig config; - config.defaults.service = "testsvc"; + config.service = "testsvc"; const auto logger = std::make_shared(); config.logger = logger; @@ -311,7 +310,7 @@ TEST_CASE("TracerConfig::log_on_startup") { TEST_CASE("TracerConfig::report_traces") { TracerConfig config; - config.defaults.service = "testsvc"; + config.service = "testsvc"; const auto collector = std::make_shared(); config.collector = collector; config.logger = std::make_shared(); @@ -380,7 +379,7 @@ TEST_CASE("TracerConfig::report_traces") { TEST_CASE("TracerConfig::agent") { TracerConfig config; - config.defaults.service = "testsvc"; + config.service = "testsvc"; SECTION("event_scheduler") { SECTION("default") { @@ -577,7 +576,7 @@ TEST_CASE("TracerConfig::agent") { TEST_CASE("TracerConfig::trace_sampler") { TracerConfig config; - config.defaults.service = "testsvc"; + config.service = "testsvc"; SECTION("default is no rules") { auto finalized = finalize_config(config); @@ -842,7 +841,7 @@ TEST_CASE("TracerConfig::trace_sampler") { TEST_CASE("TracerConfig::span_sampler") { TracerConfig config; - config.defaults.service = "testsvc"; + config.service = "testsvc"; SECTION("default is no rules") { auto finalized = finalize_config(config); @@ -1072,7 +1071,7 @@ TEST_CASE("TracerConfig::span_sampler") { TEST_CASE("TracerConfig propagation styles") { TracerConfig config; - config.defaults.service = "testsvc"; + config.service = "testsvc"; SECTION("default style is [Datadog, W3C]") { auto finalized = finalize_config(config); @@ -1099,7 +1098,7 @@ TEST_CASE("TracerConfig propagation styles") { SECTION("injection_styles") { SECTION("need at least one") { - config.injection_styles.clear(); + config.injection_styles = std::vector{}; auto finalized = finalize_config(config); REQUIRE(!finalized); REQUIRE(finalized.error().code == Error::MISSING_SPAN_INJECTION_STYLE); @@ -1191,7 +1190,7 @@ TEST_CASE("TracerConfig propagation styles") { // This section is very much like "injection_styles", above. SECTION("extraction_styles") { SECTION("need at least one") { - config.extraction_styles.clear(); + config.extraction_styles = std::vector{}; auto finalized = finalize_config(config); REQUIRE(!finalized); REQUIRE(finalized.error().code == Error::MISSING_SPAN_EXTRACTION_STYLE); @@ -1293,9 +1292,13 @@ TEST_CASE("TracerConfig propagation styles") { TEST_CASE("configure 128-bit trace IDs") { TracerConfig config; - config.defaults.service = "testsvc"; + config.service = "testsvc"; - SECTION("defaults to true") { REQUIRE(config.trace_id_128_bit == true); } + SECTION("defaults to true") { + const auto finalized_config = finalize_config(config); + REQUIRE(finalized_config); + CHECK(finalized_config->trace_id_128_bit == true); + } SECTION("value honored in finalizer") { const auto value = GENERATE(true, false); From 254618f118464ca8a7010b2882421bf2e21bc578 Mon Sep 17 00:00:00 2001 From: Damien Mehala Date: Thu, 15 Feb 2024 12:09:35 +0100 Subject: [PATCH 2/6] code review --- BUILD.bazel | 1 + CMakeLists.txt | 1 + src/datadog/datadog_agent_config.cpp | 159 ++++----- src/datadog/http_client.cpp | 81 +++++ src/datadog/http_client.h | 2 + src/datadog/span_sampler_config.cpp | 206 ++++++++++- src/datadog/span_sampler_config.h | 6 +- src/datadog/trace_sampler_config.cpp | 145 +++++++- src/datadog/tracer.cpp | 2 +- src/datadog/tracer_config.cpp | 504 +++------------------------ src/datadog/tracer_config.h | 43 +-- test/test_span.cpp | 2 +- test/test_tracer.cpp | 4 +- test/test_tracer_config.cpp | 18 +- 14 files changed, 581 insertions(+), 593 deletions(-) create mode 100644 src/datadog/http_client.cpp diff --git a/BUILD.bazel b/BUILD.bazel index 84697f6c..1a0523a4 100644 --- a/BUILD.bazel +++ b/BUILD.bazel @@ -15,6 +15,7 @@ cc_library( "src/datadog/error.cpp", "src/datadog/extraction_util.cpp", "src/datadog/glob.cpp", + "src/datadog/http_client.cpp", "src/datadog/id_generator.cpp", "src/datadog/limiter.cpp", "src/datadog/logger.cpp", diff --git a/CMakeLists.txt b/CMakeLists.txt index 1fd9ed3c..b36d3399 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -101,6 +101,7 @@ target_sources(dd_trace_cpp-objects PRIVATE src/datadog/error.cpp src/datadog/extraction_util.cpp src/datadog/glob.cpp + src/datadog/http_client.cpp src/datadog/id_generator.cpp src/datadog/limiter.cpp src/datadog/logger.cpp diff --git a/src/datadog/datadog_agent_config.cpp b/src/datadog/datadog_agent_config.cpp index dccb2a7b..593f8a80 100644 --- a/src/datadog/datadog_agent_config.cpp +++ b/src/datadog/datadog_agent_config.cpp @@ -5,92 +5,57 @@ #include #include "default_http_client.h" +#include "environment.h" #include "parse_util.h" #include "threaded_event_scheduler.h" namespace datadog { namespace tracing { -Expected DatadogAgentConfig::parse(StringView input) { - const StringView separator = "://"; - const auto after_scheme = std::search(input.begin(), input.end(), - separator.begin(), separator.end()); - if (after_scheme == input.end()) { - std::string message; - message += "Datadog Agent URL is missing the \"://\" separator: \""; - append(message, input); - message += '\"'; - return Error{Error::URL_MISSING_SEPARATOR, std::move(message)}; - } +Expected load_datadog_agent_env_config() { + DatadogAgentConfig env_config; - const StringView scheme = range(input.begin(), after_scheme); - const StringView supported[] = {"http", "https", "unix", "http+unix", - "https+unix"}; - const auto found = - std::find(std::begin(supported), std::end(supported), scheme); - if (found == std::end(supported)) { - std::string message; - message += "Unsupported URI scheme \""; - append(message, scheme); - message += "\" in Datadog Agent URL \""; - append(message, input); - message += "\". The following are supported:"; - for (const auto& supported_scheme : supported) { - message += ' '; - append(message, supported_scheme); + if (auto raw_rc_poll_interval_value = + lookup(environment::DD_REMOTE_CONFIG_POLL_INTERVAL_SECONDS)) { + auto res = parse_int(*raw_rc_poll_interval_value, 10); + if (auto error = res.if_error()) { + return error->with_prefix( + "DatadogAgent: Remote Configuration poll interval error "); } - return Error{Error::URL_UNSUPPORTED_SCHEME, std::move(message)}; + + env_config.remote_configuration_poll_interval_seconds = *res; } - const StringView authority_and_path = - range(after_scheme + separator.size(), input.end()); - // If the scheme is for unix domain sockets, then there's no way to - // distinguish the path-to-socket from the path-to-resource. Some - // implementations require that the forward slashes in the path-to-socket - // are URL-encoded. However, URLs that we will be parsing designate the - // location of the Datadog Agent service, and so do not have a resource - // location. Thus, if the scheme is for a unix domain socket, assume that - // the entire part after the "://" is the path to the socket, and that - // there is no resource path. - if (scheme == "unix" || scheme == "http+unix" || scheme == "https+unix") { - if (authority_and_path.empty() || authority_and_path[0] != '/') { - std::string message; - message += - "Unix domain socket paths for Datadog Agent must be absolute, i.e. " - "must begin with a " - "\"/\". The path \""; - append(message, authority_and_path); - message += "\" is not absolute. Error occurred for URL: \""; - append(message, input); - message += '\"'; - return Error{Error::URL_UNIX_DOMAIN_SOCKET_PATH_NOT_ABSOLUTE, - std::move(message)}; - } - return HTTPClient::URL{std::string(scheme), std::string(authority_and_path), - ""}; + auto env_host = lookup(environment::DD_AGENT_HOST); + auto env_port = lookup(environment::DD_TRACE_AGENT_PORT); + + if (auto url_env = lookup(environment::DD_TRACE_AGENT_URL)) { + env_config.url = std::string{*url_env}; + } else if (env_host || env_port) { + std::string configured_url = "http://"; + append(configured_url, env_host.value_or("localhost")); + configured_url += ':'; + append(configured_url, env_port.value_or("8126")); + + env_config.url = std::move(configured_url); } - // The scheme is either "http" or "https". This means that the part after - // the "://" could be /, e.g. "localhost:8080/api/v1". - // Again, though, we're only parsing URLs that designate the location of - // the Datadog Agent service, and so they will not have a resource - // location. Still, let's parse it properly. - const auto after_authority = - std::find(authority_and_path.begin(), authority_and_path.end(), '/'); - return HTTPClient::URL{ - std::string(scheme), - std::string(range(authority_and_path.begin(), after_authority)), - std::string(range(after_authority, authority_and_path.end()))}; + return env_config; } Expected finalize_config( - const DatadogAgentConfig& config, const std::shared_ptr& logger, - const Clock& clock) { + const DatadogAgentConfig& user_config, + const std::shared_ptr& logger, const Clock& clock) { + auto env_config = load_datadog_agent_env_config(); + if (auto error = env_config.if_error()) { + return *error; + } + FinalizedDatadogAgentConfig result; result.clock = clock; - if (!config.http_client) { + if (!user_config.http_client) { result.http_client = default_http_client(logger, clock); // `default_http_client` might return a `Curl` instance depending on how // this library was built. If it returns `nullptr`, then there's no @@ -100,58 +65,70 @@ Expected finalize_config( "DatadogAgent: HTTP client cannot be null."}; } } else { - result.http_client = config.http_client; + result.http_client = user_config.http_client; } - if (!config.event_scheduler) { + if (!user_config.event_scheduler) { result.event_scheduler = std::make_shared(); } else { - result.event_scheduler = config.event_scheduler; + result.event_scheduler = user_config.event_scheduler; } - if (config.flush_interval_milliseconds <= 0) { + if (auto flush_interval_milliseconds = + value_or(env_config->flush_interval_milliseconds, + user_config.flush_interval_milliseconds, 200); + flush_interval_milliseconds > 0) { + result.flush_interval = + std::chrono::milliseconds(flush_interval_milliseconds); + } else { return Error{Error::DATADOG_AGENT_INVALID_FLUSH_INTERVAL, "DatadogAgent: Flush interval must be a positive number of " "milliseconds."}; } - result.flush_interval = - std::chrono::milliseconds(*config.flush_interval_milliseconds); - if (config.request_timeout_milliseconds <= 0) { + if (auto request_timeout_milliseconds = + value_or(env_config->request_timeout_milliseconds, + user_config.request_timeout_milliseconds, 200); + request_timeout_milliseconds > 0) { + result.request_timeout = + std::chrono::milliseconds(request_timeout_milliseconds); + } else { return Error{Error::DATADOG_AGENT_INVALID_REQUEST_TIMEOUT, "DatadogAgent: Request timeout must be a positive number of " "milliseconds."}; } - result.request_timeout = - std::chrono::milliseconds(*config.request_timeout_milliseconds); - - if (config.shutdown_timeout_milliseconds <= 0) { + if (auto shutdown_timeout_milliseconds = + value_or(env_config->shutdown_timeout_milliseconds, + user_config.shutdown_timeout_milliseconds, 200); + shutdown_timeout_milliseconds > 0) { + result.shutdown_timeout = + std::chrono::milliseconds(shutdown_timeout_milliseconds); + } else { return Error{Error::DATADOG_AGENT_INVALID_SHUTDOWN_TIMEOUT, "DatadogAgent: Shutdown timeout must be a positive number of " "milliseconds."}; } - result.shutdown_timeout = - std::chrono::milliseconds(*config.shutdown_timeout_milliseconds); - - int rc_poll_interval_seconds = - *config.remote_configuration_poll_interval_seconds; - - if (rc_poll_interval_seconds <= 0) { + if (int rc_poll_interval_seconds = + value_or(env_config->remote_configuration_poll_interval_seconds, + user_config.remote_configuration_poll_interval_seconds, 5); + rc_poll_interval_seconds > 0) { + result.remote_configuration_poll_interval = + std::chrono::seconds(rc_poll_interval_seconds); + } else { return Error{Error::DATADOG_AGENT_INVALID_REMOTE_CONFIG_POLL_INTERVAL, "DatadogAgent: Remote Configuration poll interval must be a " "positive number of seconds."}; } - result.remote_configuration_poll_interval = - std::chrono::seconds(rc_poll_interval_seconds); - - auto url = config.parse(*config.url); - if (auto* error = url.if_error()) { + auto url = + value_or(env_config->url, user_config.url, "http://localhost:8126"); + auto parsed_url = HTTPClient::URL::parse(url); + if (auto* error = parsed_url.if_error()) { return std::move(*error); } - result.url = *url; + result.url = *parsed_url; return result; } diff --git a/src/datadog/http_client.cpp b/src/datadog/http_client.cpp new file mode 100644 index 00000000..c7f5a654 --- /dev/null +++ b/src/datadog/http_client.cpp @@ -0,0 +1,81 @@ +#include "http_client.h" + +#include "parse_util.h" + +namespace datadog { +namespace tracing { + +Expected HTTPClient::URL::parse(StringView input) { + const StringView separator = "://"; + const auto after_scheme = std::search(input.begin(), input.end(), + separator.begin(), separator.end()); + if (after_scheme == input.end()) { + std::string message; + message += "Datadog Agent URL is missing the \"://\" separator: \""; + append(message, input); + message += '\"'; + return Error{Error::URL_MISSING_SEPARATOR, std::move(message)}; + } + + const StringView scheme = range(input.begin(), after_scheme); + const StringView supported[] = {"http", "https", "unix", "http+unix", + "https+unix"}; + const auto found = + std::find(std::begin(supported), std::end(supported), scheme); + if (found == std::end(supported)) { + std::string message; + message += "Unsupported URI scheme \""; + append(message, scheme); + message += "\" in Datadog Agent URL \""; + append(message, input); + message += "\". The following are supported:"; + for (const auto& supported_scheme : supported) { + message += ' '; + append(message, supported_scheme); + } + return Error{Error::URL_UNSUPPORTED_SCHEME, std::move(message)}; + } + + const StringView authority_and_path = + range(after_scheme + separator.size(), input.end()); + // If the scheme is for unix domain sockets, then there's no way to + // distinguish the path-to-socket from the path-to-resource. Some + // implementations require that the forward slashes in the path-to-socket + // are URL-encoded. However, URLs that we will be parsing designate the + // location of the Datadog Agent service, and so do not have a resource + // location. Thus, if the scheme is for a unix domain socket, assume that + // the entire part after the "://" is the path to the socket, and that + // there is no resource path. + if (scheme == "unix" || scheme == "http+unix" || scheme == "https+unix") { + if (authority_and_path.empty() || authority_and_path[0] != '/') { + std::string message; + message += + "Unix domain socket paths for Datadog Agent must be absolute, i.e. " + "must begin with a " + "\"/\". The path \""; + append(message, authority_and_path); + message += "\" is not absolute. Error occurred for URL: \""; + append(message, input); + message += '\"'; + return Error{Error::URL_UNIX_DOMAIN_SOCKET_PATH_NOT_ABSOLUTE, + std::move(message)}; + } + return HTTPClient::URL{std::string(scheme), std::string(authority_and_path), + ""}; + } + + // The scheme is either "http" or "https". This means that the part after + // the "://" could be /, e.g. "localhost:8080/api/v1". + // Again, though, we're only parsing URLs that designate the location of + // the Datadog Agent service, and so they will not have a resource + // location. Still, let's parse it properly. + const auto after_authority = + std::find(authority_and_path.begin(), authority_and_path.end(), '/'); + return HTTPClient::URL{ + std::string(scheme), + std::string(range(authority_and_path.begin(), after_authority)), + std::string(range(after_authority, authority_and_path.end()))}; +} + +} // namespace tracing +} // namespace datadog diff --git a/src/datadog/http_client.h b/src/datadog/http_client.h index 2931dde3..8a5a9e15 100644 --- a/src/datadog/http_client.h +++ b/src/datadog/http_client.h @@ -28,6 +28,8 @@ class HTTPClient { std::string scheme; // http, https, or unix std::string authority; // domain:port or /path/to/socket std::string path; // resource, e.g. /v0.4/traces + + static Expected parse(StringView input); }; using HeadersSetter = std::function; diff --git a/src/datadog/span_sampler_config.cpp b/src/datadog/span_sampler_config.cpp index 862221b4..f0dc295c 100644 --- a/src/datadog/span_sampler_config.cpp +++ b/src/datadog/span_sampler_config.cpp @@ -1,6 +1,7 @@ #include "span_sampler_config.h" #include +#include #include #include @@ -10,14 +11,215 @@ namespace datadog { namespace tracing { +namespace { + +// `env_var` is the name of the environment variable from which `rules_raw` was +// obtained. It's used for error messages. +Expected> parse_rules(StringView rules_raw, + StringView env_var) { + std::vector rules; + nlohmann::json json_rules; + + try { + json_rules = nlohmann::json::parse(rules_raw); + } catch (const nlohmann::json::parse_error &error) { + std::string message; + message += "Unable to parse JSON from "; + append(message, env_var); + message += " value "; + append(message, rules_raw); + message += ": "; + message += error.what(); + return Error{Error::SPAN_SAMPLING_RULES_INVALID_JSON, std::move(message)}; + } + + std::string type = json_rules.type_name(); + if (type != "array") { + std::string message; + message += "Trace sampling rules must be an array, but JSON in "; + append(message, env_var); + message += " has type \""; + message += type; + message += "\": "; + append(message, rules_raw); + return Error{Error::SPAN_SAMPLING_RULES_WRONG_TYPE, std::move(message)}; + } + + const std::unordered_set allowed_properties{ + "service", "name", "resource", "tags", "sample_rate", "max_per_second"}; + + for (const auto &json_rule : json_rules) { + auto matcher = SpanMatcher::from_json(json_rule); + if (auto *error = matcher.if_error()) { + std::string prefix; + prefix += "Unable to create a rule from "; + append(prefix, env_var); + prefix += " JSON "; + append(prefix, rules_raw); + prefix += ": "; + return error->with_prefix(prefix); + } + + SpanSamplerConfig::Rule rule{*matcher}; + + auto sample_rate = json_rule.find("sample_rate"); + if (sample_rate != json_rule.end()) { + type = sample_rate->type_name(); + if (type != "number") { + std::string message; + message += "Unable to parse a rule from "; + append(message, env_var); + message += " JSON "; + append(message, rules_raw); + message += ". The \"sample_rate\" property of the rule "; + message += json_rule.dump(); + message += " is not a number, but instead has type \""; + message += type; + message += "\"."; + return Error{Error::SPAN_SAMPLING_RULES_SAMPLE_RATE_WRONG_TYPE, + std::move(message)}; + } + rule.sample_rate = *sample_rate; + } + + auto max_per_second = json_rule.find("max_per_second"); + if (max_per_second != json_rule.end()) { + type = max_per_second->type_name(); + if (type != "number") { + std::string message; + message += "Unable to parse a rule from "; + append(message, env_var); + message += " JSON "; + append(message, rules_raw); + message += ". The \"max_per_second\" property of the rule "; + message += json_rule.dump(); + message += " is not a number, but instead has type \""; + message += type; + message += "\"."; + return Error{Error::SPAN_SAMPLING_RULES_MAX_PER_SECOND_WRONG_TYPE, + std::move(message)}; + } + rule.max_per_second = *max_per_second; + } + + // Look for unexpected properties. + for (const auto &[key, value] : json_rule.items()) { + if (allowed_properties.count(key)) { + continue; + } + std::string message; + message += "Unexpected property \""; + message += key; + message += "\" having value "; + message += value.dump(); + message += " in trace sampling rule "; + message += json_rule.dump(); + message += ". Error occurred while parsing from "; + append(message, env_var); + message += ": "; + append(message, rules_raw); + return Error{Error::SPAN_SAMPLING_RULES_UNKNOWN_PROPERTY, + std::move(message)}; + } + + rules.emplace_back(std::move(rule)); + } + + return rules; +} + +Expected load_span_sampler_env_config(Logger &logger) { + SpanSamplerConfig env_config; + + auto rules_env = lookup(environment::DD_SPAN_SAMPLING_RULES); + if (rules_env) { + auto maybe_rules = + parse_rules(*rules_env, name(environment::DD_SPAN_SAMPLING_RULES)); + if (auto *error = maybe_rules.if_error()) { + return std::move(*error); + } + env_config.rules = std::move(*maybe_rules); + } + + if (auto file_env = lookup(environment::DD_SPAN_SAMPLING_RULES_FILE)) { + if (rules_env) { + const auto rules_file_name = + name(environment::DD_SPAN_SAMPLING_RULES_FILE); + const auto rules_name = name(environment::DD_SPAN_SAMPLING_RULES); + std::string message; + append(message, rules_file_name); + message += " is overridden by "; + append(message, rules_name); + message += ". Since both are set, "; + append(message, rules_name); + message += " takes precedence, and "; + append(message, rules_file_name); + message += " will be ignored."; + logger.log_error(message); + } else { + const auto span_rules_file = std::string(*file_env); + + const auto file_error = [&](const char *operation) { + std::string message; + message += "Unable to "; + message += operation; + message += " file \""; + message += span_rules_file; + message += "\" specified as value of environment variable "; + append(message, name(environment::DD_SPAN_SAMPLING_RULES_FILE)); + + return Error{Error::SPAN_SAMPLING_RULES_FILE_IO, std::move(message)}; + }; + + std::ifstream file(span_rules_file); + if (!file) { + return file_error("open"); + } + + std::ostringstream rules_stream; + rules_stream << file.rdbuf(); + if (!file) { + return file_error("read"); + } + + auto maybe_rules = parse_rules( + rules_stream.str(), name(environment::DD_SPAN_SAMPLING_RULES_FILE)); + if (auto *error = maybe_rules.if_error()) { + std::string prefix; + prefix += "With "; + append(prefix, name(environment::DD_SPAN_SAMPLING_RULES_FILE)); + prefix += '='; + append(prefix, *file_env); + prefix += ": "; + return error->with_prefix(prefix); + } + + env_config.rules = std::move(*maybe_rules); + } + } + + return env_config; +} + +} // namespace SpanSamplerConfig::Rule::Rule(const SpanMatcher &base) : SpanMatcher(base) {} Expected finalize_config( - const SpanSamplerConfig &config) { + const SpanSamplerConfig &user_config, Logger &logger) { + auto env_config = load_span_sampler_env_config(logger); + if (auto error = env_config.if_error()) { + return *error; + } + FinalizedSpanSamplerConfig result; - std::vector rules = config.rules; + std::vector rules; + if (!env_config->rules.empty()) { + rules = env_config->rules; + } else if (!user_config.rules.empty()) { + rules = user_config.rules; + } for (const auto &rule : rules) { auto maybe_rate = Rate::from(rule.sample_rate); diff --git a/src/datadog/span_sampler_config.h b/src/datadog/span_sampler_config.h index 65c5147b..065049ac 100644 --- a/src/datadog/span_sampler_config.h +++ b/src/datadog/span_sampler_config.h @@ -11,6 +11,7 @@ #include "expected.h" #include "json_fwd.hpp" +#include "logger.h" #include "optional.h" #include "rate.h" #include "span_matcher.h" @@ -35,7 +36,7 @@ struct SpanSamplerConfig { class FinalizedSpanSamplerConfig { friend Expected finalize_config( - const SpanSamplerConfig&); + const SpanSamplerConfig&, Logger&); friend class FinalizedTracerConfig; FinalizedSpanSamplerConfig() = default; @@ -49,7 +50,8 @@ class FinalizedSpanSamplerConfig { std::vector rules; }; -Expected finalize_config(const SpanSamplerConfig&); +Expected finalize_config(const SpanSamplerConfig&, + Logger& logger); nlohmann::json to_json(const FinalizedSpanSamplerConfig::Rule&); diff --git a/src/datadog/trace_sampler_config.cpp b/src/datadog/trace_sampler_config.cpp index 1969a74b..97a65e64 100644 --- a/src/datadog/trace_sampler_config.cpp +++ b/src/datadog/trace_sampler_config.cpp @@ -9,14 +9,147 @@ namespace datadog { namespace tracing { +namespace { + +Expected load_trace_sampler_env_config() { + TraceSamplerConfig env_config; + + if (auto rules_env = lookup(environment::DD_TRACE_SAMPLING_RULES)) { + nlohmann::json json_rules; + try { + json_rules = nlohmann::json::parse(*rules_env); + } catch (const nlohmann::json::parse_error &error) { + std::string message; + message += "Unable to parse JSON from "; + append(message, name(environment::DD_TRACE_SAMPLING_RULES)); + message += " value "; + append(message, *rules_env); + message += ": "; + message += error.what(); + return Error{Error::TRACE_SAMPLING_RULES_INVALID_JSON, + std::move(message)}; + } + + std::string type = json_rules.type_name(); + if (type != "array") { + std::string message; + message += "Trace sampling rules must be an array, but "; + append(message, name(environment::DD_TRACE_SAMPLING_RULES)); + message += " has JSON type \""; + message += type; + message += "\": "; + append(message, *rules_env); + return Error{Error::TRACE_SAMPLING_RULES_WRONG_TYPE, std::move(message)}; + } + + const std::unordered_set allowed_properties{ + "service", "name", "resource", "tags", "sample_rate"}; + + for (const auto &json_rule : json_rules) { + auto matcher = SpanMatcher::from_json(json_rule); + if (auto *error = matcher.if_error()) { + std::string prefix; + prefix += "Unable to create a rule from "; + append(prefix, name(environment::DD_TRACE_SAMPLING_RULES)); + prefix += " value "; + append(prefix, *rules_env); + prefix += ": "; + return error->with_prefix(prefix); + } + + TraceSamplerConfig::Rule rule{*matcher}; + + auto sample_rate = json_rule.find("sample_rate"); + if (sample_rate != json_rule.end()) { + type = sample_rate->type_name(); + if (type != "number") { + std::string message; + message += "Unable to parse a rule from "; + append(message, name(environment::DD_TRACE_SAMPLING_RULES)); + message += " value "; + append(message, *rules_env); + message += ". The \"sample_rate\" property of the rule "; + message += json_rule.dump(); + message += " is not a number, but instead has type \""; + message += type; + message += "\"."; + return Error{Error::TRACE_SAMPLING_RULES_SAMPLE_RATE_WRONG_TYPE, + std::move(message)}; + } + rule.sample_rate = *sample_rate; + } + + // Look for unexpected properties. + for (const auto &[key, value] : json_rule.items()) { + if (allowed_properties.count(key)) { + continue; + } + std::string message; + message += "Unexpected property \""; + message += key; + message += "\" having value "; + message += value.dump(); + message += " in trace sampling rule "; + message += json_rule.dump(); + message += ". Error occurred while parsing "; + append(message, name(environment::DD_TRACE_SAMPLING_RULES)); + message += ": "; + append(message, *rules_env); + return Error{Error::TRACE_SAMPLING_RULES_UNKNOWN_PROPERTY, + std::move(message)}; + } + + env_config.rules.emplace_back(std::move(rule)); + } + } + + if (auto sample_rate_env = lookup(environment::DD_TRACE_SAMPLE_RATE)) { + auto maybe_sample_rate = parse_double(*sample_rate_env); + if (auto *error = maybe_sample_rate.if_error()) { + std::string prefix; + prefix += "While parsing "; + append(prefix, name(environment::DD_TRACE_SAMPLE_RATE)); + prefix += ": "; + return error->with_prefix(prefix); + } + env_config.sample_rate = *maybe_sample_rate; + } + + if (auto limit_env = lookup(environment::DD_TRACE_RATE_LIMIT)) { + auto maybe_max_per_second = parse_double(*limit_env); + if (auto *error = maybe_max_per_second.if_error()) { + std::string prefix; + prefix += "While parsing "; + append(prefix, name(environment::DD_TRACE_RATE_LIMIT)); + prefix += ": "; + return error->with_prefix(prefix); + } + env_config.max_per_second = *maybe_max_per_second; + } + + return env_config; +} + +} // namespace TraceSamplerConfig::Rule::Rule(const SpanMatcher &base) : SpanMatcher(base) {} Expected finalize_config( const TraceSamplerConfig &config) { + auto env_config = load_trace_sampler_env_config(); + if (auto error = env_config.if_error()) { + return *error; + } + FinalizedTraceSamplerConfig result; - std::vector rules = config.rules; + std::vector rules; + + if (!env_config->rules.empty()) { + rules = std::move(env_config->rules); + } else if (!config.rules.empty()) { + rules = std::move(config.rules); + } for (const auto &rule : rules) { auto maybe_rate = Rate::from(rule.sample_rate); @@ -36,7 +169,12 @@ Expected finalize_config( result.rules.push_back(std::move(finalized)); } - auto sample_rate = config.sample_rate; + Optional sample_rate; + if (env_config->sample_rate) { + sample_rate = env_config->sample_rate; + } else if (config.sample_rate) { + sample_rate = config.sample_rate; + } // If `sample_rate` was specified, then it translates to a "catch-all" rule // appended to the end of `rules`. First, though, we have to make sure the @@ -53,7 +191,8 @@ Expected finalize_config( result.rules.push_back(std::move(catch_all)); } - auto max_per_second = config.max_per_second.value_or(200); + auto max_per_second = + value_or(env_config->max_per_second, config.max_per_second, 200); const auto allowed_types = {FP_NORMAL, FP_SUBNORMAL}; if (!(max_per_second > 0) || diff --git a/src/datadog/tracer.cpp b/src/datadog/tracer.cpp index 24dcf749..6a35d0dc 100644 --- a/src/datadog/tracer.cpp +++ b/src/datadog/tracer.cpp @@ -31,7 +31,7 @@ namespace datadog { namespace tracing { Tracer::Tracer(const FinalizedTracerConfig& config) - : Tracer(config, default_id_generator(config.trace_id_128_bit)) {} + : Tracer(config, default_id_generator(config.generate_128bit_trace_ids)) {} Tracer::Tracer(const FinalizedTracerConfig& config, const std::shared_ptr& generator) diff --git a/src/datadog/tracer_config.cpp b/src/datadog/tracer_config.cpp index 793892bf..244bdd41 100644 --- a/src/datadog/tracer_config.cpp +++ b/src/datadog/tracer_config.cpp @@ -4,7 +4,6 @@ #include #include #include -#include #include #include #include @@ -163,122 +162,7 @@ std::string json_quoted(StringView text) { return nlohmann::json(std::move(unquoted)).dump(); } -// `env_var` is the name of the environment variable from which `rules_raw` was -// obtained. It's used for error messages. -Expected> parse_rules(StringView rules_raw, - StringView env_var) { - std::vector rules; - nlohmann::json json_rules; - - try { - json_rules = nlohmann::json::parse(rules_raw); - } catch (const nlohmann::json::parse_error &error) { - std::string message; - message += "Unable to parse JSON from "; - append(message, env_var); - message += " value "; - append(message, rules_raw); - message += ": "; - message += error.what(); - return Error{Error::SPAN_SAMPLING_RULES_INVALID_JSON, std::move(message)}; - } - - std::string type = json_rules.type_name(); - if (type != "array") { - std::string message; - message += "Trace sampling rules must be an array, but JSON in "; - append(message, env_var); - message += " has type \""; - message += type; - message += "\": "; - append(message, rules_raw); - return Error{Error::SPAN_SAMPLING_RULES_WRONG_TYPE, std::move(message)}; - } - - const std::unordered_set allowed_properties{ - "service", "name", "resource", "tags", "sample_rate", "max_per_second"}; - - for (const auto &json_rule : json_rules) { - auto matcher = SpanMatcher::from_json(json_rule); - if (auto *error = matcher.if_error()) { - std::string prefix; - prefix += "Unable to create a rule from "; - append(prefix, env_var); - prefix += " JSON "; - append(prefix, rules_raw); - prefix += ": "; - return error->with_prefix(prefix); - } - - SpanSamplerConfig::Rule rule{*matcher}; - - auto sample_rate = json_rule.find("sample_rate"); - if (sample_rate != json_rule.end()) { - type = sample_rate->type_name(); - if (type != "number") { - std::string message; - message += "Unable to parse a rule from "; - append(message, env_var); - message += " JSON "; - append(message, rules_raw); - message += ". The \"sample_rate\" property of the rule "; - message += json_rule.dump(); - message += " is not a number, but instead has type \""; - message += type; - message += "\"."; - return Error{Error::SPAN_SAMPLING_RULES_SAMPLE_RATE_WRONG_TYPE, - std::move(message)}; - } - rule.sample_rate = *sample_rate; - } - - auto max_per_second = json_rule.find("max_per_second"); - if (max_per_second != json_rule.end()) { - type = max_per_second->type_name(); - if (type != "number") { - std::string message; - message += "Unable to parse a rule from "; - append(message, env_var); - message += " JSON "; - append(message, rules_raw); - message += ". The \"max_per_second\" property of the rule "; - message += json_rule.dump(); - message += " is not a number, but instead has type \""; - message += type; - message += "\"."; - return Error{Error::SPAN_SAMPLING_RULES_MAX_PER_SECOND_WRONG_TYPE, - std::move(message)}; - } - rule.max_per_second = *max_per_second; - } - - // Look for unexpected properties. - for (const auto &[key, value] : json_rule.items()) { - if (allowed_properties.count(key)) { - continue; - } - std::string message; - message += "Unexpected property \""; - message += key; - message += "\" having value "; - message += value.dump(); - message += " in trace sampling rule "; - message += json_rule.dump(); - message += ". Error occurred while parsing from "; - append(message, env_var); - message += ": "; - append(message, rules_raw); - return Error{Error::SPAN_SAMPLING_RULES_UNKNOWN_PROPERTY, - std::move(message)}; - } - - rules.emplace_back(std::move(rule)); - } - - return rules; -} - -Expected load_env_config(Logger &logger) { +Expected load_tracer_env_config(Logger &logger) { TracerConfig env_cfg; if (auto service_env = lookup(environment::DD_SERVICE)) { @@ -320,214 +204,7 @@ Expected load_env_config(Logger &logger) { } if (auto enabled_env = lookup(environment::DD_TRACE_128_BIT_TRACEID_GENERATION_ENABLED)) { - env_cfg.trace_id_128_bit = !falsy(*enabled_env); - } - - if (auto rules_env = lookup(environment::DD_TRACE_SAMPLING_RULES)) { - nlohmann::json json_rules; - try { - json_rules = nlohmann::json::parse(*rules_env); - } catch (const nlohmann::json::parse_error &error) { - std::string message; - message += "Unable to parse JSON from "; - append(message, name(environment::DD_TRACE_SAMPLING_RULES)); - message += " value "; - append(message, *rules_env); - message += ": "; - message += error.what(); - return Error{Error::TRACE_SAMPLING_RULES_INVALID_JSON, - std::move(message)}; - } - - std::string type = json_rules.type_name(); - if (type != "array") { - std::string message; - message += "Trace sampling rules must be an array, but "; - append(message, name(environment::DD_TRACE_SAMPLING_RULES)); - message += " has JSON type \""; - message += type; - message += "\": "; - append(message, *rules_env); - return Error{Error::TRACE_SAMPLING_RULES_WRONG_TYPE, std::move(message)}; - } - - const std::unordered_set allowed_properties{ - "service", "name", "resource", "tags", "sample_rate"}; - - for (const auto &json_rule : json_rules) { - auto matcher = SpanMatcher::from_json(json_rule); - if (auto *error = matcher.if_error()) { - std::string prefix; - prefix += "Unable to create a rule from "; - append(prefix, name(environment::DD_TRACE_SAMPLING_RULES)); - prefix += " value "; - append(prefix, *rules_env); - prefix += ": "; - return error->with_prefix(prefix); - } - - TraceSamplerConfig::Rule rule{*matcher}; - - auto sample_rate = json_rule.find("sample_rate"); - if (sample_rate != json_rule.end()) { - type = sample_rate->type_name(); - if (type != "number") { - std::string message; - message += "Unable to parse a rule from "; - append(message, name(environment::DD_TRACE_SAMPLING_RULES)); - message += " value "; - append(message, *rules_env); - message += ". The \"sample_rate\" property of the rule "; - message += json_rule.dump(); - message += " is not a number, but instead has type \""; - message += type; - message += "\"."; - return Error{Error::TRACE_SAMPLING_RULES_SAMPLE_RATE_WRONG_TYPE, - std::move(message)}; - } - rule.sample_rate = *sample_rate; - } - - // Look for unexpected properties. - for (const auto &[key, value] : json_rule.items()) { - if (allowed_properties.count(key)) { - continue; - } - std::string message; - message += "Unexpected property \""; - message += key; - message += "\" having value "; - message += value.dump(); - message += " in trace sampling rule "; - message += json_rule.dump(); - message += ". Error occurred while parsing "; - append(message, name(environment::DD_TRACE_SAMPLING_RULES)); - message += ": "; - append(message, *rules_env); - return Error{Error::TRACE_SAMPLING_RULES_UNKNOWN_PROPERTY, - std::move(message)}; - } - - env_cfg.trace_sampler.rules.emplace_back(std::move(rule)); - } - } - - if (auto sample_rate_env = lookup(environment::DD_TRACE_SAMPLE_RATE)) { - auto maybe_sample_rate = parse_double(*sample_rate_env); - if (auto *error = maybe_sample_rate.if_error()) { - std::string prefix; - prefix += "While parsing "; - append(prefix, name(environment::DD_TRACE_SAMPLE_RATE)); - prefix += ": "; - return error->with_prefix(prefix); - } - env_cfg.trace_sampler.sample_rate = *maybe_sample_rate; - } - - if (auto limit_env = lookup(environment::DD_TRACE_RATE_LIMIT)) { - auto maybe_max_per_second = parse_double(*limit_env); - if (auto *error = maybe_max_per_second.if_error()) { - std::string prefix; - prefix += "While parsing "; - append(prefix, name(environment::DD_TRACE_RATE_LIMIT)); - prefix += ": "; - return error->with_prefix(prefix); - } - env_cfg.trace_sampler.max_per_second = *maybe_max_per_second; - } - - // SpanSampler - auto rules_env = lookup(environment::DD_SPAN_SAMPLING_RULES); - if (rules_env) { - auto maybe_rules = - parse_rules(*rules_env, name(environment::DD_SPAN_SAMPLING_RULES)); - if (auto *error = maybe_rules.if_error()) { - return std::move(*error); - } - env_cfg.span_sampler.rules = std::move(*maybe_rules); - } - - if (auto file_env = lookup(environment::DD_SPAN_SAMPLING_RULES_FILE)) { - if (rules_env) { - const auto rules_file_name = - name(environment::DD_SPAN_SAMPLING_RULES_FILE); - const auto rules_name = name(environment::DD_SPAN_SAMPLING_RULES); - std::string message; - append(message, rules_file_name); - message += " is overridden by "; - append(message, rules_name); - message += ". Since both are set, "; - append(message, rules_name); - message += " takes precedence, and "; - append(message, rules_file_name); - message += " will be ignored."; - logger.log_error(message); - } else { - const auto span_rules_file = std::string(*file_env); - - const auto file_error = [&](const char *operation) { - std::string message; - message += "Unable to "; - message += operation; - message += " file \""; - message += span_rules_file; - message += "\" specified as value of environment variable "; - append(message, name(environment::DD_SPAN_SAMPLING_RULES_FILE)); - - return Error{Error::SPAN_SAMPLING_RULES_FILE_IO, std::move(message)}; - }; - - std::ifstream file(span_rules_file); - if (!file) { - return file_error("open"); - } - - std::ostringstream rules_stream; - rules_stream << file.rdbuf(); - if (!file) { - return file_error("read"); - } - - auto maybe_rules = parse_rules( - rules_stream.str(), name(environment::DD_SPAN_SAMPLING_RULES_FILE)); - if (auto *error = maybe_rules.if_error()) { - std::string prefix; - prefix += "With "; - append(prefix, name(environment::DD_SPAN_SAMPLING_RULES_FILE)); - prefix += '='; - append(prefix, *file_env); - prefix += ": "; - return error->with_prefix(prefix); - } - - env_cfg.span_sampler.rules = std::move(*maybe_rules); - } - } - - // DatadogAgentConfig - if (auto raw_rc_poll_interval_value = - lookup(environment::DD_REMOTE_CONFIG_POLL_INTERVAL_SECONDS)) { - auto res = parse_int(*raw_rc_poll_interval_value, 10); - if (auto error = res.if_error()) { - return error->with_prefix( - "DatadogAgent: Remote Configuration poll interval error "); - } - - env_cfg.agent.remote_configuration_poll_interval_seconds = *res; - } - - auto env_host = lookup(environment::DD_AGENT_HOST); - auto env_port = lookup(environment::DD_TRACE_AGENT_PORT); - - if (auto url_env = lookup(environment::DD_TRACE_AGENT_URL)) { - env_cfg.agent.url = std::string{*url_env}; - } else if (env_host || env_port) { - std::string configured_url = "http://"; - append(configured_url, env_host.value_or("localhost")); - configured_url += ':'; - append(configured_url, env_port.value_or("8126")); - - env_cfg.agent.url = std::move(configured_url); + env_cfg.generate_128bit_trace_ids = !falsy(*enabled_env); } // PropagationStyle @@ -632,173 +309,106 @@ Expected load_env_config(Logger &logger) { } // namespace -static const TracerDefaultConfig k_default_cfg; - Expected finalize_config(const TracerConfig &config) { - return finalize_config(config, k_default_cfg, default_clock); + return finalize_config(config, default_clock); } Expected finalize_config(const TracerConfig &user_config, const Clock &clock) { - return finalize_config(user_config, k_default_cfg, clock); -} + auto logger = + user_config.logger ? user_config.logger : std::make_shared(); -Expected finalize_config( - const TracerConfig &config, const TracerDefaultConfig &default_config) { - return finalize_config(config, default_config, default_clock); -} - -Expected finalize_config( - const TracerConfig &user_config, const TracerDefaultConfig &default_config, - const Clock &clock) { - FinalizedTracerConfig final_cfg; - - if (user_config.logger) { - final_cfg.logger = user_config.logger; - } else { - final_cfg.logger = std::make_shared(); - } - - auto env = load_env_config(*final_cfg.logger); + auto env = load_tracer_env_config(*logger); if (auto error = env.if_error()) { return *error; } - final_cfg.clock = clock; - final_cfg.defaults.service = value_or(env->service, user_config.service, ""); - final_cfg.defaults.service_type = value_or( - env->service_type, user_config.service_type, default_config.service_type); - final_cfg.defaults.environment = - value_or(env->environment, user_config.environment, ""); - final_cfg.defaults.version = value_or(env->version, user_config.version, ""); - final_cfg.defaults.name = value_or(env->name, user_config.name, ""); - - std::unordered_map default_tags; - final_cfg.defaults.tags = value_or(env->tags, user_config.tags, default_tags); + FinalizedTracerConfig final_config; - final_cfg.extraction_styles = + final_config.clock = clock; + final_config.logger = logger; + final_config.defaults.service = + value_or(env->service, user_config.service, ""); + final_config.defaults.service_type = + value_or(env->service_type, user_config.service_type, "web"); + final_config.defaults.environment = + value_or(env->environment, user_config.environment, ""); + final_config.defaults.version = + value_or(env->version, user_config.version, ""); + final_config.defaults.name = value_or(env->name, user_config.name, ""); + final_config.defaults.tags = + value_or(env->tags, user_config.tags, + std::unordered_map{}); + final_config.extraction_styles = value_or(env->extraction_styles, user_config.extraction_styles, - default_config.extraction_styles); - final_cfg.injection_styles = + std::vector{PropagationStyle::DATADOG, + PropagationStyle::W3C}); + final_config.injection_styles = value_or(env->injection_styles, user_config.injection_styles, - default_config.injection_styles); - - final_cfg.log_on_startup = - value_or(env->log_on_startup, user_config.log_on_startup, - default_config.log_on_startup); - final_cfg.report_traces = - value_or(env->report_traces, user_config.report_traces, - default_config.report_traces); - final_cfg.report_telemetry = - value_or(env->report_telemetry, user_config.report_telemetry, - default_config.report_telemetry); - final_cfg.report_hostname = - value_or(env->report_hostname, user_config.report_hostname, - default_config.report_hostname); - final_cfg.delegate_trace_sampling = value_or( - env->delegate_trace_sampling, user_config.delegate_trace_sampling, - default_config.delegate_trace_sampling); - final_cfg.tags_header_size = - value_or(env->tags_header_size, user_config.tags_header_size, - default_config.max_tags_header_size); - final_cfg.trace_id_128_bit = - value_or(env->trace_id_128_bit, user_config.trace_id_128_bit, - default_config.generate_128bit_trace_ids); - final_cfg.integration_name = + std::vector{PropagationStyle::DATADOG, + PropagationStyle::W3C}); + final_config.log_on_startup = + value_or(env->log_on_startup, user_config.log_on_startup, true); + final_config.report_traces = + value_or(env->report_traces, user_config.report_traces, true); + final_config.report_telemetry = + value_or(env->report_telemetry, user_config.report_telemetry, true); + final_config.report_hostname = + value_or(env->report_hostname, user_config.report_hostname, false); + final_config.delegate_trace_sampling = value_or( + env->delegate_trace_sampling, user_config.delegate_trace_sampling, false); + final_config.tags_header_size = value_or( + env->max_tags_header_size, user_config.max_tags_header_size, 512); + final_config.generate_128bit_trace_ids = + value_or(env->generate_128bit_trace_ids, + user_config.generate_128bit_trace_ids, true); + final_config.integration_name = value_or(env->integration_name, user_config.integration_name, ""); - final_cfg.integration_version = + final_config.integration_version = value_or(env->integration_version, user_config.integration_version, ""); if (user_config.runtime_id) { - final_cfg.runtime_id = user_config.runtime_id; - } - - DatadogAgentConfig merged_agent_config; - merged_agent_config.url = - value_or(env->agent.url, user_config.agent.url, default_config.agent_url); - merged_agent_config.flush_interval_milliseconds = - value_or(env->agent.flush_interval_milliseconds, - user_config.agent.flush_interval_milliseconds, - default_config.flush_interval_milliseconds); - merged_agent_config.shutdown_timeout_milliseconds = - value_or(env->agent.shutdown_timeout_milliseconds, - user_config.agent.shutdown_timeout_milliseconds, - default_config.shutdown_timeout_milliseconds); - merged_agent_config.request_timeout_milliseconds = - value_or(env->agent.request_timeout_milliseconds, - user_config.agent.request_timeout_milliseconds, - default_config.request_timeout_milliseconds); - merged_agent_config.remote_configuration_poll_interval_seconds = - value_or(env->agent.remote_configuration_poll_interval_seconds, - user_config.agent.remote_configuration_poll_interval_seconds, - default_config.remote_configuration_poll_interval_seconds); - if (user_config.agent.http_client) { - merged_agent_config.http_client = user_config.agent.http_client; - } - if (user_config.agent.event_scheduler) { - merged_agent_config.event_scheduler = user_config.agent.event_scheduler; - } - - TraceSamplerConfig ts; - if (env->trace_sampler.sample_rate) { - ts.sample_rate = env->trace_sampler.sample_rate; - } else if (user_config.trace_sampler.sample_rate) { - ts.sample_rate = user_config.trace_sampler.sample_rate; - } - - if (!env->trace_sampler.rules.empty()) { - ts.rules = env->trace_sampler.rules; - } else { - ts.rules = user_config.trace_sampler.rules; - } - - ts.max_per_second = value_or(env->trace_sampler.max_per_second, - user_config.trace_sampler.max_per_second, 200); - - SpanSamplerConfig ss; - if (!env->span_sampler.rules.empty()) { - ss.rules = env->span_sampler.rules; - } else { - ss.rules = user_config.span_sampler.rules; + final_config.runtime_id = user_config.runtime_id; } - if (final_cfg.defaults.service.empty()) { + if (final_config.defaults.service.empty()) { return Error{Error::SERVICE_NAME_REQUIRED, "Service name is required."}; } - if (final_cfg.extraction_styles.empty()) { + if (final_config.extraction_styles.empty()) { return Error{Error::MISSING_SPAN_EXTRACTION_STYLE, "At least one extraction style must be specified."}; } - if (final_cfg.injection_styles.empty()) { + if (final_config.injection_styles.empty()) { return Error{Error::MISSING_SPAN_INJECTION_STYLE, "At least one injection style must be specified."}; } if (!user_config.collector) { auto finalized = - finalize_config(merged_agent_config, final_cfg.logger, clock); + finalize_config(user_config.agent, final_config.logger, clock); if (auto *error = finalized.if_error()) { return std::move(*error); } - final_cfg.collector = *finalized; + final_config.collector = *finalized; } else { - final_cfg.collector = user_config.collector; + final_config.collector = user_config.collector; } - if (auto trace_sampler_config = finalize_config(ts)) { - final_cfg.trace_sampler = std::move(*trace_sampler_config); + if (auto trace_sampler_config = finalize_config(user_config.trace_sampler)) { + final_config.trace_sampler = std::move(*trace_sampler_config); } else { return std::move(trace_sampler_config.error()); } - if (auto span_sampler_config = finalize_config(ss)) { - final_cfg.span_sampler = std::move(*span_sampler_config); + if (auto span_sampler_config = + finalize_config(user_config.span_sampler, *logger)) { + final_config.span_sampler = std::move(*span_sampler_config); } else { return std::move(span_sampler_config.error()); } - return final_cfg; + return final_config; } } // namespace tracing diff --git a/src/datadog/tracer_config.h b/src/datadog/tracer_config.h index 0425c071..eb8ce942 100644 --- a/src/datadog/tracer_config.h +++ b/src/datadog/tracer_config.h @@ -116,11 +116,11 @@ struct TracerConfig { // `gethostname` with traces sent to the collector. Optional report_hostname; - // `tags_header_size` is the maximum allowed size, in bytes, of the serialized - // value of the "X-Datadog-Tags" header used when injecting trace context for - // propagation. If the serialized value of the header would exceed - // `tags_header_size`, the header will be omitted instead. - Optional tags_header_size; + // `max_tags_header_size` is the maximum allowed size, in bytes, of the + // serialized value of the "X-Datadog-Tags" header used when injecting trace + // context for propagation. If the serialized value of the header would + // exceed `tags_header_size`, the header will be omitted instead. + Optional max_tags_header_size; // `logger` specifies how the tracer will issue diagnostic messages. If // `logger` is null, then it defaults to a logger that inserts into @@ -137,7 +137,7 @@ struct TracerConfig { // IDs. If true, the tracer will generate 128-bit trace IDs. If false, the // tracer will generate 64-bit trace IDs. `trace_id_128_bit` is overridden by // the `DD_TRACE_128_BIT_TRACEID_GENERATION_ENABLED` environment variable. - Optional trace_id_128_bit; + Optional generate_128bit_trace_ids; // `runtime_id` denotes the current run of the application in which the tracer // is embedded. If `runtime_id` is not specified, then it defaults to a @@ -155,34 +155,12 @@ struct TracerConfig { Optional integration_version; }; -struct TracerDefaultConfig final { - bool log_on_startup = true; - bool report_traces = true; - bool report_hostname = false; - bool report_telemetry = true; - bool delegate_trace_sampling = false; - bool generate_128bit_trace_ids = true; - std::string service_type = "web"; - std::size_t max_tags_header_size = 512; - std::vector injection_styles = {PropagationStyle::DATADOG, - PropagationStyle::W3C}; - std::vector extraction_styles = {PropagationStyle::DATADOG, - PropagationStyle::W3C}; - - std::string agent_url = "http://localhost:8126"; - int flush_interval_milliseconds = 2000; - int request_timeout_milliseconds = 2000; - int shutdown_timeout_milliseconds = 2000; - int remote_configuration_poll_interval_seconds = 5; -}; - // `FinalizedTracerConfig` contains `Tracer` implementation details derived from // a valid `TracerConfig` and accompanying environment. // `FinalizedTracerConfig` must be obtained by calling `finalize_config`. class FinalizedTracerConfig final { friend Expected finalize_config( - const TracerConfig& config, const TracerDefaultConfig& default_config, - const Clock& clock); + const TracerConfig& config, const Clock& clock); FinalizedTracerConfig() = default; public: @@ -202,7 +180,7 @@ class FinalizedTracerConfig final { std::size_t tags_header_size; std::shared_ptr logger; bool log_on_startup; - bool trace_id_128_bit; + bool generate_128bit_trace_ids; bool report_telemetry; Optional runtime_id; Clock clock; @@ -221,11 +199,6 @@ class FinalizedTracerConfig final { Expected finalize_config(const TracerConfig& config); Expected finalize_config(const TracerConfig& config, const Clock& clock); -Expected finalize_config( - const TracerConfig& config, const TracerDefaultConfig& default_config); -Expected finalize_config( - const TracerConfig& config, const TracerDefaultConfig& default_config, - const Clock& clock); } // namespace tracing } // namespace datadog diff --git a/test/test_span.cpp b/test/test_span.cpp index dd8868a7..3cdf599c 100644 --- a/test/test_span.cpp +++ b/test/test_span.cpp @@ -705,7 +705,7 @@ TEST_CASE("128-bit trace ID injection") { TracerConfig config; config.service = "testsvc"; config.logger = std::make_shared(); - config.trace_id_128_bit = true; + config.generate_128bit_trace_ids = true; std::vector injection_styles{ PropagationStyle::W3C, PropagationStyle::DATADOG, PropagationStyle::B3}; diff --git a/test/test_tracer.cpp b/test/test_tracer.cpp index 565a3c77..987a7ba2 100644 --- a/test/test_tracer.cpp +++ b/test/test_tracer.cpp @@ -1072,7 +1072,7 @@ TEST_CASE("128-bit trace IDs") { TracerConfig config; config.service = "testsvc"; - config.trace_id_128_bit = true; + config.generate_128bit_trace_ids = true; const auto collector = std::make_shared(); config.collector = collector; const auto logger = std::make_shared(); @@ -1191,7 +1191,7 @@ TEST_CASE( TracerConfig config; config.service = "testsvc"; - config.trace_id_128_bit = true; + config.generate_128bit_trace_ids = true; const auto collector = std::make_shared(); config.collector = collector; const auto logger = std::make_shared(); diff --git a/test/test_tracer_config.cpp b/test/test_tracer_config.cpp index 3d87f43e..736dec9c 100644 --- a/test/test_tracer_config.cpp +++ b/test/test_tracer_config.cpp @@ -1297,15 +1297,15 @@ TEST_CASE("configure 128-bit trace IDs") { SECTION("defaults to true") { const auto finalized_config = finalize_config(config); REQUIRE(finalized_config); - CHECK(finalized_config->trace_id_128_bit == true); + CHECK(finalized_config->generate_128bit_trace_ids == true); } SECTION("value honored in finalizer") { const auto value = GENERATE(true, false); - config.trace_id_128_bit = value; + config.generate_128bit_trace_ids = value; const auto finalized = finalize_config(config); REQUIRE(finalized); - REQUIRE(finalized->trace_id_128_bit == value); + REQUIRE(finalized->generate_128bit_trace_ids == value); } SECTION("value overridden by DD_TRACE_128_BIT_TRACEID_GENERATION_ENABLED") { @@ -1332,16 +1332,16 @@ TEST_CASE("configure 128-bit trace IDs") { EnvGuard guard{"DD_TRACE_128_BIT_TRACEID_GENERATION_ENABLED", test_case.env_value}; - config.trace_id_128_bit = true; - CAPTURE(config.trace_id_128_bit); + config.generate_128bit_trace_ids = true; + CAPTURE(config.generate_128bit_trace_ids); auto finalized = finalize_config(config); REQUIRE(finalized); - REQUIRE(finalized->trace_id_128_bit == test_case.expected_value); + REQUIRE(finalized->generate_128bit_trace_ids == test_case.expected_value); - config.trace_id_128_bit = false; - CAPTURE(config.trace_id_128_bit); + config.generate_128bit_trace_ids = false; + CAPTURE(config.generate_128bit_trace_ids); finalized = finalize_config(config); REQUIRE(finalized); - REQUIRE(finalized->trace_id_128_bit == test_case.expected_value); + REQUIRE(finalized->generate_128bit_trace_ids == test_case.expected_value); } } From 04a350f826019ab773edb78c64053029daacb80c Mon Sep 17 00:00:00 2001 From: Damien Mehala Date: Thu, 15 Feb 2024 12:40:42 +0100 Subject: [PATCH 3/6] final code review --- src/datadog/tracer_config.h | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/datadog/tracer_config.h b/src/datadog/tracer_config.h index eb8ce942..8c448361 100644 --- a/src/datadog/tracer_config.h +++ b/src/datadog/tracer_config.h @@ -153,6 +153,15 @@ struct TracerConfig { // library. // Example: "1.2.3", "6c44da20", "2020.02.13" Optional integration_version; + + // This field allows for overriding the service name origin to default. + // + // Mainly exist for integration configurations purposes. + // For instance, the default service name for the nginx integration will + // resolve as 'nginx'. Without this customization, it would be reported as a + // programmatic value in Datadog's Active Configuration, whereas it is + // actually the default value for the integration. + Optional report_service_as_default; }; // `FinalizedTracerConfig` contains `Tracer` implementation details derived from From 2482ff1a7d92e393ebae769568b57beb7bf9f08f Mon Sep 17 00:00:00 2001 From: Damien Mehala Date: Tue, 12 Mar 2024 12:18:20 +0100 Subject: [PATCH 4/6] [2/2] feat: active configuration - report configuration to telemetry (#98) --- BUILD.bazel | 3 + CMakeLists.txt | 3 + src/datadog/config.h | 72 +++++++++ src/datadog/config_manager.cpp | 114 +++++++++----- src/datadog/config_manager.h | 51 ++++++- src/datadog/config_update.h | 4 +- src/datadog/datadog_agent.cpp | 46 +++--- src/datadog/datadog_agent.h | 6 +- src/datadog/datadog_agent_config.cpp | 14 +- src/datadog/datadog_agent_config.h | 3 + src/datadog/parse_util.cpp | 71 ++++++++- src/datadog/parse_util.h | 16 ++ src/datadog/remote_config.cpp | 70 +++------ src/datadog/remote_config.h | 6 +- src/datadog/span_sampler_config.cpp | 17 ++- src/datadog/span_sampler_config.h | 3 + src/datadog/string_util.cpp | 74 +++++++++ src/datadog/string_util.h | 29 ++++ src/datadog/trace_sampler_config.cpp | 30 +++- src/datadog/trace_sampler_config.h | 4 + src/datadog/tracer.cpp | 2 +- src/datadog/tracer_config.cpp | 220 +++++++++++++-------------- src/datadog/tracer_config.h | 4 +- src/datadog/tracer_telemetry.cpp | 112 +++++++++++++- src/datadog/tracer_telemetry.h | 26 +++- test/test_datadog_agent.cpp | 65 +++++++- test/test_remote_config.cpp | 24 ++- test/test_smoke.cpp | 2 + test/test_trace_segment.cpp | 2 + test/test_tracer_telemetry.cpp | 93 ++++++++++- 30 files changed, 916 insertions(+), 270 deletions(-) create mode 100644 src/datadog/config.h create mode 100644 src/datadog/string_util.cpp create mode 100644 src/datadog/string_util.h diff --git a/BUILD.bazel b/BUILD.bazel index 1a0523a4..ae4a9631 100644 --- a/BUILD.bazel +++ b/BUILD.bazel @@ -35,6 +35,7 @@ cc_library( "src/datadog/span_matcher.cpp", "src/datadog/span_sampler_config.cpp", "src/datadog/span_sampler.cpp", + "src/datadog/string_util.cpp", "src/datadog/tag_propagation.cpp", "src/datadog/tags.cpp", "src/datadog/threaded_event_scheduler.cpp", @@ -51,6 +52,7 @@ cc_library( hdrs = [ "src/datadog/base64.h", "src/datadog/cerr_logger.h", + "src/datadog/config.h", "src/datadog/clock.h", "src/datadog/config_manager.h", "src/datadog/config_update.h", @@ -99,6 +101,7 @@ cc_library( "src/datadog/span_matcher.h", "src/datadog/span_sampler_config.h", "src/datadog/span_sampler.h", + "src/datadog/string_util.h", "src/datadog/string_view.h", "src/datadog/tag_propagation.h", "src/datadog/tags.h", diff --git a/CMakeLists.txt b/CMakeLists.txt index b36d3399..c1838193 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -121,6 +121,7 @@ target_sources(dd_trace_cpp-objects PRIVATE src/datadog/span_matcher.cpp src/datadog/span_sampler_config.cpp src/datadog/span_sampler.cpp + src/datadog/string_util.cpp src/datadog/tags.cpp src/datadog/tag_propagation.cpp src/datadog/threaded_event_scheduler.cpp @@ -142,6 +143,7 @@ target_sources(dd_trace_cpp-objects PUBLIC BASE_DIRS src/ FILES src/datadog/base64.h + src/datadog/config.h src/datadog/cerr_logger.h src/datadog/clock.h src/datadog/config_manager.h @@ -191,6 +193,7 @@ target_sources(dd_trace_cpp-objects PUBLIC src/datadog/span_matcher.h src/datadog/span_sampler_config.h src/datadog/span_sampler.h + src/datadog/string_util.h src/datadog/string_view.h src/datadog/tag_propagation.h src/datadog/tags.h diff --git a/src/datadog/config.h b/src/datadog/config.h new file mode 100644 index 00000000..aa987865 --- /dev/null +++ b/src/datadog/config.h @@ -0,0 +1,72 @@ +#pragma once + +#include "error.h" +#include "optional.h" + +namespace datadog { +namespace tracing { + +// Enumerates available configuration names for the tracing library +enum class ConfigName : char { + SERVICE_NAME, + SERVICE_ENV, + SERVICE_VERSION, + REPORT_TRACES, + TAGS, + EXTRACTION_STYLES, + INJECTION_STYLES, + STARTUP_LOGS, + REPORT_TELEMETRY, + DELEGATE_SAMPLING, + GENEREATE_128BIT_TRACE_IDS, + AGENT_URL, + RC_POLL_INTERVAL, + TRACE_SAMPLING_RATE, + TRACE_SAMPLING_LIMIT, + TRACE_SAMPLING_RULES, + SPAN_SAMPLING_RULES, +}; + +// Represents metadata for configuration parameters +struct ConfigMetadata { + enum class Origin : char { + ENVIRONMENT_VARIABLE, // Originating from environment variables + CODE, // Defined in code + REMOTE_CONFIG, // Retrieeved from remote configuration + DEFAULT // Default value + }; + + // Name of the configuration parameter + ConfigName name; + // Value of the configuration parameter + std::string value; + // Origin of the configuration parameter + Origin origin; + // Optional error associated with the configuration parameter + Optional error; + + ConfigMetadata() = default; + ConfigMetadata(ConfigName n, std::string v, Origin orig, + Optional err = nullopt) + : name(n), value(std::move(v)), origin(orig), error(err) {} +}; + +// Return a pair containing the configuration origin and value of a +// configuration value chosen from one of the specified `from_env`, +// `from_config`, and `fallback`. This function defines the relative precedence +// among configuration values originating from the environment, programmatic +// configuration, and default configuration. +template +std::pair pick(const Optional &from_env, + const Optional &from_user, + DefaultValue fallback) { + if (from_env) { + return {ConfigMetadata::Origin::ENVIRONMENT_VARIABLE, *from_env}; + } else if (from_user) { + return {ConfigMetadata::Origin::CODE, *from_user}; + } + return {ConfigMetadata::Origin::DEFAULT, fallback}; +} + +} // namespace tracing +} // namespace datadog diff --git a/src/datadog/config_manager.cpp b/src/datadog/config_manager.cpp index 3789c34a..00b11b4f 100644 --- a/src/datadog/config_manager.cpp +++ b/src/datadog/config_manager.cpp @@ -1,5 +1,9 @@ #include "config_manager.h" +#include + +#include "parse_util.h" +#include "string_util.h" #include "trace_sampler.h" namespace datadog { @@ -7,74 +11,110 @@ namespace tracing { ConfigManager::ConfigManager(const FinalizedTracerConfig& config) : clock_(config.clock), - default_trace_sampler_( + default_metadata_(config.metadata), + trace_sampler_( std::make_shared(config.trace_sampler, clock_)), - current_trace_sampler_(default_trace_sampler_), - default_span_defaults_(std::make_shared(config.defaults)), - current_span_defaults_(default_span_defaults_), - default_report_traces_(config.report_traces), - current_report_traces_(default_report_traces_) {} + span_defaults_(std::make_shared(config.defaults)), + report_traces_(config.report_traces) {} std::shared_ptr ConfigManager::trace_sampler() { std::lock_guard lock(mutex_); - return current_trace_sampler_; + return trace_sampler_.value(); } std::shared_ptr ConfigManager::span_defaults() { std::lock_guard lock(mutex_); - return current_span_defaults_; + return span_defaults_.value(); } bool ConfigManager::report_traces() { std::lock_guard lock(mutex_); - return current_report_traces_; + return report_traces_.value(); } -void ConfigManager::update(const ConfigUpdate& conf) { +std::vector ConfigManager::update(const ConfigUpdate& conf) { + std::vector metadata; + std::lock_guard lock(mutex_); - if (conf.trace_sampler) { - if (auto finalized_trace_sampler_cfg = - finalize_config(*conf.trace_sampler)) { - current_trace_sampler_ = - std::make_shared(*finalized_trace_sampler_cfg, clock_); - } else { - // TODO: report error - } + if (!conf.trace_sampling_rate) { + reset_config(ConfigName::TRACE_SAMPLING_RATE, trace_sampler_, metadata); } else { - current_trace_sampler_ = default_trace_sampler_; - } + ConfigMetadata trace_sampling_metadata( + ConfigName::TRACE_SAMPLING_RATE, + to_string(*conf.trace_sampling_rate, 1), + ConfigMetadata::Origin::REMOTE_CONFIG); + + TraceSamplerConfig trace_sampler_cfg; + trace_sampler_cfg.sample_rate = *conf.trace_sampling_rate; - if (conf.tags) { - auto new_span_defaults = - std::make_shared(*current_span_defaults_); - new_span_defaults->tags = std::move(*conf.tags); + auto finalized_trace_sampler_cfg = finalize_config(trace_sampler_cfg); + if (auto error = finalized_trace_sampler_cfg.if_error()) { + trace_sampling_metadata.error = *error; + } + + auto trace_sampler = + std::make_shared(*finalized_trace_sampler_cfg, clock_); - current_span_defaults_ = new_span_defaults; + // This reset rate limiting and `TraceSampler` has no `operator==`. + // TODO: Instead of creating another `TraceSampler`, we should + // update the default sampling rate. + trace_sampler_ = std::move(trace_sampler); + metadata.emplace_back(std::move(trace_sampling_metadata)); + } + + if (!conf.tags) { + reset_config(ConfigName::TAGS, span_defaults_, metadata); } else { - current_span_defaults_ = default_span_defaults_; + ConfigMetadata tags_metadata(ConfigName::TAGS, join(*conf.tags, ","), + ConfigMetadata::Origin::REMOTE_CONFIG); + + auto parsed_tags = parse_tags(*conf.tags); + if (auto error = parsed_tags.if_error()) { + tags_metadata.error = *error; + } + + if (*parsed_tags != span_defaults_.value()->tags) { + auto new_span_defaults = + std::make_shared(*span_defaults_.value()); + new_span_defaults->tags = std::move(*parsed_tags); + + span_defaults_ = new_span_defaults; + metadata.emplace_back(std::move(tags_metadata)); + } } - if (conf.report_traces) { - current_report_traces_ = *conf.report_traces; + if (!conf.report_traces) { + reset_config(ConfigName::REPORT_TRACES, report_traces_, metadata); } else { - current_report_traces_ = default_report_traces_; + if (conf.report_traces != report_traces_.value()) { + report_traces_ = *conf.report_traces; + metadata.emplace_back(ConfigName::REPORT_TRACES, + to_string(*conf.report_traces), + ConfigMetadata::Origin::REMOTE_CONFIG); + } } + + return metadata; } -void ConfigManager::reset() { - std::lock_guard lock(mutex_); - current_trace_sampler_ = default_trace_sampler_; - current_span_defaults_ = default_span_defaults_; - current_report_traces_ = default_report_traces_; +template +void ConfigManager::reset_config(ConfigName name, T& conf, + std::vector& metadata) { + if (conf.is_original_value()) return; + + conf.reset(); + metadata.emplace_back(default_metadata_[name]); } +std::vector ConfigManager::reset() { return update({}); } + nlohmann::json ConfigManager::config_json() const { std::lock_guard lock(mutex_); return nlohmann::json{ - {"default", to_json(*current_span_defaults_)}, - {"trace_sampler", current_trace_sampler_->config_json()}, - {"report_traces", current_report_traces_}}; + {"default", to_json(*span_defaults_.value())}, + {"trace_sampler", trace_sampler_.value()->config_json()}, + {"report_traces", report_traces_.value()}}; } } // namespace tracing diff --git a/src/datadog/config_manager.h b/src/datadog/config_manager.h index 51b3c6b0..190cb9cb 100644 --- a/src/datadog/config_manager.h +++ b/src/datadog/config_manager.h @@ -10,6 +10,7 @@ #include "clock.h" #include "config_update.h" #include "json.hpp" +#include "optional.h" #include "span_defaults.h" #include "tracer_config.h" @@ -17,16 +18,50 @@ namespace datadog { namespace tracing { class ConfigManager { + // A class template for managing dynamic configuration values. + // + // This class allows storing and managing dynamic configuration values. It + // maintains an original value and a current value, allowing for updates and + // resets. + // + // Additionally, it provides methods for accessing the current value and + // checking whether it has been modified from its original state. + template + class DynamicConfig { + Value original_value_; + Optional current_value_; + + public: + // Constructs a DynamicConf object with the given initial value + explicit DynamicConfig(Value original_value) + : original_value_(original_value) {} + + // Resets the current value of the configuration to the original value + void reset() { current_value_ = nullopt; } + + // Returns whether the current value is the original value + bool is_original_value() const { return !current_value_.has_value(); } + + const Value& value() const { + return current_value_.has_value() ? *current_value_ : original_value_; + } + + // Updates the current value of the configuration + void operator=(const Value& rhs) { current_value_ = rhs; } + }; + mutable std::mutex mutex_; Clock clock_; - std::shared_ptr default_trace_sampler_; - std::shared_ptr current_trace_sampler_; + std::unordered_map default_metadata_; - std::shared_ptr default_span_defaults_; - std::shared_ptr current_span_defaults_; + DynamicConfig> trace_sampler_; + DynamicConfig> span_defaults_; + DynamicConfig report_traces_; - bool default_report_traces_; - bool current_report_traces_; + private: + template + void reset_config(ConfigName name, T& conf, + std::vector& metadata); public: ConfigManager(const FinalizedTracerConfig& config); @@ -41,11 +76,11 @@ class ConfigManager { bool report_traces(); // Apply the specified `conf` update. - void update(const ConfigUpdate& conf); + std::vector update(const ConfigUpdate& conf); // Restore the configuration that was passed to this object's constructor, // overriding any previous calls to `update`. - void reset(); + std::vector reset(); // Return a JSON representation of the current configuration managed by this // object. diff --git a/src/datadog/config_update.h b/src/datadog/config_update.h index 20001e22..cd7e07f4 100644 --- a/src/datadog/config_update.h +++ b/src/datadog/config_update.h @@ -15,8 +15,8 @@ namespace tracing { // remote configuration value. struct ConfigUpdate { Optional report_traces; - Optional trace_sampler; - Optional> tags; + Optional trace_sampling_rate; + Optional> tags; }; } // namespace tracing diff --git a/src/datadog/datadog_agent.cpp b/src/datadog/datadog_agent.cpp index 457101c2..484a2b81 100644 --- a/src/datadog/datadog_agent.cpp +++ b/src/datadog/datadog_agent.cpp @@ -360,40 +360,33 @@ void DatadogAgent::flush() { } } -void DatadogAgent::send_app_started() { - auto payload = tracer_telemetry_->app_started(); +void DatadogAgent::send_telemetry(std::string payload) { auto post_result = http_client_->post(telemetry_endpoint_, set_content_type_json, std::move(payload), telemetry_on_response_, telemetry_on_error_, clock_().tick + request_timeout_); if (auto* error = post_result.if_error()) { - logger_->log_error(error->with_prefix( - "Unexpected error submitting telemetry app-started event: ")); + logger_->log_error( + error->with_prefix("Unexpected error submitting telemetry event: ")); } } +void DatadogAgent::send_app_started( + const std::unordered_map& config_metadata) { + send_telemetry(tracer_telemetry_->app_started(config_metadata)); +} + void DatadogAgent::send_heartbeat_and_telemetry() { - auto payload = tracer_telemetry_->heartbeat_and_telemetry(); - auto post_result = - http_client_->post(telemetry_endpoint_, set_content_type_json, - std::move(payload), telemetry_on_response_, - telemetry_on_error_, clock_().tick + request_timeout_); - if (auto* error = post_result.if_error()) { - logger_->log_error(error->with_prefix( - "Unexpected error submitting telemetry app-heartbeat event: ")); - } + send_telemetry(tracer_telemetry_->heartbeat_and_telemetry()); } void DatadogAgent::send_app_closing() { - auto payload = tracer_telemetry_->app_closing(); - auto post_result = - http_client_->post(telemetry_endpoint_, set_content_type_json, - std::move(payload), telemetry_on_response_, - telemetry_on_error_, clock_().tick + request_timeout_); - if (auto* error = post_result.if_error()) { - logger_->log_error(error->with_prefix( - "Unexpected error submitting telemetry app-closing event: ")); - } + send_telemetry(tracer_telemetry_->app_closing()); +} + +void DatadogAgent::send_configuration_change( + const std::vector& config) { + send_telemetry(tracer_telemetry_->configuration_change(config)); } void DatadogAgent::get_and_apply_remote_configuration_updates() { @@ -433,10 +426,11 @@ void DatadogAgent::get_and_apply_remote_configuration_updates() { } if (!response_json.empty()) { - // TODO (during Active Configuration): `process_response` should - // return a list of configuration update and should be consumed by - // telemetry. - remote_config_.process_response(response_json); + auto updated_configuration = + remote_config_.process_response(response_json); + if (!updated_configuration.empty()) { + send_configuration_change(updated_configuration); + } } }; diff --git a/src/datadog/datadog_agent.h b/src/datadog/datadog_agent.h index 1b7f6787..2a23aaff 100644 --- a/src/datadog/datadog_agent.h +++ b/src/datadog/datadog_agent.h @@ -59,6 +59,7 @@ class DatadogAgent : public Collector { RemoteConfigurationManager remote_config_; void flush(); + void send_telemetry(std::string); void send_heartbeat_and_telemetry(); void send_app_closing(); @@ -73,7 +74,10 @@ class DatadogAgent : public Collector { std::vector>&& spans, const std::shared_ptr& response_handler) override; - void send_app_started(); + void send_app_started( + const std::unordered_map& config_metadata); + + void send_configuration_change(const std::vector& config); void get_and_apply_remote_configuration_updates(); diff --git a/src/datadog/datadog_agent_config.cpp b/src/datadog/datadog_agent_config.cpp index 593f8a80..c42f306d 100644 --- a/src/datadog/datadog_agent_config.cpp +++ b/src/datadog/datadog_agent_config.cpp @@ -46,7 +46,7 @@ Expected load_datadog_agent_env_config() { Expected finalize_config( const DatadogAgentConfig& user_config, const std::shared_ptr& logger, const Clock& clock) { - auto env_config = load_datadog_agent_env_config(); + Expected env_config = load_datadog_agent_env_config(); if (auto error = env_config.if_error()) { return *error; } @@ -76,7 +76,7 @@ Expected finalize_config( if (auto flush_interval_milliseconds = value_or(env_config->flush_interval_milliseconds, - user_config.flush_interval_milliseconds, 200); + user_config.flush_interval_milliseconds, 2000); flush_interval_milliseconds > 0) { result.flush_interval = std::chrono::milliseconds(flush_interval_milliseconds); @@ -88,7 +88,7 @@ Expected finalize_config( if (auto request_timeout_milliseconds = value_or(env_config->request_timeout_milliseconds, - user_config.request_timeout_milliseconds, 200); + user_config.request_timeout_milliseconds, 2000); request_timeout_milliseconds > 0) { result.request_timeout = std::chrono::milliseconds(request_timeout_milliseconds); @@ -100,7 +100,7 @@ Expected finalize_config( if (auto shutdown_timeout_milliseconds = value_or(env_config->shutdown_timeout_milliseconds, - user_config.shutdown_timeout_milliseconds, 200); + user_config.shutdown_timeout_milliseconds, 2000); shutdown_timeout_milliseconds > 0) { result.shutdown_timeout = std::chrono::milliseconds(shutdown_timeout_milliseconds); @@ -122,13 +122,15 @@ Expected finalize_config( "positive number of seconds."}; } - auto url = - value_or(env_config->url, user_config.url, "http://localhost:8126"); + const auto [origin, url] = + pick(env_config->url, user_config.url, "http://localhost:8126"); auto parsed_url = HTTPClient::URL::parse(url); if (auto* error = parsed_url.if_error()) { return std::move(*error); } result.url = *parsed_url; + result.metadata[ConfigName::AGENT_URL] = + ConfigMetadata(ConfigName::AGENT_URL, url, origin); return result; } diff --git a/src/datadog/datadog_agent_config.h b/src/datadog/datadog_agent_config.h index a95bd454..ef4e13f4 100644 --- a/src/datadog/datadog_agent_config.h +++ b/src/datadog/datadog_agent_config.h @@ -14,9 +14,11 @@ #include #include #include +#include #include #include "clock.h" +#include "config.h" #include "expected.h" #include "http_client.h" #include "string_view.h" @@ -76,6 +78,7 @@ class FinalizedDatadogAgentConfig { std::chrono::steady_clock::duration request_timeout; std::chrono::steady_clock::duration shutdown_timeout; std::chrono::steady_clock::duration remote_configuration_poll_interval; + std::unordered_map metadata; }; Expected finalize_config( diff --git a/src/datadog/parse_util.cpp b/src/datadog/parse_util.cpp index d7ecd1bc..7632419f 100644 --- a/src/datadog/parse_util.cpp +++ b/src/datadog/parse_util.cpp @@ -47,9 +47,9 @@ StringView strip(StringView input) { const auto not_whitespace = [](unsigned char ch) { return !std::isspace(ch); }; - const char* const begin = + const char *const begin = std::find_if(input.begin(), input.end(), not_whitespace); - const char* const end = + const char *const end = std::find_if(input.rbegin(), std::make_reverse_iterator(begin), not_whitespace) .base(); @@ -107,10 +107,75 @@ bool starts_with(StringView subject, StringView prefix) { prefix.end(); } -void to_lower(std::string& text) { +void to_lower(std::string &text) { std::transform(text.begin(), text.end(), text.begin(), [](unsigned char ch) { return std::tolower(ch); }); } +// List items are separated by an optional comma (",") and any amount of +// whitespace. +// Leading and trailing whitespace is ignored. +std::vector parse_list(StringView input) { + using uchar = unsigned char; + + input = strip(input); + std::vector items; + if (input.empty()) { + return items; + } + + const char *const end = input.end(); + + const char *current = input.begin(); + const char *begin_delim; + do { + const char *begin_item = + std::find_if(current, end, [](uchar ch) { return !std::isspace(ch); }); + begin_delim = std::find_if(begin_item, end, [](uchar ch) { + return std::isspace(ch) || ch == ','; + }); + + items.emplace_back(begin_item, std::size_t(begin_delim - begin_item)); + + const char *end_delim = std::find_if( + begin_delim, end, [](uchar ch) { return !std::isspace(ch); }); + + if (end_delim != end && *end_delim == ',') { + ++end_delim; + } + + current = end_delim; + } while (begin_delim != end); + + return items; +} + +Expected> parse_tags( + std::vector list) { + std::unordered_map tags; + + for (const StringView &token : list) { + const auto separator = std::find(token.begin(), token.end(), ':'); + if (separator == token.end()) { + std::string message; + message += "Unable to parse a key/value from the tag text \""; + append(message, token); + // message += + // "\" because it does not contain the separator character \":\". " + // "Error occurred in list of tags \""; + // append(message, input); + message += "."; + return Error{Error::TAG_MISSING_SEPARATOR, std::move(message)}; + } + + std::string key{token.begin(), separator}; + std::string value{separator + 1, token.end()}; + // If there are duplicate values, then the last one wins. + tags.insert_or_assign(std::move(key), std::move(value)); + } + + return tags; +} + } // namespace tracing } // namespace datadog diff --git a/src/datadog/parse_util.h b/src/datadog/parse_util.h index 65233e87..c2104447 100644 --- a/src/datadog/parse_util.h +++ b/src/datadog/parse_util.h @@ -4,6 +4,8 @@ #include #include +#include +#include #include "expected.h" #include "string_view.h" @@ -39,5 +41,19 @@ bool starts_with(StringView subject, StringView prefix); // Convert the specified `text` to lower case in-place. void to_lower(std::string& text); +// List items are separated by an optional comma (",") and any amount of +// whitespace. +// Leading and trailing whitespace are ignored. +std::vector parse_list(StringView input); + +Expected> parse_tags( + std::vector list); + +inline Expected> parse_tags( + StringView input) { + // Within a tag, the key and value are separated by a colon (":"). + return parse_tags(parse_list(input)); +} + } // namespace tracing } // namespace datadog diff --git a/src/datadog/remote_config.cpp b/src/datadog/remote_config.cpp index 4afc5031..08ce8c6a 100644 --- a/src/datadog/remote_config.cpp +++ b/src/datadog/remote_config.cpp @@ -51,57 +51,22 @@ constexpr std::array k_apm_capabilities = constexpr StringView k_apm_product = "APM_TRACING"; constexpr StringView k_apm_product_path_substring = "/APM_TRACING/"; -Expected> parse_tags( - const std::vector& list_of_tags) { - std::unordered_map tags; - - // Within a tag, the key and value are separated by a colon (":"). - for (const StringView& token : list_of_tags) { - const auto separator = std::find(token.begin(), token.end(), ':'); - if (separator == token.end()) { - std::string message; - message += "Unable to parse a key/value from the tag text \""; - append(message, token); - message += - "\" because it does not contain the separator character \":\"."; - return Error{Error::TAG_MISSING_SEPARATOR, std::move(message)}; - } - - std::string key{token.begin(), separator}; - std::string value{separator + 1, token.end()}; - // If there are duplicate values, then the last one wins. - tags.insert_or_assign(std::move(key), std::move(value)); - } - - return tags; -} - ConfigUpdate parse_dynamic_config(const nlohmann::json& j) { ConfigUpdate config_update; if (auto sampling_rate_it = j.find("tracing_sampling_rate"); sampling_rate_it != j.cend()) { - TraceSamplerConfig trace_sampler_cfg; - trace_sampler_cfg.sample_rate = *sampling_rate_it; - - config_update.trace_sampler = trace_sampler_cfg; + config_update.trace_sampling_rate = *sampling_rate_it; } if (auto tags_it = j.find("tracing_tags"); tags_it != j.cend()) { - auto parsed_tags = parse_tags(*tags_it); - if (parsed_tags.if_error()) { - // TODO: report to telemetry - } else { - config_update.tags = std::move(*parsed_tags); - } + config_update.tags = *tags_it; } if (auto tracing_enabled_it = j.find("tracing_enabled"); tracing_enabled_it != j.cend()) { if (tracing_enabled_it->is_boolean()) { config_update.report_traces = tracing_enabled_it->get(); - } else { - // TODO: report to telemetry } } @@ -171,7 +136,11 @@ nlohmann::json RemoteConfigurationManager::make_request_payload() { return j; } -void RemoteConfigurationManager::process_response(const nlohmann::json& json) { +std::vector RemoteConfigurationManager::process_response( + const nlohmann::json& json) { + std::vector config_update; + config_update.reserve(8); + state_.error_message = nullopt; try { @@ -189,10 +158,12 @@ void RemoteConfigurationManager::process_response(const nlohmann::json& json) { if (client_configs_it == json.cend()) { if (!applied_config_.empty()) { std::for_each(applied_config_.cbegin(), applied_config_.cend(), - [this](const auto it) { revert_config(it.second); }); + [this, &config_update](const auto it) { + config_update = revert_config(it.second); + }); applied_config_.clear(); } - return; + return config_update; } // Keep track of config path received to know which ones to revert. @@ -223,7 +194,7 @@ void RemoteConfigurationManager::process_response(const nlohmann::json& json) { "target file having path \""; append(*state_.error_message, config_path); *state_.error_message += '\"'; - return; + return config_update; } const auto config_json = nlohmann::json::parse( @@ -243,14 +214,14 @@ void RemoteConfigurationManager::process_response(const nlohmann::json& json) { new_config.version = config_json.at("revision"); new_config.content = parse_dynamic_config(config_json.at("lib_config")); - apply_config(new_config); + config_update = apply_config(new_config); applied_config_[std::string{config_path}] = new_config; } // Applied configuration not present must be reverted. for (auto it = applied_config_.cbegin(); it != applied_config_.cend();) { if (!visited_config.count(it->first)) { - revert_config(it->second); + config_update = revert_config(it->second); it = applied_config_.erase(it); } else { it++; @@ -261,15 +232,20 @@ void RemoteConfigurationManager::process_response(const nlohmann::json& json) { error_message += e.what(); state_.error_message = std::move(error_message); + return config_update; } + + return config_update; } -void RemoteConfigurationManager::apply_config(Configuration config) { - config_manager_->update(config.content); +std::vector RemoteConfigurationManager::apply_config( + Configuration config) { + return config_manager_->update(config.content); } -void RemoteConfigurationManager::revert_config(Configuration) { - config_manager_->reset(); +std::vector RemoteConfigurationManager::revert_config( + Configuration) { + return config_manager_->reset(); } } // namespace tracing diff --git a/src/datadog/remote_config.h b/src/datadog/remote_config.h index 86ccfc2d..6d8310e1 100644 --- a/src/datadog/remote_config.h +++ b/src/datadog/remote_config.h @@ -62,17 +62,17 @@ class RemoteConfigurationManager { // Handles the response received from a remote source and udates the internal // state accordingly. - void process_response(const nlohmann::json& json); + std::vector process_response(const nlohmann::json& json); private: // Tell if a `config_path` is a new configuration update. bool is_new_config(StringView config_path, const nlohmann::json& config_meta); // Apply a remote configuration. - void apply_config(Configuration config); + std::vector apply_config(Configuration config); // Revert a remote configuration. - void revert_config(Configuration config); + std::vector revert_config(Configuration config); }; } // namespace tracing diff --git a/src/datadog/span_sampler_config.cpp b/src/datadog/span_sampler_config.cpp index f0dc295c..bde23eb8 100644 --- a/src/datadog/span_sampler_config.cpp +++ b/src/datadog/span_sampler_config.cpp @@ -13,6 +13,15 @@ namespace datadog { namespace tracing { namespace { +std::string to_string(const std::vector &rules) { + nlohmann::json res; + for (const auto &r : rules) { + res.emplace_back(r.to_json()); + } + + return res.dump(); +} + // `env_var` is the name of the environment variable from which `rules_raw` was // obtained. It's used for error messages. Expected> parse_rules(StringView rules_raw, @@ -207,7 +216,7 @@ SpanSamplerConfig::Rule::Rule(const SpanMatcher &base) : SpanMatcher(base) {} Expected finalize_config( const SpanSamplerConfig &user_config, Logger &logger) { - auto env_config = load_span_sampler_env_config(logger); + Expected env_config = load_span_sampler_env_config(logger); if (auto error = env_config.if_error()) { return *error; } @@ -217,8 +226,14 @@ Expected finalize_config( std::vector rules; if (!env_config->rules.empty()) { rules = env_config->rules; + result.metadata[ConfigName::SPAN_SAMPLING_RULES] = + ConfigMetadata(ConfigName::SPAN_SAMPLING_RULES, to_string(rules), + ConfigMetadata::Origin::ENVIRONMENT_VARIABLE); } else if (!user_config.rules.empty()) { rules = user_config.rules; + result.metadata[ConfigName::SPAN_SAMPLING_RULES] = + ConfigMetadata(ConfigName::SPAN_SAMPLING_RULES, to_string(rules), + ConfigMetadata::Origin::CODE); } for (const auto &rule : rules) { diff --git a/src/datadog/span_sampler_config.h b/src/datadog/span_sampler_config.h index 065049ac..a89e25f8 100644 --- a/src/datadog/span_sampler_config.h +++ b/src/datadog/span_sampler_config.h @@ -7,8 +7,10 @@ // `SpanSamplerConfig` is specified as the `span_sampler` property of // `TracerConfig`. +#include #include +#include "config.h" #include "expected.h" #include "json_fwd.hpp" #include "logger.h" @@ -48,6 +50,7 @@ class FinalizedSpanSamplerConfig { }; std::vector rules; + std::unordered_map metadata; }; Expected finalize_config(const SpanSamplerConfig&, diff --git a/src/datadog/string_util.cpp b/src/datadog/string_util.cpp new file mode 100644 index 00000000..9384c81f --- /dev/null +++ b/src/datadog/string_util.cpp @@ -0,0 +1,74 @@ +#include "string_util.h" + +#include +#include + +namespace datadog { +namespace tracing { +namespace { + +template +std::string join(const Sequence& elements, StringView separator, + Func&& append_element) { + auto iter = std::begin(elements); + const auto end = std::end(elements); + std::string result; + if (iter == end) { + return result; + } + append_element(result, *iter); + for (++iter; iter != end; ++iter) { + append(result, separator); + append_element(result, *iter); + } + return result; +} + +} // namespace + +std::string to_string(bool b) { return b ? "true" : "false"; } + +std::string to_string(double d, size_t precision) { + std::stringstream stream; + stream << std::fixed << std::setprecision(precision) << d; + return stream.str(); +} + +std::string join(const std::vector& values, StringView separator) { + return join(values, separator, [](std::string& result, StringView value) { + append(result, value); + }); +} + +std::string join_propagation_styles( + const std::vector& values) { + return join(values, ",", [](std::string& result, PropagationStyle style) { + switch (style) { + case PropagationStyle::B3: + result += "b3"; + break; + case PropagationStyle::DATADOG: + result += "datadog"; + break; + case PropagationStyle::W3C: + result += "tracecontext"; + break; + case PropagationStyle::NONE: + result += "none"; + break; + } + }); +} + +std::string join_tags( + const std::unordered_map& values) { + return join(values, ",", [](std::string& result, const auto& entry) { + const auto& [key, value] = entry; + result += key; + result += ':'; + result += value; + }); +} + +} // namespace tracing +} // namespace datadog diff --git a/src/datadog/string_util.h b/src/datadog/string_util.h new file mode 100644 index 00000000..255a4b3c --- /dev/null +++ b/src/datadog/string_util.h @@ -0,0 +1,29 @@ +#pragma once + +#include +#include + +#include "propagation_style.h" + +namespace datadog { +namespace tracing { + +// Return a string representation of the specified boolean `value`. +// The result is "true" for `true` and "false" for `false`. +std::string to_string(bool b); + +// Converts a double value to a string +std::string to_string(double d, size_t precision); + +// Joins elements of a vector into a single string with a specified separator +std::string join(const std::vector& values, StringView separator); + +// Joins propagation styles into a single comma-separated string +std::string join_propagation_styles(const std::vector&); + +// Joins key-value pairs into a single comma-separated string +std::string join_tags( + const std::unordered_map& values); + +} // namespace tracing +} // namespace datadog diff --git a/src/datadog/trace_sampler_config.cpp b/src/datadog/trace_sampler_config.cpp index 97a65e64..ac4afcd6 100644 --- a/src/datadog/trace_sampler_config.cpp +++ b/src/datadog/trace_sampler_config.cpp @@ -6,6 +6,7 @@ #include "environment.h" #include "json.hpp" #include "parse_util.h" +#include "string_util.h" namespace datadog { namespace tracing { @@ -130,13 +131,22 @@ Expected load_trace_sampler_env_config() { return env_config; } +std::string to_string(const std::vector &rules) { + nlohmann::json res; + for (const auto &r : rules) { + res.emplace_back(r.to_json()); + } + + return res.dump(); +} + } // namespace TraceSamplerConfig::Rule::Rule(const SpanMatcher &base) : SpanMatcher(base) {} Expected finalize_config( const TraceSamplerConfig &config) { - auto env_config = load_trace_sampler_env_config(); + Expected env_config = load_trace_sampler_env_config(); if (auto error = env_config.if_error()) { return *error; } @@ -147,8 +157,14 @@ Expected finalize_config( if (!env_config->rules.empty()) { rules = std::move(env_config->rules); + result.metadata[ConfigName::TRACE_SAMPLING_RULES] = + ConfigMetadata(ConfigName::TRACE_SAMPLING_RULES, to_string(rules), + ConfigMetadata::Origin::ENVIRONMENT_VARIABLE); } else if (!config.rules.empty()) { rules = std::move(config.rules); + result.metadata[ConfigName::TRACE_SAMPLING_RULES] = + ConfigMetadata(ConfigName::TRACE_SAMPLING_RULES, to_string(rules), + ConfigMetadata::Origin::CODE); } for (const auto &rule : rules) { @@ -172,8 +188,14 @@ Expected finalize_config( Optional sample_rate; if (env_config->sample_rate) { sample_rate = env_config->sample_rate; + result.metadata[ConfigName::TRACE_SAMPLING_RATE] = ConfigMetadata( + ConfigName::TRACE_SAMPLING_RATE, to_string(*sample_rate, 1), + ConfigMetadata::Origin::ENVIRONMENT_VARIABLE); } else if (config.sample_rate) { sample_rate = config.sample_rate; + result.metadata[ConfigName::TRACE_SAMPLING_RATE] = ConfigMetadata( + ConfigName::TRACE_SAMPLING_RATE, to_string(*sample_rate, 1), + ConfigMetadata::Origin::CODE); } // If `sample_rate` was specified, then it translates to a "catch-all" rule @@ -191,8 +213,10 @@ Expected finalize_config( result.rules.push_back(std::move(catch_all)); } - auto max_per_second = - value_or(env_config->max_per_second, config.max_per_second, 200); + const auto [origin, max_per_second] = + pick(env_config->max_per_second, config.max_per_second, 200); + result.metadata[ConfigName::TRACE_SAMPLING_LIMIT] = ConfigMetadata( + ConfigName::TRACE_SAMPLING_LIMIT, std::to_string(max_per_second), origin); const auto allowed_types = {FP_NORMAL, FP_SUBNORMAL}; if (!(max_per_second > 0) || diff --git a/src/datadog/trace_sampler_config.h b/src/datadog/trace_sampler_config.h index 5fab1845..bfe93a50 100644 --- a/src/datadog/trace_sampler_config.h +++ b/src/datadog/trace_sampler_config.h @@ -7,8 +7,10 @@ // `TraceSamplerConfig` is specified as the `trace_sampler` property of // `TracerConfig`. +#include #include +#include "config.h" #include "expected.h" #include "json_fwd.hpp" #include "optional.h" @@ -45,6 +47,8 @@ class FinalizedTraceSamplerConfig { std::vector rules; double max_per_second; + + std::unordered_map metadata; }; Expected finalize_config( diff --git a/src/datadog/tracer.cpp b/src/datadog/tracer.cpp index 6a35d0dc..583ecd74 100644 --- a/src/datadog/tracer.cpp +++ b/src/datadog/tracer.cpp @@ -67,7 +67,7 @@ Tracer::Tracer(const FinalizedTracerConfig& config, collector_ = agent; if (tracer_telemetry_->enabled()) { - agent->send_app_started(); + agent->send_app_started(config.metadata); } } diff --git a/src/datadog/tracer_config.cpp b/src/datadog/tracer_config.cpp index 244bdd41..5df8ee3e 100644 --- a/src/datadog/tracer_config.cpp +++ b/src/datadog/tracer_config.cpp @@ -14,6 +14,7 @@ #include "environment.h" #include "json.hpp" #include "parse_util.h" +#include "string_util.h" #include "string_view.h" namespace datadog { @@ -26,44 +27,6 @@ bool falsy(StringView text) { return lower == "0" || lower == "false" || lower == "no"; } -// List items are separated by an optional comma (",") and any amount of -// whitespace. -// Leading and trailing whitespace is ignored. -std::vector parse_list(StringView input) { - using uchar = unsigned char; - - input = strip(input); - std::vector items; - if (input.empty()) { - return items; - } - - const char *const end = input.end(); - - const char *current = input.begin(); - const char *begin_delim; - do { - const char *begin_item = - std::find_if(current, end, [](uchar ch) { return !std::isspace(ch); }); - begin_delim = std::find_if(begin_item, end, [](uchar ch) { - return std::isspace(ch) || ch == ','; - }); - - items.emplace_back(begin_item, std::size_t(begin_delim - begin_item)); - - const char *end_delim = std::find_if( - begin_delim, end, [](uchar ch) { return !std::isspace(ch); }); - - if (end_delim != end && *end_delim == ',') { - ++end_delim; - } - - current = end_delim; - } while (begin_delim != end); - - return items; -} - Expected> parse_propagation_styles( StringView input) { std::vector styles; @@ -108,33 +71,6 @@ Expected> parse_propagation_styles( return styles; } -Expected> parse_tags( - StringView input) { - std::unordered_map tags; - - // Within a tag, the key and value are separated by a colon (":"). - for (const StringView &token : parse_list(input)) { - const auto separator = std::find(token.begin(), token.end(), ':'); - if (separator == token.end()) { - std::string message; - message += "Unable to parse a key/value from the tag text \""; - append(message, token); - message += - "\" because it does not contain the separator character \":\". " - "Error occurred in list of tags \""; - append(message, input); - message += "\"."; - return Error{Error::TAG_MISSING_SEPARATOR, std::move(message)}; - } - std::string key{token.begin(), separator}; - std::string value{separator + 1, token.end()}; - // If there are duplicate values, then the last one wins. - tags.insert_or_assign(std::move(key), std::move(value)); - } - - return tags; -} - // Return a `std::vector` parsed from the specified `env_var`. // If `env_var` is not in the environment, return `nullopt`. If an error occurs, // throw an `Error`. @@ -268,7 +204,7 @@ Expected load_tracer_env_config(Logger &logger) { if (!value_override) { continue; } - // TODO: log + const auto var_name = name(var); const auto var_name_override = name(var_override); @@ -318,71 +254,130 @@ Expected finalize_config(const TracerConfig &user_config, auto logger = user_config.logger ? user_config.logger : std::make_shared(); - auto env = load_tracer_env_config(*logger); - if (auto error = env.if_error()) { + Expected env_config = load_tracer_env_config(*logger); + if (auto error = env_config.if_error()) { return *error; } FinalizedTracerConfig final_config; - final_config.clock = clock; final_config.logger = logger; - final_config.defaults.service = - value_or(env->service, user_config.service, ""); - final_config.defaults.service_type = - value_or(env->service_type, user_config.service_type, "web"); - final_config.defaults.environment = - value_or(env->environment, user_config.environment, ""); - final_config.defaults.version = - value_or(env->version, user_config.version, ""); - final_config.defaults.name = value_or(env->name, user_config.name, ""); - final_config.defaults.tags = - value_or(env->tags, user_config.tags, - std::unordered_map{}); - final_config.extraction_styles = - value_or(env->extraction_styles, user_config.extraction_styles, - std::vector{PropagationStyle::DATADOG, - PropagationStyle::W3C}); - final_config.injection_styles = - value_or(env->injection_styles, user_config.injection_styles, - std::vector{PropagationStyle::DATADOG, - PropagationStyle::W3C}); - final_config.log_on_startup = - value_or(env->log_on_startup, user_config.log_on_startup, true); - final_config.report_traces = - value_or(env->report_traces, user_config.report_traces, true); - final_config.report_telemetry = - value_or(env->report_telemetry, user_config.report_telemetry, true); - final_config.report_hostname = - value_or(env->report_hostname, user_config.report_hostname, false); - final_config.delegate_trace_sampling = value_or( - env->delegate_trace_sampling, user_config.delegate_trace_sampling, false); - final_config.tags_header_size = value_or( - env->max_tags_header_size, user_config.max_tags_header_size, 512); - final_config.generate_128bit_trace_ids = - value_or(env->generate_128bit_trace_ids, - user_config.generate_128bit_trace_ids, true); - final_config.integration_name = - value_or(env->integration_name, user_config.integration_name, ""); - final_config.integration_version = - value_or(env->integration_version, user_config.integration_version, ""); - if (user_config.runtime_id) { - final_config.runtime_id = user_config.runtime_id; - } + ConfigMetadata::Origin origin; + + std::tie(origin, final_config.defaults.service) = + pick(env_config->service, user_config.service, ""); if (final_config.defaults.service.empty()) { return Error{Error::SERVICE_NAME_REQUIRED, "Service name is required."}; } + final_config.metadata[ConfigName::SERVICE_NAME] = ConfigMetadata( + ConfigName::SERVICE_NAME, final_config.defaults.service, origin); + + final_config.defaults.service_type = + value_or(env_config->service_type, user_config.service_type, "web"); + + // DD_ENV + std::tie(origin, final_config.defaults.environment) = + pick(env_config->environment, user_config.environment, ""); + final_config.metadata[ConfigName::SERVICE_ENV] = ConfigMetadata( + ConfigName::SERVICE_ENV, final_config.defaults.environment, origin); + + // DD_VERSION + std::tie(origin, final_config.defaults.version) = + pick(env_config->version, user_config.version, ""); + final_config.metadata[ConfigName::SERVICE_VERSION] = ConfigMetadata( + ConfigName::SERVICE_VERSION, final_config.defaults.version, origin); + + final_config.defaults.name = value_or(env_config->name, user_config.name, ""); + + // DD_TAGS + std::tie(origin, final_config.defaults.tags) = + pick(env_config->tags, user_config.tags, + std::unordered_map{}); + final_config.metadata[ConfigName::TAGS] = ConfigMetadata( + ConfigName::TAGS, join_tags(final_config.defaults.tags), origin); + + // Extraction Styles + const std::vector default_propagation_styles{ + PropagationStyle::DATADOG, PropagationStyle::W3C}; + + std::tie(origin, final_config.extraction_styles) = + pick(env_config->extraction_styles, user_config.extraction_styles, + default_propagation_styles); if (final_config.extraction_styles.empty()) { return Error{Error::MISSING_SPAN_EXTRACTION_STYLE, "At least one extraction style must be specified."}; } + final_config.metadata[ConfigName::EXTRACTION_STYLES] = ConfigMetadata( + ConfigName::EXTRACTION_STYLES, + join_propagation_styles(final_config.extraction_styles), origin); + + // Injection Styles + std::tie(origin, final_config.injection_styles) = + pick(env_config->injection_styles, user_config.injection_styles, + default_propagation_styles); if (final_config.injection_styles.empty()) { return Error{Error::MISSING_SPAN_INJECTION_STYLE, "At least one injection style must be specified."}; } + final_config.metadata[ConfigName::INJECTION_STYLES] = ConfigMetadata( + ConfigName::INJECTION_STYLES, + join_propagation_styles(final_config.injection_styles), origin); + + // Startup Logs + std::tie(origin, final_config.log_on_startup) = + pick(env_config->log_on_startup, user_config.log_on_startup, true); + final_config.metadata[ConfigName::STARTUP_LOGS] = ConfigMetadata( + ConfigName::STARTUP_LOGS, to_string(final_config.log_on_startup), origin); + + // Report traces + std::tie(origin, final_config.report_traces) = + pick(env_config->report_traces, user_config.report_traces, true); + final_config.metadata[ConfigName::REPORT_TRACES] = ConfigMetadata( + ConfigName::REPORT_TRACES, to_string(final_config.report_traces), origin); + + // Report telemetry + std::tie(origin, final_config.report_telemetry) = + pick(env_config->report_telemetry, user_config.report_telemetry, true); + final_config.metadata[ConfigName::REPORT_TELEMETRY] = + ConfigMetadata(ConfigName::REPORT_TELEMETRY, + to_string(final_config.report_traces), origin); + + // Report hostname + final_config.report_hostname = + value_or(env_config->report_hostname, user_config.report_hostname, false); + + // Delegate Sampling + std::tie(origin, final_config.delegate_trace_sampling) = + pick(env_config->delegate_trace_sampling, + user_config.delegate_trace_sampling, false); + final_config.metadata[ConfigName::DELEGATE_SAMPLING] = + ConfigMetadata(ConfigName::DELEGATE_SAMPLING, + to_string(final_config.delegate_trace_sampling), origin); + + // Tags Header Size + final_config.tags_header_size = value_or( + env_config->max_tags_header_size, user_config.max_tags_header_size, 512); + + // 128b Trace IDs + std::tie(origin, final_config.generate_128bit_trace_ids) = + pick(env_config->generate_128bit_trace_ids, + user_config.generate_128bit_trace_ids, true); + final_config.metadata[ConfigName::GENEREATE_128BIT_TRACE_IDS] = + ConfigMetadata(ConfigName::GENEREATE_128BIT_TRACE_IDS, + to_string(final_config.delegate_trace_sampling), origin); + + // Integration name & version + final_config.integration_name = + value_or(env_config->integration_name, user_config.integration_name, ""); + final_config.integration_version = value_or( + env_config->integration_version, user_config.integration_version, ""); + + if (user_config.runtime_id) { + final_config.runtime_id = user_config.runtime_id; + } if (!user_config.collector) { auto finalized = @@ -391,11 +386,13 @@ Expected finalize_config(const TracerConfig &user_config, return std::move(*error); } final_config.collector = *finalized; + final_config.metadata.merge(finalized->metadata); } else { final_config.collector = user_config.collector; } if (auto trace_sampler_config = finalize_config(user_config.trace_sampler)) { + final_config.metadata.merge(trace_sampler_config->metadata); final_config.trace_sampler = std::move(*trace_sampler_config); } else { return std::move(trace_sampler_config.error()); @@ -403,6 +400,7 @@ Expected finalize_config(const TracerConfig &user_config, if (auto span_sampler_config = finalize_config(user_config.span_sampler, *logger)) { + final_config.metadata.merge(span_sampler_config->metadata); final_config.span_sampler = std::move(*span_sampler_config); } else { return std::move(span_sampler_config.error()); diff --git a/src/datadog/tracer_config.h b/src/datadog/tracer_config.h index 8c448361..49cf6ab2 100644 --- a/src/datadog/tracer_config.h +++ b/src/datadog/tracer_config.h @@ -10,6 +10,7 @@ #include #include "clock.h" +#include "config.h" #include "datadog_agent_config.h" #include "error.h" #include "expected.h" @@ -42,7 +43,7 @@ struct TracerConfig { // Example: `prod`, `pre-prod` or `staging`. Optional environment; - // Set the application version. + // Set the service version. // // Overriden by the `DD_VERSION` environment variable. // Example values: `1.2.3`, `6c44da20`, `2020.02.13`. @@ -197,6 +198,7 @@ class FinalizedTracerConfig final { std::string integration_version; bool delegate_trace_sampling; bool report_traces; + std::unordered_map metadata; }; // Return a `FinalizedTracerConfig` from the specified `config` and from any diff --git a/src/datadog/tracer_telemetry.cpp b/src/datadog/tracer_telemetry.cpp index 9ebda3c5..d58ab82e 100644 --- a/src/datadog/tracer_telemetry.cpp +++ b/src/datadog/tracer_telemetry.cpp @@ -7,6 +7,50 @@ namespace datadog { namespace tracing { +namespace { + +std::string to_string(datadog::tracing::ConfigName name) { + switch (name) { + case ConfigName::SERVICE_NAME: + return "service"; + case ConfigName::SERVICE_ENV: + return "env"; + case ConfigName::SERVICE_VERSION: + return "application_version"; + case ConfigName::REPORT_TRACES: + return "trace_enabled"; + case ConfigName::TAGS: + return "trace_tags"; + case ConfigName::EXTRACTION_STYLES: + return "trace_propagation_style_extract"; + case ConfigName::INJECTION_STYLES: + return "trace_propagation_style_inject"; + case ConfigName::STARTUP_LOGS: + return "trace_startup_logs_enabled"; + case ConfigName::REPORT_TELEMETRY: + return "instrumentation_telemetry_enabled"; + case ConfigName::DELEGATE_SAMPLING: + return "DD_TRACE_DELEGATE_SAMPLING"; + case ConfigName::GENEREATE_128BIT_TRACE_IDS: + return "trace_128_bits_id_enabled"; + case ConfigName::AGENT_URL: + return "trace_agent_url"; + case ConfigName::RC_POLL_INTERVAL: + return "remote_config_poll_interval"; + case ConfigName::TRACE_SAMPLING_RATE: + return "trace_sample_rate"; + case ConfigName::TRACE_SAMPLING_LIMIT: + return "trace_rate_limit"; + case ConfigName::SPAN_SAMPLING_RULES: + return "span_sample_rules"; + case ConfigName::TRACE_SAMPLING_RULES: + return "trace_sample_rules"; + } + + std::abort(); +} + +} // namespace TracerTelemetry::TracerTelemetry(bool enabled, const Clock& clock, const std::shared_ptr& logger, @@ -83,12 +127,59 @@ nlohmann::json TracerTelemetry::generate_telemetry_body( }); } -std::string TracerTelemetry::app_started() { +nlohmann::json TracerTelemetry::generate_configuration_field( + const ConfigMetadata& config_metadata) { + // NOTE(@dmehala): `seq_id` should start at 1 so that the go backend can + // detect between non set fields. + config_seq_ids[config_metadata.name] += 1; + auto seq_id = config_seq_ids[config_metadata.name]; + + auto j = nlohmann::json{{"name", to_string(config_metadata.name)}, + {"value", config_metadata.value}, + {"seq_id", seq_id}}; + + switch (config_metadata.origin) { + case ConfigMetadata::Origin::ENVIRONMENT_VARIABLE: + j["origin"] = "env_var"; + break; + case ConfigMetadata::Origin::CODE: + j["origin"] = "code"; + break; + case ConfigMetadata::Origin::REMOTE_CONFIG: + j["origin"] = "remote_config"; + break; + case ConfigMetadata::Origin::DEFAULT: + j["origin"] = "default"; + break; + } + + if (config_metadata.error) { + // clang-format off + j["error"] = { + {"code", config_metadata.error->code}, + {"message", config_metadata.error->message} + }; + // clang-format on + } + + return j; +} + +std::string TracerTelemetry::app_started( + const std::unordered_map& configurations) { + auto configuration_json = nlohmann::json::array(); + for (const auto& [_, config_metadata] : configurations) { + // if (config_metadata.value.empty()) continue; + + configuration_json.emplace_back( + generate_configuration_field(config_metadata)); + } + // clang-format off auto app_started_msg = nlohmann::json{ {"request_type", "app-started"}, {"payload", nlohmann::json{ - {"configuration", nlohmann::json::array()} + {"configuration", configuration_json} }} }; @@ -241,5 +332,22 @@ std::string TracerTelemetry::app_closing() { return message_batch_payload; } +std::string TracerTelemetry::configuration_change( + const std::vector& new_configuration) { + auto configuration_json = nlohmann::json::array(); + for (const auto& config_metadata : new_configuration) { + // if (config_metadata.value.empty()) continue; + configuration_json.emplace_back( + generate_configuration_field(config_metadata)); + } + + auto configuration_change = + generate_telemetry_body("app-client-configuration-change"); + configuration_change["payload"] = + nlohmann::json{{"configuration", configuration_json}}; + + return configuration_change.dump(); +} + } // namespace tracing } // namespace datadog diff --git a/src/datadog/tracer_telemetry.h b/src/datadog/tracer_telemetry.h index f29231a6..6a6ffb33 100644 --- a/src/datadog/tracer_telemetry.h +++ b/src/datadog/tracer_telemetry.h @@ -13,6 +13,7 @@ // - `app-heartbeat` // - `generate-metrics` // - `app-closing` +// - `app-client-configuration-change` // // `app-started` messages are sent as part of initializing the tracer. // @@ -25,9 +26,12 @@ // last `app-heartbeat` event, a `generate-metrics` message is also included in // the batch. // +// `app-client-configuration-change` messages are sent as soon as the tracer +// configuration has been updated by a Remote Configuration event. #include #include "clock.h" +#include "config.h" #include "json.hpp" #include "metrics.h" #include "runtime_id.h" @@ -48,7 +52,10 @@ class TracerTelemetry { std::string hostname_; std::string integration_name_; std::string integration_version_; + // Track sequence id per payload generated uint64_t seq_id_ = 0; + // Track sequence id per configuration field + std::unordered_map config_seq_ids; // This structure contains all the metrics that are exposed by tracer // telemetry. struct { @@ -99,6 +106,9 @@ class TracerTelemetry { nlohmann::json generate_telemetry_body(std::string request_type); + nlohmann::json generate_configuration_field( + const ConfigMetadata& config_metadata); + public: TracerTelemetry(bool enabled, const Clock& clock, const std::shared_ptr& logger, @@ -111,17 +121,21 @@ class TracerTelemetry { auto& metrics() { return metrics_; }; // Constructs an `app-started` message using information provided when // constructed and the tracer_config value passed in. - std::string app_started(); - // This is used to take a snapshot of the current state of metrics and collect - // timestamped "points" of values. These values are later submitted in - // `generate-metrics` messages. + std::string app_started( + const std::unordered_map& configurations); + // This is used to take a snapshot of the current state of metrics and + // collect timestamped "points" of values. These values are later submitted + // in `generate-metrics` messages. void capture_metrics(); - // Constructs a messsage-batch containing `app-heartbeat`, and if metrics have - // been modified, a `generate-metrics` message. + // Constructs a messsage-batch containing `app-heartbeat`, and if metrics + // have been modified, a `generate-metrics` message. std::string heartbeat_and_telemetry(); // Constructs a message-batch containing `app-closing`, and if metrics have // been modified, a `generate-metrics` message. std::string app_closing(); + // Construct an `app-client-configuration-change` message. + std::string configuration_change( + const std::vector& new_configuration); }; } // namespace tracing diff --git a/test/test_datadog_agent.cpp b/test/test_datadog_agent.cpp index d45bae12..cc97acb3 100644 --- a/test/test_datadog_agent.cpp +++ b/test/test_datadog_agent.cpp @@ -1,8 +1,10 @@ #include +#include #include #include #include +#include #include #include "mocks/event_schedulers.h" @@ -11,8 +13,9 @@ #include "test.h" using namespace datadog::tracing; +using namespace std::chrono_literals; -TEST_CASE("CollectorResponse") { +TEST_CASE("CollectorResponse", "[datadog_agent]") { TracerConfig config; config.service = "testsvc"; const auto logger = @@ -166,3 +169,63 @@ TEST_CASE("CollectorResponse") { REQUIRE(logger->first_error().code == error.code); } } + +// NOTE: `report_telemetry` is too vague for now. +// Does it mean no telemetry at all or just metrics are not generated? +// +// TODO: Use cases to implement: +// - telemetry is disabled, no event scheduled. +// - telemetry is enabled, after x sec generate metrics is called. +// - send_app_started? +TEST_CASE("Remote Configuration", "[datadog_agent]") { + const auto logger = + std::make_shared(std::cerr, MockLogger::ERRORS_ONLY); + logger->echo = nullptr; + const auto event_scheduler = std::make_shared(); + const auto http_client = std::make_shared(); + + TracerConfig config; + config.service = "testsvc"; + config.logger = logger; + config.agent.event_scheduler = event_scheduler; + config.agent.http_client = http_client; + config.report_telemetry = false; + + auto finalized = finalize_config(config); + REQUIRE(finalized); + + const TracerSignature signature(RuntimeID::generate(), "testsvc", "test"); + auto config_manager = std::make_shared(*finalized); + + auto telemetry = std::make_shared( + finalized->report_telemetry, finalized->clock, finalized->logger, + signature, "", ""); + + const auto& agent_config = + std::get(finalized->collector); + DatadogAgent agent(agent_config, telemetry, config.logger, signature, + config_manager); + + SECTION("404 do not log an error") { + http_client->response_status = 404; + agent.get_and_apply_remote_configuration_updates(); + http_client->drain(std::chrono::steady_clock::now()); + CHECK(logger->error_count() == 0); + } + + SECTION("5xx log an error") { + http_client->response_status = 500; + agent.get_and_apply_remote_configuration_updates(); + http_client->drain(std::chrono::steady_clock::now()); + CHECK(logger->error_count() == 1); + } + + SECTION("non json input") { + http_client->response_status = 200; + http_client->response_body << "hello, mars!"; + + agent.get_and_apply_remote_configuration_updates(); + http_client->drain(std::chrono::steady_clock::now()); + CHECK(logger->error_count() == 1); + } +} diff --git a/test/test_remote_config.cpp b/test/test_remote_config.cpp index 1a8fde74..2778f4f6 100644 --- a/test/test_remote_config.cpp +++ b/test/test_remote_config.cpp @@ -151,7 +151,8 @@ REMOTE_CONFIG_TEST("response processing") { /* allow_exceptions = */ false); REQUIRE(!response_json.is_discarded()); - rc.process_response(response_json); + const auto config_updated = rc.process_response(response_json); + CHECK(config_updated.empty()); // Next payload should contains an error. const auto payload = rc.make_request_payload(); @@ -201,7 +202,8 @@ REMOTE_CONFIG_TEST("response processing") { const auto old_trace_sampler = config_manager->trace_sampler(); const auto old_span_defaults = config_manager->span_defaults(); const auto old_report_traces = config_manager->report_traces(); - rc.process_response(response_json); + const auto config_updated = rc.process_response(response_json); + REQUIRE(config_updated.size() == 3); const auto new_trace_sampler = config_manager->trace_sampler(); const auto new_span_defaults = config_manager->span_defaults(); const auto new_report_traces = config_manager->report_traces(); @@ -210,7 +212,7 @@ REMOTE_CONFIG_TEST("response processing") { CHECK(new_span_defaults != old_span_defaults); CHECK(new_report_traces != old_report_traces); - SECTION("reset confguration") { + SECTION("reset configuration") { SECTION( "missing from client_configs -> all configurations should be reset") { // clang-format off @@ -227,7 +229,9 @@ REMOTE_CONFIG_TEST("response processing") { REQUIRE(!response_json.is_discarded()); - rc.process_response(response_json); + const auto config_updated = rc.process_response(response_json); + REQUIRE(config_updated.size() == 3); + const auto current_trace_sampler = config_manager->trace_sampler(); const auto current_span_defaults = config_manager->span_defaults(); const auto current_report_traces = config_manager->report_traces(); @@ -237,7 +241,9 @@ REMOTE_CONFIG_TEST("response processing") { CHECK(old_report_traces == current_report_traces); } - SECTION("missing configuration field -> field should be reset") { + SECTION( + "missing the trace_sampling_rate field -> only this field should be " + "reset") { // clang-format off const std::string json_input = R"({ "targets": "ewogICAgInNpZ25lZCI6IHsKICAgICAgICAiY3VzdG9tIjogewogICAgICAgICAgICAiYWdlbnRfcmVmcmVzaF9pbnRlcnZhbCI6IDUsCiAgICAgICAgICAgICJvcGFxdWVfYmFja2VuZF9zdGF0ZSI6ICJleUoyWlhKemFXOXVJam95TENKemRHRjBaU0k2ZXlKbWFXeGxYMmhoYzJobGN5STZleUprWVhSaFpHOW5MekV3TURBeE1qVTROREF2UVZCTlgxUlNRVU5KVGtjdk9ESTNaV0ZqWmpoa1ltTXpZV0l4TkRNMFpETXlNV05pT0RGa1ptSm1OMkZtWlRZMU5HRTBZall4TVRGalpqRTJOakJpTnpGalkyWTRPVGM0TVRrek9DOHlPVEE0Tm1Ka1ltVTFNRFpsTmpoaU5UQm1NekExTlRneU0yRXpaR0UxWTJVd05USTRaakUyTkRCa05USmpaamc0TmpFNE1UWmhZV0U1Wm1ObFlXWTBJanBiSW05WVpESnBlVU16ZUM5b1JXc3hlWFZoWTFoR04xbHFjWEpwVGs5QldVdHVaekZ0V0UwMU5WWktUSGM5SWwxOWZYMD0iCiAgICAgICAgfSwKICAgICAgICAic3BlY192ZXJzaW9uIjogIjEuMC4wIiwKICAgICAgICAidGFyZ2V0cyI6IHsKICAgICAgICAgICAgImZvby9BUE1fVFJBQ0lORy8zMCI6IHsKICAgICAgICAgICAgICAgICJoYXNoZXMiOiB7CiAgICAgICAgICAgICAgICAgICAgInNoYTI1NiI6ICI2OWUzNDZiNWZmY2U4NDVlMjk5ODRlNzU5YjcxZDdiMDdjNTYxOTc5ZmFlOWU4MmVlZDA4MmMwMzhkODZlNmIwIgogICAgICAgICAgICAgICAgfSwKICAgICAgICAgICAgICAgICJsZW5ndGgiOiAzNzQKICAgICAgICAgICAgfQogICAgICAgIH0sCiAgICAgICAgInZlcnNpb24iOiA2NjIwNDMyMAogICAgfQp9", @@ -245,7 +251,7 @@ REMOTE_CONFIG_TEST("response processing") { "target_files": [ { "path": "foo/APM_TRACING/30", - "raw": "eyAiaWQiOiAiODI3ZWFjZjhkYmMzYWIxNDM0ZDMyMWNiODFkZmJmN2FmZTY1NGE0YjYxMTFjZjE2NjBiNzFjY2Y4OTc4MTkzOCIsICJyZXZpc2lvbiI6IDE2OTgxNjcxMjYwNjQsICJzY2hlbWFfdmVyc2lvbiI6ICJ2MS4wLjAiLCAiYWN0aW9uIjogImVuYWJsZSIsICJsaWJfY29uZmlnIjogeyAibGlicmFyeV9sYW5ndWFnZSI6ICJhbGwiLCAibGlicmFyeV92ZXJzaW9uIjogImxhdGVzdCIsICJzZXJ2aWNlX25hbWUiOiAidGVzdHN2YyIsICJlbnYiOiAidGVzdCIsICJ0cmFjaW5nX2VuYWJsZWQiOiB0cnVlIH0sICJzZXJ2aWNlX3RhcmdldCI6IHsgInNlcnZpY2UiOiAidGVzdHN2YyIsICJlbnYiOiAidGVzdCIgfSB9" + "raw": "eyAiaWQiOiAiODI3ZWFjZjhkYmMzYWIxNDM0ZDMyMWNiODFkZmJmN2FmZTY1NGE0YjYxMTFjZjE2NjBiNzFjY2Y4OTc4MTkzOCIsICJyZXZpc2lvbiI6IDE2OTgxNjcxMjYwNjQsICJzY2hlbWFfdmVyc2lvbiI6ICJ2MS4wLjAiLCAiYWN0aW9uIjogImVuYWJsZSIsICJsaWJfY29uZmlnIjogeyAibGlicmFyeV9sYW5ndWFnZSI6ICJhbGwiLCAibGlicmFyeV92ZXJzaW9uIjogImxhdGVzdCIsICJzZXJ2aWNlX25hbWUiOiAidGVzdHN2YyIsICJlbnYiOiAidGVzdCIsICJ0cmFjaW5nX2VuYWJsZWQiOiBmYWxzZSwgInRyYWNpbmdfdGFncyI6IFsiaGVsbG86d29ybGQiLCAiZm9vOmJhciJdIH0sICJzZXJ2aWNlX3RhcmdldCI6IHsgInNlcnZpY2UiOiAidGVzdHN2YyIsICJlbnYiOiAidGVzdCIgfSB9" } ] })"; @@ -258,7 +264,8 @@ REMOTE_CONFIG_TEST("response processing") { REQUIRE(!response_json.is_discarded()); - rc.process_response(response_json); + const auto config_updated = rc.process_response(response_json); + REQUIRE(config_updated.size() == 1); const auto current_trace_sampler = config_manager->trace_sampler(); CHECK(old_trace_sampler == current_trace_sampler); } @@ -303,9 +310,10 @@ REMOTE_CONFIG_TEST("response processing") { REQUIRE(!response_json.is_discarded()); const auto old_sampling_rate = config_manager->trace_sampler(); - rc.process_response(response_json); + const auto config_updated = rc.process_response(response_json); const auto new_sampling_rate = config_manager->trace_sampler(); + CHECK(config_updated.empty()); CHECK(new_sampling_rate == old_sampling_rate); } } diff --git a/test/test_smoke.cpp b/test/test_smoke.cpp index c71fb7bf..924a9312 100644 --- a/test/test_smoke.cpp +++ b/test/test_smoke.cpp @@ -1,3 +1,4 @@ +#include #include #include #include @@ -12,6 +13,7 @@ TEST_CASE("smoke") { TracerConfig config; config.service = "testsvc"; config.logger = std::make_shared(); + config.collector = std::make_shared(); auto maybe_config = finalize_config(config); REQUIRE(maybe_config); diff --git a/test/test_trace_segment.cpp b/test/test_trace_segment.cpp index 50c949da..eddaffd9 100644 --- a/test/test_trace_segment.cpp +++ b/test/test_trace_segment.cpp @@ -1,3 +1,4 @@ +#include #include #include #include @@ -467,6 +468,7 @@ TEST_CASE("independent of Tracer") { TracerConfig config; config.service = "testsvc"; config.name = "do.thing"; + config.collector = std::make_shared(); config.logger = std::make_shared(); auto maybe_tracer = finalize_config(config); diff --git a/test/test_tracer_telemetry.cpp b/test/test_tracer_telemetry.cpp index 5fe1b144..534a17d4 100644 --- a/test/test_tracer_telemetry.cpp +++ b/test/test_tracer_telemetry.cpp @@ -35,17 +35,20 @@ TEST_CASE("Tracer telemetry", "[telemetry]") { SECTION("generates app-started message") { SECTION("Without a defined integration") { - auto app_started_message = tracer_telemetry.app_started(); + auto app_started_message = tracer_telemetry.app_started({}); auto app_started = nlohmann::json::parse(app_started_message); REQUIRE(app_started["request_type"] == "message-batch"); REQUIRE(app_started["payload"].size() == 1); - CHECK(app_started["payload"][0]["request_type"] == "app-started"); + + auto& app_started_payload = app_started["payload"][0]; + CHECK(app_started_payload["request_type"] == "app-started"); + CHECK(app_started_payload["payload"]["configuration"].empty()); } SECTION("With an integration") { TracerTelemetry tracer_telemetry{ true, clock, logger, tracer_signature, "nginx", "1.25.2"}; - auto app_started_message = tracer_telemetry.app_started(); + auto app_started_message = tracer_telemetry.app_started({}); auto app_started = nlohmann::json::parse(app_started_message); REQUIRE(app_started["request_type"] == "message-batch"); REQUIRE(app_started["payload"].size() == 2); @@ -57,6 +60,90 @@ TEST_CASE("Tracer telemetry", "[telemetry]") { CHECK(expected.find(payload["request_type"]) != expected.cend()); } } + + SECTION("With configuration") { + std::unordered_map configuration{ + {ConfigName::SERVICE_NAME, + ConfigMetadata(ConfigName::SERVICE_NAME, "foo", + ConfigMetadata::Origin::CODE)}}; + + auto app_started_message = tracer_telemetry.app_started(configuration); + + auto app_started = nlohmann::json::parse(app_started_message); + REQUIRE(app_started["request_type"] == "message-batch"); + REQUIRE(app_started["payload"].is_array()); + REQUIRE(app_started["payload"].size() == 1); + + auto& app_started_payload = app_started["payload"][0]; + CHECK(app_started_payload["request_type"] == "app-started"); + + auto cfg_payload = app_started_payload["payload"]["configuration"]; + REQUIRE(cfg_payload.is_array()); + REQUIRE(cfg_payload.size() == 1); + + // clang-format off + const auto expected_conf = nlohmann::json({ + {"name", "service"}, + {"value", "foo"}, + {"seq_id", 1}, + {"origin", "code"}, + }); + // clang-format on + + CHECK(cfg_payload[0] == expected_conf); + + SECTION("generates a configuration change event") { + SECTION("empty configuration generate a valid payload") { + auto config_change_message = nlohmann::json::parse( + tracer_telemetry.configuration_change({}), nullptr, false); + REQUIRE(config_change_message.is_discarded() == false); + + CHECK(config_change_message["request_type"] == + "app-client-configuration-change"); + CHECK(config_change_message["payload"]["configuration"].is_array()); + CHECK(config_change_message["payload"]["configuration"].empty()); + } + + SECTION("valid configurations update") { + const std::vector new_config{ + {ConfigName::SERVICE_NAME, "increase seq_id", + ConfigMetadata::Origin::ENVIRONMENT_VARIABLE}, + {ConfigName::REPORT_TRACES, "", ConfigMetadata::Origin::DEFAULT, + Error{Error::Code::OTHER, "empty field"}}}; + + auto config_change_message = nlohmann::json::parse( + tracer_telemetry.configuration_change(new_config), nullptr, + false); + REQUIRE(config_change_message.is_discarded() == false); + + CHECK(config_change_message["request_type"] == + "app-client-configuration-change"); + CHECK(config_change_message["payload"]["configuration"].is_array()); + CHECK(config_change_message["payload"]["configuration"].size() == 2); + + const std::unordered_map expected_json{ + {"service", nlohmann::json{{"name", "service"}, + {"value", "increase seq_id"}, + {"seq_id", 2}, + {"origin", "env_var"}}}, + {"trace_enabled", + nlohmann::json{{"name", "trace_enabled"}, + {"value", ""}, + {"seq_id", 1}, + {"origin", "default"}, + {"error", + {{"code", Error::Code::OTHER}, + {"message", "empty field"}}}}}}; + + for (const auto& conf : + config_change_message["payload"]["configuration"]) { + auto expected_conf = expected_json.find(conf["name"]); + REQUIRE(expected_conf != expected_json.cend()); + CHECK(expected_conf->second == conf); + } + } + } + } } SECTION("generates a heartbeat message") { From 89738616b8d613d777d58e07211b6c0e7962f797 Mon Sep 17 00:00:00 2001 From: Damien Mehala Date: Tue, 12 Mar 2024 13:18:28 +0100 Subject: [PATCH 5/6] Update README.md --- README.md | 2 +- src/datadog/config.h | 4 ++-- src/datadog/config_manager.cpp | 2 -- src/datadog/span_sampler_config.h | 2 +- 4 files changed, 4 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index e193b08d..73a823a9 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ int main() { namespace dd = datadog::tracing; dd::TracerConfig config; - config.defaults.service = "my-service"; + config.service = "my-service"; const auto validated_config = dd::finalize_config(config); if (!validated_config) { diff --git a/src/datadog/config.h b/src/datadog/config.h index aa987865..18b73031 100644 --- a/src/datadog/config.h +++ b/src/datadog/config.h @@ -32,7 +32,7 @@ struct ConfigMetadata { enum class Origin : char { ENVIRONMENT_VARIABLE, // Originating from environment variables CODE, // Defined in code - REMOTE_CONFIG, // Retrieeved from remote configuration + REMOTE_CONFIG, // Retrieved from remote configuration DEFAULT // Default value }; @@ -48,7 +48,7 @@ struct ConfigMetadata { ConfigMetadata() = default; ConfigMetadata(ConfigName n, std::string v, Origin orig, Optional err = nullopt) - : name(n), value(std::move(v)), origin(orig), error(err) {} + : name(n), value(std::move(v)), origin(orig), error(std::move(err)) {} }; // Return a pair containing the configuration origin and value of a diff --git a/src/datadog/config_manager.cpp b/src/datadog/config_manager.cpp index 00b11b4f..b10d41ed 100644 --- a/src/datadog/config_manager.cpp +++ b/src/datadog/config_manager.cpp @@ -1,7 +1,5 @@ #include "config_manager.h" -#include - #include "parse_util.h" #include "string_util.h" #include "trace_sampler.h" diff --git a/src/datadog/span_sampler_config.h b/src/datadog/span_sampler_config.h index a89e25f8..7061c410 100644 --- a/src/datadog/span_sampler_config.h +++ b/src/datadog/span_sampler_config.h @@ -54,7 +54,7 @@ class FinalizedSpanSamplerConfig { }; Expected finalize_config(const SpanSamplerConfig&, - Logger& logger); + Logger&); nlohmann::json to_json(const FinalizedSpanSamplerConfig::Rule&); From 9f81de64cd29bbef7f52c46ce938005a8e048fe0 Mon Sep 17 00:00:00 2001 From: Damien Mehala Date: Wed, 13 Mar 2024 16:28:28 +0100 Subject: [PATCH 6/6] Update src/datadog/config_manager.h --- src/datadog/config_manager.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/datadog/config_manager.h b/src/datadog/config_manager.h index 190cb9cb..88698616 100644 --- a/src/datadog/config_manager.h +++ b/src/datadog/config_manager.h @@ -34,7 +34,7 @@ class ConfigManager { public: // Constructs a DynamicConf object with the given initial value explicit DynamicConfig(Value original_value) - : original_value_(original_value) {} + : original_value_(std::move(original_value)) {} // Resets the current value of the configuration to the original value void reset() { current_value_ = nullopt; }