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,57 @@
--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";
// _dd.p.foo not included (was in upstream _dd.p.* tags, now dropped)
echo "link_has_foo: " . (isset($link->attributes['_dd.p.foo']) ? "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";

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_has_foo: no
baggage: user.id=123
foo_in_tags: no
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
51 changes: 51 additions & 0 deletions tracer/distributed_tracing_headers.c
Original file line number Diff line number Diff line change
Expand Up @@ -617,6 +617,57 @@ 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;

int behavior = get_DD_TRACE_PROPAGATION_BEHAVIOR_EXTRACT();

if (behavior == DD_TRACE_PROPAGATION_BEHAVIOR_EXTRACT_IGNORE) {
// behavior=ignore: drop all extracted context including baggage
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); }
return;
}

if (behavior == DD_TRACE_PROPAGATION_BEHAVIOR_EXTRACT_RESTART && (result->trace_id.low || result->trace_id.high)) {
// behavior=restart: zero trace_id so a fresh trace starts; upstream captured as span link
// drop _dd.p.* first so the builder does not include them in link attributes

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.

Why would we want to drop e.g. _dd.p.usr.id? Shouldn't that be an useful attribute to see?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

As far as I understand it, the role of this feature is to explicitly discard all incoming tags other than baggage. That way, let's say it is a service extracting a trace coming from outside it can decide to restart a trace in its own terms (own sampling decision and everything else). That being said, I am new on the subject so I'll look again in other tracers

zend_string *mk;
zend_string *to_delete[64];
int to_delete_count = 0;
ZEND_HASH_FOREACH_STR_KEY(&result->meta_tags, mk) {
if (mk && ZSTR_LEN(mk) > 6 && strncmp(ZSTR_VAL(mk), "_dd.p.", 6) == 0) {
to_delete[to_delete_count++] = mk;
}
} ZEND_HASH_FOREACH_END();
for (int i = 0; i < to_delete_count; i++) {
zend_hash_del(&result->meta_tags, to_delete[i]);
}
Comment on lines +636 to +646

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.

There's no need to split collection (to a fixed array?!) and iteration with PHP hashmaps. You can call zend_hash_del during iteration.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

I agree that the fixed array was stupid, but I didn't know zend_hash_del was safe to call, I'll use it then, thanks !

zend_hash_clean(&result->propagated_tags);

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);

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);

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

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);
}
}

zend_array *root_meta = span ? ddtrace_property_array(&span->property_meta) : &DDTRACE_G(root_span_tags_preset);
if (span) {
zend_string *tagname;
Expand Down
1 change: 1 addition & 0 deletions tracer/distributed_tracing_headers.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,5 +24,6 @@ ddtrace_distributed_tracing_result ddtrace_read_distributed_tracing_ids(ddtrace_
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
36 changes: 22 additions & 14 deletions tracer/functions.c
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,25 @@ PHP_METHOD(DDTrace_SpanLink, jsonSerialize) {
RETURN_ARR(array);
}

void ddtrace_build_span_link_from_result(ddtrace_distributed_tracing_result *result, ddtrace_span_link *link) {
ZVAL_STR(&link->property_trace_id, datadog_trace_id_as_hex_string(result->trace_id));
ZVAL_STR(&link->property_span_id, ddtrace_span_id_as_hex_string(result->parent_id));
array_init(&link->property_attributes);
// The span link owns its own references to the copied values, independent of the
// result->meta_tags lifetime: callers may keep meta_tags alive and hand it to other
// consumers (e.g. the root span's meta) or destroy it right after.
zend_hash_copy(Z_ARR(link->property_attributes), &result->meta_tags, (copy_ctor_func_t)zval_add_ref);

zend_string *propagated_tags = ddtrace_format_propagated_tags(&result->propagated_tags, &result->meta_tags);
zend_string *full_tracestate = ddtrace_format_tracestate(result->tracestate, 0, result->origin, result->priority_sampling, propagated_tags, &result->tracestate_unknown_dd_keys);
if (propagated_tags) {
zend_string_release(propagated_tags);
}
if (full_tracestate) {
ZVAL_STR(&link->property_trace_state, full_tracestate);
}
}

static ddtrace_distributed_tracing_result dd_parse_distributed_tracing_headers_function(INTERNAL_FUNCTION_PARAMETERS, bool *success);
ZEND_METHOD(DDTrace_SpanLink, fromHeaders) {
bool success;
Expand All @@ -245,21 +264,10 @@ ZEND_METHOD(DDTrace_SpanLink, fromHeaders) {
return;
}

ZVAL_STR(&link->property_trace_id, datadog_trace_id_as_hex_string(result.trace_id));
ZVAL_STR(&link->property_span_id, ddtrace_span_id_as_hex_string(result.parent_id));
array_init(&link->property_attributes);
zend_hash_copy(Z_ARR(link->property_attributes), &result.meta_tags, NULL);

zend_string *propagated_tags = ddtrace_format_propagated_tags(&result.propagated_tags, &result.meta_tags);
zend_string *full_tracestate = ddtrace_format_tracestate(result.tracestate, 0, result.origin, result.priority_sampling, propagated_tags, &result.tracestate_unknown_dd_keys);
if (propagated_tags) {
zend_string_release(propagated_tags);
}
if (full_tracestate) {
ZVAL_STR(&link->property_trace_state, full_tracestate);
}
ddtrace_build_span_link_from_result(&result, link);

result.meta_tags.pDestructor = NULL; // we moved values directly

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.

You did not like the do-not-touch-the-refcount approach? :-) Seeing you susbtituted it for zval_add_ref copying.

// The span link took its own references (zval_add_ref) on the copied values, so destroy
// meta_tags normally to release the references it still owns.
zend_hash_destroy(&result.meta_tags);
zend_hash_destroy(&result.propagated_tags);
zend_hash_destroy(&result.tracestate_unknown_dd_keys);
Expand Down
7 changes: 7 additions & 0 deletions tracer/span.c
Original file line number Diff line number Diff line change
Expand Up @@ -296,6 +296,13 @@ ddtrace_span_data *ddtrace_open_span(enum ddtrace_span_dataype type) {
span->parent = NULL;

ddtrace_set_root_span_properties(root);

if (primary_stack && Z_TYPE(DDTRACE_G(pending_upstream_span_link)) == IS_OBJECT) {
// attach upstream link queued by PROPAGATION_BEHAVIOR_EXTRACT=restart
zend_array *links = ddtrace_property_array(&span->property_links);
zend_hash_next_index_insert(links, &DDTRACE_G(pending_upstream_span_link));
ZVAL_NULL(&DDTRACE_G(pending_upstream_span_link));
}
} else {
++parent_span->active_child_spans;

Expand Down
Loading