diff --git a/BUILD.bazel b/BUILD.bazel index 84697f6c..ae4a9631 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", @@ -34,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", @@ -50,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", @@ -98,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 1fd9ed3c..c1838193 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 @@ -120,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 @@ -141,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 @@ -190,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/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/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/config.h b/src/datadog/config.h new file mode 100644 index 00000000..18b73031 --- /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, // Retrieved 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(std::move(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 9f619fd5..d2badc83 100644 --- a/src/datadog/config_manager.cpp +++ b/src/datadog/config_manager.cpp @@ -1,5 +1,7 @@ #include "config_manager.h" +#include "parse_util.h" +#include "string_util.h" #include "trace_sampler.h" namespace datadog { @@ -7,74 +9,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); - if (conf.tags) { - auto new_span_defaults = - std::make_shared(*current_span_defaults_); - new_span_defaults->tags = std::move(*conf.tags); + TraceSamplerConfig trace_sampler_cfg; + trace_sampler_cfg.sample_rate = *conf.trace_sampling_rate; + + auto finalized_trace_sampler_cfg = finalize_config(trace_sampler_cfg); + if (auto error = finalized_trace_sampler_cfg.if_error()) { + trace_sampling_metadata.error = *error; + } - current_span_defaults_ = new_span_defaults; + auto trace_sampler = + std::make_shared(*finalized_trace_sampler_cfg, clock_); + + // 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{ - {"defaults", to_json(*current_span_defaults_)}, - {"trace_sampler", current_trace_sampler_->config_json()}, - {"report_traces", current_report_traces_}}; + {"defaults", 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..88698616 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_(std::move(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 fd98d81d..c42f306d 100644 --- a/src/datadog/datadog_agent_config.cpp +++ b/src/datadog/datadog_agent_config.cpp @@ -12,86 +12,50 @@ 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) { + Expected 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 @@ -101,83 +65,72 @@ 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, 2000); + 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, 2000); + 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, 2000); + 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 (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; - } - - 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 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); - if (auto* error = url.if_error()) { + 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 = *url; + 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 59d9f10b..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" @@ -33,7 +35,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 +49,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); }; @@ -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/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/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 40fe2dc6..bde23eb8 100644 --- a/src/datadog/span_sampler_config.cpp +++ b/src/datadog/span_sampler_config.cpp @@ -8,12 +8,20 @@ #include "environment.h" #include "expected.h" #include "json.hpp" -#include "logger.h" 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, @@ -129,15 +137,8 @@ Expected> parse_rules(StringView rules_raw, return rules; } -} // namespace - -SpanSamplerConfig::Rule::Rule(const SpanMatcher &base) : SpanMatcher(base) {} - -Expected finalize_config( - const SpanSamplerConfig &config, Logger &logger) { - FinalizedSpanSamplerConfig result; - - std::vector rules = config.rules; +Expected load_span_sampler_env_config(Logger &logger) { + SpanSamplerConfig env_config; auto rules_env = lookup(environment::DD_SPAN_SAMPLING_RULES); if (rules_env) { @@ -146,7 +147,7 @@ Expected finalize_config( if (auto *error = maybe_rules.if_error()) { return std::move(*error); } - rules = std::move(*maybe_rules); + env_config.rules = std::move(*maybe_rules); } if (auto file_env = lookup(environment::DD_SPAN_SAMPLING_RULES_FILE)) { @@ -202,10 +203,39 @@ Expected finalize_config( return error->with_prefix(prefix); } - rules = std::move(*maybe_rules); + env_config.rules = std::move(*maybe_rules); } } + return env_config; +} + +} // namespace + +SpanSamplerConfig::Rule::Rule(const SpanMatcher &base) : SpanMatcher(base) {} + +Expected finalize_config( + const SpanSamplerConfig &user_config, Logger &logger) { + Expected env_config = load_span_sampler_env_config(logger); + if (auto error = env_config.if_error()) { + return *error; + } + + FinalizedSpanSamplerConfig result; + + 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) { 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..7061c410 100644 --- a/src/datadog/span_sampler_config.h +++ b/src/datadog/span_sampler_config.h @@ -7,10 +7,13 @@ // `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" #include "optional.h" #include "rate.h" #include "span_matcher.h" @@ -18,8 +21,6 @@ namespace datadog { namespace tracing { -class Logger; - struct SpanSamplerConfig { struct Rule : public SpanMatcher { double sample_rate = 1.0; @@ -49,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 10813a64..ac4afcd6 100644 --- a/src/datadog/trace_sampler_config.cpp +++ b/src/datadog/trace_sampler_config.cpp @@ -6,20 +6,16 @@ #include "environment.h" #include "json.hpp" #include "parse_util.h" +#include "string_util.h" namespace datadog { namespace tracing { +namespace { -TraceSamplerConfig::Rule::Rule(const SpanMatcher &base) : SpanMatcher(base) {} - -Expected finalize_config( - const TraceSamplerConfig &config) { - FinalizedTraceSamplerConfig result; - - std::vector rules = config.rules; +Expected load_trace_sampler_env_config() { + TraceSamplerConfig env_config; if (auto rules_env = lookup(environment::DD_TRACE_SAMPLING_RULES)) { - rules.clear(); nlohmann::json json_rules; try { json_rules = nlohmann::json::parse(*rules_env); @@ -104,8 +100,71 @@ Expected finalize_config( std::move(message)}; } - rules.emplace_back(std::move(rule)); + 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; +} + +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) { + Expected env_config = load_trace_sampler_env_config(); + if (auto error = env_config.if_error()) { + return *error; + } + + FinalizedTraceSamplerConfig result; + + std::vector rules; + + 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) { @@ -126,17 +185,17 @@ Expected finalize_config( result.rules.push_back(std::move(finalized)); } - 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; + 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 @@ -154,18 +213,10 @@ 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; - } + 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) || @@ -175,7 +226,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..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" @@ -28,7 +30,7 @@ struct TraceSamplerConfig { Optional sample_rate; std::vector rules; - double max_per_second = 200; + Optional max_per_second; }; class FinalizedTraceSamplerConfig { @@ -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 24dcf749..583ecd74 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) @@ -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 b7a726dc..5df8ee3e 100644 --- a/src/datadog/tracer_config.cpp +++ b/src/datadog/tracer_config.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include #include "cerr_logger.h" @@ -13,6 +14,7 @@ #include "environment.h" #include "json.hpp" #include "parse_util.h" +#include "string_util.h" #include "string_view.h" namespace datadog { @@ -25,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; @@ -107,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`. @@ -161,17 +98,59 @@ 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; +Expected load_tracer_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.generate_128bit_trace_ids = !falsy(*enabled_env); + } + + // 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 +166,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,40 +204,43 @@ Expected finalize_propagation_styles(FinalizedTracerConfig &result, if (!value_override) { continue; } + 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 @@ -267,112 +249,164 @@ Expected finalize_config(const TracerConfig &config) { return finalize_config(config, default_clock); } -Expected finalize_config(const TracerConfig &config, +Expected finalize_config(const TracerConfig &user_config, const Clock &clock) { - FinalizedTracerConfig result; + auto logger = + user_config.logger ? user_config.logger : std::make_shared(); - result.clock = clock; - result.defaults = config.defaults; - - if (auto service_env = lookup(environment::DD_SERVICE)) { - assign(result.defaults.service, *service_env); - } - if (result.defaults.service.empty()) { - return Error{Error::SERVICE_NAME_REQUIRED, "Service name is required."}; + Expected env_config = load_tracer_env_config(*logger); + if (auto error = env_config.if_error()) { + return *error; } - if (auto environment_env = lookup(environment::DD_ENV)) { - assign(result.defaults.environment, *environment_env); - } - if (auto version_env = lookup(environment::DD_VERSION)) { - assign(result.defaults.version, *version_env); - } + FinalizedTracerConfig final_config; + final_config.clock = clock; + final_config.logger = logger; - 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); - } + ConfigMetadata::Origin origin; - if (config.logger) { - result.logger = config.logger; - } else { - result.logger = std::make_shared(); - } + std::tie(origin, final_config.defaults.service) = + pick(env_config->service, user_config.service, ""); - 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); + if (final_config.defaults.service.empty()) { + return Error{Error::SERVICE_NAME_REQUIRED, "Service name is required."}; } - result.report_traces = config.report_traces; - if (auto enabled_env = lookup(environment::DD_TRACE_ENABLED)) { - result.report_traces = !falsy(*enabled_env); + 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 (!config.collector) { - auto finalized = finalize_config(config.agent, result.logger, clock); + if (!user_config.collector) { + auto finalized = + finalize_config(user_config.agent, final_config.logger, clock); if (auto *error = finalized.if_error()) { return std::move(*error); } - result.collector = *finalized; + final_config.collector = *finalized; + final_config.metadata.merge(finalized->metadata); } else { - result.collector = config.collector; + final_config.collector = user_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); - } - - 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(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()); } if (auto span_sampler_config = - finalize_config(config.span_sampler, *result.logger)) { - result.span_sampler = std::move(*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()); } - 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_config; } } // namespace tracing diff --git a/src/datadog/tracer_config.h b/src/datadog/tracer_config.h index c9ed4dda..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" @@ -28,10 +29,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 service 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 +66,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 +101,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 +111,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; + // `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 // `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 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 @@ -127,17 +149,26 @@ 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; + + // 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 // 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); FinalizedTracerConfig() = default; @@ -159,7 +190,7 @@ class FinalizedTracerConfig { 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; @@ -167,6 +198,7 @@ class FinalizedTracerConfig { 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_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..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,10 +13,11 @@ #include "test.h" using namespace datadog::tracing; +using namespace std::chrono_literals; -TEST_CASE("CollectorResponse") { +TEST_CASE("CollectorResponse", "[datadog_agent]") { 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(); @@ -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 0e7ff156..2778f4f6 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 = @@ -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 fc94807e..924a9312 100644 --- a/test/test_smoke.cpp +++ b/test/test_smoke.cpp @@ -1,3 +1,4 @@ +#include #include #include #include @@ -10,8 +11,9 @@ using namespace datadog::tracing; TEST_CASE("smoke") { TracerConfig config; - config.defaults.service = "testsvc"; + 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_span.cpp b/test/test_span.cpp index 671cea10..3cdf599c 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); + config.generate_128bit_trace_ids = true; + + 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..eddaffd9 100644 --- a/test/test_trace_segment.cpp +++ b/test/test_trace_segment.cpp @@ -1,3 +1,4 @@ +#include #include #include #include @@ -29,21 +30,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 +53,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 +144,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 +165,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 +466,9 @@ 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.collector = std::make_shared(); 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..987a7ba2 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.trace_id_128_bit = true; + config.service = "testsvc"; + config.generate_128bit_trace_ids = 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.trace_id_128_bit = true; + config.service = "testsvc"; + config.generate_128bit_trace_ids = 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..736dec9c 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,16 +1292,20 @@ 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->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") { @@ -1329,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); } } 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") {