Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions include/datadog/config.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ enum class ConfigName : char {
EXTRACTION_STYLES,
INJECTION_STYLES,
PROPAGATION_BEHAVIOR_EXTRACT,
PROPAGATION_EXTRACT_FIRST,
STARTUP_LOGS,
REPORT_TELEMETRY,
DELEGATE_SAMPLING,
Expand Down
1 change: 1 addition & 0 deletions include/datadog/environment.h
Original file line number Diff line number Diff line change
Expand Up @@ -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, \
Expand Down
1 change: 1 addition & 0 deletions include/datadog/tracer.h
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ class Tracer {
std::vector<PropagationStyle> injection_styles_;
std::vector<PropagationStyle> extraction_styles_;
PropagationBehaviorExtract propagation_behavior_extract_;
bool propagation_extract_first_;
Optional<std::string> hostname_;
std::size_t tags_header_max_size_;
// Store the tracer configuration in an in-memory file, allowing it to be
Expand Down
6 changes: 6 additions & 0 deletions include/datadog/tracer_config.h
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,11 @@ struct TracerConfig {
// DD_TRACE_PROPAGATION_BEHAVIOR_EXTRACT
Optional<PropagationBehaviorExtract> propagation_behavior_extract;

// `propagation_extract_first` indicates whether extraction stops after the
// first successful trace context. Overridden by
// DD_TRACE_PROPAGATION_EXTRACT_FIRST.
Optional<bool> propagation_extract_first;

// `report_hostname` indicates whether the tracer will include the result of
// `gethostname` with traces sent to the collector.
Optional<bool> report_hostname;
Expand Down Expand Up @@ -234,6 +239,7 @@ class FinalizedTracerConfig final {
std::vector<PropagationStyle> extraction_styles;

PropagationBehaviorExtract propagation_behavior_extract;
bool propagation_extract_first;

bool report_hostname;
std::size_t tags_header_size;
Expand Down
2 changes: 2 additions & 0 deletions src/datadog/telemetry/telemetry_impl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
27 changes: 23 additions & 4 deletions src/datadog/tracer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down Expand Up @@ -274,6 +275,7 @@ Expected<Span> Tracer::extract_span(const DictReader& reader,
AuditedReader audited_reader{reader};

auto span_data = std::make_unique<SpanData>();
Optional<PropagationStyle> first_style_with_valid_context;
Optional<PropagationStyle> first_style_with_trace_id;
Optional<PropagationStyle> first_style_with_parent_id;
std::unordered_map<PropagationStyle, ExtractedData> extracted_contexts;
Expand Down Expand Up @@ -309,20 +311,37 @@ Expected<Span> 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();

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the extracted_trace_context name is misleading:

  • It makes me think this variable is a trace context.
  • This only checks that a trace id was extracted, not a full context, which, I think, relates to the comment from Codex below.

Actually, because the existing code is not clean, I find it very difficult to reason about this:

  • The data name does not convey any useful meaning.
  • The on-going modification is only about a part of the huge (270 lines!) Tracer::extract_span() function.

I suggest adding a dedicated refactoring PR before this one to clean the Tracer::extract_span() function:

  • At least extract the part to be modified, to clarify its purpose, and improve variable names. This could also allow to unit test easier the new behavior.
  • Ideally, split the function in big blocks (skimming the function, it seems to me there are at least 4 different blocks).

if (!first_style_with_trace_id && extracted_trace_context) {
first_style_with_trace_id = style;
}

if (!first_style_with_parent_id && data->parent_id.has_value()) {
first_style_with_parent_id = style;
}

const bool extracted_valid_context =
extracted_trace_context && *data->trace_id != 0 &&
(data->parent_id.has_value() || data->origin.has_value());
if (!first_style_with_valid_context && extracted_valid_context) {
first_style_with_valid_context = style;
}

data->headers_examined = audited_reader.entries_found;
extracted_contexts.emplace(style, std::move(*data));

if (propagation_extract_first_ && extracted_valid_context) {
break;
}
}

Optional<PropagationStyle> primary_style = first_style_with_valid_context;
if (!primary_style) {
primary_style = first_style_with_trace_id;
}

ExtractedData merged_context;
if (!first_style_with_trace_id) {
if (!primary_style) {
// Nothing extracted a trace ID. Return the first context that includes a
// parent ID, if any, or otherwise just return an empty `ExtractedData`.
// The purpose of looking for a parent ID is to allow for the error
Expand All @@ -333,7 +352,7 @@ Expected<Span> Tracer::extract_span(const DictReader& reader,
merged_context = other->second;
}
} else {
merged_context = merge(*first_style_with_trace_id, extracted_contexts);
merged_context = merge(*primary_style, extracted_contexts);
}

// Some information might be missing.
Expand Down Expand Up @@ -495,7 +514,7 @@ Expected<Span> Tracer::extract_span(const DictReader& reader,
case PropagationBehaviorExtract::RESTART: {
// restart: create a new trace, with a span link to the previous one

std::string context_headers{to_string_view(*first_style_with_trace_id)};
std::string context_headers{to_string_view(*primary_style)};
to_lower(context_headers);
auto link_attributes = SpanLinkAttributes{};
link_attributes.emplace("reason", "propagation_behavior_extract");
Expand Down
11 changes: 11 additions & 0 deletions src/datadog/tracer_config.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,11 @@ Expected<TracerConfig> 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);
Expand Down Expand Up @@ -421,6 +426,12 @@ Expected<FinalizedTracerConfig> 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;
Expand Down
7 changes: 7 additions & 0 deletions supported-configurations.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
89 changes: 89 additions & 0 deletions test/test_tracer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
#include <type_traits>
#include <utility>

#include "common/environment.h"
#include "matchers.h"
#include "mocks/collectors.h"
#include "mocks/dict_readers.h"
Expand Down Expand Up @@ -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<MockCollector>();
config.collector = collector;
Expand Down Expand Up @@ -1545,6 +1549,75 @@ 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<NullCollector>();
config.telemetry.enabled = false;
config.extraction_styles = std::vector<PropagationStyle>{
PropagationStyle::DATADOG, PropagationStyle::W3C};

const auto finalized_config = finalize_config(config);
REQUIRE(finalized_config);
Tracer tracer{*finalized_config};

const std::unordered_map<std::string, std::string> 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("extract first tries later styles after an incomplete extraction") {
struct TestCase {
int line;
std::string description;
std::string traceparent;
TraceID expected_trace_id;
};

const auto test_case = GENERATE(values<TestCase>({
{__LINE__, "matching trace ID",
"00-00000000000000000000000000000001-0000000000000002-01", TraceID{1}},
{__LINE__, "different trace ID",
"00-00000000000000000000000000000003-0000000000000002-01", TraceID{3}},
}));

CAPTURE(test_case.line);
CAPTURE(test_case.description);

const datadog::test::EnvGuard guard{"DD_TRACE_PROPAGATION_EXTRACT_FIRST",
"true"};
TracerConfig config;
config.service = "testsvc";
config.collector = std::make_shared<NullCollector>();
config.telemetry.enabled = false;
config.extraction_styles = std::vector<PropagationStyle>{
PropagationStyle::DATADOG, PropagationStyle::W3C};

const auto finalized_config = finalize_config(config);
REQUIRE(finalized_config);
Tracer tracer{*finalized_config};

const std::unordered_map<std::string, std::string> headers{
{"x-datadog-trace-id", "1"},
{"traceparent", test_case.traceparent},
};

MockDictReader reader{headers};
const auto span = tracer.extract_span(reader);
REQUIRE(span);
CHECK(span->trace_id() == test_case.expected_trace_id);
CHECK(span->parent_id() == 2);
}

TEST_TRACER("continue extraction resumes the extracted trace") {
TracerConfig config;
config.service = "testsvc";
Expand Down Expand Up @@ -2019,6 +2092,7 @@ TEST_TRACER("heterogeneous extraction") {
std::vector<PropagationStyle> injection_styles;
std::unordered_map<std::string, std::string> extracted_headers;
std::unordered_map<std::string, std::string> expected_injected_headers;
bool extract_first = false;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be added in the capture block (lines 2121-2126).

};

// clang-format off
Expand All @@ -2041,6 +2115,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},
Expand Down Expand Up @@ -2087,8 +2172,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<NullLogger>();

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<MockIDGenerator>()};
Expand Down
49 changes: 49 additions & 0 deletions test/test_tracer_config.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down
Loading