Skip to content

Commit 1071d94

Browse files
MilanGarnierclaude
andcommitted
feat: implement DD_TRACE_PROPAGATION_BEHAVIOR_EXTRACT behavior
Adds three behaviors at the single extraction chokepoint (ddtrace_apply_distributed_tracing_result): - continue (default): inherit upstream trace context unchanged - restart: start a fresh trace; upstream captured as a span link with reason=propagation_behavior_extract. When the root span doesn't exist yet at request-init time, the link is queued in DDTRACE_G(pending_upstream_span_link) and attached in ddtrace_open_span. - ignore: drop all extracted context including baggage and sampling priority Also extracts ddtrace_build_span_link_from_result() from the SpanLink fromHeaders method so it can be reused by the restart path. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 1a401b1 commit 1071d94

6 files changed

Lines changed: 83 additions & 13 deletions

File tree

tracer/ddtrace.c

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -436,6 +436,7 @@ static void dd_initialize_request(void) {
436436
DDTRACE_G(default_priority_sampling) = DDTRACE_PRIORITY_SAMPLING_UNKNOWN;
437437
DDTRACE_G(propagated_priority_sampling) = DDTRACE_PRIORITY_SAMPLING_UNSET;
438438
DDTRACE_G(inferred_span_created) = false;
439+
ZVAL_NULL(&DDTRACE_G(pending_upstream_span_link));
439440
zend_hash_init(&DDTRACE_G(root_span_tags_preset), 8, unused, ZVAL_PTR_DTOR, 0);
440441
zend_hash_init(&DDTRACE_G(propagated_root_span_tags), 8, unused, ZVAL_PTR_DTOR, 0);
441442
zend_hash_init(&DDTRACE_G(tracestate_unknown_dd_keys), 8, unused, ZVAL_PTR_DTOR, 0);
@@ -527,6 +528,8 @@ static void dd_clean_globals(void) {
527528
zend_hash_destroy(&DDTRACE_G(tracestate_unknown_dd_keys));
528529
zend_hash_destroy(&DDTRACE_G(propagated_root_span_tags));
529530
zend_hash_destroy(&DDTRACE_G(baggage));
531+
zval_ptr_dtor(&DDTRACE_G(pending_upstream_span_link));
532+
ZVAL_NULL(&DDTRACE_G(pending_upstream_span_link));
530533

531534
if (DDTRACE_G(curl_multi_injecting_spans)) {
532535
if (GC_DELREF(DDTRACE_G(curl_multi_injecting_spans)) == 0) {

tracer/ddtrace_globals.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ typedef struct {
8989
zend_object *git_object;
9090

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

9394
HashTable resource_weak_storage;
9495
dtor_func_t resource_dtor_func;

tracer/distributed_tracing_headers.c

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -617,6 +617,59 @@ void apply_baggage_span_tags(zend_string *key, zval *val, zend_array *meta) {
617617
void ddtrace_apply_distributed_tracing_result(ddtrace_distributed_tracing_result *result, ddtrace_root_span_data *span) {
618618
zval zv;
619619

620+
int behavior = get_DD_TRACE_PROPAGATION_BEHAVIOR_EXTRACT();
621+
622+
if (behavior == DD_TRACE_PROPAGATION_BEHAVIOR_EXTRACT_IGNORE) {
623+
// behavior=ignore: drop all extracted context including baggage
624+
zend_hash_destroy(&result->propagated_tags);
625+
zend_hash_destroy(&result->meta_tags);
626+
zend_hash_destroy(&result->tracestate_unknown_dd_keys);
627+
zend_hash_destroy(&result->baggage);
628+
if (result->origin) { zend_string_release(result->origin); }
629+
if (result->tracestate) { zend_string_release(result->tracestate); }
630+
return;
631+
}
632+
633+
if (behavior == DD_TRACE_PROPAGATION_BEHAVIOR_EXTRACT_RESTART && (result->trace_id.low || result->trace_id.high)) {
634+
// behavior=restart: zero trace_id so a fresh trace starts; upstream captured as span link
635+
// drop _dd.p.* first so the builder does not include them in link attributes
636+
zend_string *mk;
637+
zend_string *to_delete[64];
638+
int to_delete_count = 0;
639+
ZEND_HASH_FOREACH_STR_KEY(&result->meta_tags, mk) {
640+
if (mk && ZSTR_LEN(mk) > 6 && strncmp(ZSTR_VAL(mk), "_dd.p.", 6) == 0) {
641+
to_delete[to_delete_count++] = mk;
642+
}
643+
} ZEND_HASH_FOREACH_END();
644+
for (int i = 0; i < to_delete_count; i++) {
645+
zend_hash_del(&result->meta_tags, to_delete[i]);
646+
}
647+
zend_hash_clean(&result->propagated_tags);
648+
649+
zval link_zv;
650+
object_init_ex(&link_zv, ddtrace_ce_span_link);
651+
ddtrace_span_link *link = (ddtrace_span_link *)Z_OBJ(link_zv);
652+
ddtrace_build_span_link_from_result(result, link);
653+
654+
zval reason_val;
655+
ZVAL_STR(&reason_val, zend_string_init(ZEND_STRL("reason"), 0));
656+
zval reason_str;
657+
ZVAL_STR(&reason_str, zend_string_init(ZEND_STRL("propagation_behavior_extract"), 0));
658+
zend_hash_update(Z_ARR(link->property_attributes), Z_STR(reason_val), &reason_str);
659+
zval_ptr_dtor(&reason_val);
660+
661+
result->trace_id = (datadog_trace_id){0};
662+
result->parent_id = 0;
663+
664+
if (span) {
665+
zend_array *links = ddtrace_property_array(&span->property_links);
666+
zend_hash_next_index_insert(links, &link_zv);
667+
} else {
668+
zval_ptr_dtor(&DDTRACE_G(pending_upstream_span_link));
669+
ZVAL_COPY_VALUE(&DDTRACE_G(pending_upstream_span_link), &link_zv);
670+
}
671+
}
672+
620673
zend_array *root_meta = span ? ddtrace_property_array(&span->property_meta) : &DDTRACE_G(root_span_tags_preset);
621674
if (span) {
622675
zend_string *tagname;

tracer/distributed_tracing_headers.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
#define DD_DISTRIBUTED_TRACING_HEADERS_H
33

44
#include "ddtrace.h"
5+
#include "span.h"
56
#include "priority_sampling/priority_sampling.h"
67
#include <zai_string/string.h>
78

@@ -24,5 +25,6 @@ ddtrace_distributed_tracing_result ddtrace_read_distributed_tracing_ids(ddtrace_
2425
void ddtrace_apply_distributed_tracing_result(ddtrace_distributed_tracing_result *result, ddtrace_root_span_data *span);
2526
bool ddtrace_read_zai_header(zai_str zai_header, const char *lowercase_header, zend_string **header_value, void *data);
2627
bool ddtrace_read_array_header(zai_str zai_header, const char *lowercase_header, zend_string **header_value, void *data);
28+
void ddtrace_build_span_link_from_result(ddtrace_distributed_tracing_result *result, ddtrace_span_link *link);
2729

2830
#endif // DD_DISTRIBUTED_TRACING_HEADERS_H

tracer/functions.c

Lines changed: 17 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -231,6 +231,22 @@ PHP_METHOD(DDTrace_SpanLink, jsonSerialize) {
231231
RETURN_ARR(array);
232232
}
233233

234+
void ddtrace_build_span_link_from_result(ddtrace_distributed_tracing_result *result, ddtrace_span_link *link) {
235+
ZVAL_STR(&link->property_trace_id, datadog_trace_id_as_hex_string(result->trace_id));
236+
ZVAL_STR(&link->property_span_id, ddtrace_span_id_as_hex_string(result->parent_id));
237+
array_init(&link->property_attributes);
238+
zend_hash_copy(Z_ARR(link->property_attributes), &result->meta_tags, NULL);
239+
240+
zend_string *propagated_tags = ddtrace_format_propagated_tags(&result->propagated_tags, &result->meta_tags);
241+
zend_string *full_tracestate = ddtrace_format_tracestate(result->tracestate, 0, result->origin, result->priority_sampling, propagated_tags, &result->tracestate_unknown_dd_keys);
242+
if (propagated_tags) {
243+
zend_string_release(propagated_tags);
244+
}
245+
if (full_tracestate) {
246+
ZVAL_STR(&link->property_trace_state, full_tracestate);
247+
}
248+
}
249+
234250
static ddtrace_distributed_tracing_result dd_parse_distributed_tracing_headers_function(INTERNAL_FUNCTION_PARAMETERS, bool *success);
235251
ZEND_METHOD(DDTrace_SpanLink, fromHeaders) {
236252
bool success;
@@ -245,19 +261,7 @@ ZEND_METHOD(DDTrace_SpanLink, fromHeaders) {
245261
return;
246262
}
247263

248-
ZVAL_STR(&link->property_trace_id, datadog_trace_id_as_hex_string(result.trace_id));
249-
ZVAL_STR(&link->property_span_id, ddtrace_span_id_as_hex_string(result.parent_id));
250-
array_init(&link->property_attributes);
251-
zend_hash_copy(Z_ARR(link->property_attributes), &result.meta_tags, NULL);
252-
253-
zend_string *propagated_tags = ddtrace_format_propagated_tags(&result.propagated_tags, &result.meta_tags);
254-
zend_string *full_tracestate = ddtrace_format_tracestate(result.tracestate, 0, result.origin, result.priority_sampling, propagated_tags, &result.tracestate_unknown_dd_keys);
255-
if (propagated_tags) {
256-
zend_string_release(propagated_tags);
257-
}
258-
if (full_tracestate) {
259-
ZVAL_STR(&link->property_trace_state, full_tracestate);
260-
}
264+
ddtrace_build_span_link_from_result(&result, link);
261265

262266
result.meta_tags.pDestructor = NULL; // we moved values directly
263267
zend_hash_destroy(&result.meta_tags);

tracer/span.c

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -296,6 +296,13 @@ ddtrace_span_data *ddtrace_open_span(enum ddtrace_span_dataype type) {
296296
span->parent = NULL;
297297

298298
ddtrace_set_root_span_properties(root);
299+
300+
if (primary_stack && Z_TYPE(DDTRACE_G(pending_upstream_span_link)) == IS_OBJECT) {
301+
// attach upstream link queued by PROPAGATION_BEHAVIOR_EXTRACT=restart
302+
zend_array *links = ddtrace_property_array(&span->property_links);
303+
zend_hash_next_index_insert(links, &DDTRACE_G(pending_upstream_span_link));
304+
ZVAL_NULL(&DDTRACE_G(pending_upstream_span_link));
305+
}
299306
} else {
300307
++parent_span->active_child_spans;
301308

0 commit comments

Comments
 (0)