diff --git a/include/datadog/config.h b/include/datadog/config.h index 3489a69c..73abedb4 100644 --- a/include/datadog/config.h +++ b/include/datadog/config.h @@ -22,6 +22,7 @@ enum class ConfigName : char { EXTRACTION_STYLES, INJECTION_STYLES, PROPAGATION_BEHAVIOR_EXTRACT, + PROPAGATION_EXTRACT_FIRST, STARTUP_LOGS, REPORT_TELEMETRY, DELEGATE_SAMPLING, diff --git a/include/datadog/environment.h b/include/datadog/environment.h index 93046601..114d642a 100644 --- a/include/datadog/environment.h +++ b/include/datadog/environment.h @@ -53,6 +53,7 @@ namespace environment { MACRO(DD_SPAN_SAMPLING_RULES, ARRAY, "[]") \ MACRO(DD_SPAN_SAMPLING_RULES_FILE, STRING, "") \ MACRO(DD_TRACE_PROPAGATION_BEHAVIOR_EXTRACT, STRING, "continue") \ + MACRO(DD_TRACE_PROPAGATION_EXTRACT_FIRST, BOOLEAN, false) \ MACRO(DD_TRACE_PROPAGATION_STYLE_EXTRACT, ARRAY, \ "datadog,tracecontext,baggage") \ MACRO(DD_TRACE_PROPAGATION_STYLE_INJECT, ARRAY, \ diff --git a/include/datadog/tracer.h b/include/datadog/tracer.h index 0bb72c05..ab8d6a2a 100644 --- a/include/datadog/tracer.h +++ b/include/datadog/tracer.h @@ -48,6 +48,7 @@ class Tracer { std::vector injection_styles_; std::vector extraction_styles_; PropagationBehaviorExtract propagation_behavior_extract_; + bool propagation_extract_first_; Optional hostname_; std::size_t tags_header_max_size_; // Store the tracer configuration in an in-memory file, allowing it to be diff --git a/include/datadog/tracer_config.h b/include/datadog/tracer_config.h index de36e6fc..9fa47cec 100644 --- a/include/datadog/tracer_config.h +++ b/include/datadog/tracer_config.h @@ -116,6 +116,11 @@ struct TracerConfig { // DD_TRACE_PROPAGATION_BEHAVIOR_EXTRACT Optional propagation_behavior_extract; + // `propagation_extract_first` indicates whether extraction stops after the + // first successful trace context. Overridden by + // DD_TRACE_PROPAGATION_EXTRACT_FIRST. + Optional propagation_extract_first; + // `report_hostname` indicates whether the tracer will include the result of // `gethostname` with traces sent to the collector. Optional report_hostname; @@ -234,6 +239,7 @@ class FinalizedTracerConfig final { std::vector extraction_styles; PropagationBehaviorExtract propagation_behavior_extract; + bool propagation_extract_first; bool report_hostname; std::size_t tags_header_size; diff --git a/src/datadog/telemetry/telemetry_impl.cpp b/src/datadog/telemetry/telemetry_impl.cpp index b76738f7..fd9a7c6d 100644 --- a/src/datadog/telemetry/telemetry_impl.cpp +++ b/src/datadog/telemetry/telemetry_impl.cpp @@ -94,6 +94,8 @@ std::string to_string(datadog::tracing::ConfigName name) { return "trace_propagation_style_inject"; case ConfigName::PROPAGATION_BEHAVIOR_EXTRACT: return "trace_propagation_behavior_extract"; + case ConfigName::PROPAGATION_EXTRACT_FIRST: + return "trace_propagation_extract_first"; case ConfigName::STARTUP_LOGS: return "trace_startup_logs_enabled"; case ConfigName::REPORT_TELEMETRY: diff --git a/src/datadog/tracer.cpp b/src/datadog/tracer.cpp index 8c0e6bd7..303b7aeb 100644 --- a/src/datadog/tracer.cpp +++ b/src/datadog/tracer.cpp @@ -64,6 +64,7 @@ Tracer::Tracer(const FinalizedTracerConfig& config, injection_styles_(config.injection_styles), extraction_styles_(config.extraction_styles), propagation_behavior_extract_(config.propagation_behavior_extract), + propagation_extract_first_(config.propagation_extract_first), tags_header_max_size_(config.tags_header_size), baggage_opts_(config.baggage_opts), baggage_injection_enabled_(false), @@ -309,7 +310,8 @@ Expected Tracer::extract_span(const DictReader& reader, telemetry::counter::increment(metrics::tracer::trace_context::extracted, {extracted_tag}); - if (!first_style_with_trace_id && data->trace_id.has_value()) { + const bool extracted_trace_context = data->trace_id.has_value(); + if (!first_style_with_trace_id && extracted_trace_context) { first_style_with_trace_id = style; } @@ -319,6 +321,10 @@ Expected Tracer::extract_span(const DictReader& reader, data->headers_examined = audited_reader.entries_found; extracted_contexts.emplace(style, std::move(*data)); + + if (propagation_extract_first_ && extracted_trace_context) { + break; + } } ExtractedData merged_context; diff --git a/src/datadog/tracer_config.cpp b/src/datadog/tracer_config.cpp index 4fe4f2fc..c6607651 100644 --- a/src/datadog/tracer_config.cpp +++ b/src/datadog/tracer_config.cpp @@ -243,6 +243,11 @@ Expected load_tracer_env_config(Logger &logger) { propagation_behavior_extract.value()); } + if (auto propagation_extract_first = + lookup(environment::DD_TRACE_PROPAGATION_EXTRACT_FIRST)) { + env_cfg.propagation_extract_first = !falsy(*propagation_extract_first); + } + try { const auto global_styles = styles_from_env(environment::DD_TRACE_PROPAGATION_STYLE); @@ -421,6 +426,12 @@ Expected finalize_config(const TracerConfig &user_config, return std::string{to_string_view(behavior)}; }); + final_config.propagation_extract_first = resolve_and_record_config( + env_config->propagation_extract_first, + user_config.propagation_extract_first, &final_config.metadata, + ConfigName::PROPAGATION_EXTRACT_FIRST, false, + [](const bool &value) { return to_string(value); }); + final_config.runtime_id = user_config.runtime_id; final_config.root_session_id = user_config.root_session_id; final_config.process_tags = user_config.process_tags; diff --git a/supported-configurations.json b/supported-configurations.json index 186db9f5..97b5b36b 100644 --- a/supported-configurations.json +++ b/supported-configurations.json @@ -210,6 +210,13 @@ "type": "string" } ], + "DD_TRACE_PROPAGATION_EXTRACT_FIRST": [ + { + "default": "false", + "implementation": "A", + "type": "boolean" + } + ], "DD_TRACE_PROPAGATION_STYLE": [ { "default": "datadog,tracecontext,baggage", diff --git a/test/test_tracer.cpp b/test/test_tracer.cpp index 55f8d15c..01c2c59c 100644 --- a/test/test_tracer.cpp +++ b/test/test_tracer.cpp @@ -31,6 +31,7 @@ #include #include +#include "common/environment.h" #include "matchers.h" #include "mocks/collectors.h" #include "mocks/dict_readers.h" @@ -70,6 +71,9 @@ TEST_TRACER("tracer span defaults") { config.name = "test.thing"; config.tags = {{"some.thing", "thing value"}, {"another.thing", "another value"}}; + // The test does not cover telemetry. Disabling it prevents an asynchronous + // request to a local agent from racing the logger assertion below. + config.telemetry.enabled = false; const auto collector = std::make_shared(); config.collector = collector; @@ -1545,6 +1549,32 @@ TEST_TRACER("span extraction") { } } +TEST_TRACER( + "extract first tries later styles after an unsuccessful extraction") { + const datadog::test::EnvGuard guard{"DD_TRACE_PROPAGATION_EXTRACT_FIRST", + "true"}; + TracerConfig config; + config.service = "testsvc"; + config.collector = std::make_shared(); + config.telemetry.enabled = false; + config.extraction_styles = std::vector{ + PropagationStyle::DATADOG, PropagationStyle::W3C}; + + const auto finalized_config = finalize_config(config); + REQUIRE(finalized_config); + Tracer tracer{*finalized_config}; + + const std::unordered_map headers{ + {"traceparent", + "00-00000000000000000000000000000001-0000000000000002-01"}, + }; + + MockDictReader reader{headers}; + const auto span = tracer.extract_span(reader); + REQUIRE(span); + CHECK(span->parent_id() == 2); +} + TEST_TRACER("continue extraction resumes the extracted trace") { TracerConfig config; config.service = "testsvc"; @@ -2019,6 +2049,7 @@ TEST_TRACER("heterogeneous extraction") { std::vector injection_styles; std::unordered_map extracted_headers; std::unordered_map expected_injected_headers; + bool extract_first = false; }; // clang-format off @@ -2041,6 +2072,17 @@ TEST_TRACER("heterogeneous extraction") { {{"traceparent", "00-00000000000000000000000000000030-000000000000002a-01"}, {"tracestate", "dd=s:2;p:000000000000002a;o:Kansas;ah:choo,competitor=stuff"}}}, + {__LINE__, "extract first ignores tracestate from subsequent style", + {PropagationStyle::DATADOG, PropagationStyle::W3C}, + {PropagationStyle::W3C}, + {{"x-datadog-trace-id", "48"}, {"x-datadog-parent-id", "64"}, + {"x-datadog-origin", "Kansas"}, {"x-datadog-sampling-priority", "2"}, + {"traceparent", "00-00000000000000000000000000000030-0000000000000040-01"}, + {"tracestate", "competitor=stuff,dd=o:Nebraska;s:1;ah:choo"}}, + {{"traceparent", "00-00000000000000000000000000000030-000000000000002a-01"}, + {"tracestate", "dd=s:2;p:000000000000002a;o:Kansas"}}, + true}, + {__LINE__, "ignore interlopers", {PropagationStyle::DATADOG, PropagationStyle::B3, PropagationStyle::W3C}, {PropagationStyle::W3C}, @@ -2087,8 +2129,12 @@ TEST_TRACER("heterogeneous extraction") { config.service = "testsvc"; config.extraction_styles = test_case.extraction_styles; config.injection_styles = test_case.injection_styles; + config.telemetry.enabled = false; config.logger = std::make_shared(); + const datadog::test::EnvGuard extract_first{ + "DD_TRACE_PROPAGATION_EXTRACT_FIRST", + test_case.extract_first ? "true" : "false"}; auto finalized_config = finalize_config(config); REQUIRE(finalized_config); Tracer tracer{*finalized_config, std::make_shared()}; diff --git a/test/test_tracer_config.cpp b/test/test_tracer_config.cpp index f23f8893..64af5f93 100644 --- a/test/test_tracer_config.cpp +++ b/test/test_tracer_config.cpp @@ -1378,6 +1378,55 @@ TRACER_CONFIG_TEST("TracerConfig propagation behavior extract") { } } +TRACER_CONFIG_TEST("TracerConfig propagation extract first") { + TracerConfig config; + config.service = "testsvc"; + + const auto extract_first_metadata = + [](const FinalizedTracerConfig& finalized) { + return finalized.metadata.at(ConfigName::PROPAGATION_EXTRACT_FIRST); + }; + + SECTION("defaults to false") { + const auto finalized = finalize_config(config); + REQUIRE(finalized); + CHECK_FALSE(finalized->propagation_extract_first); + + const auto& metadata = extract_first_metadata(*finalized); + REQUIRE(metadata.size() == 1); + CHECK(metadata.back().origin == ConfigMetadata::Origin::DEFAULT); + CHECK(metadata.back().value == "false"); + } + + SECTION("uses programmatic configuration") { + config.propagation_extract_first = true; + + const auto finalized = finalize_config(config); + REQUIRE(finalized); + CHECK(finalized->propagation_extract_first); + + const auto& metadata = extract_first_metadata(*finalized); + REQUIRE(metadata.size() == 2); + CHECK(metadata.back().origin == ConfigMetadata::Origin::CODE); + CHECK(metadata.back().value == "true"); + } + + SECTION("environment configuration overrides programmatic configuration") { + const EnvGuard guard{"DD_TRACE_PROPAGATION_EXTRACT_FIRST", "true"}; + config.propagation_extract_first = false; + + const auto finalized = finalize_config(config); + REQUIRE(finalized); + CHECK(finalized->propagation_extract_first); + + const auto& metadata = extract_first_metadata(*finalized); + REQUIRE(metadata.size() == 3); + CHECK(metadata.back().origin == + ConfigMetadata::Origin::ENVIRONMENT_VARIABLE); + CHECK(metadata.back().value == "true"); + } +} + TRACER_CONFIG_TEST("configure 128-bit trace IDs") { TracerConfig config; config.service = "testsvc";