From 581c1297758ddbd63d5f83aec057d50b14c2b16a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Chojnacki?= Date: Tue, 29 Sep 2026 10:11:53 +0200 Subject: [PATCH 1/3] Add native C++ stable configuration loader --- BUILD.bazel | 35 ++- CMakeLists.txt | 40 +++ LICENSE-3rdparty.csv | 3 +- MODULE.bazel | 1 + README.md | 43 +++ WORKSPACE | 6 + cmake/dd-trace-cpp-config.cmake.in | 4 + cmake/deps/yaml-cpp.cmake | 18 ++ include/datadog/config.h | 38 ++- include/datadog/datadog_agent_config.h | 7 + include/datadog/environment.h | 3 + include/datadog/span_sampler_config.h | 7 + include/datadog/stable_config.h | 63 ++++ include/datadog/stable_config_loader.h | 20 ++ include/datadog/trace_sampler_config.h | 6 + include/datadog/tracer_config.h | 12 + src/datadog/datadog_agent_config.cpp | 133 +++++++-- src/datadog/datadog_agent_config_internal.h | 5 +- src/datadog/environment.cpp | 17 ++ src/datadog/span_sampler_config.cpp | 26 +- src/datadog/stable_config.cpp | 30 ++ src/datadog/stable_config_loader.cpp | 179 ++++++++++++ src/datadog/stable_config_loader_internal.h | 20 ++ src/datadog/stable_config_parser.cpp | 308 ++++++++++++++++++++ src/datadog/telemetry/telemetry_impl.cpp | 10 + src/datadog/trace_sampler_config.cpp | 44 ++- src/datadog/tracer_config.cpp | 215 +++++++++----- test/bazel_stable_config_test.cpp | 43 +++ test/fixtures/stable_config/fleet.yaml | 4 + test/fixtures/stable_config/invalid.yaml | 1 + test/fixtures/stable_config/local.yaml | 15 + test/test_stable_config.cpp | 251 ++++++++++++++++ 32 files changed, 1461 insertions(+), 146 deletions(-) create mode 100644 cmake/deps/yaml-cpp.cmake create mode 100644 include/datadog/stable_config.h create mode 100644 include/datadog/stable_config_loader.h create mode 100644 src/datadog/stable_config.cpp create mode 100644 src/datadog/stable_config_loader.cpp create mode 100644 src/datadog/stable_config_loader_internal.h create mode 100644 src/datadog/stable_config_parser.cpp create mode 100644 test/bazel_stable_config_test.cpp create mode 100644 test/fixtures/stable_config/fleet.yaml create mode 100644 test/fixtures/stable_config/invalid.yaml create mode 100644 test/fixtures/stable_config/local.yaml create mode 100644 test/test_stable_config.cpp diff --git a/BUILD.bazel b/BUILD.bazel index 0a8541300..7c6417726 100644 --- a/BUILD.bazel +++ b/BUILD.bazel @@ -1,4 +1,35 @@ -load("@rules_cc//cc:defs.bzl", "cc_library") +load("@rules_cc//cc:defs.bzl", "cc_library", "cc_test") + +cc_library( + name = "dd_trace_cpp_stable_config", + srcs = [ + "src/datadog/stable_config_loader.cpp", + "src/datadog/stable_config_loader_internal.h", + "src/datadog/stable_config_parser.cpp", + ], + hdrs = ["include/datadog/stable_config_loader.h"], + strip_include_prefix = "include/", + visibility = ["//visibility:public"], + linkopts = select({ + "@platforms//os:windows": ["-lshell32"], + "//conditions:default": [], + }), + deps = [ + ":dd_trace_cpp", + "@yaml-cpp//:yaml-cpp", + ], +) + +cc_test( + name = "stable_config_loader_test", + size = "small", + srcs = ["test/bazel_stable_config_test.cpp"], + data = [ + "test/fixtures/stable_config/fleet.yaml", + "test/fixtures/stable_config/local.yaml", + ], + deps = [":dd_trace_cpp_stable_config"], +) cc_library( name = "dd_trace_cpp", @@ -67,6 +98,7 @@ cc_library( "src/datadog/span_sampler.cpp", "src/datadog/span_sampler.h", "src/datadog/span_sampler_config.cpp", + "src/datadog/stable_config.cpp", "src/datadog/string_util.cpp", "src/datadog/string_util.h", "src/datadog/tag_propagation.cpp", @@ -145,6 +177,7 @@ cc_library( "include/datadog/span_defaults.h", "include/datadog/span_matcher.h", "include/datadog/span_sampler_config.h", + "include/datadog/stable_config.h", "include/datadog/string_view.h", "include/datadog/telemetry/configuration.h", "include/datadog/telemetry/metrics.h", diff --git a/CMakeLists.txt b/CMakeLists.txt index 787f91fb6..95af3983e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -19,6 +19,7 @@ project( option(BUILD_SHARED_LIBS "Build shared libraries" ON) option(BUILD_STATIC_LIBS "Build static libraries" ON) option(DD_TRACE_BUILD_C_BINDING "Build C binding" OFF) +option(DD_TRACE_BUILD_STABLE_CONFIG "Build the native stable config loader" OFF) if (WIN32) option(DD_TRACE_STATIC_CRT "Build dd-trace-cpp with static CRT with MSVC" OFF) @@ -166,6 +167,7 @@ target_sources(dd-trace-cpp-objects include/datadog/span_defaults.h include/datadog/span_matcher.h include/datadog/span_sampler_config.h + include/datadog/stable_config.h include/datadog/string_view.h include/datadog/trace_id.h include/datadog/trace_sampler_config.h @@ -214,6 +216,7 @@ target_sources(dd-trace-cpp-objects src/datadog/span_matcher.cpp src/datadog/span_sampler_config.cpp src/datadog/span_sampler.cpp + src/datadog/stable_config.cpp src/datadog/string_util.cpp src/datadog/tags.cpp src/datadog/tag_propagation.cpp @@ -367,6 +370,43 @@ if (BUILD_STATIC_LIBS) ) endif () +if (DD_TRACE_BUILD_STABLE_CONFIG) + include(cmake/deps/yaml-cpp.cmake) + add_library(dd-trace-cpp-stable-config STATIC + src/datadog/stable_config_loader.cpp + src/datadog/stable_config_parser.cpp) + add_library(dd-trace-cpp::stable-config ALIAS dd-trace-cpp-stable-config) + set_target_properties(dd-trace-cpp-stable-config PROPERTIES EXPORT_NAME stable-config) + target_compile_features(dd-trace-cpp-stable-config PUBLIC cxx_std_17) + target_sources(dd-trace-cpp-stable-config PUBLIC + FILE_SET public_headers TYPE HEADERS BASE_DIRS include + FILES include/datadog/stable_config_loader.h) + target_link_libraries(dd-trace-cpp-stable-config PUBLIC yaml-cpp::yaml-cpp) + if (WIN32) + target_link_libraries(dd-trace-cpp-stable-config PUBLIC shell32) + endif () + if (BUILD_STATIC_LIBS) + target_link_libraries(dd-trace-cpp-stable-config PUBLIC dd-trace-cpp::static) + elseif (BUILD_SHARED_LIBS) + target_link_libraries(dd-trace-cpp-stable-config PUBLIC dd-trace-cpp::shared) + else () + message(FATAL_ERROR "Stable config requires a dd-trace-cpp library") + endif () + install(TARGETS dd-trace-cpp-stable-config + EXPORT dd-trace-cpp-targets + FILE_SET public_headers DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}) + install(FILES "${yaml-cpp_SOURCE_DIR}/LICENSE" + DESTINATION ${CMAKE_INSTALL_DATADIR}/dd-trace-cpp/licenses + RENAME yaml-cpp-LICENSE) + if (DD_TRACE_BUILD_TESTING) + target_sources(tests PRIVATE test/test_stable_config.cpp) + target_link_libraries(tests PRIVATE dd-trace-cpp::stable-config) + target_compile_definitions(tests PRIVATE + "DD_TRACE_SOURCE_DIR=\"${CMAKE_CURRENT_SOURCE_DIR}\"") + endif () +endif () + # install target write_basic_package_version_file( "${PROJECT_NAME}-config-version.cmake" diff --git a/LICENSE-3rdparty.csv b/LICENSE-3rdparty.csv index 1dc243874..157b23899 100644 --- a/LICENSE-3rdparty.csv +++ b/LICENSE-3rdparty.csv @@ -3,4 +3,5 @@ vendored,nlohmann/json,MIT,Copyright (c) 2013-2022 Niels Lohmann + +datadog::tracing::TracerConfig config; +auto finalized = + datadog::tracing::finalize_config_with_stable_config(config, "cpp"); +if (!finalized) { + // Handle finalized.error(). +} +``` + +The loader reads the platform's local and fleet `application_monitoring.yaml` +files by default. Pass explicit paths as the third and fourth arguments to +`finalize_config_with_stable_config`, or the second and third arguments to +`load_stable_config`. Files may contain `config_id`, +`apm_configuration_default`, `tags`, and `rules`. Rules select by language, +process arguments, environment variables, or tags. The first matching rule in +each file applies, and its values can contain `{{ language }}` or indexed +variables such as `{{ environment_variables[REGION] }}`. + +Settings resolve in this order: defaults, local YAML, environment variables, +fleet YAML, and values in `TracerConfig`. Stable configuration records the +winning source and config ID in configuration telemetry. The existing +`finalize_config(config)` overload keeps its original behavior. Missing or +oversized files are ignored; malformed YAML and other read errors are reported. + ### Optional: Linking to the shared library In case you decided to build the shared library: diff --git a/WORKSPACE b/WORKSPACE index 39f0fb101..bbee86dd4 100644 --- a/WORKSPACE +++ b/WORKSPACE @@ -14,6 +14,12 @@ # accessed December 6, 2022. load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") +http_archive( + name = "yaml-cpp", + sha256 = "298593d9c440fd9034b8b193d96318b76d49bc97c6ceadb7b0836edf0b6d7539", + urls = ["https://github.com/jbeder/yaml-cpp/releases/download/yaml-cpp-0.9.0/yaml-cpp-yaml-cpp-0.9.0.tar.gz"], +) + http_archive( name = "com_google_absl", sha256 = "1692f77d1739bacf3f94337188b78583cf09bab7e420d2dc6c5605a4f86785a1", diff --git a/cmake/dd-trace-cpp-config.cmake.in b/cmake/dd-trace-cpp-config.cmake.in index 7ed689a22..eba5aeff0 100644 --- a/cmake/dd-trace-cpp-config.cmake.in +++ b/cmake/dd-trace-cpp-config.cmake.in @@ -9,4 +9,8 @@ if(DD_TRACE_TRANSPORT STREQUAL "curl") find_dependency(CURL) endif() +if(@DD_TRACE_BUILD_STABLE_CONFIG@) + find_dependency(yaml-cpp 0.9.0 CONFIG) +endif() + include("${CMAKE_CURRENT_LIST_DIR}/dd-trace-cpp-targets.cmake") diff --git a/cmake/deps/yaml-cpp.cmake b/cmake/deps/yaml-cpp.cmake new file mode 100644 index 000000000..96db94c28 --- /dev/null +++ b/cmake/deps/yaml-cpp.cmake @@ -0,0 +1,18 @@ +include(FetchContent) + +set(YAML_BUILD_SHARED_LIBS OFF CACHE BOOL "" FORCE) +set(YAML_CPP_BUILD_TESTS OFF CACHE BOOL "" FORCE) +set(YAML_CPP_BUILD_TOOLS OFF CACHE BOOL "" FORCE) +set(YAML_CPP_INSTALL ON CACHE BOOL "" FORCE) +set(YAML_CPP_DISABLE_UNINSTALL ON CACHE BOOL "" FORCE) +set(YAML_ENABLE_PIC ON CACHE BOOL "" FORCE) +if (DD_TRACE_STATIC_CRT) + set(YAML_MSVC_SHARED_RT OFF CACHE BOOL "" FORCE) +endif () + +FetchContent_Declare(yaml-cpp + URL https://github.com/jbeder/yaml-cpp/releases/download/yaml-cpp-0.9.0/yaml-cpp-yaml-cpp-0.9.0.tar.gz + URL_HASH SHA256=298593d9c440fd9034b8b193d96318b76d49bc97c6ceadb7b0836edf0b6d7539 + SYSTEM +) +FetchContent_MakeAvailable(yaml-cpp) diff --git a/include/datadog/config.h b/include/datadog/config.h index 3489a69c2..e2e6cac2f 100644 --- a/include/datadog/config.h +++ b/include/datadog/config.h @@ -45,7 +45,9 @@ struct ConfigMetadata { ENVIRONMENT_VARIABLE, // Originating from environment variables CODE, // Defined in code REMOTE_CONFIG, // Retrieved from remote configuration - DEFAULT // Default value + DEFAULT, // Default value + LOCAL_STABLE_CONFIG, + FLEET_STABLE_CONFIG }; // Name of the configuration parameter @@ -56,11 +58,17 @@ struct ConfigMetadata { Origin origin; // Optional error associated with the configuration parameter Optional error; + Optional config_id; 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)) {} + Optional err = nullopt, + Optional id = nullopt) + : name(n), + value(std::move(v)), + origin(orig), + error(std::move(err)), + config_id(std::move(id)) {} }; // Returns the final configuration value using the following @@ -96,7 +104,11 @@ Value resolve_and_record_config( const Optional& from_env, const Optional& from_user, std::unordered_map>* metadata, ConfigName config_name, DefaultValue fallback = nullptr, - Stringifier to_string_fn = nullptr) { + Stringifier to_string_fn = nullptr, + ConfigMetadata::Origin from_env_origin = + ConfigMetadata::Origin::ENVIRONMENT_VARIABLE, + Optional from_env_config_id = nullopt, + bool programmatic_wins = false) { auto stringify = [&](const Value& v) -> std::string { if constexpr (!std::is_same_v) { return to_string_fn(v); // use provided function @@ -112,9 +124,11 @@ Value resolve_and_record_config( std::vector metadata_entries; Optional chosen_value; - auto add_entry = [&](ConfigMetadata::Origin origin, const Value& val) { + auto add_entry = [&](ConfigMetadata::Origin origin, const Value& val, + Optional config_id = nullopt) { std::string val_str = stringify(val); - metadata_entries.emplace_back(ConfigMetadata{config_name, val_str, origin}); + metadata_entries.emplace_back( + ConfigMetadata{config_name, val_str, origin, nullopt, config_id}); chosen_value = val; }; @@ -123,12 +137,12 @@ Value resolve_and_record_config( add_entry(ConfigMetadata::Origin::DEFAULT, fallback); } - if (from_user) { - add_entry(ConfigMetadata::Origin::CODE, *from_user); - } - - if (from_env) { - add_entry(ConfigMetadata::Origin::ENVIRONMENT_VARIABLE, *from_env); + if (programmatic_wins) { + if (from_env) add_entry(from_env_origin, *from_env, from_env_config_id); + if (from_user) add_entry(ConfigMetadata::Origin::CODE, *from_user); + } else { + if (from_user) add_entry(ConfigMetadata::Origin::CODE, *from_user); + if (from_env) add_entry(from_env_origin, *from_env, from_env_config_id); } if (!metadata_entries.empty()) { diff --git a/include/datadog/datadog_agent_config.h b/include/datadog/datadog_agent_config.h index a6d1483c0..679c40f13 100644 --- a/include/datadog/datadog_agent_config.h +++ b/include/datadog/datadog_agent_config.h @@ -23,6 +23,7 @@ namespace datadog::tracing { class EventScheduler; class Logger; +class StableConfig; struct DatadogAgentConfig { // The `HTTPClient` used to submit traces to the Datadog Agent. If this @@ -65,6 +66,9 @@ struct DatadogAgentConfig { class FinalizedDatadogAgentConfig { friend Expected finalize_config( const DatadogAgentConfig&, const std::shared_ptr&, const Clock&); + friend Expected finalize_config( + const DatadogAgentConfig&, const std::shared_ptr&, const Clock&, + const StableConfig*); FinalizedDatadogAgentConfig() = default; @@ -94,5 +98,8 @@ class FinalizedDatadogAgentConfig { Expected finalize_config( const DatadogAgentConfig& config, const std::shared_ptr& logger, const Clock& clock); +Expected finalize_config( + const DatadogAgentConfig& config, const std::shared_ptr& logger, + const Clock& clock, const StableConfig* stable_config); } // namespace datadog::tracing diff --git a/include/datadog/environment.h b/include/datadog/environment.h index 4b4e8dbb4..5e13f64e6 100644 --- a/include/datadog/environment.h +++ b/include/datadog/environment.h @@ -16,6 +16,7 @@ namespace datadog { namespace tracing { +class StableConfig; namespace environment { // Central registry for supported environment variables. @@ -117,6 +118,8 @@ StringView name(Variable variable); // Return the value of the specified environment `variable`, or return // `nullopt` if that variable is not set in the environment. Optional lookup(Variable variable); +Optional lookup(Variable variable, + const StableConfig* stable_config); std::string to_json(); diff --git a/include/datadog/span_sampler_config.h b/include/datadog/span_sampler_config.h index 9dc853910..bba641b83 100644 --- a/include/datadog/span_sampler_config.h +++ b/include/datadog/span_sampler_config.h @@ -20,6 +20,8 @@ namespace datadog { namespace tracing { +class StableConfig; + struct SpanSamplerConfig { struct Rule : public SpanMatcher { double sample_rate = 1.0; @@ -38,6 +40,8 @@ struct SpanSamplerConfig { class FinalizedSpanSamplerConfig { friend Expected finalize_config( const SpanSamplerConfig&, Logger&); + friend Expected finalize_config( + const SpanSamplerConfig&, Logger&, const StableConfig*); friend class FinalizedTracerConfig; FinalizedSpanSamplerConfig() = default; @@ -54,6 +58,9 @@ class FinalizedSpanSamplerConfig { Expected finalize_config(const SpanSamplerConfig&, Logger&); +Expected finalize_config(const SpanSamplerConfig&, + Logger&, + const StableConfig*); std::string to_string(const FinalizedSpanSamplerConfig::Rule&); diff --git a/include/datadog/stable_config.h b/include/datadog/stable_config.h new file mode 100644 index 000000000..683268214 --- /dev/null +++ b/include/datadog/stable_config.h @@ -0,0 +1,63 @@ +#pragma once + +#include +#include + +#include "config.h" +#include "environment.h" +#include "string_view.h" + +namespace datadog::tracing { + +enum class StableConfigSource { LOCAL, FLEET }; + +struct StableConfigEntry { + std::string value; + StableConfigSource source; + std::string config_id; +}; + +struct ConfigValueSource { + ConfigMetadata::Origin origin; + Optional config_id; +}; + +// Values returned by the stable configuration parser. +class StableConfig { + public: + void set(std::string name, StableConfigEntry entry); + const StableConfigEntry* find(StringView name) const; + + private: + std::unordered_map entries_; +}; + +ConfigValueSource config_value_source(environment::Variable variable, + const StableConfig* stable_config); + +template +Value resolve_with_stable_config( + const Optional& from_environment, const Optional& from_user, + std::unordered_map>* metadata, + ConfigName config_name, environment::Variable variable, + const StableConfig* stable_config, DefaultValue fallback = nullptr, + Stringifier to_string_fn = nullptr) { + const ConfigValueSource source = config_value_source(variable, stable_config); + return resolve_and_record_config( + from_environment, from_user, metadata, config_name, fallback, + to_string_fn, source.origin, source.config_id, stable_config != nullptr); +} + +template +Value choose_with_stable_config(const Optional& from_environment, + const Optional& from_user, + Fallback fallback, + const StableConfig* stable_config) { + if (stable_config) { + return value_or(from_user, from_environment, fallback); + } + return value_or(from_environment, from_user, fallback); +} + +} // namespace datadog::tracing diff --git a/include/datadog/stable_config_loader.h b/include/datadog/stable_config_loader.h new file mode 100644 index 000000000..e474f9b8e --- /dev/null +++ b/include/datadog/stable_config_loader.h @@ -0,0 +1,20 @@ +#pragma once + +#include +#include +#include + +namespace datadog::tracing { + +// Load local and fleet stable configuration from YAML. +// Empty paths select the platform defaults. +Expected load_stable_config(StringView language, + StringView local_path = {}, + StringView fleet_path = {}); + +// Load stable config and use it to finalize the tracer configuration. +Expected finalize_config_with_stable_config( + const TracerConfig& config, StringView language, StringView local_path = {}, + StringView fleet_path = {}); + +} // namespace datadog::tracing diff --git a/include/datadog/trace_sampler_config.h b/include/datadog/trace_sampler_config.h index 57dd6f6ef..423250f34 100644 --- a/include/datadog/trace_sampler_config.h +++ b/include/datadog/trace_sampler_config.h @@ -20,6 +20,8 @@ namespace datadog { namespace tracing { +class StableConfig; + struct TraceSamplerRule final { Rate rate; SpanMatcher matcher; @@ -43,6 +45,8 @@ struct TraceSamplerConfig { class FinalizedTraceSamplerConfig { friend Expected finalize_config( const TraceSamplerConfig& config); + friend Expected finalize_config( + const TraceSamplerConfig& config, const StableConfig* stable_config); friend class FinalizedTracerConfig; FinalizedTraceSamplerConfig() = default; @@ -59,6 +63,8 @@ class FinalizedTraceSamplerConfig { Expected finalize_config( const TraceSamplerConfig& config); +Expected finalize_config( + const TraceSamplerConfig& config, const StableConfig* stable_config); } // namespace tracing } // namespace datadog diff --git a/include/datadog/tracer_config.h b/include/datadog/tracer_config.h index de36e6fc4..05acea37d 100644 --- a/include/datadog/tracer_config.h +++ b/include/datadog/tracer_config.h @@ -28,6 +28,7 @@ namespace tracing { class Collector; class Logger; +class StableConfig; class SpanSampler; class TraceSampler; @@ -217,6 +218,9 @@ struct TracerConfig { class FinalizedTracerConfig final { friend Expected finalize_config( const TracerConfig& config, const Clock& clock); + friend Expected finalize_config( + const TracerConfig& config, const StableConfig* stable_config, + const Clock& clock); FinalizedTracerConfig() = default; public: @@ -265,6 +269,14 @@ class FinalizedTracerConfig final { Expected finalize_config(const TracerConfig& config); Expected finalize_config(const TracerConfig& config, const Clock& clock); +Expected finalize_config( + const TracerConfig& config, const StableConfig& stable_config); +Expected finalize_config( + const TracerConfig& config, const StableConfig& stable_config, + const Clock& clock); +Expected finalize_config( + const TracerConfig& config, const StableConfig* stable_config, + const Clock& clock); } // namespace tracing } // namespace datadog diff --git a/src/datadog/datadog_agent_config.cpp b/src/datadog/datadog_agent_config.cpp index 4e3abd591..7a99d12cb 100644 --- a/src/datadog/datadog_agent_config.cpp +++ b/src/datadog/datadog_agent_config.cpp @@ -1,6 +1,8 @@ #include #include +#include +#include #include #include "datadog_agent_config_internal.h" @@ -36,15 +38,56 @@ std::pair select_agent_url( detect_default_agent_url(default_socket_path)); } -Optional build_agent_url_from_environment_variables() { - Optional url_env = lookup(environment::DD_TRACE_AGENT_URL); - if (url_env && !url_env->empty()) { +namespace { + +int agent_url_source_rank(environment::Variable variable, + const StableConfig* stable_config) { + const Optional value = lookup(variable, stable_config); + if (!value || value->empty()) return -1; + if (!stable_config) return 0; + + switch (config_value_source(variable, stable_config).origin) { + case ConfigMetadata::Origin::LOCAL_STABLE_CONFIG: + return 0; + case ConfigMetadata::Origin::ENVIRONMENT_VARIABLE: + return 1; + case ConfigMetadata::Origin::FLEET_STABLE_CONFIG: + return 2; + default: + return -1; + } +} + +environment::Variable agent_url_source(const StableConfig* stable_config) { + const int url_rank = + agent_url_source_rank(environment::DD_TRACE_AGENT_URL, stable_config); + const int host_rank = + agent_url_source_rank(environment::DD_AGENT_HOST, stable_config); + const int port_rank = + agent_url_source_rank(environment::DD_TRACE_AGENT_PORT, stable_config); + if (url_rank >= 0 && url_rank >= std::max(host_rank, port_rank)) { + return environment::DD_TRACE_AGENT_URL; + } + return host_rank >= port_rank ? environment::DD_AGENT_HOST + : environment::DD_TRACE_AGENT_PORT; +} + +} // namespace + +Optional build_agent_url_from_environment_variables( + const StableConfig* stable_config) { + Optional url_env = + lookup(environment::DD_TRACE_AGENT_URL, stable_config); + if (url_env && !url_env->empty() && + agent_url_source(stable_config) == environment::DD_TRACE_AGENT_URL) { return std::string{*url_env}; } - Optional env_host = lookup(environment::DD_AGENT_HOST); + Optional env_host = + lookup(environment::DD_AGENT_HOST, stable_config); if (env_host && env_host->empty()) env_host = nullopt; - Optional env_port = lookup(environment::DD_TRACE_AGENT_PORT); + Optional env_port = + lookup(environment::DD_TRACE_AGENT_PORT, stable_config); if (env_port && env_port->empty()) env_port = nullopt; if (env_host || env_port) { std::string agent_url = "http://"; @@ -66,15 +109,17 @@ Optional build_agent_url_from_environment_variables() { return nullopt; } -Expected load_datadog_agent_env_config() { +Expected load_datadog_agent_env_config( + const StableConfig* stable_config) { DatadogAgentConfig env_config; - if (auto rc_enabled = lookup(environment::DD_REMOTE_CONFIGURATION_ENABLED)) { + if (auto rc_enabled = + lookup(environment::DD_REMOTE_CONFIGURATION_ENABLED, stable_config)) { env_config.remote_configuration_enabled = !falsy(*rc_enabled); } - if (auto raw_rc_poll_interval_value = - lookup(environment::DD_REMOTE_CONFIG_POLL_INTERVAL_SECONDS)) { + if (auto raw_rc_poll_interval_value = lookup( + environment::DD_REMOTE_CONFIG_POLL_INTERVAL_SECONDS, stable_config)) { auto res = parse_double(*raw_rc_poll_interval_value); if (auto error = res.if_error()) { return error->with_prefix( @@ -85,7 +130,7 @@ Expected load_datadog_agent_env_config() { } if (Optional agent_url = - build_agent_url_from_environment_variables()) { + build_agent_url_from_environment_variables(stable_config)) { env_config.url = *std::move(agent_url); } @@ -95,7 +140,15 @@ Expected load_datadog_agent_env_config() { Expected finalize_config( const DatadogAgentConfig& user_config, const std::shared_ptr& logger, const Clock& clock) { - Expected env_config = load_datadog_agent_env_config(); + return finalize_config(user_config, logger, clock, nullptr); +} + +Expected finalize_config( + const DatadogAgentConfig& user_config, + const std::shared_ptr& logger, const Clock& clock, + const StableConfig* stable_config) { + Expected env_config = + load_datadog_agent_env_config(stable_config); if (auto error = env_config.if_error()) { return *error; } @@ -126,9 +179,9 @@ Expected finalize_config( result.remote_configuration_listeners = user_config.remote_configuration_listeners; - if (auto flush_interval_milliseconds = - value_or(env_config->flush_interval_milliseconds, - user_config.flush_interval_milliseconds, 2000); + if (auto flush_interval_milliseconds = choose_with_stable_config( + env_config->flush_interval_milliseconds, + user_config.flush_interval_milliseconds, 2000, stable_config); flush_interval_milliseconds > 0) { result.flush_interval = std::chrono::milliseconds(flush_interval_milliseconds); @@ -138,9 +191,9 @@ Expected finalize_config( "milliseconds."}; } - if (auto request_timeout_milliseconds = - value_or(env_config->request_timeout_milliseconds, - user_config.request_timeout_milliseconds, 2000); + if (auto request_timeout_milliseconds = choose_with_stable_config( + env_config->request_timeout_milliseconds, + user_config.request_timeout_milliseconds, 2000, stable_config); request_timeout_milliseconds > 0) { result.request_timeout = std::chrono::milliseconds(request_timeout_milliseconds); @@ -150,9 +203,9 @@ Expected finalize_config( "milliseconds."}; } - if (auto shutdown_timeout_milliseconds = - value_or(env_config->shutdown_timeout_milliseconds, - user_config.shutdown_timeout_milliseconds, 2000); + if (auto shutdown_timeout_milliseconds = choose_with_stable_config( + env_config->shutdown_timeout_milliseconds, + user_config.shutdown_timeout_milliseconds, 2000, stable_config); shutdown_timeout_milliseconds > 0) { result.shutdown_timeout = std::chrono::milliseconds(shutdown_timeout_milliseconds); @@ -162,9 +215,10 @@ Expected finalize_config( "milliseconds."}; } - if (double rc_poll_interval_seconds = - value_or(env_config->remote_configuration_poll_interval_seconds, - user_config.remote_configuration_poll_interval_seconds, 5.0); + if (double rc_poll_interval_seconds = choose_with_stable_config( + env_config->remote_configuration_poll_interval_seconds, + user_config.remote_configuration_poll_interval_seconds, 5.0, + stable_config); rc_poll_interval_seconds >= 0.0) { result.remote_configuration_poll_interval = std::chrono::duration_cast( @@ -175,25 +229,40 @@ Expected finalize_config( "positive number of seconds."}; } - result.remote_configuration_enabled = - value_or(env_config->remote_configuration_enabled, - user_config.remote_configuration_enabled, true); - - const auto [origin, url] = - select_agent_url(env_config->url, user_config.url, - std::filesystem::path{default_agent_socket_path}); + result.remote_configuration_enabled = choose_with_stable_config( + env_config->remote_configuration_enabled, + user_config.remote_configuration_enabled, true, stable_config); + + const auto [original_origin, url] = + stable_config + ? select_agent_url(user_config.url, env_config->url, + std::filesystem::path{default_agent_socket_path}) + : select_agent_url(env_config->url, user_config.url, + std::filesystem::path{default_agent_socket_path}); + ConfigMetadata::Origin origin = original_origin; + Optional config_id; + if (stable_config) { + if (user_config.url) { + origin = ConfigMetadata::Origin::CODE; + } else if (env_config->url) { + const ConfigValueSource source = + config_value_source(agent_url_source(stable_config), stable_config); + origin = source.origin; + config_id = source.config_id; + } + } auto parsed_url = HTTPClient::URL::parse(url); if (auto* error = parsed_url.if_error()) { return std::move(*error); } result.url = *parsed_url; result.metadata[ConfigName::AGENT_URL] = { - ConfigMetadata(ConfigName::AGENT_URL, url, origin)}; + ConfigMetadata(ConfigName::AGENT_URL, url, origin, nullopt, config_id)}; // Starting Datadog Agent 7.62.0, the admission controller inject a unique // identifier through `DD_EXTERNAL_ENV`. This uid is used for origin // detection. - if (auto external_env = lookup(environment::DD_EXTERNAL_ENV)) { + if (auto external_env = lookup(environment::DD_EXTERNAL_ENV, stable_config)) { result.admission_controller_uid = std::string(*external_env); } diff --git a/src/datadog/datadog_agent_config_internal.h b/src/datadog/datadog_agent_config_internal.h index 7d0246509..9980260b3 100644 --- a/src/datadog/datadog_agent_config_internal.h +++ b/src/datadog/datadog_agent_config_internal.h @@ -7,6 +7,8 @@ namespace datadog::tracing { +class StableConfig; + // The well-known path where the Datadog Agent listens on a Unix domain socket. // Single-step instrumentation mounts the socket here. inline constexpr char default_agent_socket_path[] = @@ -15,7 +17,8 @@ inline constexpr char default_agent_socket_path[] = // Return the Agent URL configured by the environment, or null if the // environment does not configure one. `DD_TRACE_AGENT_URL` wins over // `DD_AGENT_HOST` and `DD_TRACE_AGENT_PORT`. Empty values count as unset. -Optional build_agent_url_from_environment_variables(); +Optional build_agent_url_from_environment_variables( + const StableConfig* stable_config = nullptr); // Return the origin and value of the Agent URL, given the URL from the // environment, the URL from programmatic configuration, and the path where the diff --git a/src/datadog/environment.cpp b/src/datadog/environment.cpp index 4dc3eb3fe..5e3d4fe8a 100644 --- a/src/datadog/environment.cpp +++ b/src/datadog/environment.cpp @@ -1,4 +1,5 @@ #include +#include #include @@ -19,6 +20,22 @@ Optional lookup(Variable variable) { return StringView{value}; } +Optional lookup(Variable variable, + const StableConfig *stable_config) { + const StableConfigEntry *entry = + stable_config ? stable_config->find(name(variable)) : nullptr; + if (entry && entry->source == StableConfigSource::FLEET) { + return StringView{entry->value.data(), entry->value.size()}; + } + if (Optional value = lookup(variable)) { + return value; + } + if (entry) { + return StringView{entry->value.data(), entry->value.size()}; + } + return nullopt; +} + std::string to_json() { auto result = nlohmann::json::object({}); diff --git a/src/datadog/span_sampler_config.cpp b/src/datadog/span_sampler_config.cpp index 28d5c4ef3..05197f545 100644 --- a/src/datadog/span_sampler_config.cpp +++ b/src/datadog/span_sampler_config.cpp @@ -1,6 +1,7 @@ #include #include #include +#include #include #include @@ -143,10 +144,11 @@ Expected> parse_rules(StringView rules_raw, return rules; } -Expected load_span_sampler_env_config(Logger &logger) { +Expected load_span_sampler_env_config( + Logger &logger, const StableConfig *stable_config) { SpanSamplerConfig env_config; - auto rules_env = lookup(environment::DD_SPAN_SAMPLING_RULES); + auto rules_env = lookup(environment::DD_SPAN_SAMPLING_RULES, stable_config); if (rules_env) { auto maybe_rules = parse_rules(*rules_env, name(environment::DD_SPAN_SAMPLING_RULES)); @@ -156,7 +158,8 @@ Expected load_span_sampler_env_config(Logger &logger) { env_config.rules = std::move(*maybe_rules); } - if (auto file_env = lookup(environment::DD_SPAN_SAMPLING_RULES_FILE)) { + if (auto file_env = + lookup(environment::DD_SPAN_SAMPLING_RULES_FILE, stable_config)) { if (rules_env) { const auto rules_file_name = name(environment::DD_SPAN_SAMPLING_RULES_FILE); @@ -222,7 +225,14 @@ 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); + return finalize_config(user_config, logger, nullptr); +} + +Expected finalize_config( + const SpanSamplerConfig &user_config, Logger &logger, + const StableConfig *stable_config) { + Expected env_config = + load_span_sampler_env_config(logger, stable_config); if (auto error = env_config.if_error()) { return *error; } @@ -237,9 +247,13 @@ Expected finalize_config( user_rules = user_config.rules; } - std::vector rules = resolve_and_record_config( + std::vector rules = resolve_with_stable_config( env_rules, user_rules, &result.metadata, ConfigName::SPAN_SAMPLING_RULES, - nullptr, [](const std::vector &r) { + lookup(environment::DD_SPAN_SAMPLING_RULES, stable_config) + ? environment::DD_SPAN_SAMPLING_RULES + : environment::DD_SPAN_SAMPLING_RULES_FILE, + stable_config, nullptr, + [](const std::vector &r) { return to_string(r); }); diff --git a/src/datadog/stable_config.cpp b/src/datadog/stable_config.cpp new file mode 100644 index 000000000..9b85c064c --- /dev/null +++ b/src/datadog/stable_config.cpp @@ -0,0 +1,30 @@ +#include + +namespace datadog::tracing { + +void StableConfig::set(std::string name, StableConfigEntry entry) { + entries_.insert_or_assign(std::move(name), std::move(entry)); +} + +const StableConfigEntry* StableConfig::find(StringView name) const { + const auto entry = entries_.find(std::string(name.data(), name.size())); + return entry == entries_.end() ? nullptr : &entry->second; +} + +ConfigValueSource config_value_source(environment::Variable variable, + const StableConfig* stable_config) { + const StableConfigEntry* entry = + stable_config ? stable_config->find(environment::name(variable)) + : nullptr; + if (entry && (entry->source == StableConfigSource::FLEET || + !environment::lookup(variable))) { + return {entry->source == StableConfigSource::FLEET + ? ConfigMetadata::Origin::FLEET_STABLE_CONFIG + : ConfigMetadata::Origin::LOCAL_STABLE_CONFIG, + entry->config_id.empty() ? nullopt + : Optional{entry->config_id}}; + } + return {ConfigMetadata::Origin::ENVIRONMENT_VARIABLE, nullopt}; +} + +} // namespace datadog::tracing diff --git a/src/datadog/stable_config_loader.cpp b/src/datadog/stable_config_loader.cpp new file mode 100644 index 000000000..dec8061bc --- /dev/null +++ b/src/datadog/stable_config_loader.cpp @@ -0,0 +1,179 @@ +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#if defined(__APPLE__) +#include +#elif defined(_WIN32) +#include +#include +#endif + +#include "stable_config_loader_internal.h" + +#if !defined(_WIN32) && !defined(__APPLE__) +extern char** environ; +#endif + +namespace datadog::tracing { +namespace { + +constexpr std::size_t max_config_file_size = 100 * 1024 * 1024; + +Expected read_config_file(const std::string& path, + const char* source) { + const std::string prefix = + "Unable to load stable configuration: failed to read " + + std::string{source} + " config file: "; +#if defined(_WIN32) + if (path.size() > INT_MAX) + return Error{Error::OTHER, prefix + "path is too long"}; + const int length = + MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, path.data(), + static_cast(path.size()), nullptr, 0); + if (length == 0) return Error{Error::OTHER, prefix + "invalid UTF-8 path"}; + std::wstring wide_path(static_cast(length), L'\0'); + MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, path.data(), + static_cast(path.size()), wide_path.data(), length); + const std::filesystem::path native_path{wide_path}; +#else + const std::filesystem::path native_path{path}; +#endif + std::error_code error; + const std::uintmax_t size = std::filesystem::file_size(native_path, error); + if (error == std::errc::no_such_file_or_directory) return std::string{}; + if (error) { + return Error{Error::OTHER, prefix + error.message()}; + } + if (size > max_config_file_size) return std::string{}; + std::ifstream file(native_path, std::ios::binary); + if (!file) { + if (errno == ENOENT) return std::string{}; + return Error{Error::OTHER, prefix + std::strerror(errno)}; + } + std::string content; + content.reserve(static_cast(size)); + char buffer[8192]; + while (file.read(buffer, sizeof(buffer)) || file.gcount() != 0) { + content.append(buffer, static_cast(file.gcount())); + if (content.size() > max_config_file_size) return std::string{}; + } + if (file.bad()) { + return Error{Error::OTHER, prefix + "read failed"}; + } + return content; +} + +std::string local_default_path() { +#if defined(_WIN32) + return "C:\\ProgramData\\Datadog\\application_monitoring.yaml"; +#elif defined(__APPLE__) + return "/opt/datadog-agent/etc/application_monitoring.yaml"; +#else + return "/etc/datadog-agent/application_monitoring.yaml"; +#endif +} + +std::string fleet_default_path() { +#if defined(_WIN32) + return "C:\\ProgramData\\Datadog\\managed\\datadog-" + "agent\\stable\\application_monitoring.yaml"; +#elif defined(__APPLE__) + return "/opt/datadog-agent/etc/stable/application_monitoring.yaml"; +#else + return "/etc/datadog-agent/managed/datadog-agent/stable/" + "application_monitoring.yaml"; +#endif +} + +#if defined(_WIN32) +std::string utf8(const wchar_t* value, int length) { + const int size = WideCharToMultiByte(CP_UTF8, 0, value, length, nullptr, 0, + nullptr, nullptr); + std::string result(static_cast(size), '\0'); + if (size != 0) { + WideCharToMultiByte(CP_UTF8, 0, value, length, result.data(), size, nullptr, + nullptr); + } + return result; +} +#endif + +stable_config_internal::ProcessInfo current_process(StringView language) { + stable_config_internal::ProcessInfo result; + if (!language.empty()) + result.language.assign(language.data(), language.size()); +#if defined(_WIN32) + int count = 0; + wchar_t** args = CommandLineToArgvW(GetCommandLineW(), &count); + if (args) { + for (int index = 0; index < count; ++index) { + result.args.push_back(utf8(args[index], -1)); + if (!result.args.back().empty()) result.args.back().pop_back(); + } + LocalFree(args); + } + wchar_t* environment = GetEnvironmentStringsW(); + if (environment) { + for (const wchar_t* entry = environment; *entry; + entry += std::wcslen(entry) + 1) { + result.environment.push_back(utf8(entry, -1)); + if (!result.environment.back().empty()) + result.environment.back().pop_back(); + } + FreeEnvironmentStringsW(environment); + } +#elif defined(__APPLE__) + for (int index = 0; index < *_NSGetArgc(); ++index) { + result.args.emplace_back((*_NSGetArgv())[index]); + } + for (char** entry = *_NSGetEnviron(); entry && *entry; ++entry) { + result.environment.emplace_back(*entry); + } +#else + std::ifstream args("/proc/self/cmdline", std::ios::binary); + std::string item; + while (std::getline(args, item, '\0')) result.args.push_back(item); + for (char** entry = environ; entry && *entry; ++entry) { + result.environment.emplace_back(*entry); + } +#endif + return result; +} + +} // namespace + +Expected load_stable_config(StringView language, + StringView local_path, + StringView fleet_path) { + const std::string local = + local_path.empty() ? local_default_path() + : std::string(local_path.data(), local_path.size()); + const std::string fleet = + fleet_path.empty() ? fleet_default_path() + : std::string(fleet_path.data(), fleet_path.size()); + Expected local_yaml = read_config_file(local, "local"); + if (Error* error = local_yaml.if_error()) return *error; + Expected fleet_yaml = read_config_file(fleet, "fleet"); + if (Error* error = fleet_yaml.if_error()) return *error; + return stable_config_internal::load_yaml(*local_yaml, *fleet_yaml, + current_process(language)); +} + +Expected finalize_config_with_stable_config( + const TracerConfig& config, StringView language, StringView local_path, + StringView fleet_path) { + Expected stable_config = + load_stable_config(language, local_path, fleet_path); + if (Error* error = stable_config.if_error()) return *error; + return finalize_config(config, *stable_config); +} + +} // namespace datadog::tracing diff --git a/src/datadog/stable_config_loader_internal.h b/src/datadog/stable_config_loader_internal.h new file mode 100644 index 000000000..3d8621f7d --- /dev/null +++ b/src/datadog/stable_config_loader_internal.h @@ -0,0 +1,20 @@ +#pragma once + +#include +#include + +#include +#include + +namespace datadog::tracing::stable_config_internal { + +struct ProcessInfo { + std::string language; + std::vector args; + std::vector environment; +}; + +Expected load_yaml(StringView local_yaml, StringView fleet_yaml, + const ProcessInfo& process); + +} // namespace datadog::tracing::stable_config_internal diff --git a/src/datadog/stable_config_parser.cpp b/src/datadog/stable_config_parser.cpp new file mode 100644 index 000000000..e410bb769 --- /dev/null +++ b/src/datadog/stable_config_parser.cpp @@ -0,0 +1,308 @@ +#include + +#include +#include +#include +#include +#include + +#include "stable_config_loader_internal.h" + +namespace datadog::tracing::stable_config_internal { +namespace { + +struct Selector { + std::string origin; + std::string key; + bool has_key = false; + std::string operation; + std::vector matches; +}; + +struct Rule { + std::vector selectors; + std::vector> configuration; +}; + +struct ParsedConfig { + std::string config_id; + std::vector> defaults; + std::map tags; + std::vector rules; +}; + +std::string scalar(const YAML::Node& node, const char* name) { + if (!node || !node.IsScalar()) { + throw std::runtime_error(std::string{name} + " must be a scalar"); + } + return node.Scalar(); +} + +std::vector> parse_map( + const YAML::Node& node, const char* name) { + if (!node || !node.IsMap()) { + throw std::runtime_error(std::string{name} + " must be a map"); + } + std::vector> entries; + for (YAML::const_iterator entry = node.begin(); entry != node.end(); + ++entry) { + if (!entry->first.IsScalar()) continue; + entries.emplace_back(entry->first.Scalar(), + scalar(entry->second, "configuration value")); + } + return entries; +} + +Selector parse_selector(const YAML::Node& node) { + if (!node.IsMap()) throw std::runtime_error("selector must be a map"); + Selector result; + result.origin = scalar(node["origin"], "selector origin"); + if (result.origin != "language" && result.origin != "process_arguments" && + result.origin != "environment_variables" && result.origin != "tags") { + throw std::runtime_error("unknown selector origin: " + result.origin); + } + if (const YAML::Node key = node["key"]; key && !key.IsNull()) { + result.key = scalar(key, "selector key"); + result.has_key = true; + } + result.operation = scalar(node["operator"], "selector operator"); + if (result.operation != "exists" && result.operation != "equals" && + result.operation != "prefix_matches" && + result.operation != "suffix_matches") { + throw std::runtime_error("unknown selector operator: " + result.operation); + } + if (result.operation != "exists") { + const YAML::Node matches = node["matches"]; + if (!matches || !matches.IsSequence()) { + throw std::runtime_error("selector matches must be a sequence"); + } + for (const YAML::Node& match : matches) { + result.matches.push_back(scalar(match, "selector match")); + } + } + return result; +} + +ParsedConfig parse_yaml(StringView content) { + const std::string text = content.empty() + ? std::string{} + : std::string(content.data(), content.size()); + const YAML::Node document = YAML::Load(text); + ParsedConfig result; + if (!document || document.IsNull()) return result; + if (!document.IsMap()) throw std::runtime_error("root must be a map"); + if (const YAML::Node id = document["config_id"]; id && !id.IsNull()) { + result.config_id = scalar(id, "config_id"); + } + if (const YAML::Node defaults = document["apm_configuration_default"]; + defaults) { + result.defaults = parse_map(defaults, "apm_configuration_default"); + } + if (const YAML::Node tags = document["tags"]; tags) { + if (!tags.IsMap()) throw std::runtime_error("tags must be a map"); + for (YAML::const_iterator tag = tags.begin(); tag != tags.end(); ++tag) { + result.tags.insert_or_assign(scalar(tag->first, "tag key"), + scalar(tag->second, "tag value")); + } + } + if (const YAML::Node rules = document["rules"]; rules) { + if (!rules.IsSequence()) + throw std::runtime_error("rules must be a sequence"); + for (const YAML::Node& item : rules) { + if (!item.IsMap()) throw std::runtime_error("rule must be a map"); + const YAML::Node selectors = item["selectors"]; + if (!selectors || !selectors.IsSequence()) { + throw std::runtime_error("rule selectors must be a sequence"); + } + Rule rule; + for (const YAML::Node& selector : selectors) { + rule.selectors.push_back(parse_selector(selector)); + } + rule.configuration = + parse_map(item["configuration"], "rule configuration"); + result.rules.push_back(std::move(rule)); + } + } + return result; +} + +bool valid_utf8(const std::string& value) { + for (std::size_t index = 0; index < value.size();) { + const unsigned char first = static_cast(value[index]); + if (first < 0x80) { + ++index; + continue; + } + const std::size_t length = first >= 0xf0 ? 4 : first >= 0xe0 ? 3 : 2; + if (first < 0xc2 || first > 0xf4 || index + length > value.size()) + return false; + for (std::size_t offset = 1; offset < length; ++offset) { + const unsigned char next = + static_cast(value[index + offset]); + if ((next & 0xc0) != 0x80) return false; + } + const unsigned char second = static_cast(value[index + 1]); + if ((first == 0xe0 && second < 0xa0) || (first == 0xed && second >= 0xa0) || + (first == 0xf0 && second < 0x90) || (first == 0xf4 && second >= 0x90)) + return false; + index += length; + } + return true; +} + +std::map split_entries( + const std::vector& entries, bool include_without_equals) { + std::map result; + for (const std::string& entry : entries) { + if (!valid_utf8(entry)) continue; + const std::size_t equal = entry.find('='); + if (equal == std::string::npos && !include_without_equals) continue; + result.insert_or_assign( + entry.substr(0, equal), + equal == std::string::npos ? "" : entry.substr(equal + 1)); + } + return result; +} + +bool string_match(const Selector& selector, const std::string& value) { + if (selector.operation == "exists") return true; + for (const std::string& match : selector.matches) { + if (selector.operation == "equals" && value == match) return true; + if (selector.operation == "prefix_matches" && + value.compare(0, match.size(), match) == 0) + return true; + if (selector.operation == "suffix_matches" && + value.size() >= match.size() && + value.compare(value.size() - match.size(), match.size(), match) == 0) { + return true; + } + } + return false; +} + +bool selector_matches(const Selector& selector, const ParsedConfig& source, + const ProcessInfo& process, + const std::map& args, + const std::map& environment) { + if (selector.origin == "language") + return string_match(selector, process.language); + if (selector.origin == "tags") { + if (!selector.has_key) return false; + const auto found = source.tags.find(selector.key); + return found != source.tags.end() && string_match(selector, found->second); + } + if (selector.has_key) { + const std::map& values = + selector.origin == "process_arguments" ? args : environment; + const auto found = values.find(selector.key); + return found != values.end() && string_match(selector, found->second); + } + const std::vector& values = + selector.origin == "process_arguments" ? process.args + : process.environment; + for (const std::string& value : values) { + if (string_match(selector, value)) return true; + } + return false; +} + +std::string trim(std::string value) { + const std::size_t start = value.find_first_not_of(" \t\n\r"); + if (start == std::string::npos) return ""; + const std::size_t end = value.find_last_not_of(" \t\n\r"); + return value.substr(start, end - start + 1); +} + +std::string template_value( + const std::string& input, const ParsedConfig& source, + const ProcessInfo& process, const std::map& args, + const std::map& environment) { + std::string output; + std::size_t cursor = 0; + while (true) { + const std::size_t open = input.find("{{", cursor); + if (open == std::string::npos) return output + input.substr(cursor); + output += input.substr(cursor, open - cursor); + const std::size_t close = input.find("}}", open + 2); + if (close == std::string::npos) + throw std::runtime_error("unterminated template in config"); + const std::string variable = trim(input.substr(open + 2, close - open - 2)); + const std::size_t bracket = variable.find('['); + const std::string name = + bracket == std::string::npos ? variable : variable.substr(0, bracket); + std::string key; + bool has_index = false; + if (bracket != std::string::npos) { + const std::size_t end = variable.find(']', bracket + 1); + if (end != std::string::npos) { + key = trim(variable.substr(bracket + 1, end - bracket - 1)); + has_index = true; + } + } + if (name == "language") { + output += process.language; + } else { + const std::map* values = nullptr; + if (name == "tags") values = &source.tags; + if (name == "process_arguments") values = &args; + if (name == "environment_variables") values = &environment; + if (values && has_index) { + const auto found = values->find(key); + output += found == values->end() ? "UNDEFINED" : found->second; + } else { + output += "UNDEFINED"; + } + } + cursor = close + 2; + } +} + +void apply_source(StableConfig& result, const ParsedConfig& source, + StableConfigSource origin, const ProcessInfo& process) { + for (const std::pair& entry : source.defaults) { + result.set(entry.first, {entry.second, origin, source.config_id}); + } + const std::map args = + split_entries(process.args, false); + const std::map environment = + split_entries(process.environment, true); + for (const Rule& rule : source.rules) { + bool matches = true; + for (const Selector& selector : rule.selectors) { + if (!selector_matches(selector, source, process, args, environment)) { + matches = false; + break; + } + } + if (!matches) continue; + for (const std::pair& entry : + rule.configuration) { + result.set(entry.first, {template_value(entry.second, source, process, + args, environment), + origin, source.config_id}); + } + break; + } +} + +} // namespace + +Expected load_yaml(StringView local_yaml, StringView fleet_yaml, + const ProcessInfo& process) { + try { + if (!valid_utf8(process.language)) { + throw std::runtime_error("language is not UTF-8"); + } + const ParsedConfig local = parse_yaml(local_yaml); + const ParsedConfig fleet = parse_yaml(fleet_yaml); + StableConfig result; + apply_source(result, local, StableConfigSource::LOCAL, process); + apply_source(result, fleet, StableConfigSource::FLEET, process); + return result; + } catch (const std::exception& error) { + return Error{Error::OTHER, "Unable to load stable configuration: " + + std::string{error.what()}}; + } +} + +} // namespace datadog::tracing::stable_config_internal diff --git a/src/datadog/telemetry/telemetry_impl.cpp b/src/datadog/telemetry/telemetry_impl.cpp index 33159ea33..45ba60996 100644 --- a/src/datadog/telemetry/telemetry_impl.cpp +++ b/src/datadog/telemetry/telemetry_impl.cpp @@ -761,6 +761,16 @@ nlohmann::json Telemetry::serialize_configuration_field( case ConfigMetadata::Origin::DEFAULT: j["origin"] = "default"; break; + case ConfigMetadata::Origin::LOCAL_STABLE_CONFIG: + j["origin"] = "local_stable_config"; + break; + case ConfigMetadata::Origin::FLEET_STABLE_CONFIG: + j["origin"] = "fleet_stable_config"; + break; + } + + if (config_metadata.config_id) { + j["config_id"] = *config_metadata.config_id; } if (config_metadata.error) { diff --git a/src/datadog/trace_sampler_config.cpp b/src/datadog/trace_sampler_config.cpp index b573bae8c..ce7f0d790 100644 --- a/src/datadog/trace_sampler_config.cpp +++ b/src/datadog/trace_sampler_config.cpp @@ -1,4 +1,5 @@ #include +#include #include #include @@ -15,10 +16,12 @@ namespace datadog { namespace tracing { namespace { -Expected load_trace_sampler_env_config() { +Expected load_trace_sampler_env_config( + const StableConfig *stable_config) { TraceSamplerConfig env_config; - if (auto rules_env = lookup(environment::DD_TRACE_SAMPLING_RULES)) { + if (auto rules_env = + lookup(environment::DD_TRACE_SAMPLING_RULES, stable_config)) { nlohmann::json json_rules; try { json_rules = nlohmann::json::parse(*rules_env); @@ -107,7 +110,8 @@ Expected load_trace_sampler_env_config() { } } - if (auto sample_rate_env = lookup(environment::DD_TRACE_SAMPLE_RATE)) { + if (auto sample_rate_env = + lookup(environment::DD_TRACE_SAMPLE_RATE, stable_config)) { auto maybe_sample_rate = parse_double(*sample_rate_env); if (auto *error = maybe_sample_rate.if_error()) { std::string prefix; @@ -119,7 +123,8 @@ Expected load_trace_sampler_env_config() { env_config.sample_rate = *maybe_sample_rate; } - if (auto limit_env = lookup(environment::DD_TRACE_RATE_LIMIT)) { + if (auto limit_env = + lookup(environment::DD_TRACE_RATE_LIMIT, stable_config)) { auto maybe_max_per_second = parse_double(*limit_env); if (auto *error = maybe_max_per_second.if_error()) { std::string prefix; @@ -151,7 +156,13 @@ TraceSamplerConfig::Rule::Rule(const SpanMatcher &base) : SpanMatcher(base) {} Expected finalize_config( const TraceSamplerConfig &config) { - Expected env_config = load_trace_sampler_env_config(); + return finalize_config(config, nullptr); +} + +Expected finalize_config( + const TraceSamplerConfig &config, const StableConfig *stable_config) { + Expected env_config = + load_trace_sampler_env_config(stable_config); if (auto error = env_config.if_error()) { return *error; } @@ -160,11 +171,18 @@ Expected finalize_config( std::vector rules; - if (!env_config->rules.empty()) { + if (stable_config && !config.rules.empty()) { + rules = config.rules; + result.metadata[ConfigName::TRACE_SAMPLING_RULES] = { + ConfigMetadata(ConfigName::TRACE_SAMPLING_RULES, to_string(rules), + ConfigMetadata::Origin::CODE)}; + } else if (!env_config->rules.empty()) { rules = std::move(env_config->rules); + const ConfigValueSource source = config_value_source( + environment::DD_TRACE_SAMPLING_RULES, stable_config); result.metadata[ConfigName::TRACE_SAMPLING_RULES] = { ConfigMetadata(ConfigName::TRACE_SAMPLING_RULES, to_string(rules), - ConfigMetadata::Origin::ENVIRONMENT_VARIABLE)}; + source.origin, nullopt, source.config_id)}; } else if (!config.rules.empty()) { rules = std::move(config.rules); result.metadata[ConfigName::TRACE_SAMPLING_RULES] = { @@ -191,10 +209,10 @@ Expected finalize_config( result.rules.emplace_back(std::move(finalized_rule)); } - Optional sample_rate = resolve_and_record_config( + Optional sample_rate = resolve_with_stable_config( env_config->sample_rate, config.sample_rate, &result.metadata, - ConfigName::TRACE_SAMPLING_RATE, 1.0, - [](const double &d) { return to_string(d, 1); }); + ConfigName::TRACE_SAMPLING_RATE, environment::DD_TRACE_SAMPLE_RATE, + stable_config, 1.0, [](const double &d) { return to_string(d, 1); }); bool is_sample_rate_provided = env_config->sample_rate || config.sample_rate; // If `sample_rate` was specified, then it translates to a "catch-all" rule @@ -214,10 +232,10 @@ Expected finalize_config( result.rules.emplace_back(std::move(finalized_rule)); } - double max_per_second = resolve_and_record_config( + double max_per_second = resolve_with_stable_config( env_config->max_per_second, config.max_per_second, &result.metadata, - ConfigName::TRACE_SAMPLING_LIMIT, 100.0, - [](const double &d) { return std::to_string(d); }); + ConfigName::TRACE_SAMPLING_LIMIT, environment::DD_TRACE_RATE_LIMIT, + stable_config, 100.0, [](const double &d) { return std::to_string(d); }); const auto allowed_types = {FP_NORMAL, FP_SUBNORMAL}; if (!(max_per_second > 0) || diff --git a/src/datadog/tracer_config.cpp b/src/datadog/tracer_config.cpp index 4fe4f2fc6..cd75a5934 100644 --- a/src/datadog/tracer_config.cpp +++ b/src/datadog/tracer_config.cpp @@ -1,4 +1,5 @@ #include +#include #include #include @@ -71,8 +72,8 @@ Expected> parse_propagation_styles( // If `env_var` is not in the environment, return `nullopt`. If an error occurs, // throw an `Error`. Optional> styles_from_env( - environment::Variable env_var) { - const auto styles_env = lookup(env_var); + environment::Variable env_var, const StableConfig *stable_config) { + const auto styles_env = lookup(env_var, stable_config); if (!styles_env) { return {}; } @@ -94,21 +95,30 @@ std::string json_quoted(StringView text) { return nlohmann::json(std::move(unquoted)).dump(); } -Expected load_tracer_env_config(Logger &logger) { +environment::Variable propagation_style_source( + environment::Variable specific, environment::Variable legacy, + const StableConfig *stable_config) { + if (lookup(specific, stable_config)) return specific; + if (lookup(legacy, stable_config)) return legacy; + return environment::DD_TRACE_PROPAGATION_STYLE; +} + +Expected load_tracer_env_config( + Logger &logger, const StableConfig *stable_config) { TracerConfig env_cfg; - if (auto service_env = lookup(environment::DD_SERVICE)) { + if (auto service_env = lookup(environment::DD_SERVICE, stable_config)) { env_cfg.service = std::string{*service_env}; } - if (auto environment_env = lookup(environment::DD_ENV)) { + if (auto environment_env = lookup(environment::DD_ENV, stable_config)) { env_cfg.environment = std::string{*environment_env}; } - if (auto version_env = lookup(environment::DD_VERSION)) { + if (auto version_env = lookup(environment::DD_VERSION, stable_config)) { env_cfg.version = std::string{*version_env}; } - if (auto tags_env = lookup(environment::DD_TAGS)) { + if (auto tags_env = lookup(environment::DD_TAGS, stable_config)) { auto tags = parse_tags(*tags_env); if (auto *error = tags.if_error()) { std::string prefix; @@ -120,34 +130,38 @@ Expected load_tracer_env_config(Logger &logger) { env_cfg.tags = std::move(*tags); } - if (auto startup_env = lookup(environment::DD_TRACE_STARTUP_LOGS)) { + if (auto startup_env = + lookup(environment::DD_TRACE_STARTUP_LOGS, stable_config)) { env_cfg.log_on_startup = !falsy(*startup_env); } - if (auto enabled_env = lookup(environment::DD_TRACE_ENABLED)) { + if (auto enabled_env = lookup(environment::DD_TRACE_ENABLED, stable_config)) { env_cfg.report_traces = !falsy(*enabled_env); } if (auto enabled_env = - lookup(environment::DD_TRACE_128_BIT_TRACEID_GENERATION_ENABLED)) { + lookup(environment::DD_TRACE_128_BIT_TRACEID_GENERATION_ENABLED, + stable_config)) { env_cfg.generate_128bit_trace_ids = !falsy(*enabled_env); } - if (auto apm_enabled_env = lookup(environment::DD_APM_TRACING_ENABLED)) { + if (auto apm_enabled_env = + lookup(environment::DD_APM_TRACING_ENABLED, stable_config)) { env_cfg.tracing_enabled = !falsy(*apm_enabled_env); } - if (auto resource_renaming_enabled_env = - lookup(environment::DD_TRACE_RESOURCE_RENAMING_ENABLED)) { + if (auto resource_renaming_enabled_env = lookup( + environment::DD_TRACE_RESOURCE_RENAMING_ENABLED, stable_config)) { env_cfg.resource_renaming_enabled = !falsy(*resource_renaming_enabled_env); } if (auto resource_renaming_always_simplified_endpoint_env = lookup( - environment::DD_TRACE_RESOURCE_RENAMING_ALWAYS_SIMPLIFIED_ENDPOINT)) { + environment::DD_TRACE_RESOURCE_RENAMING_ALWAYS_SIMPLIFIED_ENDPOINT, + stable_config)) { env_cfg.resource_renaming_always_simplified_endpoint = !falsy(*resource_renaming_always_simplified_endpoint_env); } // Baggage if (auto baggage_items_env = - lookup(environment::DD_TRACE_BAGGAGE_MAX_ITEMS)) { + lookup(environment::DD_TRACE_BAGGAGE_MAX_ITEMS, stable_config)) { auto maybe_value = parse_uint64(*baggage_items_env, 10); if (auto *error = maybe_value.if_error()) { return *error; @@ -157,7 +171,7 @@ Expected load_tracer_env_config(Logger &logger) { } if (auto baggage_bytes_env = - lookup(environment::DD_TRACE_BAGGAGE_MAX_BYTES)) { + lookup(environment::DD_TRACE_BAGGAGE_MAX_BYTES, stable_config)) { auto maybe_value = parse_uint64(*baggage_bytes_env, 10); if (auto *error = maybe_value.if_error()) { return *error; @@ -219,11 +233,11 @@ Expected load_tracer_env_config(Logger &logger) { }; for (const auto &[var, var_override] : questionable_combinations) { - const auto value = lookup(var); + const auto value = lookup(var, stable_config); if (!value) { continue; } - const auto value_override = lookup(var_override); + const auto value_override = lookup(var_override, stable_config); if (!value_override) { continue; } @@ -237,7 +251,7 @@ Expected load_tracer_env_config(Logger &logger) { } const auto propagation_behavior_extract = - lookup(environment::DD_TRACE_PROPAGATION_BEHAVIOR_EXTRACT); + lookup(environment::DD_TRACE_PROPAGATION_BEHAVIOR_EXTRACT, stable_config); if (propagation_behavior_extract.has_value()) { env_cfg.propagation_behavior_extract = parse_propagation_behavior_extract( propagation_behavior_extract.value()); @@ -245,23 +259,23 @@ Expected load_tracer_env_config(Logger &logger) { try { const auto global_styles = - styles_from_env(environment::DD_TRACE_PROPAGATION_STYLE); + styles_from_env(environment::DD_TRACE_PROPAGATION_STYLE, stable_config); - if (auto trace_extraction_styles = - styles_from_env(environment::DD_TRACE_PROPAGATION_STYLE_EXTRACT)) { + if (auto trace_extraction_styles = styles_from_env( + environment::DD_TRACE_PROPAGATION_STYLE_EXTRACT, stable_config)) { env_cfg.extraction_styles = std::move(*trace_extraction_styles); - } else if (auto extraction_styles = - styles_from_env(environment::DD_PROPAGATION_STYLE_EXTRACT)) { + } else if (auto extraction_styles = styles_from_env( + environment::DD_PROPAGATION_STYLE_EXTRACT, stable_config)) { env_cfg.extraction_styles = std::move(*extraction_styles); } else { env_cfg.extraction_styles = global_styles; } - if (auto trace_injection_styles = - styles_from_env(environment::DD_TRACE_PROPAGATION_STYLE_INJECT)) { + if (auto trace_injection_styles = styles_from_env( + environment::DD_TRACE_PROPAGATION_STYLE_INJECT, stable_config)) { env_cfg.injection_styles = std::move(*trace_injection_styles); - } else if (auto injection_styles = - styles_from_env(environment::DD_PROPAGATION_STYLE_INJECT)) { + } else if (auto injection_styles = styles_from_env( + environment::DD_PROPAGATION_STYLE_INJECT, stable_config)) { env_cfg.injection_styles = std::move(*injection_styles); } else { env_cfg.injection_styles = global_styles; @@ -276,15 +290,33 @@ Expected load_tracer_env_config(Logger &logger) { } // namespace Expected finalize_config(const TracerConfig &config) { - return finalize_config(config, default_clock); + return finalize_config(config, nullptr, default_clock); } -Expected finalize_config(const TracerConfig &user_config, +Expected finalize_config(const TracerConfig &config, const Clock &clock) { + return finalize_config(config, nullptr, clock); +} + +Expected finalize_config( + const TracerConfig &config, const StableConfig &stable_config) { + return finalize_config(config, &stable_config, default_clock); +} + +Expected finalize_config( + const TracerConfig &config, const StableConfig &stable_config, + const Clock &clock) { + return finalize_config(config, &stable_config, clock); +} + +Expected finalize_config( + const TracerConfig &user_config, const StableConfig *stable_config, + const Clock &clock) { auto logger = user_config.logger ? user_config.logger : std::make_shared(); - Expected env_config = load_tracer_env_config(*logger); + Expected env_config = + load_tracer_env_config(*logger, stable_config); if (auto error = env_config.if_error()) { return *error; } @@ -294,31 +326,34 @@ Expected finalize_config(const TracerConfig &user_config, final_config.logger = logger; // DD_SERVICE - final_config.defaults.service = resolve_and_record_config( + final_config.defaults.service = resolve_with_stable_config( env_config->service, user_config.service, &final_config.metadata, - ConfigName::SERVICE_NAME, get_process_name()); + ConfigName::SERVICE_NAME, environment::DD_SERVICE, stable_config, + get_process_name()); // Service type - final_config.defaults.service_type = - value_or(env_config->service_type, user_config.service_type, "web"); + final_config.defaults.service_type = choose_with_stable_config( + env_config->service_type, user_config.service_type, "web", stable_config); // DD_ENV - final_config.defaults.environment = resolve_and_record_config( + final_config.defaults.environment = resolve_with_stable_config( env_config->environment, user_config.environment, &final_config.metadata, - ConfigName::SERVICE_ENV); + ConfigName::SERVICE_ENV, environment::DD_ENV, stable_config); // DD_VERSION - final_config.defaults.version = resolve_and_record_config( + final_config.defaults.version = resolve_with_stable_config( env_config->version, user_config.version, &final_config.metadata, - ConfigName::SERVICE_VERSION); + ConfigName::SERVICE_VERSION, environment::DD_VERSION, stable_config); // Span name - final_config.defaults.name = value_or(env_config->name, user_config.name, ""); + final_config.defaults.name = choose_with_stable_config( + env_config->name, user_config.name, "", stable_config); // DD_TAGS - final_config.defaults.tags = resolve_and_record_config( + final_config.defaults.tags = resolve_with_stable_config( env_config->tags, user_config.tags, &final_config.metadata, - ConfigName::TAGS, std::unordered_map{}, + ConfigName::TAGS, environment::DD_TAGS, stable_config, + std::unordered_map{}, [](const auto &tags) { return join_tags(tags); }); // Extraction Styles @@ -326,10 +361,13 @@ Expected finalize_config(const TracerConfig &user_config, PropagationStyle::DATADOG, PropagationStyle::W3C, PropagationStyle::BAGGAGE}; - final_config.extraction_styles = resolve_and_record_config( + final_config.extraction_styles = resolve_with_stable_config( env_config->extraction_styles, user_config.extraction_styles, &final_config.metadata, ConfigName::EXTRACTION_STYLES, - default_propagation_styles, + propagation_style_source(environment::DD_TRACE_PROPAGATION_STYLE_EXTRACT, + environment::DD_PROPAGATION_STYLE_EXTRACT, + stable_config), + stable_config, default_propagation_styles, [](const std::vector &styles) { return join_propagation_styles(styles); }); @@ -340,10 +378,13 @@ Expected finalize_config(const TracerConfig &user_config, } // Injection Styles - final_config.injection_styles = resolve_and_record_config( + final_config.injection_styles = resolve_with_stable_config( env_config->injection_styles, user_config.injection_styles, &final_config.metadata, ConfigName::INJECTION_STYLES, - default_propagation_styles, + propagation_style_source(environment::DD_TRACE_PROPAGATION_STYLE_INJECT, + environment::DD_PROPAGATION_STYLE_INJECT, + stable_config), + stable_config, default_propagation_styles, [](const std::vector &styles) { return join_propagation_styles(styles); }); @@ -354,49 +395,57 @@ Expected finalize_config(const TracerConfig &user_config, } // Startup Logs - final_config.log_on_startup = resolve_and_record_config( + final_config.log_on_startup = resolve_with_stable_config( env_config->log_on_startup, user_config.log_on_startup, - &final_config.metadata, ConfigName::STARTUP_LOGS, true, + &final_config.metadata, ConfigName::STARTUP_LOGS, + environment::DD_TRACE_STARTUP_LOGS, stable_config, true, [](const bool &b) { return to_string(b); }); // Report traces - final_config.report_traces = resolve_and_record_config( + final_config.report_traces = resolve_with_stable_config( env_config->report_traces, user_config.report_traces, - &final_config.metadata, ConfigName::REPORT_TRACES, true, + &final_config.metadata, ConfigName::REPORT_TRACES, + environment::DD_TRACE_ENABLED, stable_config, true, [](const bool &b) { return to_string(b); }); // Report hostname - final_config.report_hostname = - value_or(env_config->report_hostname, user_config.report_hostname, false); + final_config.report_hostname = choose_with_stable_config( + env_config->report_hostname, user_config.report_hostname, false, + stable_config); // Tags Header Size - final_config.tags_header_size = value_or( - env_config->max_tags_header_size, user_config.max_tags_header_size, 512); + final_config.tags_header_size = choose_with_stable_config( + env_config->max_tags_header_size, user_config.max_tags_header_size, + std::size_t{512}, stable_config); // 128b Trace IDs - final_config.generate_128bit_trace_ids = resolve_and_record_config( + final_config.generate_128bit_trace_ids = resolve_with_stable_config( env_config->generate_128bit_trace_ids, user_config.generate_128bit_trace_ids, &final_config.metadata, - ConfigName::GENEREATE_128BIT_TRACE_IDS, true, - [](const bool &b) { return to_string(b); }); + ConfigName::GENEREATE_128BIT_TRACE_IDS, + environment::DD_TRACE_128_BIT_TRACEID_GENERATION_ENABLED, stable_config, + true, [](const bool &b) { return to_string(b); }); // Integration name & version - final_config.integration_name = value_or( - env_config->integration_name, user_config.integration_name, "datadog"); - final_config.integration_version = - value_or(env_config->integration_version, user_config.integration_version, - tracer_version); + final_config.integration_name = choose_with_stable_config( + env_config->integration_name, user_config.integration_name, "datadog", + stable_config); + final_config.integration_version = choose_with_stable_config( + env_config->integration_version, user_config.integration_version, + tracer_version, stable_config); // Baggage - max items - final_config.baggage_opts.max_items = resolve_and_record_config( + final_config.baggage_opts.max_items = resolve_with_stable_config( env_config->baggage_max_items, user_config.baggage_max_items, - &final_config.metadata, ConfigName::TRACE_BAGGAGE_MAX_ITEMS, 64UL, + &final_config.metadata, ConfigName::TRACE_BAGGAGE_MAX_ITEMS, + environment::DD_TRACE_BAGGAGE_MAX_ITEMS, stable_config, 64UL, [](const size_t &i) { return std::to_string(i); }); // Baggage - max bytes - final_config.baggage_opts.max_bytes = resolve_and_record_config( + final_config.baggage_opts.max_bytes = resolve_with_stable_config( env_config->baggage_max_bytes, user_config.baggage_max_bytes, - &final_config.metadata, ConfigName::TRACE_BAGGAGE_MAX_BYTES, 8192UL, + &final_config.metadata, ConfigName::TRACE_BAGGAGE_MAX_BYTES, + environment::DD_TRACE_BAGGAGE_MAX_BYTES, stable_config, 8192UL, [](const size_t &i) { return std::to_string(i); }); if (final_config.baggage_opts.max_items <= 0 || @@ -412,10 +461,11 @@ Expected finalize_config(const TracerConfig &user_config, final_config.injection_styles.erase(it); } - final_config.propagation_behavior_extract = resolve_and_record_config( + final_config.propagation_behavior_extract = resolve_with_stable_config( env_config->propagation_behavior_extract, user_config.propagation_behavior_extract, &final_config.metadata, ConfigName::PROPAGATION_BEHAVIOR_EXTRACT, + environment::DD_TRACE_PROPAGATION_BEHAVIOR_EXTRACT, stable_config, PropagationBehaviorExtract::CONTINUE, [](const PropagationBehaviorExtract &behavior) { return std::string{to_string_view(behavior)}; @@ -425,13 +475,14 @@ Expected finalize_config(const TracerConfig &user_config, final_config.root_session_id = user_config.root_session_id; final_config.process_tags = user_config.process_tags; - auto agent_finalized = - finalize_config(user_config.agent, final_config.logger, clock); + auto agent_finalized = finalize_config(user_config.agent, final_config.logger, + clock, stable_config); if (auto *error = agent_finalized.if_error()) { return std::move(*error); } - if (auto trace_sampler_config = finalize_config(user_config.trace_sampler)) { + if (auto trace_sampler_config = + finalize_config(user_config.trace_sampler, stable_config)) { // Merge metadata vectors for (auto &[key, values] : trace_sampler_config->metadata) { auto &dest = final_config.metadata[key]; @@ -443,7 +494,7 @@ Expected finalize_config(const TracerConfig &user_config, } if (auto span_sampler_config = - finalize_config(user_config.span_sampler, *logger)) { + finalize_config(user_config.span_sampler, *logger, stable_config)) { // Merge metadata vectors for (auto &[key, values] : span_sampler_config->metadata) { auto &dest = final_config.metadata[key]; @@ -469,35 +520,35 @@ Expected finalize_config(const TracerConfig &user_config, if (auto telemetry_final_config = telemetry::finalize_config(user_config.telemetry)) { final_config.telemetry = std::move(*telemetry_final_config); - final_config.telemetry.products.emplace_back(telemetry::Product{ - telemetry::Product::Name::tracing, true, tracer_version, nullopt, - nullopt, final_config.metadata}); } else { return std::move(telemetry_final_config.error()); } // APM Tracing Enabled - final_config.tracing_enabled = resolve_and_record_config( + final_config.tracing_enabled = resolve_with_stable_config( env_config->tracing_enabled, user_config.tracing_enabled, - &final_config.metadata, ConfigName::APM_TRACING_ENABLED, true, + &final_config.metadata, ConfigName::APM_TRACING_ENABLED, + environment::DD_APM_TRACING_ENABLED, stable_config, true, [](const bool &b) { return to_string(b); }); { // Resource Renaming Enabled - const bool resource_renaming_enabled = resolve_and_record_config( + const bool resource_renaming_enabled = resolve_with_stable_config( env_config->resource_renaming_enabled, user_config.resource_renaming_enabled, &final_config.metadata, - ConfigName::TRACE_RESOURCE_RENAMING_ENABLED, false, + ConfigName::TRACE_RESOURCE_RENAMING_ENABLED, + environment::DD_TRACE_RESOURCE_RENAMING_ENABLED, stable_config, false, [](const bool &b) { return to_string(b); }); // Resource Renaming Always Simplified Endpoint const bool resource_renaming_always_simplified_endpoint = - resolve_and_record_config( + resolve_with_stable_config( env_config->resource_renaming_always_simplified_endpoint, user_config.resource_renaming_always_simplified_endpoint, &final_config.metadata, ConfigName::TRACE_RESOURCE_RENAMING_ALWAYS_SIMPLIFIED_ENDPOINT, - false, [](const bool &b) { return to_string(b); }); + environment::DD_TRACE_RESOURCE_RENAMING_ALWAYS_SIMPLIFIED_ENDPOINT, + stable_config, false, [](const bool &b) { return to_string(b); }); if (!resource_renaming_enabled) { final_config.resource_renaming_mode = @@ -535,6 +586,10 @@ Expected finalize_config(const TracerConfig &user_config, final_config.collector = user_config.collector; } + final_config.telemetry.products.emplace_back(telemetry::Product{ + telemetry::Product::Name::tracing, true, tracer_version, nullopt, nullopt, + final_config.metadata}); + return final_config; } diff --git a/test/bazel_stable_config_test.cpp b/test/bazel_stable_config_test.cpp new file mode 100644 index 000000000..5d083575b --- /dev/null +++ b/test/bazel_stable_config_test.cpp @@ -0,0 +1,43 @@ +#include + +#include +#include +#include + +using namespace datadog::tracing; + +namespace { + +struct TestHTTPClient : HTTPClient { + Expected post(const URL&, HeadersSetter, std::string, ResponseHandler, + ErrorHandler, + std::chrono::steady_clock::time_point) override { + return {}; + } + + void drain(std::chrono::steady_clock::time_point) override {} + + std::string config() const override { return "{}"; } +}; + +} // namespace + +int main() { + const char* runfiles = std::getenv("TEST_SRCDIR"); + const char* workspace = std::getenv("TEST_WORKSPACE"); + if (!runfiles || !workspace) return 1; + const std::string base = + std::string(runfiles) + "/" + workspace + "/test/fixtures/stable_config/"; + + TracerConfig config; + config.agent.http_client = std::make_shared(); + auto finalized = finalize_config_with_stable_config( + config, "cpp", base + "local.yaml", base + "fleet.yaml"); + if (!finalized) return 2; + if (finalized->defaults.service != "fleet-service") return 3; + if (finalized->extraction_styles.size() != 1 || + finalized->extraction_styles.front() != PropagationStyle::B3) { + return 4; + } + return 0; +} diff --git a/test/fixtures/stable_config/fleet.yaml b/test/fixtures/stable_config/fleet.yaml new file mode 100644 index 000000000..ab6b9dc2d --- /dev/null +++ b/test/fixtures/stable_config/fleet.yaml @@ -0,0 +1,4 @@ +config_id: fleet-test +apm_configuration_default: + DD_SERVICE: fleet-service + DD_TRACE_SAMPLE_RATE: "0.5" diff --git a/test/fixtures/stable_config/invalid.yaml b/test/fixtures/stable_config/invalid.yaml new file mode 100644 index 000000000..fe111806c --- /dev/null +++ b/test/fixtures/stable_config/invalid.yaml @@ -0,0 +1 @@ +rules: [ diff --git a/test/fixtures/stable_config/local.yaml b/test/fixtures/stable_config/local.yaml new file mode 100644 index 000000000..2486b575e --- /dev/null +++ b/test/fixtures/stable_config/local.yaml @@ -0,0 +1,15 @@ +config_id: local-test +apm_configuration_default: + DD_SERVICE: local-service + DD_ENV: local-env + DD_VERSION: local-version + DD_APM_TRACING_ENABLED: "false" + DD_TRACE_SAMPLE_RATE: "0.25" + DD_TRACE_AGENT_URL: http://local-agent:8126 +rules: + - selectors: + - origin: language + matches: ["cpp"] + operator: equals + configuration: + DD_TRACE_PROPAGATION_STYLE_EXTRACT: b3 diff --git a/test/test_stable_config.cpp b/test/test_stable_config.cpp new file mode 100644 index 000000000..96e7672ed --- /dev/null +++ b/test/test_stable_config.cpp @@ -0,0 +1,251 @@ +#include + +#include +#include +#include + +#include "common/environment.h" +#include "mocks/http_clients.h" +#include "stable_config_loader_internal.h" +#include "test.h" + +using namespace datadog::tracing; +using datadog::test::EnvGuard; + +#define STABLE_CONFIG_TEST(name) TEST_CASE(name, "[stable.config]") + +STABLE_CONFIG_TEST("stable config observes source precedence") { + const std::string local = std::string{DD_TRACE_SOURCE_DIR} + + "/test/fixtures/stable_config/local.yaml"; + const std::string fleet = std::string{DD_TRACE_SOURCE_DIR} + + "/test/fixtures/stable_config/fleet.yaml"; + EnvGuard service_env{"DD_SERVICE", "env-service"}; + EnvGuard environment_env{"DD_ENV", "env-environment"}; + EnvGuard version_env{"DD_VERSION"}; + EnvGuard sample_rate_env{"DD_TRACE_SAMPLE_RATE", "0.75"}; + EnvGuard apm_enabled_env{"DD_APM_TRACING_ENABLED"}; + EnvGuard agent_url_env{"DD_TRACE_AGENT_URL"}; + EnvGuard agent_host_env{"DD_AGENT_HOST", "env-agent"}; + EnvGuard agent_port_env{"DD_TRACE_AGENT_PORT"}; + + auto loaded = load_stable_config("cpp", local, fleet); + REQUIRE(loaded); + + TracerConfig code; + code.service = "code-service"; + code.agent.http_client = std::make_shared(); + auto with_code = finalize_config(code, *loaded); + REQUIRE(with_code); + CHECK(with_code->defaults.service == "code-service"); + CHECK(with_code->defaults.environment == "env-environment"); + CHECK(with_code->defaults.version == "local-version"); + CHECK(with_code->agent_url.authority == "env-agent:8126"); + CHECK(with_code->metadata.at(ConfigName::AGENT_URL).back().origin == + ConfigMetadata::Origin::ENVIRONMENT_VARIABLE); + REQUIRE(with_code->extraction_styles.size() == 1); + CHECK(with_code->extraction_styles.front() == PropagationStyle::B3); + CHECK_FALSE(with_code->tracing_enabled); + CHECK(with_code->metadata.at(ConfigName::SERVICE_NAME).back().origin == + ConfigMetadata::Origin::CODE); + const auto& sample_rate = + with_code->metadata.at(ConfigName::TRACE_SAMPLING_RATE).back(); + CHECK(sample_rate.origin == ConfigMetadata::Origin::FLEET_STABLE_CONFIG); + REQUIRE(sample_rate.config_id); + CHECK(*sample_rate.config_id == "fleet-test"); + const auto& extraction = + with_code->metadata.at(ConfigName::EXTRACTION_STYLES).back(); + CHECK(extraction.origin == ConfigMetadata::Origin::LOCAL_STABLE_CONFIG); + REQUIRE(extraction.config_id); + CHECK(*extraction.config_id == "local-test"); + + code.trace_sampler.sample_rate = 0.9; + auto with_code_rate = finalize_config(code, *loaded); + REQUIRE(with_code_rate); + CHECK(with_code_rate->metadata.at(ConfigName::TRACE_SAMPLING_RATE) + .back() + .origin == ConfigMetadata::Origin::CODE); + + TracerConfig no_code; + no_code.agent.http_client = code.agent.http_client; + auto without_code = finalize_config(no_code, *loaded); + REQUIRE(without_code); + CHECK(without_code->defaults.service == "fleet-service"); + CHECK(without_code->metadata.at(ConfigName::SERVICE_NAME).back().origin == + ConfigMetadata::Origin::FLEET_STABLE_CONFIG); +} + +STABLE_CONFIG_TEST("missing stable config files are ignored") { + auto loaded = load_stable_config("cpp", "/nonexistent/local.yaml", + "/nonexistent/fleet.yaml"); + REQUIRE(loaded); + CHECK_FALSE(loaded->find("DD_SERVICE")); +} + +STABLE_CONFIG_TEST("invalid stable config is reported") { + const std::string invalid = std::string{DD_TRACE_SOURCE_DIR} + + "/test/fixtures/stable_config/invalid.yaml"; + auto loaded = load_stable_config("cpp", invalid, "/nonexistent/fleet.yaml"); + CHECK_FALSE(loaded); +} + +STABLE_CONFIG_TEST("stable config applies one matching rule per source") { + stable_config_internal::ProcessInfo process{"cpp", {}, {}}; + auto loaded = stable_config_internal::load_yaml( + R"(config_id: local +apm_configuration_default: + DD_SERVICE: local-default + DD_ENV: local-env +rules: + - selectors: + - {origin: language, operator: equals, matches: [java]} + configuration: {DD_SERVICE: ignored} + - selectors: + - {origin: language, operator: equals, matches: [cpp]} + configuration: {DD_SERVICE: local-rule, DD_VERSION: local-version} + - selectors: [] + configuration: {DD_SERVICE: later-rule, DD_VERSION: later-version} +)", + R"(config_id: fleet +apm_configuration_default: + DD_ENV: fleet-env +rules: + - selectors: [] + configuration: {DD_SERVICE: fleet-rule} +)", + process); + REQUIRE(loaded); + REQUIRE(loaded->find("DD_SERVICE")); + CHECK(loaded->find("DD_SERVICE")->value == "fleet-rule"); + CHECK(loaded->find("DD_SERVICE")->source == StableConfigSource::FLEET); + CHECK(loaded->find("DD_SERVICE")->config_id == "fleet"); + CHECK(loaded->find("DD_ENV")->value == "fleet-env"); + CHECK(loaded->find("DD_VERSION")->value == "local-version"); + CHECK(loaded->find("DD_VERSION")->source == StableConfigSource::LOCAL); +} + +STABLE_CONFIG_TEST("stable config selectors use all origins and operators") { + stable_config_internal::ProcessInfo process{ + "cpp", + {"--mode=fast", "run", "--mode=final"}, + {"REGION=eu-west", "FLAG", "REGION=us-east"}}; + auto loaded = stable_config_internal::load_yaml( + R"(tags: {cluster: blue-prod} +rules: + - selectors: + - {origin: language, operator: equals, matches: [java, cpp]} + - {origin: process_arguments, key: --mode, operator: suffix_matches, matches: [final]} + - {origin: process_arguments, operator: equals, matches: [run]} + - {origin: environment_variables, key: REGION, operator: prefix_matches, matches: [us-]} + - {origin: environment_variables, operator: exists} + - {origin: environment_variables, key: FLAG, operator: exists} + - {origin: tags, key: cluster, operator: prefix_matches, matches: [blue]} + configuration: {DD_SERVICE: matched} +)", + "", process); + REQUIRE(loaded); + REQUIRE(loaded->find("DD_SERVICE")); + CHECK(loaded->find("DD_SERVICE")->value == "matched"); + + auto no_match = stable_config_internal::load_yaml( + R"(rules: + - selectors: + - {origin: tags, operator: exists} + configuration: {DD_SERVICE: wrong} +)", + "", process); + REQUIRE(no_match); + CHECK_FALSE(no_match->find("DD_SERVICE")); +} + +STABLE_CONFIG_TEST("stable config templates only matched rule values") { + stable_config_internal::ProcessInfo process{ + "cpp", {"--name=worker=one"}, {"ZONE=east"}}; + auto loaded = stable_config_internal::load_yaml( + R"(apm_configuration_default: {DD_ENV: "{{ language }}"} +tags: {cluster: alpha} +rules: + - selectors: [] + configuration: + DD_SERVICE: "{{ language }}-{{ tags[cluster] }}-{{ process_arguments[--name] }}-{{ environment_variables[ZONE] }}-{{ missing }}" + DD_VERSION: "{{ tags[absent] }}" +)", + "", process); + REQUIRE(loaded); + CHECK(loaded->find("DD_ENV")->value == "{{ language }}"); + CHECK(loaded->find("DD_SERVICE")->value == + "cpp-alpha-worker=one-east-UNDEFINED"); + CHECK(loaded->find("DD_VERSION")->value == "UNDEFINED"); + + auto invalid = stable_config_internal::load_yaml( + "rules: [{selectors: [], configuration: {DD_SERVICE: '{{ broken'}}]", "", + process); + CHECK_FALSE(invalid); +} + +STABLE_CONFIG_TEST("stable config accepts scalar values and duplicate keys") { + stable_config_internal::ProcessInfo process{"cpp", {}, {}}; + auto loaded = stable_config_internal::load_yaml( + R"(unknown_field: ignored +apm_configuration_default: + DD_SERVICE: first + DD_SERVICE: second + DD_TRACE_ENABLED: true + DD_TRACE_SAMPLE_RATE: 0.25 +)", + "", process); + REQUIRE(loaded); + CHECK(loaded->find("DD_SERVICE")->value == "second"); + CHECK(loaded->find("DD_TRACE_ENABLED")->value == "true"); + CHECK(loaded->find("DD_TRACE_SAMPLE_RATE")->value == "0.25"); +} + +STABLE_CONFIG_TEST("stable config validates YAML fields") { + stable_config_internal::ProcessInfo process{"cpp", {}, {}}; + const std::vector invalid_documents = { + "[not, a, map]", + "apm_configuration_default: []", + "apm_configuration_default: {DD_SERVICE: null}", + "tags: []", + "rules: {}", + "rules: [{configuration: {DD_SERVICE: x}}]", + "rules: [{selectors: [], configuration: []}]", + "rules: [{selectors: [{origin: invalid, operator: exists}], " + "configuration: {}}]", + "rules: [{selectors: [{origin: language, operator: invalid}], " + "configuration: {}}]", + "rules: [{selectors: [{origin: language, operator: equals}], " + "configuration: {}}]", + }; + for (const std::string& document : invalid_documents) { + INFO(document); + CHECK_FALSE(stable_config_internal::load_yaml(document, "", process)); + } + CHECK(stable_config_internal::load_yaml("", "# comment\n", process)); +} + +STABLE_CONFIG_TEST("stable config skips oversized files and reads fleet") { + const std::filesystem::path oversized = + std::filesystem::temp_directory_path() / + ("dd-trace-cpp-stable-config-" + + std::to_string( + std::chrono::steady_clock::now().time_since_epoch().count())); + { + std::ofstream file(oversized); + REQUIRE(file.good()); + } + std::filesystem::resize_file(oversized, 100 * 1024 * 1024 + 1); + const std::string fleet = std::string{DD_TRACE_SOURCE_DIR} + + "/test/fixtures/stable_config/fleet.yaml"; + auto loaded = load_stable_config("cpp", oversized.string(), fleet); + std::filesystem::remove(oversized); + REQUIRE(loaded); + REQUIRE(loaded->find("DD_SERVICE")); + CHECK(loaded->find("DD_SERVICE")->value == "fleet-service"); + CHECK(loaded->find("DD_SERVICE")->source == StableConfigSource::FLEET); +} + +STABLE_CONFIG_TEST("stable config reports file read errors") { + auto loaded = + load_stable_config("cpp", DD_TRACE_SOURCE_DIR, "/nonexistent/fleet.yaml"); + CHECK_FALSE(loaded); +} From 927fe6c96496bb39290a9d2ef1385dff41f652b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Chojnacki?= Date: Tue, 29 Sep 2026 12:05:29 +0200 Subject: [PATCH 2/3] Simplify stable configuration rule and template parsing --- src/datadog/stable_config_parser.cpp | 90 ++++++++++++++-------------- test/test_stable_config.cpp | 6 ++ 2 files changed, 51 insertions(+), 45 deletions(-) diff --git a/src/datadog/stable_config_parser.cpp b/src/datadog/stable_config_parser.cpp index e410bb769..29077c1bf 100644 --- a/src/datadog/stable_config_parser.cpp +++ b/src/datadog/stable_config_parser.cpp @@ -1,5 +1,6 @@ #include +#include #include #include #include @@ -83,6 +84,20 @@ Selector parse_selector(const YAML::Node& node) { return result; } +Rule parse_rule(const YAML::Node& node) { + if (!node.IsMap()) throw std::runtime_error("rule must be a map"); + const YAML::Node selectors = node["selectors"]; + if (!selectors || !selectors.IsSequence()) { + throw std::runtime_error("rule selectors must be a sequence"); + } + Rule rule; + for (const YAML::Node& selector : selectors) { + rule.selectors.push_back(parse_selector(selector)); + } + rule.configuration = parse_map(node["configuration"], "rule configuration"); + return rule; +} + ParsedConfig parse_yaml(StringView content) { const std::string text = content.empty() ? std::string{} @@ -109,18 +124,7 @@ ParsedConfig parse_yaml(StringView content) { if (!rules.IsSequence()) throw std::runtime_error("rules must be a sequence"); for (const YAML::Node& item : rules) { - if (!item.IsMap()) throw std::runtime_error("rule must be a map"); - const YAML::Node selectors = item["selectors"]; - if (!selectors || !selectors.IsSequence()) { - throw std::runtime_error("rule selectors must be a sequence"); - } - Rule rule; - for (const YAML::Node& selector : selectors) { - rule.selectors.push_back(parse_selector(selector)); - } - rule.configuration = - parse_map(item["configuration"], "rule configuration"); - result.rules.push_back(std::move(rule)); + result.rules.push_back(parse_rule(item)); } } return result; @@ -213,6 +217,28 @@ std::string trim(std::string value) { return value.substr(start, end - start + 1); } +std::string resolve_template_variable( + const std::string& variable, const ParsedConfig& source, + const ProcessInfo& process, const std::map& args, + const std::map& environment) { + const std::size_t bracket = variable.find('['); + const std::string name = variable.substr(0, bracket); + if (name == "language") return process.language; + if (bracket == std::string::npos) return "UNDEFINED"; + const std::size_t end = variable.find(']', bracket + 1); + if (end == std::string::npos) return "UNDEFINED"; + + const std::map* values = nullptr; + if (name == "tags") values = &source.tags; + if (name == "process_arguments") values = &args; + if (name == "environment_variables") values = &environment; + if (!values) return "UNDEFINED"; + + const std::string key = trim(variable.substr(bracket + 1, end - bracket - 1)); + const auto found = values->find(key); + return found == values->end() ? "UNDEFINED" : found->second; +} + std::string template_value( const std::string& input, const ParsedConfig& source, const ProcessInfo& process, const std::map& args, @@ -227,32 +253,8 @@ std::string template_value( if (close == std::string::npos) throw std::runtime_error("unterminated template in config"); const std::string variable = trim(input.substr(open + 2, close - open - 2)); - const std::size_t bracket = variable.find('['); - const std::string name = - bracket == std::string::npos ? variable : variable.substr(0, bracket); - std::string key; - bool has_index = false; - if (bracket != std::string::npos) { - const std::size_t end = variable.find(']', bracket + 1); - if (end != std::string::npos) { - key = trim(variable.substr(bracket + 1, end - bracket - 1)); - has_index = true; - } - } - if (name == "language") { - output += process.language; - } else { - const std::map* values = nullptr; - if (name == "tags") values = &source.tags; - if (name == "process_arguments") values = &args; - if (name == "environment_variables") values = &environment; - if (values && has_index) { - const auto found = values->find(key); - output += found == values->end() ? "UNDEFINED" : found->second; - } else { - output += "UNDEFINED"; - } - } + output += + resolve_template_variable(variable, source, process, args, environment); cursor = close + 2; } } @@ -267,13 +269,11 @@ void apply_source(StableConfig& result, const ParsedConfig& source, const std::map environment = split_entries(process.environment, true); for (const Rule& rule : source.rules) { - bool matches = true; - for (const Selector& selector : rule.selectors) { - if (!selector_matches(selector, source, process, args, environment)) { - matches = false; - break; - } - } + const bool matches = std::all_of( + rule.selectors.begin(), rule.selectors.end(), + [&](const Selector& selector) { + return selector_matches(selector, source, process, args, environment); + }); if (!matches) continue; for (const std::pair& entry : rule.configuration) { diff --git a/test/test_stable_config.cpp b/test/test_stable_config.cpp index 96e7672ed..72a28bd0d 100644 --- a/test/test_stable_config.cpp +++ b/test/test_stable_config.cpp @@ -168,6 +168,8 @@ tags: {cluster: alpha} configuration: DD_SERVICE: "{{ language }}-{{ tags[cluster] }}-{{ process_arguments[--name] }}-{{ environment_variables[ZONE] }}-{{ missing }}" DD_VERSION: "{{ tags[absent] }}" + DD_TRACE_AGENT_URL: "prefix{{ tags[ cluster ] }}{{ language[ }}suffix" + DD_TAGS: "{{ tags }}-{{ tags[cluster }}-{{ unknown[key] }}" )", "", process); REQUIRE(loaded); @@ -175,6 +177,8 @@ tags: {cluster: alpha} CHECK(loaded->find("DD_SERVICE")->value == "cpp-alpha-worker=one-east-UNDEFINED"); CHECK(loaded->find("DD_VERSION")->value == "UNDEFINED"); + CHECK(loaded->find("DD_TRACE_AGENT_URL")->value == "prefixalphacppsuffix"); + CHECK(loaded->find("DD_TAGS")->value == "UNDEFINED-UNDEFINED-UNDEFINED"); auto invalid = stable_config_internal::load_yaml( "rules: [{selectors: [], configuration: {DD_SERVICE: '{{ broken'}}]", "", @@ -207,6 +211,8 @@ STABLE_CONFIG_TEST("stable config validates YAML fields") { "apm_configuration_default: {DD_SERVICE: null}", "tags: []", "rules: {}", + "rules: [null]", + "rules: [{selectors: {}, configuration: {}}]", "rules: [{configuration: {DD_SERVICE: x}}]", "rules: [{selectors: [], configuration: []}]", "rules: [{selectors: [{origin: invalid, operator: exists}], " From 03029c9a438ed6fd27b25286b2538fc9b0b02eef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Chojnacki?= Date: Tue, 29 Sep 2026 16:53:27 +0200 Subject: [PATCH 3/3] Reduce complexity of stable configuration code - Share one lookup context between selectors and templates. - Parse tags with the same map helper as configuration values. - Read config files in one sized read. - Simplify agent URL source ranking and origin selection. - Merge duplicate trace sampling rule branches. - Use default arguments instead of extra finalize_config overloads. --- include/datadog/datadog_agent_config.h | 7 +- include/datadog/span_sampler_config.h | 9 +- include/datadog/trace_sampler_config.h | 7 +- include/datadog/tracer_config.h | 4 +- src/datadog/datadog_agent_config.cpp | 57 +++----- src/datadog/environment.cpp | 14 +- src/datadog/span_sampler_config.cpp | 5 - src/datadog/stable_config_loader.cpp | 56 +++----- src/datadog/stable_config_parser.cpp | 190 ++++++++++++------------- src/datadog/trace_sampler_config.cpp | 14 +- src/datadog/tracer_config.cpp | 5 - 11 files changed, 140 insertions(+), 228 deletions(-) diff --git a/include/datadog/datadog_agent_config.h b/include/datadog/datadog_agent_config.h index 679c40f13..afd3a847f 100644 --- a/include/datadog/datadog_agent_config.h +++ b/include/datadog/datadog_agent_config.h @@ -64,8 +64,6 @@ struct DatadogAgentConfig { }; class FinalizedDatadogAgentConfig { - friend Expected finalize_config( - const DatadogAgentConfig&, const std::shared_ptr&, const Clock&); friend Expected finalize_config( const DatadogAgentConfig&, const std::shared_ptr&, const Clock&, const StableConfig*); @@ -97,9 +95,6 @@ class FinalizedDatadogAgentConfig { Expected finalize_config( const DatadogAgentConfig& config, const std::shared_ptr& logger, - const Clock& clock); -Expected finalize_config( - const DatadogAgentConfig& config, const std::shared_ptr& logger, - const Clock& clock, const StableConfig* stable_config); + const Clock& clock, const StableConfig* stable_config = nullptr); } // namespace datadog::tracing diff --git a/include/datadog/span_sampler_config.h b/include/datadog/span_sampler_config.h index bba641b83..e77b5e32f 100644 --- a/include/datadog/span_sampler_config.h +++ b/include/datadog/span_sampler_config.h @@ -38,8 +38,6 @@ struct SpanSamplerConfig { }; class FinalizedSpanSamplerConfig { - friend Expected finalize_config( - const SpanSamplerConfig&, Logger&); friend Expected finalize_config( const SpanSamplerConfig&, Logger&, const StableConfig*); friend class FinalizedTracerConfig; @@ -56,11 +54,8 @@ class FinalizedSpanSamplerConfig { std::unordered_map> metadata; }; -Expected finalize_config(const SpanSamplerConfig&, - Logger&); -Expected finalize_config(const SpanSamplerConfig&, - Logger&, - const StableConfig*); +Expected finalize_config( + const SpanSamplerConfig&, Logger&, const StableConfig* = nullptr); std::string to_string(const FinalizedSpanSamplerConfig::Rule&); diff --git a/include/datadog/trace_sampler_config.h b/include/datadog/trace_sampler_config.h index 423250f34..ae7486282 100644 --- a/include/datadog/trace_sampler_config.h +++ b/include/datadog/trace_sampler_config.h @@ -43,8 +43,6 @@ struct TraceSamplerConfig { }; class FinalizedTraceSamplerConfig { - friend Expected finalize_config( - const TraceSamplerConfig& config); friend Expected finalize_config( const TraceSamplerConfig& config, const StableConfig* stable_config); friend class FinalizedTracerConfig; @@ -62,9 +60,8 @@ class FinalizedTraceSamplerConfig { }; Expected finalize_config( - const TraceSamplerConfig& config); -Expected finalize_config( - const TraceSamplerConfig& config, const StableConfig* stable_config); + const TraceSamplerConfig& config, + const StableConfig* stable_config = nullptr); } // namespace tracing } // namespace datadog diff --git a/include/datadog/tracer_config.h b/include/datadog/tracer_config.h index 05acea37d..a23f36ccf 100644 --- a/include/datadog/tracer_config.h +++ b/include/datadog/tracer_config.h @@ -269,11 +269,9 @@ class FinalizedTracerConfig final { Expected finalize_config(const TracerConfig& config); Expected finalize_config(const TracerConfig& config, const Clock& clock); -Expected finalize_config( - const TracerConfig& config, const StableConfig& stable_config); Expected finalize_config( const TracerConfig& config, const StableConfig& stable_config, - const Clock& clock); + const Clock& clock = default_clock); Expected finalize_config( const TracerConfig& config, const StableConfig* stable_config, const Clock& clock); diff --git a/src/datadog/datadog_agent_config.cpp b/src/datadog/datadog_agent_config.cpp index 7a99d12cb..035c0b61a 100644 --- a/src/datadog/datadog_agent_config.cpp +++ b/src/datadog/datadog_agent_config.cpp @@ -44,18 +44,10 @@ int agent_url_source_rank(environment::Variable variable, const StableConfig* stable_config) { const Optional value = lookup(variable, stable_config); if (!value || value->empty()) return -1; - if (!stable_config) return 0; - - switch (config_value_source(variable, stable_config).origin) { - case ConfigMetadata::Origin::LOCAL_STABLE_CONFIG: - return 0; - case ConfigMetadata::Origin::ENVIRONMENT_VARIABLE: - return 1; - case ConfigMetadata::Origin::FLEET_STABLE_CONFIG: - return 2; - default: - return -1; - } + // Rank sources as local < environment < fleet. + const auto origin = config_value_source(variable, stable_config).origin; + if (origin == ConfigMetadata::Origin::FLEET_STABLE_CONFIG) return 2; + return origin == ConfigMetadata::Origin::ENVIRONMENT_VARIABLE ? 1 : 0; } environment::Variable agent_url_source(const StableConfig* stable_config) { @@ -76,11 +68,8 @@ environment::Variable agent_url_source(const StableConfig* stable_config) { Optional build_agent_url_from_environment_variables( const StableConfig* stable_config) { - Optional url_env = - lookup(environment::DD_TRACE_AGENT_URL, stable_config); - if (url_env && !url_env->empty() && - agent_url_source(stable_config) == environment::DD_TRACE_AGENT_URL) { - return std::string{*url_env}; + if (agent_url_source(stable_config) == environment::DD_TRACE_AGENT_URL) { + return std::string{*lookup(environment::DD_TRACE_AGENT_URL, stable_config)}; } Optional env_host = @@ -137,12 +126,6 @@ Expected load_datadog_agent_env_config( return env_config; } -Expected finalize_config( - const DatadogAgentConfig& user_config, - const std::shared_ptr& logger, const Clock& clock) { - return finalize_config(user_config, logger, clock, nullptr); -} - Expected finalize_config( const DatadogAgentConfig& user_config, const std::shared_ptr& logger, const Clock& clock, @@ -233,23 +216,19 @@ Expected finalize_config( env_config->remote_configuration_enabled, user_config.remote_configuration_enabled, true, stable_config); - const auto [original_origin, url] = - stable_config - ? select_agent_url(user_config.url, env_config->url, - std::filesystem::path{default_agent_socket_path}) - : select_agent_url(env_config->url, user_config.url, - std::filesystem::path{default_agent_socket_path}); - ConfigMetadata::Origin origin = original_origin; + auto [origin, url] = + select_agent_url(env_config->url, user_config.url, + std::filesystem::path{default_agent_socket_path}); Optional config_id; - if (stable_config) { - if (user_config.url) { - origin = ConfigMetadata::Origin::CODE; - } else if (env_config->url) { - const ConfigValueSource source = - config_value_source(agent_url_source(stable_config), stable_config); - origin = source.origin; - config_id = source.config_id; - } + if (stable_config && user_config.url) { + // With stable config, code wins over the environment. + origin = ConfigMetadata::Origin::CODE; + url = *user_config.url; + } else if (origin == ConfigMetadata::Origin::ENVIRONMENT_VARIABLE) { + const ConfigValueSource source = + config_value_source(agent_url_source(stable_config), stable_config); + origin = source.origin; + config_id = source.config_id; } auto parsed_url = HTTPClient::URL::parse(url); if (auto* error = parsed_url.if_error()) { diff --git a/src/datadog/environment.cpp b/src/datadog/environment.cpp index 5e3d4fe8a..73649962a 100644 --- a/src/datadog/environment.cpp +++ b/src/datadog/environment.cpp @@ -24,16 +24,12 @@ Optional lookup(Variable variable, const StableConfig *stable_config) { const StableConfigEntry *entry = stable_config ? stable_config->find(name(variable)) : nullptr; - if (entry && entry->source == StableConfigSource::FLEET) { - return StringView{entry->value.data(), entry->value.size()}; + // Fleet config wins over the environment. Local config loses to it. + const Optional value = lookup(variable); + if (entry && (entry->source == StableConfigSource::FLEET || !value)) { + return StringView{entry->value}; } - if (Optional value = lookup(variable)) { - return value; - } - if (entry) { - return StringView{entry->value.data(), entry->value.size()}; - } - return nullopt; + return value; } std::string to_json() { diff --git a/src/datadog/span_sampler_config.cpp b/src/datadog/span_sampler_config.cpp index 05197f545..6984fef01 100644 --- a/src/datadog/span_sampler_config.cpp +++ b/src/datadog/span_sampler_config.cpp @@ -223,11 +223,6 @@ Expected load_span_sampler_env_config( SpanSamplerConfig::Rule::Rule(const SpanMatcher &base) : SpanMatcher(base) {} -Expected finalize_config( - const SpanSamplerConfig &user_config, Logger &logger) { - return finalize_config(user_config, logger, nullptr); -} - Expected finalize_config( const SpanSamplerConfig &user_config, Logger &logger, const StableConfig *stable_config) { diff --git a/src/datadog/stable_config_loader.cpp b/src/datadog/stable_config_loader.cpp index dec8061bc..79de311d9 100644 --- a/src/datadog/stable_config_loader.cpp +++ b/src/datadog/stable_config_loader.cpp @@ -49,25 +49,14 @@ Expected read_config_file(const std::string& path, std::error_code error; const std::uintmax_t size = std::filesystem::file_size(native_path, error); if (error == std::errc::no_such_file_or_directory) return std::string{}; - if (error) { - return Error{Error::OTHER, prefix + error.message()}; - } + if (error) return Error{Error::OTHER, prefix + error.message()}; if (size > max_config_file_size) return std::string{}; std::ifstream file(native_path, std::ios::binary); - if (!file) { - if (errno == ENOENT) return std::string{}; - return Error{Error::OTHER, prefix + std::strerror(errno)}; - } - std::string content; - content.reserve(static_cast(size)); - char buffer[8192]; - while (file.read(buffer, sizeof(buffer)) || file.gcount() != 0) { - content.append(buffer, static_cast(file.gcount())); - if (content.size() > max_config_file_size) return std::string{}; - } - if (file.bad()) { - return Error{Error::OTHER, prefix + "read failed"}; - } + if (!file) return Error{Error::OTHER, prefix + std::strerror(errno)}; + std::string content(static_cast(size), '\0'); + file.read(content.data(), static_cast(content.size())); + if (file.bad()) return Error{Error::OTHER, prefix + "read failed"}; + content.resize(static_cast(file.gcount())); return content; } @@ -94,29 +83,26 @@ std::string fleet_default_path() { } #if defined(_WIN32) -std::string utf8(const wchar_t* value, int length) { +std::string utf8(const wchar_t* value) { + const int length = static_cast(std::wcslen(value)); const int size = WideCharToMultiByte(CP_UTF8, 0, value, length, nullptr, 0, nullptr, nullptr); std::string result(static_cast(size), '\0'); - if (size != 0) { - WideCharToMultiByte(CP_UTF8, 0, value, length, result.data(), size, nullptr, - nullptr); - } + WideCharToMultiByte(CP_UTF8, 0, value, length, result.data(), size, nullptr, + nullptr); return result; } #endif stable_config_internal::ProcessInfo current_process(StringView language) { stable_config_internal::ProcessInfo result; - if (!language.empty()) - result.language.assign(language.data(), language.size()); + result.language = std::string{language}; #if defined(_WIN32) int count = 0; wchar_t** args = CommandLineToArgvW(GetCommandLineW(), &count); if (args) { for (int index = 0; index < count; ++index) { - result.args.push_back(utf8(args[index], -1)); - if (!result.args.back().empty()) result.args.back().pop_back(); + result.args.push_back(utf8(args[index])); } LocalFree(args); } @@ -124,9 +110,7 @@ stable_config_internal::ProcessInfo current_process(StringView language) { if (environment) { for (const wchar_t* entry = environment; *entry; entry += std::wcslen(entry) + 1) { - result.environment.push_back(utf8(entry, -1)); - if (!result.environment.back().empty()) - result.environment.back().pop_back(); + result.environment.push_back(utf8(entry)); } FreeEnvironmentStringsW(environment); } @@ -153,15 +137,13 @@ stable_config_internal::ProcessInfo current_process(StringView language) { Expected load_stable_config(StringView language, StringView local_path, StringView fleet_path) { - const std::string local = - local_path.empty() ? local_default_path() - : std::string(local_path.data(), local_path.size()); - const std::string fleet = - fleet_path.empty() ? fleet_default_path() - : std::string(fleet_path.data(), fleet_path.size()); - Expected local_yaml = read_config_file(local, "local"); + Expected local_yaml = read_config_file( + local_path.empty() ? local_default_path() : std::string{local_path}, + "local"); if (Error* error = local_yaml.if_error()) return *error; - Expected fleet_yaml = read_config_file(fleet, "fleet"); + Expected fleet_yaml = read_config_file( + fleet_path.empty() ? fleet_default_path() : std::string{fleet_path}, + "fleet"); if (Error* error = fleet_yaml.if_error()) return *error; return stable_config_internal::load_yaml(*local_yaml, *fleet_yaml, current_process(language)); diff --git a/src/datadog/stable_config_parser.cpp b/src/datadog/stable_config_parser.cpp index 29077c1bf..fb3cfd6c4 100644 --- a/src/datadog/stable_config_parser.cpp +++ b/src/datadog/stable_config_parser.cpp @@ -1,10 +1,10 @@ #include #include +#include #include #include #include -#include #include #include "stable_config_loader_internal.h" @@ -12,26 +12,42 @@ namespace datadog::tracing::stable_config_internal { namespace { +using Map = std::map; + struct Selector { std::string origin; - std::string key; - bool has_key = false; + Optional key; std::string operation; std::vector matches; }; struct Rule { std::vector selectors; - std::vector> configuration; + Map configuration; }; struct ParsedConfig { std::string config_id; - std::vector> defaults; - std::map tags; + Map defaults; + Map tags; std::vector rules; }; +// Values a selector or template can read by key. +struct Context { + const ProcessInfo& process; + const Map& tags; + Map args; + Map environment; + + const Map* values(const std::string& origin) const { + if (origin == "tags") return &tags; + if (origin == "process_arguments") return &args; + if (origin == "environment_variables") return &environment; + return nullptr; + } +}; + std::string scalar(const YAML::Node& node, const char* name) { if (!node || !node.IsScalar()) { throw std::runtime_error(std::string{name} + " must be a scalar"); @@ -39,17 +55,26 @@ std::string scalar(const YAML::Node& node, const char* name) { return node.Scalar(); } -std::vector> parse_map( - const YAML::Node& node, const char* name) { +std::string one_of(const YAML::Node& node, const char* name, + std::initializer_list allowed) { + std::string value = scalar(node, name); + if (std::find(allowed.begin(), allowed.end(), value) == allowed.end()) { + throw std::runtime_error("unknown " + std::string{name} + ": " + value); + } + return value; +} + +// Later duplicate keys win. Non-scalar keys are skipped. +Map parse_map(const YAML::Node& node, const char* name) { if (!node || !node.IsMap()) { throw std::runtime_error(std::string{name} + " must be a map"); } - std::vector> entries; + Map entries; for (YAML::const_iterator entry = node.begin(); entry != node.end(); ++entry) { if (!entry->first.IsScalar()) continue; - entries.emplace_back(entry->first.Scalar(), - scalar(entry->second, "configuration value")); + entries.insert_or_assign(entry->first.Scalar(), + scalar(entry->second, "map value")); } return entries; } @@ -57,21 +82,15 @@ std::vector> parse_map( Selector parse_selector(const YAML::Node& node) { if (!node.IsMap()) throw std::runtime_error("selector must be a map"); Selector result; - result.origin = scalar(node["origin"], "selector origin"); - if (result.origin != "language" && result.origin != "process_arguments" && - result.origin != "environment_variables" && result.origin != "tags") { - throw std::runtime_error("unknown selector origin: " + result.origin); - } + result.origin = one_of( + node["origin"], "selector origin", + {"language", "process_arguments", "environment_variables", "tags"}); if (const YAML::Node key = node["key"]; key && !key.IsNull()) { result.key = scalar(key, "selector key"); - result.has_key = true; - } - result.operation = scalar(node["operator"], "selector operator"); - if (result.operation != "exists" && result.operation != "equals" && - result.operation != "prefix_matches" && - result.operation != "suffix_matches") { - throw std::runtime_error("unknown selector operator: " + result.operation); } + result.operation = + one_of(node["operator"], "selector operator", + {"exists", "equals", "prefix_matches", "suffix_matches"}); if (result.operation != "exists") { const YAML::Node matches = node["matches"]; if (!matches || !matches.IsSequence()) { @@ -99,27 +118,18 @@ Rule parse_rule(const YAML::Node& node) { } ParsedConfig parse_yaml(StringView content) { - const std::string text = content.empty() - ? std::string{} - : std::string(content.data(), content.size()); - const YAML::Node document = YAML::Load(text); + const YAML::Node document = YAML::Load(std::string{content}); ParsedConfig result; if (!document || document.IsNull()) return result; if (!document.IsMap()) throw std::runtime_error("root must be a map"); if (const YAML::Node id = document["config_id"]; id && !id.IsNull()) { result.config_id = scalar(id, "config_id"); } - if (const YAML::Node defaults = document["apm_configuration_default"]; - defaults) { - result.defaults = parse_map(defaults, "apm_configuration_default"); - } - if (const YAML::Node tags = document["tags"]; tags) { - if (!tags.IsMap()) throw std::runtime_error("tags must be a map"); - for (YAML::const_iterator tag = tags.begin(); tag != tags.end(); ++tag) { - result.tags.insert_or_assign(scalar(tag->first, "tag key"), - scalar(tag->second, "tag value")); - } + if (document["apm_configuration_default"]) { + result.defaults = parse_map(document["apm_configuration_default"], + "apm_configuration_default"); } + if (document["tags"]) result.tags = parse_map(document["tags"], "tags"); if (const YAML::Node rules = document["rules"]; rules) { if (!rules.IsSequence()) throw std::runtime_error("rules must be a sequence"); @@ -154,9 +164,9 @@ bool valid_utf8(const std::string& value) { return true; } -std::map split_entries( - const std::vector& entries, bool include_without_equals) { - std::map result; +Map split_entries(const std::vector& entries, + bool include_without_equals) { + Map result; for (const std::string& entry : entries) { if (!valid_utf8(entry)) continue; const std::size_t equal = entry.find('='); @@ -184,30 +194,22 @@ bool string_match(const Selector& selector, const std::string& value) { return false; } -bool selector_matches(const Selector& selector, const ParsedConfig& source, - const ProcessInfo& process, - const std::map& args, - const std::map& environment) { - if (selector.origin == "language") - return string_match(selector, process.language); - if (selector.origin == "tags") { - if (!selector.has_key) return false; - const auto found = source.tags.find(selector.key); - return found != source.tags.end() && string_match(selector, found->second); +bool selector_matches(const Selector& selector, const Context& context) { + if (selector.origin == "language") { + return string_match(selector, context.process.language); } - if (selector.has_key) { - const std::map& values = - selector.origin == "process_arguments" ? args : environment; - const auto found = values.find(selector.key); + if (selector.key) { + const Map& values = *context.values(selector.origin); + const auto found = values.find(*selector.key); return found != values.end() && string_match(selector, found->second); } + if (selector.origin == "tags") return false; const std::vector& values = - selector.origin == "process_arguments" ? process.args - : process.environment; - for (const std::string& value : values) { - if (string_match(selector, value)) return true; - } - return false; + selector.origin == "process_arguments" ? context.process.args + : context.process.environment; + return std::any_of(values.begin(), values.end(), [&](const std::string& v) { + return string_match(selector, v); + }); } std::string trim(std::string value) { @@ -217,32 +219,21 @@ std::string trim(std::string value) { return value.substr(start, end - start + 1); } -std::string resolve_template_variable( - const std::string& variable, const ParsedConfig& source, - const ProcessInfo& process, const std::map& args, - const std::map& environment) { +// Resolve `language` or `origin[key]`. Unknown values become "UNDEFINED". +std::string resolve_template_variable(const std::string& variable, + const Context& context) { const std::size_t bracket = variable.find('['); const std::string name = variable.substr(0, bracket); - if (name == "language") return process.language; - if (bracket == std::string::npos) return "UNDEFINED"; - const std::size_t end = variable.find(']', bracket + 1); - if (end == std::string::npos) return "UNDEFINED"; - - const std::map* values = nullptr; - if (name == "tags") values = &source.tags; - if (name == "process_arguments") values = &args; - if (name == "environment_variables") values = &environment; - if (!values) return "UNDEFINED"; - - const std::string key = trim(variable.substr(bracket + 1, end - bracket - 1)); - const auto found = values->find(key); + if (name == "language") return context.process.language; + const std::size_t end = variable.find(']', bracket); + const Map* values = context.values(name); + if (end == std::string::npos || !values) return "UNDEFINED"; + const auto found = + values->find(trim(variable.substr(bracket + 1, end - bracket - 1))); return found == values->end() ? "UNDEFINED" : found->second; } -std::string template_value( - const std::string& input, const ParsedConfig& source, - const ProcessInfo& process, const std::map& args, - const std::map& environment) { +std::string template_value(const std::string& input, const Context& context) { std::string output; std::size_t cursor = 0; while (true) { @@ -253,35 +244,32 @@ std::string template_value( if (close == std::string::npos) throw std::runtime_error("unterminated template in config"); const std::string variable = trim(input.substr(open + 2, close - open - 2)); - output += - resolve_template_variable(variable, source, process, args, environment); + output += resolve_template_variable(variable, context); cursor = close + 2; } } +// Apply the defaults, then the first rule that matches. void apply_source(StableConfig& result, const ParsedConfig& source, StableConfigSource origin, const ProcessInfo& process) { - for (const std::pair& entry : source.defaults) { - result.set(entry.first, {entry.second, origin, source.config_id}); + for (const auto& [name, value] : source.defaults) { + result.set(name, {value, origin, source.config_id}); } - const std::map args = - split_entries(process.args, false); - const std::map environment = - split_entries(process.environment, true); + const Context context{process, source.tags, + split_entries(process.args, false), + split_entries(process.environment, true)}; for (const Rule& rule : source.rules) { - const bool matches = std::all_of( - rule.selectors.begin(), rule.selectors.end(), - [&](const Selector& selector) { - return selector_matches(selector, source, process, args, environment); - }); - if (!matches) continue; - for (const std::pair& entry : - rule.configuration) { - result.set(entry.first, {template_value(entry.second, source, process, - args, environment), - origin, source.config_id}); + if (!std::all_of(rule.selectors.begin(), rule.selectors.end(), + [&](const Selector& selector) { + return selector_matches(selector, context); + })) { + continue; + } + for (const auto& [name, value] : rule.configuration) { + result.set(name, + {template_value(value, context), origin, source.config_id}); } - break; + return; } } diff --git a/src/datadog/trace_sampler_config.cpp b/src/datadog/trace_sampler_config.cpp index ce7f0d790..b3288edf0 100644 --- a/src/datadog/trace_sampler_config.cpp +++ b/src/datadog/trace_sampler_config.cpp @@ -154,11 +154,6 @@ std::string to_string(const std::vector &rules) { TraceSamplerConfig::Rule::Rule(const SpanMatcher &base) : SpanMatcher(base) {} -Expected finalize_config( - const TraceSamplerConfig &config) { - return finalize_config(config, nullptr); -} - Expected finalize_config( const TraceSamplerConfig &config, const StableConfig *stable_config) { Expected env_config = @@ -171,12 +166,9 @@ Expected finalize_config( std::vector rules; - if (stable_config && !config.rules.empty()) { - rules = config.rules; - result.metadata[ConfigName::TRACE_SAMPLING_RULES] = { - ConfigMetadata(ConfigName::TRACE_SAMPLING_RULES, to_string(rules), - ConfigMetadata::Origin::CODE)}; - } else if (!env_config->rules.empty()) { + // With stable config, code rules win over the environment. + const bool code_wins = stable_config && !config.rules.empty(); + if (!code_wins && !env_config->rules.empty()) { rules = std::move(env_config->rules); const ConfigValueSource source = config_value_source( environment::DD_TRACE_SAMPLING_RULES, stable_config); diff --git a/src/datadog/tracer_config.cpp b/src/datadog/tracer_config.cpp index cd75a5934..5aca583bd 100644 --- a/src/datadog/tracer_config.cpp +++ b/src/datadog/tracer_config.cpp @@ -298,11 +298,6 @@ Expected finalize_config(const TracerConfig &config, return finalize_config(config, nullptr, clock); } -Expected finalize_config( - const TracerConfig &config, const StableConfig &stable_config) { - return finalize_config(config, &stable_config, default_clock); -} - Expected finalize_config( const TracerConfig &config, const StableConfig &stable_config, const Clock &clock) {