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
7 changes: 7 additions & 0 deletions metadata/supported-configurations.json
Original file line number Diff line number Diff line change
Expand Up @@ -1995,6 +1995,13 @@
"default": "false"
}
],
"DD_TRACE_PROPAGATION_BEHAVIOR_EXTRACT": [
{
"implementation": "B",
"type": "string",
"default": "continue"
}
],
"DD_TRACE_PROPAGATION_STYLE": [
{
"implementation": "D",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
--TEST--
DD_TRACE_PROPAGATION_BEHAVIOR_EXTRACT config parsing: case-insensitive, invalid falls back to continue
--ENV--
DD_TRACE_GENERATE_ROOT_SPAN=0
--FILE--
<?php

// Helper: return trace_id after consuming upstream context with a given config value
function check_behavior(string $config_value): string {
ini_set('datadog.trace.propagation_behavior_extract', $config_value);

DDTrace\consume_distributed_tracing_headers([
"x-datadog-trace-id" => 42,
"x-datadog-parent-id" => 10,
]);

$span = DDTrace\start_span();
$result = DDTrace\root_span()->traceId === "0000000000000000000000000000002a" ? "continue" : "restart_or_ignore";
DDTrace\close_span();

return $result;
}

// Lowercase values
echo "continue: " . check_behavior("continue") . "\n";
echo "restart: " . check_behavior("restart") . "\n";
echo "ignore: " . check_behavior("ignore") . "\n";

// Case-insensitive
echo "CONTINUE: " . check_behavior("CONTINUE") . "\n";
echo "RESTART: " . check_behavior("RESTART") . "\n";
echo "Ignore: " . check_behavior("Ignore") . "\n";

// Invalid value falls back to default (continue)
echo "invalid: " . check_behavior("invalid_value") . "\n";

?>
--EXPECT--
continue: continue
restart: restart_or_ignore
ignore: restart_or_ignore
CONTINUE: continue
RESTART: restart_or_ignore
Ignore: restart_or_ignore
invalid: continue
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
--TEST--
DD_TRACE_PROPAGATION_BEHAVIOR_EXTRACT=continue inherits upstream context
--ENV--
DD_TRACE_GENERATE_ROOT_SPAN=0
DD_TRACE_PROPAGATION_BEHAVIOR_EXTRACT=continue
--FILE--
<?php

DDTrace\consume_distributed_tracing_headers([
"x-datadog-trace-id" => 42,
"x-datadog-parent-id" => 10,
"x-datadog-sampling-priority" => 1,
"baggage" => "user.id=123",
]);

$span = DDTrace\start_span();
$root = DDTrace\root_span();

echo "trace_id: " . $root->traceId . "\n";
echo "parent_id: " . $root->parentId . "\n";
echo "links_count: " . count($root->links) . "\n";

$headers = DDTrace\generate_distributed_tracing_headers(['baggage']);
echo "baggage: " . ($headers['baggage'] ?? 'none') . "\n";

DDTrace\close_span();
?>
--EXPECT--
trace_id: 0000000000000000000000000000002a
parent_id: 10
links_count: 0
baggage: user.id=123
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
--TEST--
DD_TRACE_PROPAGATION_BEHAVIOR_EXTRACT=ignore drops all incoming context including baggage
--ENV--
DD_TRACE_GENERATE_ROOT_SPAN=0
DD_TRACE_PROPAGATION_BEHAVIOR_EXTRACT=ignore
--FILE--
<?php

DDTrace\consume_distributed_tracing_headers([
"x-datadog-trace-id" => 42,
"x-datadog-parent-id" => 10,
"x-datadog-sampling-priority" => 2,
"x-datadog-tags" => "_dd.p.dm=-4",
"baggage" => "user.id=123",
]);

$span = DDTrace\start_span();
$root = DDTrace\root_span();

// fresh trace: not from upstream
echo "same_as_upstream: " . ($root->traceId === "0000000000000000000000000000002a" ? "yes" : "no") . "\n";
echo "parent_id: " . $root->parentId . "\n";

// no span link (context discarded entirely)
echo "links_count: " . count($root->links) . "\n";

// baggage dropped
$headers = DDTrace\generate_distributed_tracing_headers(['baggage']);
echo "baggage: " . ($headers['baggage'] ?? 'none') . "\n";

// upstream sampling priority not carried over
$dd_headers = DDTrace\generate_distributed_tracing_headers(['datadog']);
echo "sampling_priority: " . ($dd_headers['x-datadog-sampling-priority'] ?? 'none') . "\n";

DDTrace\close_span();
?>
--EXPECT--
same_as_upstream: no
parent_id: 0
links_count: 0
baggage: none
sampling_priority: none
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
--TEST--
DD_TRACE_PROPAGATION_BEHAVIOR_EXTRACT=restart starts fresh trace with span link
--ENV--
DD_TRACE_GENERATE_ROOT_SPAN=0
DD_TRACE_PROPAGATION_BEHAVIOR_EXTRACT=restart
DD_TRACE_128_BIT_TRACEID_GENERATION_ENABLED=0
DD_TRACE_DEBUG_PRNG_SEED=42
--FILE--
<?php

DDTrace\consume_distributed_tracing_headers([
"x-datadog-trace-id" => 42,
"x-datadog-parent-id" => 10,
"x-datadog-sampling-priority" => 1,
"x-datadog-tags" => "_dd.p.foo=bar",
"baggage" => "user.id=123",
]);

$span = DDTrace\start_span();
$root = DDTrace\root_span();

// fresh trace: different from upstream trace_id 42
echo "same_as_upstream: " . ($root->traceId === "0000000000000000000000000000002a" ? "yes" : "no") . "\n";

// span link attached to root span
echo "links_count: " . count($root->links) . "\n";

$link = $root->links[0] ?? null;
if ($link !== null) {
// link captures upstream trace/span ids
echo "link_trace_id: " . $link->traceId . "\n";
echo "link_span_id: " . $link->spanId . "\n";
echo "link_reason: " . ($link->attributes['reason'] ?? 'missing') . "\n";
echo "link_context_headers: " . ($link->attributes['context_headers'] ?? 'missing') . "\n";
// _dd.p.foo captured in link attributes (upstream propagation context preserved in link)
echo "link_has_foo: " . (isset($link->attributes['_dd.p.foo']) ? "yes" : "no") . "\n";
}

$tid = $root->traceId;
echo "trace_id_valid: " . (preg_match('/^[0-9a-f]{32}$/', $tid) && $tid !== "00000000000000000000000000000000" ? "yes" : "no") . "\n";

// baggage preserved
$headers = DDTrace\generate_distributed_tracing_headers(['baggage']);
echo "baggage: " . ($headers['baggage'] ?? 'none') . "\n";

// upstream _dd.p.foo not in outbound tags
$dd_headers = DDTrace\generate_distributed_tracing_headers(['datadog']);
$tags = $dd_headers['x-datadog-tags'] ?? '';
echo "foo_in_tags: " . (str_contains($tags, '_dd.p.foo') ? "yes" : "no") . "\n";

// upstream sampling priority must not leak into the fresh trace
echo "sampling_priority: " . ($dd_headers['x-datadog-sampling-priority'] ?? 'none') . "\n";

DDTrace\close_span();
?>
--EXPECT--
same_as_upstream: no
links_count: 1
link_trace_id: 0000000000000000000000000000002a
link_span_id: 000000000000000a
link_reason: propagation_behavior_extract
link_context_headers: datadog
link_has_foo: yes
trace_id_valid: yes
baggage: user.id=123
foo_in_tags: no
sampling_priority: none
7 changes: 7 additions & 0 deletions tracer/configuration.h
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@
CONFIG(SET_LOWERCASE, DD_TRACE_PROPAGATION_STYLE, "datadog,tracecontext,baggage", \
.env_config_fallback = ddtrace_conf_otel_propagators) \
CONFIG(SET, DD_TRACE_BAGGAGE_TAG_KEYS, "user.id, session.id, account.id") \
CONFIG(CUSTOM(INT), DD_TRACE_PROPAGATION_BEHAVIOR_EXTRACT, "continue", .parser = dd_parse_propagation_behavior_extract) \
CONFIG(BOOL, DD_TRACE_IGNORE_AGENT_SAMPLING_RATES, "false", .ini_change = zai_config_system_ini_change) \
CONFIG(SET, DD_TRACE_TRACED_INTERNAL_FUNCTIONS, "") \
CONFIG(INT, DD_TRACE_DEBUG_PRNG_SEED, "-1", .ini_change = ddtrace_reseed_seed_change) \
Expand Down Expand Up @@ -192,6 +193,12 @@ enum ddtrace_sampling_rules_format {
DD_TRACE_SAMPLING_RULES_FORMAT_GLOB
};

enum ddtrace_propagation_behavior_extract {
DD_TRACE_PROPAGATION_BEHAVIOR_EXTRACT_CONTINUE = 0,
DD_TRACE_PROPAGATION_BEHAVIOR_EXTRACT_RESTART,
DD_TRACE_PROPAGATION_BEHAVIOR_EXTRACT_IGNORE,
};

#define DD_CONFIGURATION DDTRACE_CONFIGURATION
#include <ext/configuration_helpers.h>

Expand Down
15 changes: 15 additions & 0 deletions tracer/configuration_dependencies.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,21 @@ static bool dd_parse_dbm_mode(zai_str value, zval *decoded_value, bool persisten
return true;
}

static bool dd_parse_propagation_behavior_extract(zai_str value, zval *decoded_value, bool persistent) {
UNUSED(persistent);
if (zai_str_eq_ci_cstr(value, "continue")) {
ZVAL_LONG(decoded_value, DD_TRACE_PROPAGATION_BEHAVIOR_EXTRACT_CONTINUE);
} else if (zai_str_eq_ci_cstr(value, "restart")) {
ZVAL_LONG(decoded_value, DD_TRACE_PROPAGATION_BEHAVIOR_EXTRACT_RESTART);
} else if (zai_str_eq_ci_cstr(value, "ignore")) {
ZVAL_LONG(decoded_value, DD_TRACE_PROPAGATION_BEHAVIOR_EXTRACT_IGNORE);
} else {
return false;
}

return true;
}

static bool dd_parse_sampling_rules_format(zai_str value, zval *decoded_value, bool persistent) {
UNUSED(persistent);
if (zai_str_eq_ci_cstr(value, "regex")) {
Expand Down
3 changes: 3 additions & 0 deletions tracer/ddtrace.c
Original file line number Diff line number Diff line change
Expand Up @@ -436,6 +436,7 @@ static void dd_initialize_request(void) {
DDTRACE_G(default_priority_sampling) = DDTRACE_PRIORITY_SAMPLING_UNKNOWN;
DDTRACE_G(propagated_priority_sampling) = DDTRACE_PRIORITY_SAMPLING_UNSET;
DDTRACE_G(inferred_span_created) = false;
ZVAL_NULL(&DDTRACE_G(pending_upstream_span_link));
zend_hash_init(&DDTRACE_G(root_span_tags_preset), 8, unused, ZVAL_PTR_DTOR, 0);
zend_hash_init(&DDTRACE_G(propagated_root_span_tags), 8, unused, ZVAL_PTR_DTOR, 0);
zend_hash_init(&DDTRACE_G(tracestate_unknown_dd_keys), 8, unused, ZVAL_PTR_DTOR, 0);
Expand Down Expand Up @@ -527,6 +528,8 @@ static void dd_clean_globals(void) {
zend_hash_destroy(&DDTRACE_G(tracestate_unknown_dd_keys));
zend_hash_destroy(&DDTRACE_G(propagated_root_span_tags));
zend_hash_destroy(&DDTRACE_G(baggage));
zval_ptr_dtor(&DDTRACE_G(pending_upstream_span_link));
ZVAL_NULL(&DDTRACE_G(pending_upstream_span_link));

if (DDTRACE_G(curl_multi_injecting_spans)) {
if (GC_DELREF(DDTRACE_G(curl_multi_injecting_spans)) == 0) {
Expand Down
1 change: 1 addition & 0 deletions tracer/ddtrace_globals.h
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ typedef struct {
zend_object *git_object;

bool inferred_span_created;
zval pending_upstream_span_link; // span link queued by PROPAGATION_BEHAVIOR_EXTRACT=restart; consumed on root span open

HashTable resource_weak_storage;
dtor_func_t resource_dtor_func;
Expand Down
55 changes: 55 additions & 0 deletions tracer/distributed_tracing_headers.c
Original file line number Diff line number Diff line change
Expand Up @@ -519,6 +519,9 @@ ddtrace_distributed_tracing_result ddtrace_read_distributed_tracing_ids(ddtrace_
}

result = func(read_header, data);
if (result.trace_id.low || result.trace_id.high) {
result.context_headers = zend_string_copy(extraction_style);
}

// As an exception, the x-datadog-origin can be submitted standalone, without valid trace id
if (existing_origin) {
Expand Down Expand Up @@ -617,6 +620,56 @@ void apply_baggage_span_tags(zend_string *key, zval *val, zend_array *meta) {
void ddtrace_apply_distributed_tracing_result(ddtrace_distributed_tracing_result *result, ddtrace_root_span_data *span) {
zval zv;

switch (get_DD_TRACE_PROPAGATION_BEHAVIOR_EXTRACT()) {
// behavior=ignore: drop all extracted context including baggage
case DD_TRACE_PROPAGATION_BEHAVIOR_EXTRACT_IGNORE:
zend_hash_destroy(&result->propagated_tags);
zend_hash_destroy(&result->meta_tags);
zend_hash_destroy(&result->tracestate_unknown_dd_keys);
zend_hash_destroy(&result->baggage);
if (result->origin) { zend_string_release(result->origin); }
if (result->tracestate) { zend_string_release(result->tracestate); }
if (result->context_headers) { zend_string_release(result->context_headers); }
return;
// behavior=restart: start fresh trace; upstream captured as span link, baggage preserved
case DD_TRACE_PROPAGATION_BEHAVIOR_EXTRACT_RESTART:
if (result->trace_id.low || result->trace_id.high) {
zval link_zv;
object_init_ex(&link_zv, ddtrace_ce_span_link);
ddtrace_span_link *link = (ddtrace_span_link *)Z_OBJ(link_zv);
ddtrace_build_span_link_from_result(result, link);

result->trace_id = (datadog_trace_id){0};
result->parent_id = 0;
result->priority_sampling = DDTRACE_PRIORITY_SAMPLING_UNKNOWN;

zend_hash_clean(&result->meta_tags);
zend_hash_clean(&result->propagated_tags);

zval reason_str;
ZVAL_STR(&reason_str, zend_string_init(ZEND_STRL("propagation_behavior_extract"), 0));
zend_hash_str_update(Z_ARR(link->property_attributes), ZEND_STRL("reason"), &reason_str);

if (result->context_headers) {
zval context_headers_zv;
ZVAL_STR(&context_headers_zv, zend_string_copy(result->context_headers));
zend_hash_str_update(Z_ARR(link->property_attributes), ZEND_STRL("context_headers"), &context_headers_zv);
}

if (span) {
zend_array *links = ddtrace_property_array(&span->property_links);
zend_hash_next_index_insert(links, &link_zv);
} else {
zval_ptr_dtor(&DDTRACE_G(pending_upstream_span_link));
ZVAL_COPY_VALUE(&DDTRACE_G(pending_upstream_span_link), &link_zv);
}
}
break;
// behavior=continue: inherit upstream context normally
case DD_TRACE_PROPAGATION_BEHAVIOR_EXTRACT_CONTINUE:
break;
}

zend_array *root_meta = span ? ddtrace_property_array(&span->property_meta) : &DDTRACE_G(root_span_tags_preset);
if (span) {
zend_string *tagname;
Expand Down Expand Up @@ -728,6 +781,8 @@ void ddtrace_apply_distributed_tracing_result(ddtrace_distributed_tracing_result
ddtrace_set_priority_sampling_on_span(span, result->priority_sampling, DD_MECHANISM_DEFAULT);
}
}

if (result->context_headers) { zend_string_release(result->context_headers); }
}

bool ddtrace_read_zai_header(zai_str zai_header, const char *lowercase_header, zend_string **header_value, void *data) {
Expand Down
2 changes: 2 additions & 0 deletions tracer/distributed_tracing_headers.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,14 @@ typedef struct {
int priority_sampling;
enum dd_sampling_mechanism sampling_mechanism;
bool conflicting_sampling_priority; // propagated priority does not match tracestate priority
zend_string *context_headers; // name of the extractor that produced the trace context (e.g. "datadog", "tracecontext")
} ddtrace_distributed_tracing_result;

typedef bool (ddtrace_read_header)(zai_str zai_header, const char *lowercase_header, zend_string **header_value, void *data);
ddtrace_distributed_tracing_result ddtrace_read_distributed_tracing_ids(ddtrace_read_header *read_header, void *data);
void ddtrace_apply_distributed_tracing_result(ddtrace_distributed_tracing_result *result, ddtrace_root_span_data *span);
bool ddtrace_read_zai_header(zai_str zai_header, const char *lowercase_header, zend_string **header_value, void *data);
bool ddtrace_read_array_header(zai_str zai_header, const char *lowercase_header, zend_string **header_value, void *data);
void ddtrace_build_span_link_from_result(ddtrace_distributed_tracing_result *result, ddtrace_span_link *link);

#endif // DD_DISTRIBUTED_TRACING_HEADERS_H
Loading
Loading