diff --git a/BUILD.bazel b/BUILD.bazel index a91d7ec6..d8ab12fd 100644 --- a/BUILD.bazel +++ b/BUILD.bazel @@ -1,8 +1,10 @@ cc_library( name = "dd_trace_cpp", srcs = [ + "src/datadog/base64.cpp", "src/datadog/cerr_logger.cpp", "src/datadog/clock.cpp", + "src/datadog/config_manager.cpp", "src/datadog/collector_response.cpp", # "src/datadog/curl.cpp", no libcurl "src/datadog/datadog_agent_config.cpp", @@ -24,6 +26,7 @@ cc_library( "src/datadog/propagation_style.cpp", "src/datadog/random.cpp", "src/datadog/rate.cpp", + "src/datadog/remote_config.cpp", "src/datadog/runtime_id.cpp", "src/datadog/span.cpp", "src/datadog/span_data.cpp", @@ -45,8 +48,11 @@ cc_library( "src/datadog/w3c_propagation.cpp", ], hdrs = [ + "src/datadog/base64.h", "src/datadog/cerr_logger.h", "src/datadog/clock.h", + "src/datadog/config_manager.h", + "src/datadog/config_update.h", "src/datadog/collector.h", "src/datadog/collector_response.h", # "src/datadog/curl.h", no libcurl @@ -78,6 +84,7 @@ cc_library( "src/datadog/propagation_style.h", "src/datadog/random.h", "src/datadog/rate.h", + "src/datadog/remote_config.h", "src/datadog/runtime_id.h", "src/datadog/sampling_decision.h", "src/datadog/sampling_mechanism.h", @@ -95,6 +102,7 @@ cc_library( "src/datadog/tags.h", "src/datadog/threaded_event_scheduler.h", "src/datadog/tracer_config.h", + "src/datadog/tracer_signature.h", "src/datadog/tracer_telemetry.h", "src/datadog/tracer.h", "src/datadog/trace_id.h", diff --git a/CMakeLists.txt b/CMakeLists.txt index 068c3619..7a777423 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -7,10 +7,14 @@ project(dd-trace-cpp) option(BUILD_COVERAGE "Build code with code coverage profiling instrumentation" OFF) option(BUILD_HASHER_EXAMPLE "Build the example program examples/hasher" OFF) option(BUILD_TESTING "Build the unit tests (test/)" OFF) +option(BUILD_FUZZERS "Build fuzzers" OFF) +option(BUILD_BENCHMARK "Build benchmark binaries" OFF) option(SANITIZE "Build with address sanitizer and undefined behavior sanitizer" OFF) -option(FUZZ_W3C_PROPAGATION "Build a fuzzer for W3C propagation" OFF) -set(CMAKE_BUILD_TYPE "RelWithDebInfo") +if (NOT CMAKE_BUILD_TYPE) + set(CMAKE_BUILD_TYPE "RelWithDebInfo") +endif () + set(CMAKE_POSITION_INDEPENDENT_CODE ON) set(CMAKE_EXPORT_COMPILE_COMMANDS ON) set(CMAKE_CXX_STANDARD 17) @@ -32,7 +36,7 @@ if (CMAKE_CXX_COMPILER_ID STREQUAL "Clang") # But there's one exception: libfuzzer is built with libstdc++ on Ubuntu, # and so won't link to libc++. So, if any of the FUZZ_* variables are set, # keep to libstdc++ (the default on most systems). - if (NOT ${FUZZ_W3C_PROPAGATION}) + if (NOT ${BUILD_FUZZERS}) add_compile_options(-stdlib=libc++) add_link_options(-stdlib=libc++) endif () @@ -45,11 +49,12 @@ function(add_sanitizers) add_link_options(-fsanitize=undefined) endfunction() -if(FUZZ_W3C_PROPAGATION) +if(BUILD_FUZZERS) + set(BUILD_TESTING OFF) add_compile_options(-fsanitize=fuzzer) add_link_options(-fsanitize=fuzzer) add_sanitizers() - add_subdirectory(fuzz/w3c-propagation) + add_subdirectory(fuzz) endif() if (SANITIZE) @@ -82,8 +87,10 @@ endif() add_library(dd_trace_cpp-objects OBJECT) target_sources(dd_trace_cpp-objects PRIVATE + src/datadog/base64.cpp src/datadog/cerr_logger.cpp src/datadog/clock.cpp + src/datadog/config_manager.cpp src/datadog/collector_response.cpp src/datadog/curl.cpp src/datadog/datadog_agent_config.cpp @@ -105,6 +112,7 @@ target_sources(dd_trace_cpp-objects PRIVATE src/datadog/propagation_style.cpp src/datadog/random.cpp src/datadog/rate.cpp + src/datadog/remote_config.cpp src/datadog/runtime_id.cpp src/datadog/span.cpp src/datadog/span_data.cpp @@ -132,8 +140,11 @@ target_sources(dd_trace_cpp-objects PUBLIC TYPE HEADERS BASE_DIRS src/ FILES + src/datadog/base64.h src/datadog/cerr_logger.h src/datadog/clock.h + src/datadog/config_manager.h + src/datadog/config_update.h src/datadog/collector.h src/datadog/collector_response.h # src/datadog/curl.h except for curl.h @@ -165,6 +176,7 @@ target_sources(dd_trace_cpp-objects PUBLIC src/datadog/propagation_style.h src/datadog/random.h src/datadog/rate.h + src/datadog/remote_config.h src/datadog/runtime_id.h src/datadog/sampling_decision.h src/datadog/sampling_mechanism.h @@ -182,6 +194,7 @@ target_sources(dd_trace_cpp-objects PUBLIC src/datadog/tags.h src/datadog/threaded_event_scheduler.h src/datadog/tracer_config.h + src/datadog/tracer_signature.h src/datadog/tracer_telemetry.h src/datadog/tracer.h src/datadog/trace_id.h diff --git a/bin/cmake-build b/bin/cmake-build index 1ba61e27..34926942 100755 --- a/bin/cmake-build +++ b/bin/cmake-build @@ -6,5 +6,5 @@ cd "$(dirname "$0")"/.. mkdir -p .build cd .build -cmake .. +cmake .. "$@" make -j "$(nproc)" diff --git a/fuzz/CMakeLists.txt b/fuzz/CMakeLists.txt new file mode 100644 index 00000000..251f9c19 --- /dev/null +++ b/fuzz/CMakeLists.txt @@ -0,0 +1,2 @@ +add_subdirectory(base64) +add_subdirectory(w3c-propagation) diff --git a/fuzz/README.md b/fuzz/README.md index 3f0181c3..648d3e40 100644 --- a/fuzz/README.md +++ b/fuzz/README.md @@ -3,24 +3,19 @@ Fuzzers Each subdirectory here contains the source of an executable that [fuzz tests][1] some part of the library using [LLVM's libfuzzer][2]. -There is a toplevel CMake boolean option associated with each fuzzer. The naming -convention is `FUZZ_`, e.g. -`FUZZ_W3C_PROPAGATION` for the fuzzer defined in -[fuzz/w3c-propagation/](./w3c-propagation/). The resulting binary is called -`fuzz` by convention. +There is a toplevel CMake boolean option that adds all of the fuzzer +executables to the build: `BUILD_FUZZERS`. -When building a fuzzer, the toolchain must be clang-based. For example, this -is how to build the fuzzer in [fuzz/w3c-propagation](./w3c-propagation/) from -the root of the repository: +When building the fuzzers, the toolchain must be clang-based. For example: ```console -$ rm -rf .build && mkdir .build # if toolchain or test setup need clearing -$ cd .build -$ CC=clang CXX=clang++ cmake .. -DFUZZ_W3C_PROPAGATION=ON -$ make -j $(nproc) -$ fuzz/w3c-propagation/fuzz +$ rm -rf .build # if toolchain needs clearing +$ bin/with-toolchain llvm bin/cmake-build -DBUILD_FUZZERS=1 +$ .build/fuzz/w3c-propagation/w3c-propagation-fuzz [... fuzzer output ...] ``` +The fuzzer executables are named `.build/fuzz/*/*-fuzz` by convention. + [1]: https://en.wikipedia.org/wiki/Fuzzing [2]: https://llvm.org/docs/LibFuzzer.html diff --git a/fuzz/base64/CMakeLists.txt b/fuzz/base64/CMakeLists.txt new file mode 100644 index 00000000..261e888b --- /dev/null +++ b/fuzz/base64/CMakeLists.txt @@ -0,0 +1,4 @@ +add_executable(base64-fuzz main.cpp) + +add_dependencies(base64-fuzz dd_trace_cpp-static) +target_link_libraries(base64-fuzz dd_trace_cpp-static) diff --git a/fuzz/base64/main.cpp b/fuzz/base64/main.cpp new file mode 100644 index 00000000..bd0e9712 --- /dev/null +++ b/fuzz/base64/main.cpp @@ -0,0 +1,11 @@ +#include +#include + +#include + +namespace dd = datadog::tracing; + +extern "C" int LLVMFuzzerTestOneInput(const std::uint8_t* data, size_t size) { + dd::base64_decode(dd::StringView{(const char*)data, size}); + return 0; +} diff --git a/fuzz/w3c-propagation/CMakeLists.txt b/fuzz/w3c-propagation/CMakeLists.txt index cf3d2fce..00d7b28e 100644 --- a/fuzz/w3c-propagation/CMakeLists.txt +++ b/fuzz/w3c-propagation/CMakeLists.txt @@ -1,4 +1,4 @@ -add_executable(fuzz fuzz.cpp) +add_executable(w3c-propagation-fuzz fuzz.cpp) -add_dependencies(fuzz dd_trace_cpp-static) -target_link_libraries(fuzz dd_trace_cpp-static) +add_dependencies(w3c-propagation-fuzz dd_trace_cpp-static) +target_link_libraries(w3c-propagation-fuzz dd_trace_cpp-static) diff --git a/src/datadog/base64.cpp b/src/datadog/base64.cpp new file mode 100644 index 00000000..decc6df4 --- /dev/null +++ b/src/datadog/base64.cpp @@ -0,0 +1,88 @@ +#include "base64.h" + +#include +#include + +namespace datadog { +namespace tracing { + +constexpr uint8_t k_sentinel = 255; +constexpr uint8_t _ = k_sentinel; // for brevity +constexpr uint8_t k_eol = 0; + +// Invalid inputs are mapped to the value 255. '=' maps to 0. +constexpr uint8_t k_base64_table[] = { + _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, + _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, + _, _, _, _, _, _, _, 62, _, _, _, 63, 52, 53, 54, 55, 56, 57, + 58, 59, 60, 61, _, _, _, k_eol, _, _, _, 0, 1, 2, 3, 4, 5, 6, + 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, + 25, _, _, _, _, _, _, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, + 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, _, _, _, + _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, + _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, + _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, + _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, + _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, + _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, + _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, + _, _, _, _}; + +std::string base64_decode(StringView input) { + const size_t in_size = input.size(); + + std::string output; + output.reserve(in_size); + + size_t i = 0; + + for (; i + 4 < in_size;) { + uint32_t c0 = k_base64_table[static_cast(input[i++])]; + uint32_t c1 = k_base64_table[static_cast(input[i++])]; + uint32_t c2 = k_base64_table[static_cast(input[i++])]; + uint32_t c3 = k_base64_table[static_cast(input[i++])]; + + if (c0 == k_sentinel || c1 == k_sentinel || c2 == k_sentinel || + c3 == k_sentinel) { + return ""; + } + + output.push_back(c0 << 2 | (c1 & 0xF0) >> 4); + output.push_back((c1 & 0x0F) << 4 | ((c2 & 0x3C) >> 2)); + output.push_back(((c2 & 0x03) << 6) | (c3 & 0x3F)); + } + + // If padding is missing, return the empty string in lieu of an Error. + if ((in_size - i) < 4) return ""; + + uint32_t c0 = k_base64_table[static_cast(input[i++])]; + uint32_t c1 = k_base64_table[static_cast(input[i++])]; + uint32_t c2 = k_base64_table[static_cast(input[i++])]; + uint32_t c3 = k_base64_table[static_cast(input[i++])]; + + if (c0 == k_sentinel || c1 == k_sentinel || c2 == k_sentinel || + c3 == k_sentinel) { + return ""; + } + + if (c2 == k_eol) { + // The last quadruplet is of the form "xx==", where only one character needs + // to be decoded. + output.push_back(c0 << 2 | (c1 & 0xF0) >> 4); + } else if (c3 == k_eol) { + // The last quadruplet is of the form "xxx=", where only two character needs + // to be decoded. + output.push_back(c0 << 2 | (c1 & 0xF0) >> 4); + output.push_back((c1 & 0x0F) << 4 | ((c2 & 0x3C) >> 2)); + } else { + // The last quadruplet is not padded -> common use case + output.push_back(c0 << 2 | (c1 & 0xF0) >> 4); + output.push_back((c1 & 0x0F) << 4 | ((c2 & 0x3C) >> 2)); + output.push_back(((c2 & 0x03) << 6) | (c3 & 0x3F)); + } + + return output; +} + +} // namespace tracing +} // namespace datadog diff --git a/src/datadog/base64.h b/src/datadog/base64.h new file mode 100644 index 00000000..f2539872 --- /dev/null +++ b/src/datadog/base64.h @@ -0,0 +1,15 @@ +#pragma once + +#include + +#include "string_view.h" + +namespace datadog { +namespace tracing { + +// Return the result of decoding the specified padded base64-encoded `input`. If +// `input` is not padded, then return the empty string instead. +std::string base64_decode(StringView input); + +} // namespace tracing +} // namespace datadog diff --git a/src/datadog/config_manager.cpp b/src/datadog/config_manager.cpp new file mode 100644 index 00000000..a13ac346 --- /dev/null +++ b/src/datadog/config_manager.cpp @@ -0,0 +1,47 @@ +#include "config_manager.h" + +#include "trace_sampler.h" + +namespace datadog { +namespace tracing { + +ConfigManager::ConfigManager(const FinalizedTracerConfig& config) + : clock_(config.clock), + default_trace_sampler_( + std::make_shared(config.trace_sampler, clock_)), + current_trace_sampler_(default_trace_sampler_) {} + +std::shared_ptr ConfigManager::get_trace_sampler() { + std::lock_guard lock(mutex_); + return current_trace_sampler_; +} + +void ConfigManager::update(const ConfigUpdate& conf) { + 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 + } + } else { + current_trace_sampler_ = default_trace_sampler_; + } +} + +void ConfigManager::reset() { + std::lock_guard lock(mutex_); + current_trace_sampler_ = default_trace_sampler_; +} + +nlohmann::json ConfigManager::config_json() const { + std::lock_guard lock(mutex_); + return nlohmann::json{ + {"trace_sampler", current_trace_sampler_->config_json()}}; +} + +} // namespace tracing +} // namespace datadog diff --git a/src/datadog/config_manager.h b/src/datadog/config_manager.h new file mode 100644 index 00000000..1b48a6a8 --- /dev/null +++ b/src/datadog/config_manager.h @@ -0,0 +1,43 @@ +#pragma once + +// The `ConfigManager` class is designed to handle configuration update +// and provide access to the current configuration. +// It utilizes a mutex to ensure thread safety when updating or accessing +// the configuration. + +#include + +#include "clock.h" +#include "config_update.h" +#include "json.hpp" +#include "tracer_config.h" + +namespace datadog { +namespace tracing { + +class ConfigManager { + mutable std::mutex mutex_; + Clock clock_; + std::shared_ptr default_trace_sampler_; + std::shared_ptr current_trace_sampler_; + + public: + ConfigManager(const FinalizedTracerConfig& config); + + // Return the `TraceSampler` consistent with the most recent configuration. + std::shared_ptr get_trace_sampler(); + + // Apply the specified `conf` update. + void update(const ConfigUpdate& conf); + + // Restore the configuration that was passed to this object's constructor, + // overriding any previous calls to `update`. + void reset(); + + // Return a JSON representation of the current configuration managed by this + // object. + nlohmann::json config_json() const; +}; + +} // namespace tracing +} // namespace datadog diff --git a/src/datadog/config_update.h b/src/datadog/config_update.h new file mode 100644 index 00000000..57818c3e --- /dev/null +++ b/src/datadog/config_update.h @@ -0,0 +1,19 @@ +#pragma once + +#include "optional" +#include "trace_sampler_config.h" + +namespace datadog { +namespace tracing { + +// The `ConfigUpdate` struct serves as a container for configuration that can +// exclusively be changed remotely. +// +// Configurations can be `nullopt` to signal the absence of a value from the +// remote configuration value. +struct ConfigUpdate { + Optional trace_sampler; +}; + +} // namespace tracing +} // namespace datadog diff --git a/src/datadog/datadog_agent.cpp b/src/datadog/datadog_agent.cpp index f5250540..30b9c0bf 100644 --- a/src/datadog/datadog_agent.cpp +++ b/src/datadog/datadog_agent.cpp @@ -10,19 +10,27 @@ #include "collector_response.h" #include "datadog_agent_config.h" #include "dict_writer.h" +#include "http_client.h" #include "json.hpp" #include "logger.h" #include "msgpack.h" #include "span_data.h" +#include "string_view.h" #include "trace_sampler.h" +#include "tracer.h" #include "version.h" namespace datadog { namespace tracing { namespace { -const StringView traces_api_path = "/v0.4/traces"; -const StringView telemetry_v2_path = "/telemetry/proxy/api/v2/apmtelemetry"; +constexpr StringView traces_api_path = "/v0.4/traces"; +constexpr StringView telemetry_v2_path = "/telemetry/proxy/api/v2/apmtelemetry"; +constexpr StringView remote_configuration_path = "/v0.7/config"; + +void set_content_type_json(DictWriter& headers) { + headers.set("Content-Type", "application/json"); +} HTTPClient::URL traces_endpoint(const HTTPClient::URL& agent_url) { auto traces_url = agent_url; @@ -36,6 +44,13 @@ HTTPClient::URL telemetry_endpoint(const HTTPClient::URL& agent_url) { return telemetry_v2_url; } +HTTPClient::URL remote_configuration_endpoint( + const HTTPClient::URL& agent_url) { + auto remote_configuration = agent_url; + append(remote_configuration.path, remote_configuration_path); + return remote_configuration; +} + Expected msgpack_encode( std::string& destination, const std::vector& trace_chunks) { @@ -134,38 +149,27 @@ std::variant parse_agent_traces_response( DatadogAgent::DatadogAgent( const FinalizedDatadogAgentConfig& config, const std::shared_ptr& tracer_telemetry, - const std::shared_ptr& logger) + const std::shared_ptr& logger, + const TracerSignature& tracer_signature, ConfigManager& config_manager) : tracer_telemetry_(tracer_telemetry), clock_(config.clock), logger_(logger), traces_endpoint_(traces_endpoint(config.url)), telemetry_endpoint_(telemetry_endpoint(config.url)), + remote_configuration_endpoint_(remote_configuration_endpoint(config.url)), http_client_(config.http_client), event_scheduler_(config.event_scheduler), cancel_scheduled_flush_(event_scheduler_->schedule_recurring_event( config.flush_interval, [this]() { flush(); })), flush_interval_(config.flush_interval), request_timeout_(config.request_timeout), - shutdown_timeout_(config.shutdown_timeout) { + shutdown_timeout_(config.shutdown_timeout), + remote_config_(tracer_signature, config_manager) { assert(logger_); assert(tracer_telemetry_); if (tracer_telemetry_->enabled()) { - // Only schedule this if telemetry is enabled. - // Every 10 seconds, have the tracer telemetry capture the metrics values. - // Every 60 seconds, also report those values to the datadog agent. - cancel_telemetry_timer_ = event_scheduler_->schedule_recurring_event( - std::chrono::seconds(10), [this, n = 0]() mutable { - n++; - tracer_telemetry_->capture_metrics(); - if (n % 6 == 0) { - send_heartbeat_and_telemetry(); - } - }); - // Callback for setting telemetry request headers. - telemetry_set_request_headers_ = [](DictWriter& headers) { - headers.set("Content-Type", "application/json"); - }; - // Callback for successful telemetry HTTP requests, to examine HTTP status. + // Callback for successful telemetry HTTP requests, to examine HTTP + // status. telemetry_on_response_ = [logger = logger_]( int response_status, const DictReader& /*response_headers*/, @@ -173,29 +177,49 @@ DatadogAgent::DatadogAgent( if (response_status < 200 || response_status >= 300) { logger->log_error([&](auto& stream) { stream << "Unexpected telemetry response status " << response_status - << " with body (starts on next line):\n" + << " with body (if any, starts on next line):\n" << response_body; }); } }; + // Callback for unsuccessful telemetry HTTP requests. telemetry_on_error_ = [logger = logger_](Error error) { logger->log_error(error.with_prefix( "Error occurred during HTTP request for telemetry: ")); }; + + // Only schedule this if telemetry is enabled. + // Every 10 seconds, have the tracer telemetry capture the metrics + // values. Every 60 seconds, also report those values to the datadog + // agent. + cancel_telemetry_timer_ = event_scheduler_->schedule_recurring_event( + std::chrono::seconds(10), [this, n = 0]() mutable { + n++; + tracer_telemetry_->capture_metrics(); + if (n % 6 == 0) { + send_heartbeat_and_telemetry(); + } + }); } + + cancel_remote_configuration_task_ = + event_scheduler_->schedule_recurring_event( + config.remote_configuration_poll_interval, + [this] { get_and_apply_remote_configuration_updates(); }); } DatadogAgent::~DatadogAgent() { const auto deadline = clock_().tick + shutdown_timeout_; cancel_scheduled_flush_(); flush(); + cancel_remote_configuration_task_(); if (tracer_telemetry_->enabled()) { // This action only needs to occur if tracer telemetry is enabled. cancel_telemetry_timer_(); tracer_telemetry_->capture_metrics(); - // The app-closing message is bundled with a message containing the final - // metric values. + // The app-closing message is bundled with a message containing the + // final metric values. send_app_closing(); } http_client_->drain(deadline); @@ -216,6 +240,7 @@ nlohmann::json DatadogAgent::config_json() const { {"config", nlohmann::json::object({ {"traces_url", (traces_endpoint_.scheme + "://" + traces_endpoint_.authority + traces_endpoint_.path)}, {"telemetry_url", (telemetry_endpoint_.scheme + "://" + telemetry_endpoint_.authority + telemetry_endpoint_.path)}, + {"remote_configuration_url", (remote_configuration_endpoint_.scheme + "://" + remote_configuration_endpoint_.authority + remote_configuration_endpoint_.path)}, {"flush_interval_milliseconds", std::chrono::duration_cast(flush_interval_).count() }, {"request_timeout_milliseconds", std::chrono::duration_cast(request_timeout_).count() }, {"shutdown_timeout_milliseconds", std::chrono::duration_cast(shutdown_timeout_).count() }, @@ -293,9 +318,9 @@ void DatadogAgent::flush() { if (response_body.empty()) { logger->log_error([](auto& stream) { - stream - << "Datadog Agent returned response without a body." - " This tracer might be sending batches of traces too frequently"; + stream << "Datadog Agent returned response without a body." + " This tracer might be sending batches of traces too " + "frequently"; }); return; } @@ -337,7 +362,7 @@ void DatadogAgent::flush() { void DatadogAgent::send_app_started() { auto payload = tracer_telemetry_->app_started(); auto post_result = - http_client_->post(telemetry_endpoint_, telemetry_set_request_headers_, + 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()) { @@ -349,7 +374,7 @@ void DatadogAgent::send_app_started() { void DatadogAgent::send_heartbeat_and_telemetry() { auto payload = tracer_telemetry_->heartbeat_and_telemetry(); auto post_result = - http_client_->post(telemetry_endpoint_, telemetry_set_request_headers_, + 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()) { @@ -361,7 +386,7 @@ void DatadogAgent::send_heartbeat_and_telemetry() { void DatadogAgent::send_app_closing() { auto payload = tracer_telemetry_->app_closing(); auto post_result = - http_client_->post(telemetry_endpoint_, telemetry_set_request_headers_, + 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()) { @@ -370,5 +395,66 @@ void DatadogAgent::send_app_closing() { } } +void DatadogAgent::get_and_apply_remote_configuration_updates() { + auto remote_configuration_on_response = + [this](int response_status, const DictReader& /*response_headers*/, + std::string response_body) { + if (response_status < 200 || response_status >= 300) { + if (response_status == 404) { + /* + * 404 is not considered as an error as the agent use it to + * signal remote configuration is disabled. At any point, the + * feature could be enabled, so the tracer must continuously check + * for new remote configuration. + */ + return; + } + + logger_->log_error([&](auto& stream) { + stream << "Unexpected Remote Configuration status " + << response_status + << " with body (if any, starts on next line):\n" + << response_body; + }); + + return; + } + + const auto response_json = + nlohmann::json::parse(/* input = */ response_body, + /* parser_callback = */ nullptr, + /* allow_exceptions = */ false); + if (response_json.is_discarded()) { + logger_->log_error([](auto& stream) { + stream << "Could not parse Remote Configuration response body"; + }); + return; + } + + 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 remote_configuration_on_error = [logger = logger_](Error error) { + logger->log_error(error.with_prefix( + "Error occurred during HTTP request for Remote Configuration: ")); + }; + + auto post_result = http_client_->post( + remote_configuration_endpoint_, set_content_type_json, + remote_config_.make_request_payload().dump(), + remote_configuration_on_response, remote_configuration_on_error, + clock_().tick + request_timeout_); + if (auto error = post_result.if_error()) { + logger_->log_error( + error->with_prefix("Unexpected error while requesting Remote " + "Configuration updates: ")); + } +} + } // namespace tracing } // namespace datadog diff --git a/src/datadog/datadog_agent.h b/src/datadog/datadog_agent.h index 30671fab..1457d493 100644 --- a/src/datadog/datadog_agent.h +++ b/src/datadog/datadog_agent.h @@ -12,9 +12,11 @@ #include "clock.h" #include "collector.h" +#include "config_manager.h" #include "event_scheduler.h" #include "http_client.h" #include "metrics.h" +#include "remote_config.h" #include "tracer_telemetry.h" namespace datadog { @@ -24,6 +26,7 @@ class FinalizedDatadogAgentConfig; class Logger; struct SpanData; class TraceSampler; +struct TracerSignature; class DatadogAgent : public Collector { public: @@ -40,18 +43,21 @@ class DatadogAgent : public Collector { std::vector trace_chunks_; HTTPClient::URL traces_endpoint_; HTTPClient::URL telemetry_endpoint_; + HTTPClient::URL remote_configuration_endpoint_; std::shared_ptr http_client_; std::shared_ptr event_scheduler_; EventScheduler::Cancel cancel_scheduled_flush_; EventScheduler::Cancel cancel_telemetry_timer_; + EventScheduler::Cancel cancel_remote_configuration_task_; std::chrono::steady_clock::duration flush_interval_; // Callbacks for submitting telemetry data - HTTPClient::HeadersSetter telemetry_set_request_headers_; HTTPClient::ResponseHandler telemetry_on_response_; HTTPClient::ErrorHandler telemetry_on_error_; std::chrono::steady_clock::duration request_timeout_; std::chrono::steady_clock::duration shutdown_timeout_; + RemoteConfigurationManager remote_config_; + void flush(); void send_heartbeat_and_telemetry(); void send_app_closing(); @@ -59,7 +65,8 @@ class DatadogAgent : public Collector { public: DatadogAgent(const FinalizedDatadogAgentConfig&, const std::shared_ptr&, - const std::shared_ptr&); + const std::shared_ptr&, const TracerSignature& id, + ConfigManager& config_manager); ~DatadogAgent(); Expected send( @@ -68,6 +75,8 @@ class DatadogAgent : public Collector { void send_app_started(); + void get_and_apply_remote_configuration_updates(); + nlohmann::json config_json() const override; }; diff --git a/src/datadog/datadog_agent_config.cpp b/src/datadog/datadog_agent_config.cpp index e29b5abf..fd98d81d 100644 --- a/src/datadog/datadog_agent_config.cpp +++ b/src/datadog/datadog_agent_config.cpp @@ -1,6 +1,7 @@ #include "datadog_agent_config.h" #include +#include #include #include "default_http_client.h" @@ -122,6 +123,7 @@ Expected finalize_config( "DatadogAgent: Request timeout must be a positive number of " "milliseconds."}; } + result.request_timeout = std::chrono::milliseconds(config.request_timeout_milliseconds); @@ -130,9 +132,33 @@ Expected finalize_config( "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) { + 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); diff --git a/src/datadog/datadog_agent_config.h b/src/datadog/datadog_agent_config.h index 1916860d..59d9f10b 100644 --- a/src/datadog/datadog_agent_config.h +++ b/src/datadog/datadog_agent_config.h @@ -54,6 +54,9 @@ struct DatadogAgentConfig { int request_timeout_milliseconds = 2000; // Maximum amount of time the process is allowed to wait before shutting down. int shutdown_timeout_milliseconds = 2000; + // How often, in seconds, to query the Datadog Agent for remote configuration + // updates. + int remote_configuration_poll_interval_seconds = 5; static Expected parse(StringView); }; @@ -65,13 +68,14 @@ class FinalizedDatadogAgentConfig { FinalizedDatadogAgentConfig() = default; public: + Clock clock; std::shared_ptr http_client; std::shared_ptr event_scheduler; HTTPClient::URL url; std::chrono::steady_clock::duration flush_interval; std::chrono::steady_clock::duration request_timeout; std::chrono::steady_clock::duration shutdown_timeout; - Clock clock; + std::chrono::steady_clock::duration remote_configuration_poll_interval; }; Expected finalize_config( diff --git a/src/datadog/environment.h b/src/datadog/environment.h index f8800d8c..69e340b0 100644 --- a/src/datadog/environment.h +++ b/src/datadog/environment.h @@ -24,30 +24,31 @@ namespace environment { // To enforce correspondence between `enum Variable` and `variable_names`, the // preprocessor is used so that the DD_* symbols are listed exactly once. -#define LIST_ENVIRONMENT_VARIABLES(MACRO) \ - MACRO(DD_AGENT_HOST) \ - MACRO(DD_ENV) \ - MACRO(DD_INSTRUMENTATION_TELEMETRY_ENABLED) \ - MACRO(DD_PROPAGATION_STYLE_EXTRACT) \ - MACRO(DD_PROPAGATION_STYLE_INJECT) \ - MACRO(DD_TRACE_PROPAGATION_STYLE_EXTRACT) \ - MACRO(DD_TRACE_PROPAGATION_STYLE_INJECT) \ - MACRO(DD_TRACE_PROPAGATION_STYLE) \ - MACRO(DD_SERVICE) \ - MACRO(DD_SPAN_SAMPLING_RULES) \ - MACRO(DD_SPAN_SAMPLING_RULES_FILE) \ - MACRO(DD_TAGS) \ - MACRO(DD_TRACE_AGENT_PORT) \ - MACRO(DD_TRACE_AGENT_URL) \ - MACRO(DD_TRACE_DEBUG) \ - MACRO(DD_TRACE_ENABLED) \ - MACRO(DD_TRACE_RATE_LIMIT) \ - MACRO(DD_TRACE_REPORT_HOSTNAME) \ - MACRO(DD_TRACE_SAMPLE_RATE) \ - MACRO(DD_TRACE_SAMPLING_RULES) \ - MACRO(DD_TRACE_STARTUP_LOGS) \ - MACRO(DD_TRACE_TAGS_PROPAGATION_MAX_LENGTH) \ - MACRO(DD_VERSION) \ +#define LIST_ENVIRONMENT_VARIABLES(MACRO) \ + MACRO(DD_AGENT_HOST) \ + MACRO(DD_ENV) \ + MACRO(DD_INSTRUMENTATION_TELEMETRY_ENABLED) \ + MACRO(DD_PROPAGATION_STYLE_EXTRACT) \ + MACRO(DD_PROPAGATION_STYLE_INJECT) \ + MACRO(DD_REMOTE_CONFIG_POLL_INTERVAL_SECONDS) \ + MACRO(DD_SERVICE) \ + MACRO(DD_SPAN_SAMPLING_RULES) \ + MACRO(DD_SPAN_SAMPLING_RULES_FILE) \ + MACRO(DD_TRACE_PROPAGATION_STYLE_EXTRACT) \ + MACRO(DD_TRACE_PROPAGATION_STYLE_INJECT) \ + MACRO(DD_TRACE_PROPAGATION_STYLE) \ + MACRO(DD_TAGS) \ + MACRO(DD_TRACE_AGENT_PORT) \ + MACRO(DD_TRACE_AGENT_URL) \ + MACRO(DD_TRACE_DEBUG) \ + MACRO(DD_TRACE_ENABLED) \ + MACRO(DD_TRACE_RATE_LIMIT) \ + MACRO(DD_TRACE_REPORT_HOSTNAME) \ + MACRO(DD_TRACE_SAMPLE_RATE) \ + MACRO(DD_TRACE_SAMPLING_RULES) \ + MACRO(DD_TRACE_STARTUP_LOGS) \ + MACRO(DD_TRACE_TAGS_PROPAGATION_MAX_LENGTH) \ + MACRO(DD_VERSION) \ MACRO(DD_TRACE_128_BIT_TRACEID_GENERATION_ENABLED) #define WITH_COMMA(ARG) ARG, @@ -60,7 +61,7 @@ enum Variable { LIST_ENVIRONMENT_VARIABLES(WITH_COMMA) }; #define QUOTED_WITH_COMMA(ARG) WITH_COMMA(QUOTED(ARG)) -inline const char* const variable_names[] = { +inline const char *const variable_names[] = { LIST_ENVIRONMENT_VARIABLES(QUOTED_WITH_COMMA)}; #undef QUOTED_WITH_COMMA diff --git a/src/datadog/error.h b/src/datadog/error.h index 93e669c3..67accc6a 100644 --- a/src/datadog/error.h +++ b/src/datadog/error.h @@ -73,6 +73,7 @@ struct Error { CURL_DEADLINE_EXCEEDED_BEFORE_REQUEST_START = 48, DATADOG_AGENT_INVALID_REQUEST_TIMEOUT = 49, DATADOG_AGENT_INVALID_SHUTDOWN_TIMEOUT = 50, + DATADOG_AGENT_INVALID_REMOTE_CONFIG_POLL_INTERVAL = 51, }; Code code; diff --git a/src/datadog/remote_config.cpp b/src/datadog/remote_config.cpp new file mode 100644 index 00000000..78b2183d --- /dev/null +++ b/src/datadog/remote_config.cpp @@ -0,0 +1,226 @@ +#include "remote_config.h" + +#include +#include +#include + +#include "base64.h" +#include "datadog/string_view.h" +#include "json.hpp" +#include "random.h" +#include "version.h" + +using namespace nlohmann::literals; + +namespace datadog { +namespace tracing { +namespace { + +// The ".client.capabilities" field of the remote config request payload +// describes which parts of the library's configuration are supported for remote +// configuration. +// +// It's a bitset, 64 bits wide, where each bit indicates whether the library +// supports a particular feature for remote configuration. +// +// The bitset is encoded in the request as a JSON array of 8 integers, where +// each integer is one byte from the 64 bits. The bytes are in big-endian order +// within the array. +enum CapabilitiesFlag : uint64_t { + APM_TRACING_SAMPLE_RATE = 1 << 12, +}; + +constexpr std::array capabilities_byte_array( + uint64_t in) { + std::size_t j = sizeof(in) - 1; + std::array res{}; + for (std::size_t i = 0; i < sizeof(in); ++i) { + res[j--] = in >> (i * 8); + } + + return res; +} + +constexpr std::array k_apm_capabilities = + capabilities_byte_array(APM_TRACING_SAMPLE_RATE); + +constexpr StringView k_apm_product = "APM_TRACING"; +constexpr StringView k_apm_product_path_substring = "/APM_TRACING/"; + +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; + } + + return config_update; +} + +} // namespace + +RemoteConfigurationManager::RemoteConfigurationManager( + const TracerSignature& tracer_signature, ConfigManager& config_manager) + : tracer_signature_(tracer_signature), + config_manager_(config_manager), + client_id_(uuid()) {} + +bool RemoteConfigurationManager::is_new_config( + StringView config_path, const nlohmann::json& config_meta) { + auto it = applied_config_.find(std::string{config_path}); + if (it == applied_config_.cend()) return true; + + return it->second.hash != + config_meta.at("/hashes/sha256"_json_pointer).get(); +} + +nlohmann::json RemoteConfigurationManager::make_request_payload() { + // clang-format off + auto j = nlohmann::json{ + {"client", { + {"id", client_id_}, + {"products", nlohmann::json::array({k_apm_product})}, + {"is_tracer", true}, + {"capabilities", k_apm_capabilities}, + {"client_tracer", { + {"runtime_id", tracer_signature_.runtime_id.string()}, + {"language", tracer_signature_.library_language}, + {"tracer_version", tracer_signature_.library_version}, + {"service", tracer_signature_.default_service}, + {"env", tracer_signature_.default_environment} + }}, + {"state", { + {"root_version", 1}, + {"targets_version", state_.targets_version}, + {"backend_client_state", state_.opaque_backend_state} + }} + }} + }; + // clang-format on + + if (!applied_config_.empty()) { + auto config_states = nlohmann::json::array(); + for (const auto& [_, config] : applied_config_) { + config_states.emplace_back(nlohmann::json{{"id", config.id}, + {"version", config.version}, + {"product", k_apm_product}}); + } + + j["config_states"] = config_states; + } + + if (state_.error_message) { + j["has_error"] = true; + j["error"] = *state_.error_message; + } + + return j; +} + +void RemoteConfigurationManager::process_response(const nlohmann::json& json) { + state_.error_message = nullopt; + + try { + const auto targets = nlohmann::json::parse( + base64_decode(json.at("targets").get())); + + state_.targets_version = targets.at("/signed/version"_json_pointer); + state_.opaque_backend_state = + targets.at("/signed/custom/opaque_backend_state"_json_pointer); + + const auto client_configs_it = json.find("client_configs"); + + // `client_configs` is absent => remove previously applied configuration if + // any applied. + 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); }); + applied_config_.clear(); + } + return; + } + + // Keep track of config path received to know which ones to revert. + std::unordered_set visited_config; + visited_config.reserve(client_configs_it->size()); + + for (const auto& client_config : *client_configs_it) { + auto config_path = client_config.get(); + visited_config.emplace(config_path); + + const auto& config_metadata = + targets.at("/signed/targets"_json_pointer).at(config_path); + if (!contains(config_path, k_apm_product_path_substring) || + !is_new_config(config_path, config_metadata)) { + continue; + } + + const auto& target_files = json.at("/target_files"_json_pointer); + auto target_it = std::find_if( + target_files.cbegin(), target_files.cend(), + [&config_path](const nlohmann::json& j) { + return j.at("/path"_json_pointer).get() == config_path; + }); + + if (target_it == target_files.cend()) { + state_.error_message = + "Missing configuration from Remote Configuration response: No " + "target file having path \""; + append(*state_.error_message, config_path); + *state_.error_message += '\"'; + return; + } + + const auto config_json = nlohmann::json::parse( + base64_decode(target_it.value().at("raw").get())); + + const auto& targeted_service = config_json.at("service_target"); + if (targeted_service.at("service").get() != + tracer_signature_.default_service || + targeted_service.at("env").get() != + tracer_signature_.default_environment) { + continue; + } + + Configuration new_config; + new_config.hash = config_metadata.at("/hashes/sha256"_json_pointer); + new_config.id = config_json.at("id"); + new_config.version = config_json.at("revision"); + new_config.content = parse_dynamic_config(config_json.at("lib_config")); + + 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); + it = applied_config_.erase(it); + } else { + it++; + } + } + } catch (const nlohmann::json::exception& e) { + std::string error_message = "Ill-formatted Remote Configuration response: "; + error_message += e.what(); + + state_.error_message = std::move(error_message); + } +} + +void RemoteConfigurationManager::apply_config(Configuration config) { + config_manager_.update(config.content); +} + +void RemoteConfigurationManager::revert_config(Configuration) { + config_manager_.reset(); +} + +} // namespace tracing +} // namespace datadog diff --git a/src/datadog/remote_config.h b/src/datadog/remote_config.h new file mode 100644 index 00000000..33d03409 --- /dev/null +++ b/src/datadog/remote_config.h @@ -0,0 +1,77 @@ +#pragma once + +// Remote Configuration is a Datadog capability that allows a user to remotely +// configure and change the behaviour of the tracing library. +// The current implementation is restricted to Application Performance +// Monitoring features. +// +// The `RemoteConfigurationManager` class implement the protocol to query, +// process and verify configuration from a remote source. It is also +// responsible for handling configuration updates received from a remote source +// and maintains the state of applied configuration. +// It interacts with the `ConfigManager` to seamlessly apply or revert +// configurations based on responses received from the remote source. + +#include + +#include "config_manager.h" +#include "logger.h" +#include "optional.h" +#include "runtime_id.h" +#include "string_view.h" +#include "trace_sampler_config.h" +#include "tracer_signature.h" + +namespace datadog { +namespace tracing { + +class RemoteConfigurationManager { + // Represents the *current* state of the RemoteConfigurationManager. + // It is also used to report errors to the remote source. + struct State { + uint64_t targets_version = 1; + std::string opaque_backend_state; + Optional error_message; + }; + + // Holds information about a specific configuration update, + // including its identifier, hash value, version number and the content. + struct Configuration { + std::string id; + std::string hash; + std::size_t version; + ConfigUpdate content; + }; + + TracerSignature tracer_signature_; + ConfigManager& config_manager_; + std::string client_id_; ///< Identifier a `RemoteConfigurationManager` + + State state_; + std::unordered_map applied_config_; + + public: + RemoteConfigurationManager(const TracerSignature& tracer_signature, + ConfigManager& config_manager); + + // Construct a JSON object representing the payload to be sent in a remote + // configuration request. + nlohmann::json make_request_payload(); + + // Handles the response received from a remote source and udates the internal + // state accordingly. + void 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); + + // Revert a remote configuration. + void revert_config(Configuration config); +}; + +} // namespace tracing +} // namespace datadog diff --git a/src/datadog/string_view.h b/src/datadog/string_view.h index cf459a0e..5e20c17c 100644 --- a/src/datadog/string_view.h +++ b/src/datadog/string_view.h @@ -58,5 +58,9 @@ inline void assign(std::string& destination, StringView text) { destination.assign(text.data(), text.size()); } +inline bool contains(StringView text, StringView pattern) { + return text.find(pattern) != text.npos; +} + } // namespace tracing } // namespace datadog diff --git a/src/datadog/tracer.cpp b/src/datadog/tracer.cpp index b43e95dd..4d0da254 100644 --- a/src/datadog/tracer.cpp +++ b/src/datadog/tracer.cpp @@ -3,6 +3,8 @@ #include #include +#include "datadog/runtime_id.h" +#include "datadog/trace_sampler_config.h" #include "datadog_agent.h" #include "dict_reader.h" #include "environment.h" @@ -21,6 +23,7 @@ #include "tags.h" #include "trace_sampler.h" #include "trace_segment.h" +#include "tracer_signature.h" #include "version.h" #include "w3c_propagation.h" @@ -37,11 +40,10 @@ Tracer::Tracer(const FinalizedTracerConfig& config, defaults_(std::make_shared(config.defaults)), runtime_id_(config.runtime_id ? *config.runtime_id : RuntimeID::generate()), + signature_{runtime_id_, config.defaults.service, + config.defaults.environment}, tracer_telemetry_(std::make_shared( - config.report_telemetry, config.clock, logger_, defaults_, - runtime_id_)), - trace_sampler_( - std::make_shared(config.trace_sampler, config.clock)), + config.report_telemetry, config.clock, logger_, signature_)), span_sampler_( std::make_shared(config.span_sampler, config.clock)), generator_(generator), @@ -49,16 +51,20 @@ Tracer::Tracer(const FinalizedTracerConfig& config, injection_styles_(config.injection_styles), extraction_styles_(config.extraction_styles), hostname_(config.report_hostname ? get_hostname() : nullopt), - tags_header_max_size_(config.tags_header_size) { + tags_header_max_size_(config.tags_header_size), + config_manager_(config) { if (auto* collector = std::get_if>(&config.collector)) { collector_ = *collector; } else { auto& agent_config = std::get(config.collector); + auto agent = std::make_shared(agent_config, tracer_telemetry_, - config.logger); + config.logger, signature_, + config_manager_); collector_ = agent; + if (tracer_telemetry_->enabled()) { agent->send_app_started(); } @@ -78,7 +84,6 @@ nlohmann::json Tracer::config_json() const { {"defaults", to_json(*defaults_)}, {"runtime_id", runtime_id_.string()}, {"collector", collector_->config_json()}, - {"trace_sampler", trace_sampler_->config_json()}, {"span_sampler", span_sampler_->config_json()}, {"injection_styles", to_json(injection_styles_)}, {"extraction_styles", to_json(extraction_styles_)}, @@ -87,6 +92,8 @@ nlohmann::json Tracer::config_json() const { }); // clang-format on + config.merge_patch(config_manager_.config_json()); + if (hostname_) { config["hostname"] = *hostname_; } @@ -99,21 +106,23 @@ Span Tracer::create_span() { return create_span(SpanConfig{}); } Span Tracer::create_span(const SpanConfig& config) { auto span_data = std::make_unique(); span_data->apply_config(*defaults_, config, clock_); - std::vector> trace_tags; span_data->trace_id = generator_->trace_id(span_data->start); + span_data->span_id = span_data->trace_id.low; + span_data->parent_id = 0; + + std::vector> trace_tags; if (span_data->trace_id.high) { trace_tags.emplace_back(tags::internal::trace_id_high, hex_padded(span_data->trace_id.high)); } - span_data->span_id = span_data->trace_id.low; - span_data->parent_id = 0; const auto span_data_ptr = span_data.get(); tracer_telemetry_->metrics().tracer.trace_segments_created_new.inc(); const auto segment = std::make_shared( - logger_, collector_, tracer_telemetry_, trace_sampler_, span_sampler_, - defaults_, runtime_id_, injection_styles_, hostname_, - nullopt /* origin */, tags_header_max_size_, std::move(trace_tags), + logger_, collector_, tracer_telemetry_, + config_manager_.get_trace_sampler(), span_sampler_, defaults_, + runtime_id_, injection_styles_, hostname_, nullopt /* origin */, + tags_header_max_size_, std::move(trace_tags), nullopt /* sampling_decision */, nullopt /* additional_w3c_tracestate */, nullopt /* additional_datadog_w3c_tracestate*/, std::move(span_data)); Span span{span_data_ptr, segment, @@ -237,8 +246,8 @@ Expected Tracer::extract_span(const DictReader& reader, // corresponding `trace_id_high` tag, so that the Datadog backend is aware // of those bits. // - // First, though, if the `trace_id_high` tag is already set and has a bogus - // value or a value inconsistent with the trace ID, tag an error. + // First, though, if the `trace_id_high` tag is already set and has a + // bogus value or a value inconsistent with the trace ID, tag an error. const auto hex_high = hex_padded(span_data->trace_id.high); const auto extant = std::find_if( trace_tags.begin(), trace_tags.end(), [&](const auto& pair) { @@ -247,9 +256,10 @@ Expected Tracer::extract_span(const DictReader& reader, if (extant == trace_tags.end()) { trace_tags.emplace_back(tags::internal::trace_id_high, hex_high); } else { - // There is already a `trace_id_high` tag. `hex_high` is its proper value. - // Check if the extant value is malformed or different from `hex_high`. In - // either case, tag an error and overwrite the tag with `hex_high`. + // There is already a `trace_id_high` tag. `hex_high` is its proper + // value. Check if the extant value is malformed or different from + // `hex_high`. In either case, tag an error and overwrite the tag with + // `hex_high`. const Optional high = parse_trace_id_high(extant->second); if (!high) { span_data->tags[tags::internal::propagation_error] = @@ -277,8 +287,9 @@ Expected Tracer::extract_span(const DictReader& reader, const auto span_data_ptr = span_data.get(); tracer_telemetry_->metrics().tracer.trace_segments_created_continued.inc(); const auto segment = std::make_shared( - logger_, collector_, tracer_telemetry_, trace_sampler_, span_sampler_, - defaults_, runtime_id_, injection_styles_, hostname_, std::move(origin), + logger_, collector_, tracer_telemetry_, + config_manager_.get_trace_sampler(), span_sampler_, defaults_, + runtime_id_, injection_styles_, hostname_, std::move(origin), tags_header_max_size_, std::move(trace_tags), std::move(sampling_decision), std::move(additional_w3c_tracestate), std::move(additional_datadog_w3c_tracestate), std::move(span_data)); diff --git a/src/datadog/tracer.h b/src/datadog/tracer.h index 27a6264c..8f1f5334 100644 --- a/src/datadog/tracer.h +++ b/src/datadog/tracer.h @@ -11,6 +11,7 @@ // `tracer_config.h`. #include "clock.h" +#include "config_manager.h" #include "error.h" #include "expected.h" #include "id_generator.h" @@ -18,6 +19,7 @@ #include "optional.h" #include "span.h" #include "tracer_config.h" +#include "tracer_signature.h" #include "tracer_telemetry.h" namespace datadog { @@ -33,8 +35,8 @@ class Tracer { std::shared_ptr collector_; std::shared_ptr defaults_; RuntimeID runtime_id_; + TracerSignature signature_; std::shared_ptr tracer_telemetry_; - std::shared_ptr trace_sampler_; std::shared_ptr span_sampler_; std::shared_ptr generator_; Clock clock_; @@ -43,6 +45,8 @@ class Tracer { Optional hostname_; std::size_t tags_header_max_size_; + ConfigManager config_manager_; + public: // Create a tracer configured using the specified `config`, and optionally: // - using the specified `generator` to create trace IDs and span IDs diff --git a/src/datadog/tracer_signature.h b/src/datadog/tracer_signature.h new file mode 100644 index 00000000..042b4d37 --- /dev/null +++ b/src/datadog/tracer_signature.h @@ -0,0 +1,56 @@ +#pragma once + +// This component provides a class, `TracerSignature`, that contains the parts +// of a tracer's configuration that are used to refer to the tracer in Datadog's +// telemetry and remote configuration APIs. +// +// `TracerSignature` is used in three contexts: +// +// 1. When telemetry is sent to the Datadog Agent, the tracer signature is +// included in the request payload. See +// `TracerTelemetry::generate_telemetry_body` in `tracer_telemetry.cpp`. +// 2. When the Datadog Agent is polled for configuration updates, part of the +// tracer signature (all but the language version) is included in the request +// payload. See `RemoteConfigurationManager::make_request_payload` in +// `remote_config.h`. +// 3. When the Datadog Agent responds with configuration updates, the service +// and environment of the tracer signature are used to determine whether the +// updates are relevant to the `Tracer` that created the collector that is +// polling the Datadog Agent. See +// `RemoteConfigurationManager::process_response` in `remote_config.h`. + +#include + +#include "runtime_id.h" +#include "string_view.h" +#include "version.h" + +#define DD_TRACE_STRINGIFY(ARG) DD_TRACE_STRINGIFY_HELPER(ARG) +#define DD_TRACE_STRINGIFY_HELPER(ARG) #ARG + +namespace datadog { +namespace tracing { + +struct TracerSignature { + RuntimeID runtime_id; + std::string default_service; + std::string default_environment; + std::string library_version; + StringView library_language; + StringView library_language_version; + + TracerSignature() = delete; + TracerSignature(RuntimeID id, std::string service, std::string environment) + : runtime_id(id), + default_service(std::move(service)), + default_environment(std::move(environment)), + library_version(tracer_version), + library_language("cpp"), + library_language_version(DD_TRACE_STRINGIFY(__cplusplus), 6) {} +}; + +} // namespace tracing +} // namespace datadog + +#undef DD_TRACE_STRINGIFY_HELPER +#undef DD_TRACE_STRINGIFY diff --git a/src/datadog/tracer_telemetry.cpp b/src/datadog/tracer_telemetry.cpp index e030ffc2..c6a06c4c 100644 --- a/src/datadog/tracer_telemetry.cpp +++ b/src/datadog/tracer_telemetry.cpp @@ -8,15 +8,13 @@ namespace datadog { namespace tracing { -TracerTelemetry::TracerTelemetry( - bool enabled, const Clock& clock, const std::shared_ptr& logger, - const std::shared_ptr& span_defaults, - const RuntimeID& runtime_id) +TracerTelemetry::TracerTelemetry(bool enabled, const Clock& clock, + const std::shared_ptr& logger, + const TracerSignature& tracer_signature) : enabled_(enabled), clock_(clock), logger_(logger), - span_defaults_(span_defaults), - runtime_id_(runtime_id), + tracer_signature_(tracer_signature), hostname_(get_hostname().value_or("hostname-unavailable")) { if (enabled_) { // Register all the metrics that we're tracking by adding them to the @@ -64,15 +62,16 @@ nlohmann::json TracerTelemetry::generate_telemetry_body( {"seq_id", seq_id_}, {"request_type", request_type}, {"tracer_time", tracer_time}, - {"runtime_id", runtime_id_.string()}, + {"runtime_id", tracer_signature_.runtime_id.string()}, {"debug", debug_}, - {"application", nlohmann::json::object({ - {"service_name", span_defaults_->service}, - {"env", span_defaults_->environment}, - {"tracer_version", tracer_version}, - {"language_name", "cpp"}, - {"language_version", std::to_string(__cplusplus)}, - })}, + {"application", + nlohmann::json::object({ + {"service_name", tracer_signature_.default_service}, + {"env", tracer_signature_.default_environment}, + {"tracer_version", tracer_signature_.library_version}, + {"language_name", tracer_signature_.library_language}, + {"language_version", tracer_signature_.library_language_version}, + })}, // TODO: host information (os, os_version, kernel, etc) {"host", nlohmann::json::object({ {"hostname", hostname_}, diff --git a/src/datadog/tracer_telemetry.h b/src/datadog/tracer_telemetry.h index 184614a3..e9488b94 100644 --- a/src/datadog/tracer_telemetry.h +++ b/src/datadog/tracer_telemetry.h @@ -31,6 +31,7 @@ #include "json.hpp" #include "metrics.h" #include "runtime_id.h" +#include "tracer_signature.h" namespace datadog { namespace tracing { @@ -43,8 +44,7 @@ class TracerTelemetry { bool debug_ = false; Clock clock_; std::shared_ptr logger_; - std::shared_ptr span_defaults_; - RuntimeID runtime_id_; + TracerSignature tracer_signature_; std::string hostname_; uint64_t seq_id_ = 0; // This structure contains all the metrics that are exposed by tracer @@ -100,8 +100,7 @@ class TracerTelemetry { public: TracerTelemetry(bool enabled, const Clock& clock, const std::shared_ptr& logger, - const std::shared_ptr& span_defaults, - const RuntimeID& runtime_id); + const TracerSignature& tracer_signature); bool enabled() { return enabled_; }; // Provides access to the telemetry metrics for updating the values. // This value should not be stored. diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 69771cd2..d666665b 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -17,6 +17,7 @@ add_executable(tests matchers.cpp # test cases + test_base64.cpp test_cerr_logger.cpp test_curl.cpp test_datadog_agent.cpp @@ -25,6 +26,7 @@ add_executable(tests test_metrics.cpp test_msgpack.cpp test_parse_util.cpp + test_remote_config.cpp test_smoke.cpp test_span.cpp test_span_sampler.cpp diff --git a/test/test_base64.cpp b/test/test_base64.cpp new file mode 100644 index 00000000..298b6577 --- /dev/null +++ b/test/test_base64.cpp @@ -0,0 +1,32 @@ +#include + +#include "catch.hpp" +#include "test.h" + +#define BASE64_TEST(x) TEST_CASE(x, "[base64]") + +using namespace datadog::tracing; + +BASE64_TEST("empty input") { CHECK(base64_decode("") == ""); } + +BASE64_TEST("invalid inputs") { + SECTION("invalid characters") { + CHECK(base64_decode("InvalidData@") == ""); + CHECK(base64_decode("In@#*!^validData") == ""); + } + + SECTION("single character without padding") { + CHECK(base64_decode("V") == ""); + } +} + +BASE64_TEST("unpadded input") { + CHECK(base64_decode("VGVzdGluZyBtdWx0aXBsZSBvZiA0IHBhZGRpbmcu") == + "Testing multiple of 4 padding."); +} + +BASE64_TEST("padding") { + CHECK(base64_decode("bGlnaHQgdw==") == "light w"); + CHECK(base64_decode("bGlnaHQgd28=") == "light wo"); + CHECK(base64_decode("bGlnaHQgd29y") == "light wor"); +} diff --git a/test/test_remote_config.cpp b/test/test_remote_config.cpp new file mode 100644 index 00000000..e0406da2 --- /dev/null +++ b/test/test_remote_config.cpp @@ -0,0 +1,279 @@ +#include + +#include "catch.hpp" +#include "datadog/json_fwd.hpp" +#include "datadog/remote_config.h" +#include "mocks/loggers.h" +#include "test.h" + +using namespace datadog::tracing; + +#define REMOTE_CONFIG_TEST(x) TEST_CASE(x, "[remote_config]") + +REMOTE_CONFIG_TEST("first payload") { + const TracerSignature tracer_signature{ + /* runtime_id = */ RuntimeID::generate(), + /* service = */ "testsvc", + /* environment = */ "test"}; + + TracerConfig tracer_cfg; + + const std::time_t mock_time = 1672484400; + const Clock clock = []() { + TimePoint result; + result.wall = std::chrono::system_clock::from_time_t(mock_time); + return result; + }; + + TracerConfig config; + config.defaults.service = "testsvc"; + config.defaults.environment = "test"; + ConfigManager config_manager(*finalize_config(config)); + + RemoteConfigurationManager rc(tracer_signature, config_manager); + + const auto payload = rc.make_request_payload(); + + CHECK(payload.contains("error") == false); + CHECK(payload["client"]["is_tracer"] == true); + CHECK(payload["client"]["client_tracer"]["language"] == "cpp"); + CHECK(payload["client"]["client_tracer"]["service"] == "testsvc"); + CHECK(payload["client"]["client_tracer"]["env"] == "test"); + CHECK(payload["client"]["state"]["root_version"] == 1); + CHECK(payload["client"]["state"]["targets_version"] == 1); +} + +REMOTE_CONFIG_TEST("response processing") { + const TracerSignature tracer_signature{ + /* runtime_id = */ RuntimeID::generate(), + /* service = */ "testsvc", + /* environment = */ "test"}; + + TracerConfig tracer_cfg; + + const std::time_t mock_time = 1672484400; + const Clock clock = []() { + TimePoint result; + result.wall = std::chrono::system_clock::from_time_t(mock_time); + return result; + }; + + TracerConfig config; + config.defaults.service = "testsvc"; + config.defaults.environment = "test"; + config.trace_sampler.sample_rate = 1.0; + ConfigManager config_manager(*finalize_config(config)); + + RemoteConfigurationManager rc(tracer_signature, config_manager); + + SECTION("ill formatted input", + "inputs not following the Remote Configuration JSON schema should " + "generate an error") { + // clang-format off + auto test_case = GENERATE(values({ + // Missing all fields + "{}", + // `targets` field is empty + R"({ "targets": "" })", + // `targets` field is not base64 encoded + R"({ "targets": "Hello, Mars!" })", + // `targets` field is not a JSON base64 encoded + // decode("bm90IGpzb24=") == "not json" + R"({ "targets": "bm90IGpzb24=" })", + // `targets` field JSON base64 encoded do not follow the expected schema + // decode("eyJmb28iOiAiYmFyIn0=") == "{"foo": "bar"}" + R"({ "targets": "eyJmb28iOiAiYmFyIn0=" })", + // `targets` is missing the `targets` field. + // decode("eyJzaWduZWQiOiB7InZlcnNpb24iOiAyLCAiY3VzdG9tIjogeyJvcGFxdWVfYmFja2VuZF9zdGF0ZSI6ICIxNSJ9fX0=") == "{"signed": {"version": 2, "custom": {"opaque_backend_state": "15"}}}" + R"({ + "targets": "eyJzaWduZWQiOiB7InZlcnNpb24iOiAyLCAiY3VzdG9tIjogeyJvcGFxdWVfYmFja2VuZF9zdGF0ZSI6ICIxNSJ9fX0=", + "client_configs": ["datadog"] + })", + // `/targets/targets` have no `datadog` entry + // {"signed": {"version": 2, "targets": {"foo": {}, "bar": {}},"custom": {"opaque_backend_state": "15"}}} + R"({ + "targets": "eyJzaWduZWQiOiB7InZlcnNpb24iOiAyLCAidGFyZ2V0cyI6IHsiZm9vIjoge30sICJiYXIiOiB7fX0sImN1c3RvbSI6IHsib3BhcXVlX2JhY2tlbmRfc3RhdGUiOiAiMTUifX19", + "client_configs": ["datadog"] + })", + // `targets` OK but no `target_files` field. + // {"signed": {"version": 2, "targets": {"foo/APM_TRACING/30": {}, "bar": {}},"custom": {"opaque_backend_state": "15"}}} + R"({ + "targets": "eyJzaWduZWQiOiB7InZlcnNpb24iOiAyLCAidGFyZ2V0cyI6IHsiZm9vL0FQTV9UUkFDSU5HLzMwIjoge30sICJiYXIiOiB7fX0sImN1c3RvbSI6IHsib3BhcXVlX2JhY2tlbmRfc3RhdGUiOiAiMTUifX19", + "client_configs": ["foo/APM_TRACING/30"] + })", + // `targets` OK. `target_files` field is empty. + // {"signed": {"version": 2, "targets": {"foo/APM_TRACING/30": {}, "bar": {}},"custom": {"opaque_backend_state": "15"}}} + R"({ + "targets": "eyJzaWduZWQiOiB7InZlcnNpb24iOiAyLCAidGFyZ2V0cyI6IHsiZm9vL0FQTV9UUkFDSU5HLzMwIjoge30sICJiYXIiOiB7fX0sImN1c3RvbSI6IHsib3BhcXVlX2JhY2tlbmRfc3RhdGUiOiAiMTUifX19", + "client_configs": ["foo/APM_TRACING/30"], + "target_files": [] + })", + // `targets` OK. `target_files` field is not an array. + // {"signed": {"version": 2, "targets": {"foo/APM_TRACING/30": {}, "bar": {}},"custom": {"opaque_backend_state": "15"}}} + R"({ + "targets": "eyJzaWduZWQiOiB7InZlcnNpb24iOiAyLCAidGFyZ2V0cyI6IHsiZm9vL0FQTV9UUkFDSU5HLzMwIjoge30sICJiYXIiOiB7fX0sImN1c3RvbSI6IHsib3BhcXVlX2JhY2tlbmRfc3RhdGUiOiAiMTUifX19", + "client_configs": ["foo/APM_TRACING/30"], + "target_files": 15 + })", + // `targets` OK. `target_files` field content is not base64 encoded. + // {"signed": {"version": 2, "targets": {"foo/APM_TRACING/30": {}, "bar": {}},"custom": {"opaque_backend_state": "15"}}} + R"({ + "targets": "eyJzaWduZWQiOiB7InZlcnNpb24iOiAyLCAidGFyZ2V0cyI6IHsiZm9vL0FQTV9UUkFDSU5HLzMwIjoge30sICJiYXIiOiB7fX0sImN1c3RvbSI6IHsib3BhcXVlX2JhY2tlbmRfc3RhdGUiOiAiMTUifX19", + "client_configs": ["foo/APM_TRACING/30"], + "target_files": [{"path": "foo/APM_TRACING/30", "raw": "Hello, Uranus!"}] + })", + // `targets` OK. `target_files` field content is not a JSON base64 encoded. + // decode("bm90IGpzb24=") == "not json" + // {"signed": {"version": 2, "targets": {"foo/APM_TRACING/30": {}, "bar": {}},"custom": {"opaque_backend_state": "15"}}} + R"({ + "targets": "eyJzaWduZWQiOiB7InZlcnNpb24iOiAyLCAidGFyZ2V0cyI6IHsiZm9vL0FQTV9UUkFDSU5HLzMwIjoge30sICJiYXIiOiB7fX0sImN1c3RvbSI6IHsib3BhcXVlX2JhY2tlbmRfc3RhdGUiOiAiMTUifX19", + "client_configs": ["foo/APM_TRACING/30"], + "target_files": [{"path": "foo/APM_TRACING/30", "raw": "bm90IGpzb24="}] + })", + // `targets` OK. `target_files` field JSON base64 content do not follow the expected schema. + // decode("eyJmb28iOiAiYmFyIn0=") == "{"foo": "bar"}" + // {"signed": {"version": 2, "targets": {"foo/APM_TRACING/30": {}, "bar": {}},"custom": {"opaque_backend_state": "15"}}} + R"({ + "targets": "eyJzaWduZWQiOiB7InZlcnNpb24iOiAyLCAidGFyZ2V0cyI6IHsiZm9vL0FQTV9UUkFDSU5HLzMwIjoge30sICJiYXIiOiB7fX0sImN1c3RvbSI6IHsib3BhcXVlX2JhY2tlbmRfc3RhdGUiOiAiMTUifX19", + "client_configs": ["foo/APM_TRACING/30"], + "target_files": [{"path": "foo/APM_TRACING/30", "raw": "eyJmb28iOiAiYmFyIn0="}] + })", + })); + // clang-format on + + CAPTURE(test_case); + const auto response_json = + nlohmann::json::parse(/* input = */ test_case, + /* parser_callback = */ nullptr, + /* allow_exceptions = */ false); + + REQUIRE(!response_json.is_discarded()); + rc.process_response(response_json); + + // Next payload should contains an error. + const auto payload = rc.make_request_payload(); + CHECK(payload.contains("error") == true); + CHECK(payload.contains("has_error") == true); + } + + SECTION("valid remote configuration") { + // clang-format off + const std::string json_input = R"({ + "targets": "ewogICAgInNpZ25lZCI6IHsKICAgICAgICAiY3VzdG9tIjogewogICAgICAgICAgICAiYWdlbnRfcmVmcmVzaF9pbnRlcnZhbCI6IDUsCiAgICAgICAgICAgICJvcGFxdWVfYmFja2VuZF9zdGF0ZSI6ICJleUoyWlhKemFXOXVJam95TENKemRHRjBaU0k2ZXlKbWFXeGxYMmhoYzJobGN5STZleUprWVhSaFpHOW5MekV3TURBeE1qVTROREF2UVZCTlgxUlNRVU5KVGtjdk9ESTNaV0ZqWmpoa1ltTXpZV0l4TkRNMFpETXlNV05pT0RGa1ptSm1OMkZtWlRZMU5HRTBZall4TVRGalpqRTJOakJpTnpGalkyWTRPVGM0TVRrek9DOHlPVEE0Tm1Ka1ltVTFNRFpsTmpoaU5UQm1NekExTlRneU0yRXpaR0UxWTJVd05USTRaakUyTkRCa05USmpaamc0TmpFNE1UWmhZV0U1Wm1ObFlXWTBJanBiSW05WVpESnBlVU16ZUM5b1JXc3hlWFZoWTFoR04xbHFjWEpwVGs5QldVdHVaekZ0V0UwMU5WWktUSGM5SWwxOWZYMD0iCiAgICAgICAgfSwKICAgICAgICAic3BlY192ZXJzaW9uIjogIjEuMC4wIiwKICAgICAgICAidGFyZ2V0cyI6IHsKICAgICAgICAgICAgImZvby9BUE1fVFJBQ0lORy8zMCI6IHsKICAgICAgICAgICAgICAgICJoYXNoZXMiOiB7CiAgICAgICAgICAgICAgICAgICAgInNoYTI1NiI6ICJhMTc3NzY4YjIwYjdjN2Y4NDQ5MzVjYWU2OWM1YzVlZDg4ZWFhZTIzNGUwMTgyYTc4MzU5OTczMzllNTUyNGJjIgogICAgICAgICAgICAgICAgfSwKICAgICAgICAgICAgICAgICJsZW5ndGgiOiAzNzQKICAgICAgICAgICAgfQogICAgICAgIH0sCiAgICAgICAgInZlcnNpb24iOiA2NjIwNDMyMAogICAgfQp9", + "client_configs": ["foo/APM_TRACING/30"], + "target_files": [ + { + "path": "foo/APM_TRACING/30", + "raw": "eyAiaWQiOiAiODI3ZWFjZjhkYmMzYWIxNDM0ZDMyMWNiODFkZmJmN2FmZTY1NGE0YjYxMTFjZjE2NjBiNzFjY2Y4OTc4MTkzOCIsICJyZXZpc2lvbiI6IDE2OTgxNjcxMjYwNjQsICJzY2hlbWFfdmVyc2lvbiI6ICJ2MS4wLjAiLCAiYWN0aW9uIjogImVuYWJsZSIsICJsaWJfY29uZmlnIjogeyAibGlicmFyeV9sYW5ndWFnZSI6ICJhbGwiLCAibGlicmFyeV92ZXJzaW9uIjogImxhdGVzdCIsICJzZXJ2aWNlX25hbWUiOiAidGVzdHN2YyIsICJlbnYiOiAidGVzdCIsICJ0cmFjaW5nX2VuYWJsZWQiOiB0cnVlLCAidHJhY2luZ19zYW1wbGluZ19yYXRlIjogMC42IH0sICJzZXJ2aWNlX3RhcmdldCI6IHsgInNlcnZpY2UiOiAidGVzdHN2YyIsICJlbnYiOiAidGVzdCIgfSB9" + } + ] + })"; + // clang-format on + + const auto response_json = + nlohmann::json::parse(/* input = */ json_input, + /* parser_callback = */ nullptr, + /* allow_exceptions = */ false); + + REQUIRE(!response_json.is_discarded()); + + const auto old_trace_sampler = config_manager.get_trace_sampler(); + rc.process_response(response_json); + const auto new_trace_sampler = config_manager.get_trace_sampler(); + + CHECK(new_trace_sampler != old_trace_sampler); + + SECTION("reset confguration") { + SECTION( + "missing from client_configs -> all configurations should be reset") { + // clang-format off + const std::string json_input = R"({ + "targets": "ewogICAgInNpZ25lZCI6IHsKICAgICAgICAiY3VzdG9tIjogewogICAgICAgICAgICAiYWdlbnRfcmVmcmVzaF9pbnRlcnZhbCI6IDUsCiAgICAgICAgICAgICJvcGFxdWVfYmFja2VuZF9zdGF0ZSI6ICJleUoyWlhKemFXOXVJam95TENKemRHRjBaU0k2ZXlKbWFXeGxYMmhoYzJobGN5STZleUprWVhSaFpHOW5MekV3TURBeE1qVTROREF2UVZCTlgxUlNRVU5KVGtjdk9ESTNaV0ZqWmpoa1ltTXpZV0l4TkRNMFpETXlNV05pT0RGa1ptSm1OMkZtWlRZMU5HRTBZall4TVRGalpqRTJOakJpTnpGalkyWTRPVGM0TVRrek9DOHlPVEE0Tm1Ka1ltVTFNRFpsTmpoaU5UQm1NekExTlRneU0yRXpaR0UxWTJVd05USTRaakUyTkRCa05USmpaamc0TmpFNE1UWmhZV0U1Wm1ObFlXWTBJanBiSW05WVpESnBlVU16ZUM5b1JXc3hlWFZoWTFoR04xbHFjWEpwVGs5QldVdHVaekZ0V0UwMU5WWktUSGM5SWwxOWZYMD0iCiAgICAgICAgfSwKICAgICAgICAic3BlY192ZXJzaW9uIjogIjEuMC4wIiwKICAgICAgICAidGFyZ2V0cyI6IHsKICAgICAgICAgICAgImZvby9BUE1fVFJBQ0lORy8zMCI6IHsKICAgICAgICAgICAgICAgICJoYXNoZXMiOiB7CiAgICAgICAgICAgICAgICAgICAgInNoYTI1NiI6ICJhMTc3NzY4YjIwYjdjN2Y4NDQ5MzVjYWU2OWM1YzVlZDg4ZWFhZTIzNGUwMTgyYTc4MzU5OTczMzllNTUyNGJjIgogICAgICAgICAgICAgICAgfSwKICAgICAgICAgICAgICAgICJsZW5ndGgiOiAzNzQKICAgICAgICAgICAgfQogICAgICAgIH0sCiAgICAgICAgInZlcnNpb24iOiA2NjIwNDMyMAogICAgfQp9", + "target_files": [] + })"; + // clang-format on + + const auto response_json = + nlohmann::json::parse(/* input = */ json_input, + /* parser_callback = */ nullptr, + /* allow_exceptions = */ false); + + REQUIRE(!response_json.is_discarded()); + + rc.process_response(response_json); + const auto current_trace_sampler = config_manager.get_trace_sampler(); + CHECK(old_trace_sampler == current_trace_sampler); + } + + SECTION("missing configuration field -> field should be reset") { + // clang-format off + const std::string json_input = R"({ + "targets": "ewogICAgInNpZ25lZCI6IHsKICAgICAgICAiY3VzdG9tIjogewogICAgICAgICAgICAiYWdlbnRfcmVmcmVzaF9pbnRlcnZhbCI6IDUsCiAgICAgICAgICAgICJvcGFxdWVfYmFja2VuZF9zdGF0ZSI6ICJleUoyWlhKemFXOXVJam95TENKemRHRjBaU0k2ZXlKbWFXeGxYMmhoYzJobGN5STZleUprWVhSaFpHOW5MekV3TURBeE1qVTROREF2UVZCTlgxUlNRVU5KVGtjdk9ESTNaV0ZqWmpoa1ltTXpZV0l4TkRNMFpETXlNV05pT0RGa1ptSm1OMkZtWlRZMU5HRTBZall4TVRGalpqRTJOakJpTnpGalkyWTRPVGM0TVRrek9DOHlPVEE0Tm1Ka1ltVTFNRFpsTmpoaU5UQm1NekExTlRneU0yRXpaR0UxWTJVd05USTRaakUyTkRCa05USmpaamc0TmpFNE1UWmhZV0U1Wm1ObFlXWTBJanBiSW05WVpESnBlVU16ZUM5b1JXc3hlWFZoWTFoR04xbHFjWEpwVGs5QldVdHVaekZ0V0UwMU5WWktUSGM5SWwxOWZYMD0iCiAgICAgICAgfSwKICAgICAgICAic3BlY192ZXJzaW9uIjogIjEuMC4wIiwKICAgICAgICAidGFyZ2V0cyI6IHsKICAgICAgICAgICAgImZvby9BUE1fVFJBQ0lORy8zMCI6IHsKICAgICAgICAgICAgICAgICJoYXNoZXMiOiB7CiAgICAgICAgICAgICAgICAgICAgInNoYTI1NiI6ICI2OWUzNDZiNWZmY2U4NDVlMjk5ODRlNzU5YjcxZDdiMDdjNTYxOTc5ZmFlOWU4MmVlZDA4MmMwMzhkODZlNmIwIgogICAgICAgICAgICAgICAgfSwKICAgICAgICAgICAgICAgICJsZW5ndGgiOiAzNzQKICAgICAgICAgICAgfQogICAgICAgIH0sCiAgICAgICAgInZlcnNpb24iOiA2NjIwNDMyMAogICAgfQp9", + "client_configs": ["foo/APM_TRACING/30"], + "target_files": [ + { + "path": "foo/APM_TRACING/30", + "raw": "eyAiaWQiOiAiODI3ZWFjZjhkYmMzYWIxNDM0ZDMyMWNiODFkZmJmN2FmZTY1NGE0YjYxMTFjZjE2NjBiNzFjY2Y4OTc4MTkzOCIsICJyZXZpc2lvbiI6IDE2OTgxNjcxMjYwNjQsICJzY2hlbWFfdmVyc2lvbiI6ICJ2MS4wLjAiLCAiYWN0aW9uIjogImVuYWJsZSIsICJsaWJfY29uZmlnIjogeyAibGlicmFyeV9sYW5ndWFnZSI6ICJhbGwiLCAibGlicmFyeV92ZXJzaW9uIjogImxhdGVzdCIsICJzZXJ2aWNlX25hbWUiOiAidGVzdHN2YyIsICJlbnYiOiAidGVzdCIsICJ0cmFjaW5nX2VuYWJsZWQiOiB0cnVlIH0sICJzZXJ2aWNlX3RhcmdldCI6IHsgInNlcnZpY2UiOiAidGVzdHN2YyIsICJlbnYiOiAidGVzdCIgfSB9" + } + ] + })"; + // clang-format on + + const auto response_json = + nlohmann::json::parse(/* input = */ json_input, + /* parser_callback = */ nullptr, + /* allow_exceptions = */ false); + + REQUIRE(!response_json.is_discarded()); + + rc.process_response(response_json); + const auto current_trace_sampler = config_manager.get_trace_sampler(); + CHECK(old_trace_sampler == current_trace_sampler); + } + } + } + + SECTION("update received not for us") { + // clang-format off + auto test_case = GENERATE(values({ + // "service_target": { "service": "not-testsvc", "env": "test" } + R"({ + "targets": "ewogICAgInNpZ25lZCI6IHsKICAgICAgICAiY3VzdG9tIjogewogICAgICAgICAgICAiYWdlbnRfcmVmcmVzaF9pbnRlcnZhbCI6IDUsCiAgICAgICAgICAgICJvcGFxdWVfYmFja2VuZF9zdGF0ZSI6ICJleUoyWlhKemFXOXVJam95TENKemRHRjBaU0k2ZXlKbWFXeGxYMmhoYzJobGN5STZleUprWVhSaFpHOW5MekV3TURBeE1qVTROREF2UVZCTlgxUlNRVU5KVGtjdk9ESTNaV0ZqWmpoa1ltTXpZV0l4TkRNMFpETXlNV05pT0RGa1ptSm1OMkZtWlRZMU5HRTBZall4TVRGalpqRTJOakJpTnpGalkyWTRPVGM0TVRrek9DOHlPVEE0Tm1Ka1ltVTFNRFpsTmpoaU5UQm1NekExTlRneU0yRXpaR0UxWTJVd05USTRaakUyTkRCa05USmpaamc0TmpFNE1UWmhZV0U1Wm1ObFlXWTBJanBiSW05WVpESnBlVU16ZUM5b1JXc3hlWFZoWTFoR04xbHFjWEpwVGs5QldVdHVaekZ0V0UwMU5WWktUSGM5SWwxOWZYMD0iCiAgICAgICAgfSwKICAgICAgICAic3BlY192ZXJzaW9uIjogIjEuMC4wIiwKICAgICAgICAidGFyZ2V0cyI6IHsKICAgICAgICAgICAgImZvby9BUE1fVFJBQ0lORy8zMCI6IHsKICAgICAgICAgICAgICAgICJoYXNoZXMiOiB7CiAgICAgICAgICAgICAgICAgICAgInNoYTI1NiI6ICJhMTc3NzY4YjIwYjdjN2Y4NDQ5MzVjYWU2OWM1YzVlZDg4ZWFhZTIzNGUwMTgyYTc4MzU5OTczMzllNTUyNGJjIgogICAgICAgICAgICAgICAgfSwKICAgICAgICAgICAgICAgICJsZW5ndGgiOiAzNzQKICAgICAgICAgICAgfQogICAgICAgIH0sCiAgICAgICAgInZlcnNpb24iOiA2NjIwNDMyMAogICAgfQp9", + "client_configs": ["foo/APM_TRACING/30"], + "target_files": [ + { + "path": "foo/APM_TRACING/30", + "raw": "eyAiaWQiOiAiODI3ZWFjZjhkYmMzYWIxNDM0ZDMyMWNiODFkZmJmN2FmZTY1NGE0YjYxMTFjZjE2NjBiNzFjY2Y4OTc4MTkzOCIsICJyZXZpc2lvbiI6IDE2OTgxNjcxMjYwNjQsICJzY2hlbWFfdmVyc2lvbiI6ICJ2MS4wLjAiLCAiYWN0aW9uIjogImVuYWJsZSIsICJsaWJfY29uZmlnIjogeyAibGlicmFyeV9sYW5ndWFnZSI6ICJhbGwiLCAibGlicmFyeV92ZXJzaW9uIjogImxhdGVzdCIsICJzZXJ2aWNlX25hbWUiOiAidGVzdHN2YyIsICJlbnYiOiAidGVzdCIsICJ0cmFjaW5nX2VuYWJsZWQiOiB0cnVlLCAidHJhY2luZ19zYW1wbGluZ19yYXRlIjogMC42IH0sICJzZXJ2aWNlX3RhcmdldCI6IHsgInNlcnZpY2UiOiAibm90LXRlc3RzdmMiLCAiZW52IjogInRlc3QiIH0gfQ==" + } + ] + })", + // "service_target": { "service": "testsvc", "env": "dev" } + R"({ + "targets": "ewogICAgInNpZ25lZCI6IHsKICAgICAgICAiY3VzdG9tIjogewogICAgICAgICAgICAiYWdlbnRfcmVmcmVzaF9pbnRlcnZhbCI6IDUsCiAgICAgICAgICAgICJvcGFxdWVfYmFja2VuZF9zdGF0ZSI6ICJleUoyWlhKemFXOXVJam95TENKemRHRjBaU0k2ZXlKbWFXeGxYMmhoYzJobGN5STZleUprWVhSaFpHOW5MekV3TURBeE1qVTROREF2UVZCTlgxUlNRVU5KVGtjdk9ESTNaV0ZqWmpoa1ltTXpZV0l4TkRNMFpETXlNV05pT0RGa1ptSm1OMkZtWlRZMU5HRTBZall4TVRGalpqRTJOakJpTnpGalkyWTRPVGM0TVRrek9DOHlPVEE0Tm1Ka1ltVTFNRFpsTmpoaU5UQm1NekExTlRneU0yRXpaR0UxWTJVd05USTRaakUyTkRCa05USmpaamc0TmpFNE1UWmhZV0U1Wm1ObFlXWTBJanBiSW05WVpESnBlVU16ZUM5b1JXc3hlWFZoWTFoR04xbHFjWEpwVGs5QldVdHVaekZ0V0UwMU5WWktUSGM5SWwxOWZYMD0iCiAgICAgICAgfSwKICAgICAgICAic3BlY192ZXJzaW9uIjogIjEuMC4wIiwKICAgICAgICAidGFyZ2V0cyI6IHsKICAgICAgICAgICAgImZvby9BUE1fVFJBQ0lORy8zMCI6IHsKICAgICAgICAgICAgICAgICJoYXNoZXMiOiB7CiAgICAgICAgICAgICAgICAgICAgInNoYTI1NiI6ICJhMTc3NzY4YjIwYjdjN2Y4NDQ5MzVjYWU2OWM1YzVlZDg4ZWFhZTIzNGUwMTgyYTc4MzU5OTczMzllNTUyNGJjIgogICAgICAgICAgICAgICAgfSwKICAgICAgICAgICAgICAgICJsZW5ndGgiOiAzNzQKICAgICAgICAgICAgfQogICAgICAgIH0sCiAgICAgICAgInZlcnNpb24iOiA2NjIwNDMyMAogICAgfQp9", + "client_configs": ["foo/APM_TRACING/30"], + "target_files": [ + { + "path": "foo/APM_TRACING/30", + "raw": "eyAiaWQiOiAiODI3ZWFjZjhkYmMzYWIxNDM0ZDMyMWNiODFkZmJmN2FmZTY1NGE0YjYxMTFjZjE2NjBiNzFjY2Y4OTc4MTkzOCIsICJyZXZpc2lvbiI6IDE2OTgxNjcxMjYwNjQsICJzY2hlbWFfdmVyc2lvbiI6ICJ2MS4wLjAiLCAiYWN0aW9uIjogImVuYWJsZSIsICJsaWJfY29uZmlnIjogeyAibGlicmFyeV9sYW5ndWFnZSI6ICJhbGwiLCAibGlicmFyeV92ZXJzaW9uIjogImxhdGVzdCIsICJzZXJ2aWNlX25hbWUiOiAidGVzdHN2YyIsICJlbnYiOiAidGVzdCIsICJ0cmFjaW5nX2VuYWJsZWQiOiB0cnVlLCAidHJhY2luZ19zYW1wbGluZ19yYXRlIjogMC42IH0sICJzZXJ2aWNlX3RhcmdldCI6IHsgInNlcnZpY2UiOiAidGVzdHN2YyIsICJlbnYiOiAiZGV2IiB9IH0=" + } + ] + })" + })); + // clang-format on + + CAPTURE(test_case); + + const auto response_json = + nlohmann::json::parse(/* input = */ test_case, + /* parser_callback = */ nullptr, + /* allow_exceptions = */ false); + + REQUIRE(!response_json.is_discarded()); + + const auto old_sampling_rate = config_manager.get_trace_sampler(); + rc.process_response(response_json); + const auto new_sampling_rate = config_manager.get_trace_sampler(); + + CHECK(new_sampling_rate == old_sampling_rate); + } +} diff --git a/test/test_tracer_config.cpp b/test/test_tracer_config.cpp index 0cd6e7fb..7f0df1a1 100644 --- a/test/test_tracer_config.cpp +++ b/test/test_tracer_config.cpp @@ -5,6 +5,7 @@ #include #include +#include #include #include #include @@ -403,6 +404,57 @@ TEST_CASE("TracerConfig::agent") { } } + SECTION("remote configuration poll interval") { + SECTION("cannot be zero") { + config.agent.remote_configuration_poll_interval_seconds = 0; + auto finalized = finalize_config(config); + REQUIRE(!finalized); + REQUIRE(finalized.error().code == + Error::DATADOG_AGENT_INVALID_REMOTE_CONFIG_POLL_INTERVAL); + } + + SECTION("cannot be negative") { + config.agent.remote_configuration_poll_interval_seconds = -1337; + auto finalized = finalize_config(config); + REQUIRE(!finalized); + REQUIRE(finalized.error().code == + Error::DATADOG_AGENT_INVALID_REMOTE_CONFIG_POLL_INTERVAL); + } + + SECTION("override default value") { + SECTION("programmatically") { + config.agent.remote_configuration_poll_interval_seconds = 42; + auto finalized = finalize_config(config); + REQUIRE(finalized); + const auto* const agent = + std::get_if(&finalized->collector); + REQUIRE(agent); + REQUIRE(agent->remote_configuration_poll_interval == + std::chrono::seconds(42)); + } + + SECTION("environment variable") { + const EnvGuard env_guard{"DD_REMOTE_CONFIG_POLL_INTERVAL_SECONDS", + "15"}; + auto finalized = finalize_config(config); + REQUIRE(finalized); + const auto* const agent = + std::get_if(&finalized->collector); + REQUIRE(agent); + REQUIRE(agent->remote_configuration_poll_interval == + std::chrono::seconds(15)); + } + + SECTION("ill-formated environment variable is an error") { + const EnvGuard env_guard{"DD_REMOTE_CONFIG_POLL_INTERVAL_SECONDS", + "ddog"}; + auto finalized = finalize_config(config); + REQUIRE(!finalized); + REQUIRE(finalized.error().code == Error::INVALID_INTEGER); + } + } + } + SECTION("url") { SECTION("parsing") { struct TestCase { diff --git a/test/test_tracer_telemetry.cpp b/test/test_tracer_telemetry.cpp index 30f31bcd..05cd00ba 100644 --- a/test/test_tracer_telemetry.cpp +++ b/test/test_tracer_telemetry.cpp @@ -7,6 +7,7 @@ #include +#include "datadog/runtime_id.h" #include "mocks/loggers.h" #include "test.h" @@ -20,11 +21,13 @@ TEST_CASE("Tracer telemetry") { return result; }; auto logger = std::make_shared(); - auto span_defaults = std::make_shared(); - span_defaults->service = "testsvc"; - span_defaults->environment = "test"; - TracerTelemetry tracer_telemetry = {true, clock, logger, span_defaults, - RuntimeID::generate()}; + + const TracerSignature tracer_signature{ + /* runtime_id = */ RuntimeID::generate(), + /* service = */ "testsvc", + /* environment = */ "test"}; + + TracerTelemetry tracer_telemetry = {true, clock, logger, tracer_signature}; SECTION("generates app-started message") { auto app_started_message = tracer_telemetry.app_started();