diff --git a/.github/workflows/dev.yml b/.github/workflows/dev.yml index 50115cecf..914c3414f 100644 --- a/.github/workflows/dev.yml +++ b/.github/workflows/dev.yml @@ -150,12 +150,16 @@ jobs: run: scoop install main/cmake@4.0.1 main/ninja - name: Build run: | - & 'C:\\Program Files\\Microsoft Visual Studio\\2022\\Enterprise\\Common7\\Tools\\Launch-VsDevShell.ps1' -arch ${{ matrix.arch }} + $vswhere = "${env:ProgramFiles(x86)}\Microsoft Visual Studio\Installer\vswhere.exe" + $vs = & $vswhere -latest -products * -property installationPath + & (Join-Path $vs 'Common7\Tools\Launch-VsDevShell.ps1') -arch ${{ matrix.arch }} cmake --preset=ci-msvc -B build -DCMAKE_BUILD_TYPE=Debug . cmake --build build -j $env:MAKE_JOB_COUNT -v - name: Test run: | - & 'C:\\Program Files\\Microsoft Visual Studio\\2022\\Enterprise\\Common7\\Tools\\Launch-VsDevShell.ps1' -arch ${{ matrix.arch }} + $vswhere = "${env:ProgramFiles(x86)}\Microsoft Visual Studio\Installer\vswhere.exe" + $vs = & $vswhere -latest -products * -property installationPath + & (Join-Path $vs 'Common7\Tools\Launch-VsDevShell.ps1') -arch ${{ matrix.arch }} .\build\test\tests.exe -r junit -o report.xml - name: Get Datadog credentials if: ${{ !cancelled() && github.event_name != 'pull_request' }} diff --git a/.gitignore b/.gitignore index cb7439c6c..5713ba108 100644 --- a/.gitignore +++ b/.gitignore @@ -11,3 +11,4 @@ build/ dist/ MODULE.bazel.lock out/ +rust/**/target/ diff --git a/.gitmodules b/.gitmodules index 597c4d846..aecbc4aac 100644 --- a/.gitmodules +++ b/.gitmodules @@ -4,3 +4,7 @@ [submodule "benchmark/tinycc"] path = benchmark/tinycc url = https://github.com/TinyCC/tinycc.git +[submodule "vendor/libdatadog"] + path = vendor/libdatadog + url = https://github.com/datadog/libdatadog.git + branch = pawel/no-std-library-config diff --git a/BUILD.bazel b/BUILD.bazel index 0a8541300..c6cf89053 100644 --- a/BUILD.bazel +++ b/BUILD.bazel @@ -1,4 +1,40 @@ -load("@rules_cc//cc:defs.bzl", "cc_library") +load("@rules_cc//cc:defs.bzl", "cc_library", "cc_test") + +filegroup( + name = "libdd_library_config_sources", + srcs = glob(["vendor/libdatadog/libdd-library-config/src/**/*.rs"], allow_empty = True), + visibility = ["//visibility:public"], +) + +exports_files( + ["vendor/libdatadog/libdd-library-config/src/lib.rs"], + visibility = ["//visibility:public"], +) + +cc_library( + name = "dd_trace_cpp_stable_config", + srcs = [ + "src/datadog/stable_config_ffi.h", + "src/datadog/stable_config_loader.cpp", + ], + hdrs = ["include/datadog/stable_config_loader.h"], + strip_include_prefix = "include/", + visibility = ["//visibility:public"], + deps = [ + ":dd_trace_cpp", + "//rust/stable-config-ffi:stable_config_ffi", + ], +) + +cc_test( + name = "stable_config_loader_test", + 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 +103,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 +182,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..2626a51a0 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 libdatadog 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,16 @@ if (BUILD_STATIC_LIBS) ) endif () +if (DD_TRACE_BUILD_STABLE_CONFIG) + include(cmake/stable_config.cmake) + 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/MODULE.bazel b/MODULE.bazel index 26bd07dfe..f250cd39f 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -7,3 +7,28 @@ bazel_dep(name = "abseil-cpp", version = "20260817.0", repo_name = "com_google_a bazel_dep(name = "bazel_skylib", version = "1.9.2") bazel_dep(name = "platforms", version = "1.1.0") bazel_dep(name = "rules_cc", version = "0.2.22") +bazel_dep(name = "rules_rs", version = "0.0.112") + +rust_toolchains = use_extension("@rules_rs//rs/toolchains:module_extension.bzl", "toolchains") +rust_toolchains.toolchain( + edition = "2021", + version = "1.92.0", +) +use_repo(rust_toolchains, "default_rust_toolchains") +register_toolchains("@default_rust_toolchains//...") + +stable_config_crates = use_extension("@rules_rs//rs:extensions.bzl", "crate") +stable_config_crates.from_cargo( + name = "stable_config_crates", + cargo_lock = "//rust/stable-config-bazel-deps:Cargo.lock", + cargo_toml = "//rust/stable-config-bazel-deps:Cargo.toml", + platform_triples = [ + "aarch64-apple-darwin", + "aarch64-unknown-linux-gnu", + "x86_64-apple-darwin", + "x86_64-pc-windows-msvc", + "x86_64-unknown-linux-gnu", + "x86_64-unknown-linux-musl", + ], +) +use_repo(stable_config_crates, "stable_config_crates") diff --git a/README.md b/README.md index ebe5aabd5..9e89aa9c2 100644 --- a/README.md +++ b/README.md @@ -91,6 +91,48 @@ Installation places a shared library and public headers into the appropriate sys cmake --install build --prefix=.install ``` +### Stable configuration + +The optional stable configuration loader uses the `libdd-library-config` crate +from the pinned `vendor/libdatadog` submodule. The CMake build requires Cargo +and Rust. + +```shell +git submodule update --init vendor/libdatadog +cmake -B build -DDD_TRACE_BUILD_STABLE_CONFIG=ON . +cmake --build build -j +``` + +With Bazel and Bzlmod, build and test the loader with: + +```shell +bazel test //:stable_config_loader_test +``` + +Link the CMake target `dd-trace-cpp::stable-config` or the Bazel target +`//:dd_trace_cpp_stable_config`. Bazel manages Rust dependencies and the +toolchain through `rules_rs` in `MODULE.bazel`. Load configuration before +creating a tracer: + +```c++ +#include + +datadog::tracing::TracerConfig config; +auto finalized = datadog::tracing::finalize_config_with_stable_config(config, "cpp"); +if (!finalized) { + // Handle finalized.error(). +} +``` + +The loader reads the standard local and fleet YAML paths used by libdatadog. +Pass local and fleet paths as the third and fourth arguments to use other files. +Settings resolve in this order: defaults, local YAML, environment variables, +fleet YAML, and values in `TracerConfig`. The stable configuration overload +records the source and config ID in configuration telemetry. The existing +`finalize_config(config)` overload keeps its original behavior. +Call `clear_rules()` on `trace_sampler` or `span_sampler` to override stable +sampling rules with an empty list. + ### Optional: Linking to the shared library In case you decided to build the shared library: diff --git a/cmake/dd-trace-cpp-config.cmake.in b/cmake/dd-trace-cpp-config.cmake.in index 7ed689a22..4e0f1dc6a 100644 --- a/cmake/dd-trace-cpp-config.cmake.in +++ b/cmake/dd-trace-cpp-config.cmake.in @@ -9,4 +9,11 @@ if(DD_TRACE_TRANSPORT STREQUAL "curl") find_dependency(CURL) endif() +if(@DD_TRACE_BUILD_STABLE_CONFIG@ AND + NOT TARGET dd-trace-cpp-stable-config-rust-lib) + add_library(dd-trace-cpp-stable-config-rust-lib STATIC IMPORTED) + set_target_properties(dd-trace-cpp-stable-config-rust-lib PROPERTIES + IMPORTED_LOCATION "${PACKAGE_PREFIX_DIR}/@CMAKE_INSTALL_LIBDIR@/@DD_TRACE_RUST_FILE_NAME@") +endif() + include("${CMAKE_CURRENT_LIST_DIR}/dd-trace-cpp-targets.cmake") diff --git a/cmake/stable_config.cmake b/cmake/stable_config.cmake new file mode 100644 index 000000000..995288a75 --- /dev/null +++ b/cmake/stable_config.cmake @@ -0,0 +1,63 @@ +find_program(DD_TRACE_CARGO cargo REQUIRED) + +file(GLOB_RECURSE DD_TRACE_LIBDD_LIBRARY_CONFIG_SOURCES CONFIGURE_DEPENDS + "${CMAKE_CURRENT_SOURCE_DIR}/vendor/libdatadog/libdd-library-config/src/*.rs") + +set(DD_TRACE_RUST_TARGET_DIR "${CMAKE_CURRENT_BINARY_DIR}/stable-config-rust") +set(DD_TRACE_RUST_FILE_NAME + "${CMAKE_STATIC_LIBRARY_PREFIX}dd_trace_cpp_stable_config_ffi${CMAKE_STATIC_LIBRARY_SUFFIX}") +set(DD_TRACE_RUST_LIBRARY + "${DD_TRACE_RUST_TARGET_DIR}/release/${DD_TRACE_RUST_FILE_NAME}") + +add_custom_command( + OUTPUT "${DD_TRACE_RUST_LIBRARY}" + COMMAND ${CMAKE_COMMAND} -E env + "CARGO_TARGET_DIR=${DD_TRACE_RUST_TARGET_DIR}" + ${DD_TRACE_CARGO} build --release --locked + --manifest-path "${CMAKE_CURRENT_SOURCE_DIR}/rust/stable-config-ffi/Cargo.toml" + DEPENDS + rust/stable-config-ffi/Cargo.toml + rust/stable-config-ffi/Cargo.lock + rust/stable-config-ffi/src/lib.rs + vendor/libdatadog/libdd-library-config/Cargo.toml + ${DD_TRACE_LIBDD_LIBRARY_CONFIG_SOURCES} + WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" + VERBATIM +) + +add_custom_target(dd-trace-cpp-stable-config-rust + DEPENDS "${DD_TRACE_RUST_LIBRARY}") +add_library(dd-trace-cpp-stable-config-rust-lib STATIC IMPORTED GLOBAL) +set_target_properties(dd-trace-cpp-stable-config-rust-lib PROPERTIES + IMPORTED_LOCATION "${DD_TRACE_RUST_LIBRARY}") +add_dependencies(dd-trace-cpp-stable-config-rust-lib + dd-trace-cpp-stable-config-rust) + +add_library(dd-trace-cpp-stable-config STATIC + src/datadog/stable_config_loader.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) +set_target_properties(dd-trace-cpp-stable-config PROPERTIES POSITION_INDEPENDENT_CODE ON) +target_compile_features(dd-trace-cpp-stable-config PUBLIC cxx_std_17) +target_include_directories(dd-trace-cpp-stable-config PUBLIC + "$" + "$") +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 dd-trace-cpp-stable-config-rust-lib) +if (BUILD_SHARED_LIBS) + target_link_libraries(dd-trace-cpp-stable-config PUBLIC dd-trace-cpp::shared) +elseif (BUILD_STATIC_LIBS) + target_link_libraries(dd-trace-cpp-stable-config PUBLIC dd-trace-cpp::static) +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 "${DD_TRACE_RUST_LIBRARY}" + DESTINATION ${CMAKE_INSTALL_LIBDIR}) 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..0c80dc468 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; @@ -33,11 +35,19 @@ struct SpanSamplerConfig { // Also, the `DD_TRACE_SAMPLE_RATE` environment variable, if present, causes a // corresponding `Rule` to be appended to `rules`. std::vector rules; + bool rules_configured = false; + + void clear_rules() { + rules.clear(); + rules_configured = true; + } }; 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 +64,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..83e804d3e --- /dev/null +++ b/include/datadog/stable_config.h @@ -0,0 +1,65 @@ +#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 libdatadog 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); +int config_value_priority(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..15ee043aa --- /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 through libdatadog. +// Empty paths select libdatadog's 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/telemetry/configuration.h b/include/datadog/telemetry/configuration.h index 7ab83e849..a212a3c37 100644 --- a/include/datadog/telemetry/configuration.h +++ b/include/datadog/telemetry/configuration.h @@ -9,6 +9,10 @@ #include #include +namespace datadog::tracing { +class StableConfig; +} + namespace datadog::telemetry { struct Configuration { @@ -69,9 +73,13 @@ struct FinalizedConfiguration { friend tracing::Expected finalize_config( const Configuration&); + friend tracing::Expected finalize_config( + const Configuration&, const tracing::StableConfig*); }; tracing::Expected finalize_config( const Configuration& = Configuration{}); +tracing::Expected finalize_config( + const Configuration&, const tracing::StableConfig*); } // namespace datadog::telemetry diff --git a/include/datadog/trace_sampler_config.h b/include/datadog/trace_sampler_config.h index 57dd6f6ef..c4e8f412a 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; @@ -37,12 +39,20 @@ struct TraceSamplerConfig { Optional sample_rate; std::vector rules; + bool rules_configured = false; Optional max_per_second; + + void clear_rules() { + rules.clear(); + rules_configured = true; + } }; 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 +69,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/rust/stable-config-bazel-deps/BUILD.bazel b/rust/stable-config-bazel-deps/BUILD.bazel new file mode 100644 index 000000000..8ff140557 --- /dev/null +++ b/rust/stable-config-bazel-deps/BUILD.bazel @@ -0,0 +1 @@ +exports_files(["Cargo.toml", "Cargo.lock"]) diff --git a/rust/stable-config-bazel-deps/Cargo.lock b/rust/stable-config-bazel-deps/Cargo.lock new file mode 100644 index 000000000..d518e91d8 --- /dev/null +++ b/rust/stable-config-bazel-deps/Cargo.lock @@ -0,0 +1,157 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "anyhow" +version = "1.0.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "330a5ed07fa54e4702c9d6c4174f74427fc0ef6e214bbd677ae50a5099946470" + +[[package]] +name = "dd-trace-cpp-stable-config-bazel-deps" +version = "0.1.0" +dependencies = [ + "anyhow", + "serde", + "thiserror", + "yaml_serde", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "hashbrown" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" + +[[package]] +name = "indexmap" +version = "2.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] +name = "itoa" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" + +[[package]] +name = "libyaml-rs" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e126dda6f34391ab7b444f9922055facc83c07a910da3eb16f1e4d9c45dc777" + +[[package]] +name = "proc-macro2" +version = "1.0.107" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "ryu" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" + +[[package]] +name = "serde" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "syn" +version = "3.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53e9bae58849f64dfa4f5d5ae372c8341f7305f82a3868709269343628b659a3" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "thiserror" +version = "2.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09a43598840e33d5b0331f38c5e30d13bb11c11210a4b58f0d9b18a5a5eefcd9" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43cbfe0cf76104d42a574802844187e84a305e531ed54455f11fbde0f10541cd" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "yaml_serde" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33b729a08a9a6be689bbad3e2bf8015926db54b6622cc89c3a5f7dc174b9e918" +dependencies = [ + "indexmap", + "itoa", + "libyaml-rs", + "ryu", + "serde", +] diff --git a/rust/stable-config-bazel-deps/Cargo.toml b/rust/stable-config-bazel-deps/Cargo.toml new file mode 100644 index 000000000..4070d7c40 --- /dev/null +++ b/rust/stable-config-bazel-deps/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "dd-trace-cpp-stable-config-bazel-deps" +version = "0.1.0" +edition = "2021" +publish = false + +[workspace] + +# Keep these versions and features in sync with libdd-library-config. +[dependencies] +anyhow = { version = "1.0", default-features = false } +serde = { version = "1.0", default-features = false, features = ["derive", "alloc"] } +thiserror = { version = "2.0.3", default-features = false } +yaml_serde = { version = "0.10.7", default-features = false } diff --git a/rust/stable-config-bazel-deps/src/lib.rs b/rust/stable-config-bazel-deps/src/lib.rs new file mode 100644 index 000000000..45dda1689 --- /dev/null +++ b/rust/stable-config-bazel-deps/src/lib.rs @@ -0,0 +1 @@ +// Dependency manifest for rules_rs. diff --git a/rust/stable-config-ffi/BUILD.bazel b/rust/stable-config-ffi/BUILD.bazel new file mode 100644 index 000000000..1b1c6f8d4 --- /dev/null +++ b/rust/stable-config-ffi/BUILD.bazel @@ -0,0 +1,27 @@ +load("@rules_rs//rs:rust_library.bzl", "rust_library") +load("@rules_rs//rs:rust_static_library.bzl", "rust_static_library") + +exports_files(["Cargo.toml", "Cargo.lock"]) + +rust_library( + name = "libdd_library_config", + srcs = ["//:libdd_library_config_sources"], + crate_root = "//:vendor/libdatadog/libdd-library-config/src/lib.rs", + crate_name = "libdd_library_config", + edition = "2021", + deps = [ + "@stable_config_crates//:anyhow", + "@stable_config_crates//:serde", + "@stable_config_crates//:thiserror", + "@stable_config_crates//:yaml_serde", + ], +) + +rust_static_library( + name = "stable_config_ffi", + srcs = ["src/lib.rs"], + crate_name = "dd_trace_cpp_stable_config_ffi", + deps = [":libdd_library_config"], + edition = "2021", + visibility = ["//visibility:public"], +) diff --git a/rust/stable-config-ffi/Cargo.lock b/rust/stable-config-ffi/Cargo.lock new file mode 100644 index 000000000..b458e82f8 --- /dev/null +++ b/rust/stable-config-ffi/Cargo.lock @@ -0,0 +1,164 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "anyhow" +version = "1.0.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "330a5ed07fa54e4702c9d6c4174f74427fc0ef6e214bbd677ae50a5099946470" + +[[package]] +name = "dd-trace-cpp-stable-config-ffi" +version = "0.1.0" +dependencies = [ + "libdd-library-config", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "hashbrown" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" + +[[package]] +name = "indexmap" +version = "2.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] +name = "itoa" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" + +[[package]] +name = "libdd-library-config" +version = "4.1.0" +dependencies = [ + "anyhow", + "serde", + "thiserror", + "yaml_serde", +] + +[[package]] +name = "libyaml-rs" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e126dda6f34391ab7b444f9922055facc83c07a910da3eb16f1e4d9c45dc777" + +[[package]] +name = "proc-macro2" +version = "1.0.107" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "ryu" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" + +[[package]] +name = "serde" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "syn" +version = "3.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53e9bae58849f64dfa4f5d5ae372c8341f7305f82a3868709269343628b659a3" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "thiserror" +version = "2.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09a43598840e33d5b0331f38c5e30d13bb11c11210a4b58f0d9b18a5a5eefcd9" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43cbfe0cf76104d42a574802844187e84a305e531ed54455f11fbde0f10541cd" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "yaml_serde" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33b729a08a9a6be689bbad3e2bf8015926db54b6622cc89c3a5f7dc174b9e918" +dependencies = [ + "indexmap", + "itoa", + "libyaml-rs", + "ryu", + "serde", +] diff --git a/rust/stable-config-ffi/Cargo.toml b/rust/stable-config-ffi/Cargo.toml new file mode 100644 index 000000000..3ae41ad27 --- /dev/null +++ b/rust/stable-config-ffi/Cargo.toml @@ -0,0 +1,16 @@ +# Copyright 2021-Present Datadog, Inc. https://www.datadoghq.com/ +# SPDX-License-Identifier: Apache-2.0 + +[package] +name = "dd-trace-cpp-stable-config-ffi" +version = "0.1.0" +edition = "2021" +publish = false + +[workspace] + +[lib] +crate-type = ["staticlib"] + +[dependencies] +libdd-library-config = { path = "../../vendor/libdatadog/libdd-library-config", default-features = false } diff --git a/rust/stable-config-ffi/src/lib.rs b/rust/stable-config-ffi/src/lib.rs new file mode 100644 index 000000000..2e179aa20 --- /dev/null +++ b/rust/stable-config-ffi/src/lib.rs @@ -0,0 +1,206 @@ +// Copyright 2021-Present Datadog, Inc. https://www.datadoghq.com/ +// SPDX-License-Identifier: Apache-2.0 + +use libdd_library_config::{ + ConfigRead, ConfigReadError, Configurator, LibraryConfig, LibraryConfigSource, LoggedResult, + ProcessInfo, MAX_CONFIG_FILE_SIZE, +}; +use std::ffi::OsString; +use std::fs::File; +use std::io::Read; +use std::panic::{catch_unwind, AssertUnwindSafe}; + +struct FileReader; + +impl ConfigRead for FileReader { + type IoError = std::io::Error; + + fn read(&self, path: &str) -> Result, ConfigReadError> { + let file = File::open(path).map_err(|error| match error.kind() { + std::io::ErrorKind::NotFound => ConfigReadError::NotFound, + _ => ConfigReadError::Io(error), + })?; + if file.metadata().map_err(ConfigReadError::Io)?.len() > MAX_CONFIG_FILE_SIZE as u64 { + return Err(ConfigReadError::TooLarge); + } + let mut bytes = Vec::new(); + file.take((MAX_CONFIG_FILE_SIZE + 1) as u64) + .read_to_end(&mut bytes) + .map_err(ConfigReadError::Io)?; + if bytes.len() > MAX_CONFIG_FILE_SIZE { + return Err(ConfigReadError::TooLarge); + } + Ok(bytes) + } +} + +fn os_bytes(value: OsString) -> Vec { + #[cfg(unix)] + { + use std::os::unix::ffi::OsStringExt; + value.into_vec() + } + #[cfg(not(unix))] + { + value.to_string_lossy().as_bytes().to_vec() + } +} + +fn process_info(language: &str) -> ProcessInfo { + let args = std::env::args_os().map(os_bytes).collect(); + let envp = std::env::vars_os() + .map(|(key, value)| { + let mut entry = os_bytes(key); + entry.push(b'='); + entry.extend(os_bytes(value)); + entry + }) + .collect(); + ProcessInfo { + args, + envp, + language: language.as_bytes().to_vec(), + } +} + +#[repr(C)] +#[derive(Clone, Copy)] +pub struct ByteSlice { + data: *const u8, + len: usize, +} + +impl ByteSlice { + fn from_str(value: &str) -> Self { + Self { + data: value.as_ptr(), + len: value.len(), + } + } +} + +#[repr(C)] +pub struct Entry { + name: ByteSlice, + value: ByteSlice, + config_id: ByteSlice, + source: u32, +} + +pub struct ResultSet { + configs: Vec, + error: Option, +} + +unsafe fn read_str<'a>(slice: ByteSlice) -> Result<&'a str, String> { + if slice.data.is_null() { + if slice.len == 0 { + return Ok(""); + } + return Err("null pointer with nonzero length".to_string()); + } + let bytes = std::slice::from_raw_parts(slice.data, slice.len); + std::str::from_utf8(bytes).map_err(|error| error.to_string()) +} + +fn load( + language: ByteSlice, + local_path: ByteSlice, + fleet_path: ByteSlice, +) -> Result, String> { + let language = unsafe { read_str(language)? }; + let local_path = unsafe { read_str(local_path)? }; + let fleet_path = unsafe { read_str(fleet_path)? }; + let local_path = if local_path.is_empty() { + Configurator::LOCAL_STABLE_CONFIGURATION_PATH + } else { + local_path + }; + let fleet_path = if fleet_path.is_empty() { + Configurator::FLEET_STABLE_CONFIGURATION_PATH + } else { + fleet_path + }; + let process_info = process_info(language); + match Configurator::new(false).get_config_from_reader( + &FileReader, + local_path, + fleet_path, + &process_info, + ) { + LoggedResult::Ok(configs, _) => Ok(configs), + LoggedResult::Err(error) => Err(error.to_string()), + } +} + +#[no_mangle] +pub extern "C" fn dd_trace_stable_config_load( + language: ByteSlice, + local_path: ByteSlice, + fleet_path: ByteSlice, +) -> *mut ResultSet { + let outcome = catch_unwind(AssertUnwindSafe(|| load(language, local_path, fleet_path))); + let result = match outcome { + Ok(Ok(configs)) => ResultSet { + configs, + error: None, + }, + Ok(Err(error)) => ResultSet { + configs: Vec::new(), + error: Some(error), + }, + Err(_) => ResultSet { + configs: Vec::new(), + error: Some("stable config parser panicked".to_string()), + }, + }; + Box::into_raw(Box::new(result)) +} + +#[no_mangle] +pub unsafe extern "C" fn dd_trace_stable_config_count(result: *const ResultSet) -> usize { + result.as_ref().map_or(0, |result| result.configs.len()) +} + +#[no_mangle] +pub unsafe extern "C" fn dd_trace_stable_config_entry( + result: *const ResultSet, + index: usize, + entry: *mut Entry, +) -> bool { + let Some(entry) = entry.as_mut() else { + return false; + }; + let Some(config) = result.as_ref().and_then(|result| result.configs.get(index)) else { + return false; + }; + *entry = Entry { + name: ByteSlice::from_str(&config.name), + value: ByteSlice::from_str(&config.value), + config_id: ByteSlice::from_str(config.config_id.as_deref().unwrap_or("")), + source: match config.source { + LibraryConfigSource::LocalStableConfig => 0, + LibraryConfigSource::FleetStableConfig => 1, + }, + }; + true +} + +#[no_mangle] +pub unsafe extern "C" fn dd_trace_stable_config_error(result: *const ResultSet) -> ByteSlice { + result + .as_ref() + .and_then(|result| result.error.as_deref()) + .map(ByteSlice::from_str) + .unwrap_or(ByteSlice { + data: std::ptr::null(), + len: 0, + }) +} + +#[no_mangle] +pub unsafe extern "C" fn dd_trace_stable_config_drop(result: *mut ResultSet) { + if !result.is_null() { + drop(Box::from_raw(result)); + } +} diff --git a/src/datadog/datadog_agent_config.cpp b/src/datadog/datadog_agent_config.cpp index 4e3abd591..3a55021f1 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,45 @@ 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; + return config_value_priority(variable, stable_config); +} + +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,26 +98,31 @@ 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, const DatadogAgentConfig& user_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( - "DatadogAgent: Remote Configuration poll interval error "); + if (!(stable_config && + user_config.remote_configuration_poll_interval_seconds)) { + return error->with_prefix( + "DatadogAgent: Remote Configuration poll interval error "); + } + } else { + env_config.remote_configuration_poll_interval_seconds = *res; } - - env_config.remote_configuration_poll_interval_seconds = *res; } 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 +132,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, user_config); if (auto error = env_config.if_error()) { return *error; } @@ -126,9 +171,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 +183,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 +195,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 +207,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 +221,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..06b201bd1 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,76 +144,84 @@ Expected> parse_rules(StringView rules_raw, return rules; } -Expected load_span_sampler_env_config(Logger &logger) { - SpanSamplerConfig env_config; - - auto rules_env = lookup(environment::DD_SPAN_SAMPLING_RULES); - if (rules_env) { - auto maybe_rules = - parse_rules(*rules_env, name(environment::DD_SPAN_SAMPLING_RULES)); - if (auto *error = maybe_rules.if_error()) { - return std::move(*error); - } - env_config.rules = std::move(*maybe_rules); - } - - if (auto file_env = lookup(environment::DD_SPAN_SAMPLING_RULES_FILE)) { - if (rules_env) { - const auto rules_file_name = - name(environment::DD_SPAN_SAMPLING_RULES_FILE); - const auto rules_name = name(environment::DD_SPAN_SAMPLING_RULES); - std::string message; - append(message, rules_file_name); - message += " is overridden by "; - append(message, rules_name); - message += ". Since both are set, "; - append(message, rules_name); - message += " takes precedence, and "; - append(message, rules_file_name); - message += " will be ignored."; - logger.log_error(message); - } else { - const auto span_rules_file = std::string(*file_env); - - const auto file_error = [&](const char *operation) { - std::string message; - message += "Unable to "; - message += operation; - message += " file \""; - message += span_rules_file; - message += "\" specified as value of environment variable "; - append(message, name(environment::DD_SPAN_SAMPLING_RULES_FILE)); - - return Error{Error::SPAN_SAMPLING_RULES_FILE_IO, std::move(message)}; - }; - - std::ifstream file(span_rules_file); - if (!file) { - return file_error("open"); - } +environment::Variable span_rules_source(const StableConfig *stable_config) { + const auto rules = environment::DD_SPAN_SAMPLING_RULES; + const auto file = environment::DD_SPAN_SAMPLING_RULES_FILE; + return config_value_priority(file, stable_config) > + config_value_priority(rules, stable_config) + ? file + : rules; +} - std::ostringstream rules_stream; - rules_stream << file.rdbuf(); - if (!file) { - return file_error("read"); - } +void log_rule_conflict(Logger &logger, environment::Variable selected) { + const auto ignored = selected == environment::DD_SPAN_SAMPLING_RULES + ? environment::DD_SPAN_SAMPLING_RULES_FILE + : environment::DD_SPAN_SAMPLING_RULES; + std::string message; + append(message, name(ignored)); + message += " is overridden by "; + append(message, name(selected)); + message += ". Since both are set, "; + append(message, name(selected)); + message += " takes precedence, and "; + append(message, name(ignored)); + message += " will be ignored."; + logger.log_error(message); +} - auto maybe_rules = parse_rules( - rules_stream.str(), name(environment::DD_SPAN_SAMPLING_RULES_FILE)); - if (auto *error = maybe_rules.if_error()) { - std::string prefix; - prefix += "With "; - append(prefix, name(environment::DD_SPAN_SAMPLING_RULES_FILE)); - prefix += '='; - append(prefix, *file_env); - prefix += ": "; - return error->with_prefix(prefix); - } +Expected> read_span_rules_file( + StringView path) { + const auto file_name = std::string(path); + const auto file_error = [&](const char *operation) { + std::string message = "Unable to "; + message += operation; + message += " file \"" + file_name + + "\" specified as value of environment variable "; + append(message, name(environment::DD_SPAN_SAMPLING_RULES_FILE)); + return Error{Error::SPAN_SAMPLING_RULES_FILE_IO, std::move(message)}; + }; + + std::ifstream file(file_name); + if (!file) return file_error("open"); + std::ostringstream stream; + stream << file.rdbuf(); + if (!file) return file_error("read"); + + auto parsed = + parse_rules(stream.str(), name(environment::DD_SPAN_SAMPLING_RULES_FILE)); + if (auto *error = parsed.if_error()) { + std::string prefix = "With "; + append(prefix, name(environment::DD_SPAN_SAMPLING_RULES_FILE)); + prefix += '='; + append(prefix, path); + prefix += ": "; + return error->with_prefix(prefix); + } + return *parsed; +} - env_config.rules = std::move(*maybe_rules); - } +Expected load_span_sampler_env_config( + Logger &logger, const StableConfig *stable_config, + const SpanSamplerConfig &user_config) { + SpanSamplerConfig env_config; + if (stable_config && + (user_config.rules_configured || !user_config.rules.empty())) { + return env_config; } + auto rules_env = lookup(environment::DD_SPAN_SAMPLING_RULES, stable_config); + auto file_env = + lookup(environment::DD_SPAN_SAMPLING_RULES_FILE, stable_config); + if (!rules_env && !file_env) return env_config; + const auto selected = span_rules_source(stable_config); + if (rules_env && file_env) log_rule_conflict(logger, selected); + auto parsed = selected == environment::DD_SPAN_SAMPLING_RULES_FILE + ? read_span_rules_file(*file_env) + : parse_rules(*rules_env, name(selected)); + if (auto *error = parsed.if_error()) return *error; + env_config.rules = std::move(*parsed); + env_config.rules_configured = true; + return env_config; } @@ -222,7 +231,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, user_config); if (auto error = env_config.if_error()) { return *error; } @@ -230,16 +246,17 @@ Expected finalize_config( FinalizedSpanSamplerConfig result; Optional> env_rules; Optional> user_rules; - if (!env_config->rules.empty()) { + if (env_config->rules_configured || !env_config->rules.empty()) { env_rules = env_config->rules; } - if (!user_config.rules.empty()) { + if (user_config.rules_configured || !user_config.rules.empty()) { 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) { + span_rules_source(stable_config), 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..894491764 --- /dev/null +++ b/src/datadog/stable_config.cpp @@ -0,0 +1,47 @@ +#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}; +} + +int config_value_priority(environment::Variable variable, + const StableConfig* stable_config) { + if (!environment::lookup(variable, stable_config)) 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; + } +} + +} // namespace datadog::tracing diff --git a/src/datadog/stable_config_ffi.h b/src/datadog/stable_config_ffi.h new file mode 100644 index 000000000..2f6682fcf --- /dev/null +++ b/src/datadog/stable_config_ffi.h @@ -0,0 +1,40 @@ +#pragma once + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct dd_trace_stable_config_result dd_trace_stable_config_result; + +typedef struct { + const uint8_t* data; + size_t len; +} dd_trace_stable_config_bytes; + +typedef struct { + dd_trace_stable_config_bytes name; + dd_trace_stable_config_bytes value; + dd_trace_stable_config_bytes config_id; + uint32_t source; // 0 is local, 1 is fleet. +} dd_trace_stable_config_entry_view; + +dd_trace_stable_config_result* dd_trace_stable_config_load( + dd_trace_stable_config_bytes language, + dd_trace_stable_config_bytes local_path, + dd_trace_stable_config_bytes fleet_path); +size_t dd_trace_stable_config_count( + const dd_trace_stable_config_result* result); +bool dd_trace_stable_config_entry(const dd_trace_stable_config_result* result, + size_t index, + dd_trace_stable_config_entry_view* entry); +dd_trace_stable_config_bytes dd_trace_stable_config_error( + const dd_trace_stable_config_result* result); +void dd_trace_stable_config_drop(dd_trace_stable_config_result* result); + +#ifdef __cplusplus +} +#endif diff --git a/src/datadog/stable_config_loader.cpp b/src/datadog/stable_config_loader.cpp new file mode 100644 index 000000000..61db8128f --- /dev/null +++ b/src/datadog/stable_config_loader.cpp @@ -0,0 +1,68 @@ +#include + +#include +#include +#include + +#include "stable_config_ffi.h" + +namespace datadog::tracing { +namespace { + +dd_trace_stable_config_bytes bytes(StringView value) { + return {reinterpret_cast(value.data()), value.size()}; +} + +std::string to_string(dd_trace_stable_config_bytes value) { + if (value.len == 0) return {}; + return {reinterpret_cast(value.data), value.len}; +} + +} // namespace + +Expected load_stable_config(StringView language, + StringView local_path, + StringView fleet_path) { + using Result = std::unique_ptr; + Result result{dd_trace_stable_config_load(bytes(language), bytes(local_path), + bytes(fleet_path)), + &dd_trace_stable_config_drop}; + if (!result) { + return Error{Error::OTHER, + "Unable to allocate stable configuration result"}; + } + const auto error = dd_trace_stable_config_error(result.get()); + if (error.data) { + return Error{Error::OTHER, + "Unable to load stable configuration: " + to_string(error)}; + } + + StableConfig config; + for (std::size_t index = 0; + index < dd_trace_stable_config_count(result.get()); ++index) { + dd_trace_stable_config_entry_view entry; + if (!dd_trace_stable_config_entry(result.get(), index, &entry)) { + return Error{Error::OTHER, "Unable to read stable configuration entry"}; + } + config.set(to_string(entry.name), + {to_string(entry.value), + entry.source == 1 ? StableConfigSource::FLEET + : StableConfigSource::LOCAL, + to_string(entry.config_id)}); + } + return config; +} + +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/telemetry/configuration.cpp b/src/datadog/telemetry/configuration.cpp index 0ad2dcefe..eb4dbcd33 100644 --- a/src/datadog/telemetry/configuration.cpp +++ b/src/datadog/telemetry/configuration.cpp @@ -1,8 +1,11 @@ #include #include +#include #include #include +#include + #include "parse_util.h" using namespace datadog::tracing; @@ -11,50 +14,44 @@ namespace datadog::telemetry { namespace { -tracing::Expected load_telemetry_env_config() { +tracing::Expected load_telemetry_env_config( + const tracing::StableConfig* stable_config, + const Configuration& user_config) { Configuration env_cfg; - if (auto enabled_env = - lookup(environment::DD_INSTRUMENTATION_TELEMETRY_ENABLED)) { + if (auto enabled_env = lookup( + environment::DD_INSTRUMENTATION_TELEMETRY_ENABLED, stable_config)) { env_cfg.enabled = !falsy(*enabled_env); } if (auto metrics_enabled = - lookup(environment::DD_TELEMETRY_METRICS_ENABLED)) { + lookup(environment::DD_TELEMETRY_METRICS_ENABLED, stable_config)) { env_cfg.report_metrics = !falsy(*metrics_enabled); } - if (auto logs_enabled = - lookup(environment::DD_TELEMETRY_LOG_COLLECTION_ENABLED)) { + if (auto logs_enabled = lookup( + environment::DD_TELEMETRY_LOG_COLLECTION_ENABLED, stable_config)) { env_cfg.report_logs = !falsy(*logs_enabled); } - if (auto metrics_interval_seconds = - lookup(environment::DD_TELEMETRY_METRICS_INTERVAL_SECONDS)) { - auto maybe_value = parse_double(*metrics_interval_seconds); - if (auto error = maybe_value.if_error()) { - return *error; - } - env_cfg.metrics_interval_seconds = *maybe_value; - } - - if (auto heartbeat_interval_seconds = - lookup(environment::DD_TELEMETRY_HEARTBEAT_INTERVAL)) { - auto maybe_value = parse_double(*heartbeat_interval_seconds); - if (auto error = maybe_value.if_error()) { - return *error; - } - - env_cfg.heartbeat_interval_seconds = *maybe_value; - } - - if (auto extended_heartbeat_interval_seconds = - lookup(environment::DD_TELEMETRY_EXTENDED_HEARTBEAT_INTERVAL)) { - auto maybe_value = parse_double(*extended_heartbeat_interval_seconds); - if (auto error = maybe_value.if_error()) { - return *error; + using Interval = tracing::Optional Configuration::*; + const std::pair intervals[] = { + {environment::DD_TELEMETRY_METRICS_INTERVAL_SECONDS, + &Configuration::metrics_interval_seconds}, + {environment::DD_TELEMETRY_HEARTBEAT_INTERVAL, + &Configuration::heartbeat_interval_seconds}, + {environment::DD_TELEMETRY_EXTENDED_HEARTBEAT_INTERVAL, + &Configuration::extended_heartbeat_interval_seconds}, + }; + for (const auto& [variable, field] : intervals) { + const auto raw = lookup(variable, stable_config); + if (!raw) continue; + auto parsed = parse_double(*raw); + if (auto error = parsed.if_error()) { + if (!(stable_config && (user_config.*field))) return *error; + } else { + env_cfg.*field = *parsed; } - env_cfg.extended_heartbeat_interval_seconds = *maybe_value; } return env_cfg; @@ -64,17 +61,26 @@ tracing::Expected load_telemetry_env_config() { tracing::Expected finalize_config( const Configuration& user_config) { - auto env_config = load_telemetry_env_config(); + return finalize_config(user_config, nullptr); +} + +tracing::Expected finalize_config( + const Configuration& user_config, + const tracing::StableConfig* stable_config) { + auto env_config = load_telemetry_env_config(stable_config, user_config); if (auto error = env_config.if_error()) { return *error; } - ConfigMetadata::Origin origin; FinalizedConfiguration result; + const auto choose = [stable_config](const auto& from_env, + const auto& from_code, auto fallback) { + return choose_with_stable_config(from_env, from_code, fallback, + stable_config); + }; // enabled - std::tie(origin, result.enabled) = - pick(env_config->enabled, user_config.enabled, true); + result.enabled = choose(env_config->enabled, user_config.enabled, true); if (!result.enabled) { // NOTE(@dmehala): if the telemetry module is disabled then report metrics @@ -83,80 +89,84 @@ tracing::Expected finalize_config( result.report_logs = false; } else { // report_metrics - std::tie(origin, result.report_metrics) = - pick(env_config->report_metrics, user_config.report_metrics, true); + result.report_metrics = + choose(env_config->report_metrics, user_config.report_metrics, true); // report_logs - std::tie(origin, result.report_logs) = - pick(env_config->report_logs, user_config.report_logs, true); + result.report_logs = + choose(env_config->report_logs, user_config.report_logs, true); } // debug - if (auto enabled_debug_env = lookup(environment::DD_TELEMETRY_DEBUG)) { + if (auto enabled_debug_env = + lookup(environment::DD_TELEMETRY_DEBUG, stable_config)) { result.debug = !falsy(*enabled_debug_env); } else { result.debug = false; } // metrics_interval_seconds - auto metrics_interval = pick(env_config->metrics_interval_seconds, - user_config.metrics_interval_seconds, 60); - if (metrics_interval.second <= 0.) { + const double metrics_interval = + choose(env_config->metrics_interval_seconds, + user_config.metrics_interval_seconds, 60.0); + if (metrics_interval <= 0.) { return Error{Error::Code::OUT_OF_RANGE_INTEGER, "Telemetry metrics polling interval must be a positive value"}; } result.metrics_interval = std::chrono::duration_cast( - std::chrono::duration(metrics_interval.second)); + std::chrono::duration(metrics_interval)); // heartbeat_interval_seconds - auto heartbeat_interval = pick(env_config->heartbeat_interval_seconds, - user_config.heartbeat_interval_seconds, 10); - if (heartbeat_interval.second <= 0.) { + const double heartbeat_interval = + choose(env_config->heartbeat_interval_seconds, + user_config.heartbeat_interval_seconds, 10.0); + if (heartbeat_interval <= 0.) { return Error{ Error::Code::OUT_OF_RANGE_INTEGER, "Telemetry heartbeat polling interval must be a positive value"}; } result.heartbeat_interval = std::chrono::duration_cast( - std::chrono::duration(heartbeat_interval.second)); + std::chrono::duration(heartbeat_interval)); // extended_heartbeat_interval_seconds - auto extended_heartbeat_interval = - pick(env_config->extended_heartbeat_interval_seconds, - user_config.extended_heartbeat_interval_seconds, 86400.); - if (extended_heartbeat_interval.second <= 0) { + const double extended_heartbeat_interval = + choose(env_config->extended_heartbeat_interval_seconds, + user_config.extended_heartbeat_interval_seconds, 86400.0); + if (extended_heartbeat_interval <= 0) { return Error{ Error::Code::OUT_OF_RANGE_INTEGER, "Telemetry extended heartbeat interval must be a positive value"}; } result.extended_heartbeat_interval = std::chrono::duration_cast( - std::chrono::duration(extended_heartbeat_interval.second)); + std::chrono::duration(extended_heartbeat_interval)); // integration_name - std::tie(origin, result.integration_name) = - pick(env_config->integration_name, user_config.integration_name, - std::string("datadog")); + result.integration_name = + choose(env_config->integration_name, user_config.integration_name, + std::string("datadog")); // integration_version - std::tie(origin, result.integration_version) = - pick(env_config->integration_version, user_config.integration_version, - tracing::tracer_version); + result.integration_version = + choose(env_config->integration_version, user_config.integration_version, + tracing::tracer_version); // products result.products = user_config.products; // onboarding data - if (auto install_id = lookup(environment::DD_INSTRUMENTATION_INSTALL_ID)) { + if (auto install_id = + lookup(environment::DD_INSTRUMENTATION_INSTALL_ID, stable_config)) { result.install_id = std::string(*install_id); } if (auto install_type = - lookup(environment::DD_INSTRUMENTATION_INSTALL_TYPE)) { + lookup(environment::DD_INSTRUMENTATION_INSTALL_TYPE, stable_config)) { result.install_type = std::string(*install_type); } if (auto install_time = - lookup(environment::DD_INSTRUMENTATION_INSTALL_TIME)) { + lookup(environment::DD_INSTRUMENTATION_INSTALL_TIME, stable_config)) { result.install_time = std::string(*install_time); } 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..9ec3f465f 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,14 @@ namespace datadog { namespace tracing { namespace { -Expected load_trace_sampler_env_config() { +Expected load_trace_sampler_env_config( + const StableConfig *stable_config, const TraceSamplerConfig &user_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); + rules_env && !(stable_config && (user_config.rules_configured || + !user_config.rules.empty()))) { nlohmann::json json_rules; try { json_rules = nlohmann::json::parse(*rules_env); @@ -45,6 +50,7 @@ Expected load_trace_sampler_env_config() { append(message, *rules_env); return Error{Error::TRACE_SAMPLING_RULES_WRONG_TYPE, std::move(message)}; } + env_config.rules_configured = true; const std::unordered_set allowed_properties{ "service", "name", "resource", "tags", "sample_rate"}; @@ -107,28 +113,36 @@ 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; - prefix += "While parsing "; - append(prefix, name(environment::DD_TRACE_SAMPLE_RATE)); - prefix += ": "; - return error->with_prefix(prefix); + if (!(stable_config && user_config.sample_rate)) { + std::string prefix; + prefix += "While parsing "; + append(prefix, name(environment::DD_TRACE_SAMPLE_RATE)); + prefix += ": "; + return error->with_prefix(prefix); + } + } else { + env_config.sample_rate = *maybe_sample_rate; } - 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; - prefix += "While parsing "; - append(prefix, name(environment::DD_TRACE_RATE_LIMIT)); - prefix += ": "; - return error->with_prefix(prefix); + if (!(stable_config && user_config.max_per_second)) { + std::string prefix; + prefix += "While parsing "; + append(prefix, name(environment::DD_TRACE_RATE_LIMIT)); + prefix += ": "; + return error->with_prefix(prefix); + } + } else { + env_config.max_per_second = *maybe_max_per_second; } - env_config.max_per_second = *maybe_max_per_second; } return env_config; @@ -151,7 +165,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, config); if (auto error = env_config.if_error()) { return *error; } @@ -160,12 +180,19 @@ Expected finalize_config( std::vector rules; - if (!env_config->rules.empty()) { + if (stable_config && (config.rules_configured || !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_configured || !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)}; - } else if (!config.rules.empty()) { + source.origin, nullopt, source.config_id)}; + } else if (config.rules_configured || !config.rules.empty()) { rules = std::move(config.rules); result.metadata[ConfigName::TRACE_SAMPLING_RULES] = { ConfigMetadata(ConfigName::TRACE_SAMPLING_RULES, to_string(rules), @@ -191,10 +218,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 +241,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..afd6ed6ac 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,76 +95,101 @@ 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) { + environment::Variable selected = specific; + int priority = config_value_priority(specific, stable_config); + for (const auto variable : + {legacy, environment::DD_TRACE_PROPAGATION_STYLE}) { + const int candidate = config_value_priority(variable, stable_config); + if (candidate > priority) { + selected = variable; + priority = candidate; + } + } + return priority < 0 ? environment::DD_TRACE_PROPAGATION_STYLE : selected; +} + +Expected load_tracer_env_config(Logger &logger, + const StableConfig *stable_config, + const TracerConfig &user_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; - prefix += "Unable to parse "; - append(prefix, name(environment::DD_TAGS)); - prefix += " environment variable: "; - return error->with_prefix(prefix); + if (!(stable_config && user_config.tags)) { + std::string prefix; + prefix += "Unable to parse "; + append(prefix, name(environment::DD_TAGS)); + prefix += " environment variable: "; + return error->with_prefix(prefix); + } + } else { + env_cfg.tags = std::move(*tags); } - 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; + if (!(stable_config && user_config.baggage_max_items)) return *error; + } else { + env_cfg.baggage_max_items = std::move(*maybe_value); } - - env_cfg.baggage_max_items = std::move(*maybe_value); } 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; + if (!(stable_config && user_config.baggage_max_bytes)) return *error; + } else { + env_cfg.baggage_max_bytes = std::move(*maybe_value); } - - env_cfg.baggage_max_bytes = std::move(*maybe_value); } // PropagationStyle @@ -218,53 +244,78 @@ Expected load_tracer_env_config(Logger &logger) { return message; }; - for (const auto &[var, var_override] : questionable_combinations) { - const auto value = lookup(var); - if (!value) { - continue; + if (!stable_config) { + for (const auto &[var, var_override] : questionable_combinations) { + const auto value = lookup(var, stable_config); + if (!value) { + continue; + } + const auto value_override = lookup(var_override, stable_config); + if (!value_override) { + continue; + } + + const auto var_name = name(var); + const auto var_name_override = name(var_override); + + logger.log_error(Error{ + Error::MULTIPLE_PROPAGATION_STYLE_ENVIRONMENT_VARIABLES, + warn_message(var_name, *value, var_name_override, *value_override)}); } - const auto value_override = lookup(var_override); - if (!value_override) { - continue; - } - - const auto var_name = name(var); - const auto var_name_override = name(var_override); - - logger.log_error(Error{ - Error::MULTIPLE_PROPAGATION_STYLE_ENVIRONMENT_VARIABLES, - warn_message(var_name, *value, var_name_override, *value_override)}); } 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()); } try { - const auto global_styles = - styles_from_env(environment::DD_TRACE_PROPAGATION_STYLE); - - if (auto trace_extraction_styles = - styles_from_env(environment::DD_TRACE_PROPAGATION_STYLE_EXTRACT)) { - env_cfg.extraction_styles = std::move(*trace_extraction_styles); - } else if (auto extraction_styles = - styles_from_env(environment::DD_PROPAGATION_STYLE_EXTRACT)) { - env_cfg.extraction_styles = std::move(*extraction_styles); + if (stable_config) { + const auto styles_or_code = [&](environment::Variable variable, + bool code_set) { + try { + return styles_from_env(variable, stable_config); + } catch (Error &) { + if (!code_set) throw; + return Optional>{}; + } + }; + env_cfg.extraction_styles = styles_or_code( + propagation_style_source( + environment::DD_TRACE_PROPAGATION_STYLE_EXTRACT, + environment::DD_PROPAGATION_STYLE_EXTRACT, stable_config), + bool(user_config.extraction_styles)); + env_cfg.injection_styles = styles_or_code( + propagation_style_source( + environment::DD_TRACE_PROPAGATION_STYLE_INJECT, + environment::DD_PROPAGATION_STYLE_INJECT, stable_config), + bool(user_config.injection_styles)); } else { - env_cfg.extraction_styles = global_styles; - } - - if (auto trace_injection_styles = - styles_from_env(environment::DD_TRACE_PROPAGATION_STYLE_INJECT)) { - env_cfg.injection_styles = std::move(*trace_injection_styles); - } else if (auto injection_styles = - styles_from_env(environment::DD_PROPAGATION_STYLE_INJECT)) { - env_cfg.injection_styles = std::move(*injection_styles); - } else { - env_cfg.injection_styles = global_styles; + const auto global_styles = styles_from_env( + environment::DD_TRACE_PROPAGATION_STYLE, stable_config); + + 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, + 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, stable_config)) { + env_cfg.injection_styles = std::move(*trace_injection_styles); + } 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; + } } } catch (Error &error) { return std::move(error); @@ -276,15 +327,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, user_config); if (auto error = env_config.if_error()) { return *error; } @@ -294,31 +363,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 +398,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 +415,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 +432,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 +498,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 +512,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 +531,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]; @@ -467,37 +555,37 @@ Expected finalize_config(const TracerConfig &user_config, // telemetry if (auto telemetry_final_config = - telemetry::finalize_config(user_config.telemetry)) { + telemetry::finalize_config(user_config.telemetry, stable_config)) { 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 +623,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/fixtures/stable_config/span_rules.json b/test/fixtures/stable_config/span_rules.json new file mode 100644 index 000000000..fe51488c7 --- /dev/null +++ b/test/fixtures/stable_config/span_rules.json @@ -0,0 +1 @@ +[] diff --git a/test/test_stable_config.cpp b/test/test_stable_config.cpp new file mode 100644 index 000000000..2446ddd77 --- /dev/null +++ b/test/test_stable_config.cpp @@ -0,0 +1,192 @@ +#include + +#include "common/environment.h" +#include "mocks/http_clients.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("fleet propagation style outranks local aliases") { + EnvGuard global_env{"DD_TRACE_PROPAGATION_STYLE"}; + EnvGuard extract_env{"DD_TRACE_PROPAGATION_STYLE_EXTRACT", "datadog"}; + EnvGuard legacy_extract_env{"DD_PROPAGATION_STYLE_EXTRACT"}; + EnvGuard inject_env{"DD_TRACE_PROPAGATION_STYLE_INJECT", "datadog"}; + EnvGuard legacy_inject_env{"DD_PROPAGATION_STYLE_INJECT"}; + StableConfig stable; + stable.set("DD_TRACE_PROPAGATION_STYLE", + {"b3", StableConfigSource::FLEET, "fleet-style"}); + stable.set("DD_TRACE_PROPAGATION_STYLE_EXTRACT", + {"datadog", StableConfigSource::LOCAL, "local-style"}); + stable.set("DD_TRACE_PROPAGATION_STYLE_INJECT", + {"datadog", StableConfigSource::LOCAL, "local-style"}); + + TracerConfig code; + code.agent.http_client = std::make_shared(); + auto finalized = finalize_config(code, stable); + REQUIRE(finalized); + REQUIRE(finalized->extraction_styles.size() == 1); + CHECK(finalized->extraction_styles.front() == PropagationStyle::B3); + REQUIRE(finalized->injection_styles.size() == 1); + CHECK(finalized->injection_styles.front() == PropagationStyle::B3); + CHECK(finalized->metadata.at(ConfigName::EXTRACTION_STYLES).back().origin == + ConfigMetadata::Origin::FLEET_STABLE_CONFIG); +} + +STABLE_CONFIG_TEST("fleet span rules file outranks inline environment rules") { + EnvGuard rules_env{"DD_SPAN_SAMPLING_RULES", "invalid JSON"}; + EnvGuard file_env{"DD_SPAN_SAMPLING_RULES_FILE"}; + StableConfig stable; + stable.set("DD_SPAN_SAMPLING_RULES_FILE", + {std::string{DD_TRACE_SOURCE_DIR} + + "/test/fixtures/stable_config/span_rules.json", + StableConfigSource::FLEET, "fleet-span-rules"}); + + TracerConfig code; + code.agent.http_client = std::make_shared(); + auto finalized = finalize_config(code, stable); + REQUIRE(finalized); + CHECK(finalized->span_sampler.rules.empty()); + CHECK(finalized->metadata.at(ConfigName::SPAN_SAMPLING_RULES).back().origin == + ConfigMetadata::Origin::FLEET_STABLE_CONFIG); +} + +STABLE_CONFIG_TEST("code clears stable sampling rules") { + EnvGuard trace_rules_env{"DD_TRACE_SAMPLING_RULES"}; + EnvGuard span_rules_env{"DD_SPAN_SAMPLING_RULES"}; + EnvGuard sample_rate_env{"DD_TRACE_SAMPLE_RATE"}; + StableConfig stable; + stable.set("DD_TRACE_SAMPLING_RULES", + {"[{\"sample_rate\":0.5}]", StableConfigSource::FLEET, "fleet"}); + stable.set("DD_SPAN_SAMPLING_RULES", + {"[{\"sample_rate\":0.5}]", StableConfigSource::FLEET, "fleet"}); + + TracerConfig code; + code.agent.http_client = std::make_shared(); + code.trace_sampler.clear_rules(); + code.span_sampler.clear_rules(); + auto finalized = finalize_config(code, stable); + REQUIRE(finalized); + CHECK(finalized->trace_sampler.rules.empty()); + CHECK(finalized->span_sampler.rules.empty()); + CHECK( + finalized->metadata.at(ConfigName::TRACE_SAMPLING_RULES).back().origin == + ConfigMetadata::Origin::CODE); + CHECK(finalized->metadata.at(ConfigName::SPAN_SAMPLING_RULES).back().origin == + ConfigMetadata::Origin::CODE); +} + +STABLE_CONFIG_TEST("code shadows invalid lower-priority values") { + EnvGuard sample_rate_env{"DD_TRACE_SAMPLE_RATE", "invalid"}; + EnvGuard styles_env{"DD_TRACE_PROPAGATION_STYLE_EXTRACT", "invalid"}; + EnvGuard telemetry_interval_env{"DD_TELEMETRY_METRICS_INTERVAL_SECONDS", + "invalid"}; + StableConfig stable; + TracerConfig code; + code.agent.http_client = std::make_shared(); + code.trace_sampler.sample_rate = 0.5; + code.extraction_styles = std::vector{PropagationStyle::B3}; + code.telemetry.metrics_interval_seconds = 30.0; + + auto finalized = finalize_config(code, stable); + REQUIRE(finalized); + CHECK(finalized->metadata.at(ConfigName::TRACE_SAMPLING_RATE).back().origin == + ConfigMetadata::Origin::CODE); + REQUIRE(finalized->extraction_styles.size() == 1); + CHECK(finalized->extraction_styles.front() == PropagationStyle::B3); + CHECK(finalized->telemetry.metrics_interval == std::chrono::seconds(30)); +} + +STABLE_CONFIG_TEST("stable telemetry honors fleet and code precedence") { + EnvGuard enabled_env{"DD_INSTRUMENTATION_TELEMETRY_ENABLED", "true"}; + StableConfig stable; + stable.set("DD_INSTRUMENTATION_TELEMETRY_ENABLED", + {"false", StableConfigSource::FLEET, "fleet-telemetry"}); + + TracerConfig code; + code.agent.http_client = std::make_shared(); + auto fleet = finalize_config(code, stable); + REQUIRE(fleet); + CHECK_FALSE(fleet->telemetry.enabled); + + code.telemetry.enabled = true; + auto overridden = finalize_config(code, stable); + REQUIRE(overridden); + CHECK(overridden->telemetry.enabled); +} diff --git a/vendor/libdatadog b/vendor/libdatadog new file mode 160000 index 000000000..4fab32380 --- /dev/null +++ b/vendor/libdatadog @@ -0,0 +1 @@ +Subproject commit 4fab32380890733100870a9a87db631a53f9e4ef