feat(tracing): add DD_TRACE_PROPAGATION_BEHAVIOR_EXTRACT#3997
feat(tracing): add DD_TRACE_PROPAGATION_BEHAVIOR_EXTRACT#3997MilanGarnier wants to merge 27 commits into
Conversation
|
Adds the new `DD_TRACE_PROPAGATION_BEHAVIOR_EXTRACT` config key (values: continue, restart, ignore) with a CUSTOM(INT) parser, the corresponding C enum, and the supported-configurations.json entry. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
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>
Covers the three behaviors (continue, restart, ignore) and config parsing (case-insensitive values, invalid value falls back to default). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
6b7e259 to
3ca8428
Compare
Co-authored-by: Bob Weinand <bob.weinand@datadoghq.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 19c7dafd9f
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| zend_hash_clean(&result->meta_tags); | ||
| zend_hash_clean(&result->propagated_tags); |
There was a problem hiding this comment.
Drop upstream tracestate on restart
When DD_TRACE_PROPAGATION_BEHAVIOR_EXTRACT=restart receives headers that include x-datadog-origin or W3C tracestate, this path only clears trace id/parent/sampling and _dd.p.*; the code below still moves result->origin, result->tracestate, and tracestate_unknown_dd_keys onto the new root/global context, and outbound injection later re-emits them. In that scenario the restarted trace continues propagating upstream trace context instead of preserving only baggage plus the span link.
Useful? React with 👍 / 👎.
Benchmarks [ tracer ]Benchmark execution time: 2026-07-09 14:57:40 Comparing candidate commit e53c3ef in PR branch Found 1 performance improvements and 0 performance regressions! Performance is the same for 193 metrics, 0 unstable metrics.
|
`dd_parse_distributed_tracing_headers_function`, but not in progagation behavior extract restart
There was a problem hiding this comment.
💡 Codex Review
When restart or ignore is used with a remote OTel parent, the code starts a fresh trace but still passes the extracted $parentContext to shouldSample(). Parent-based samplers will continue to honor the upstream sampled/drop flags, so these modes do not actually discard the incoming sampling context even though the C extraction path clears propagated priority to UNKNOWN; use a root/empty parent context for sampling in these modes.
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| 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); } |
There was a problem hiding this comment.
Null released restart context before falling through
When restart extracts a valid upstream context that includes x-datadog-origin or W3C tracestate, this block releases those strings but then falls through to the normal apply path, which still checks and assigns result->origin/result->tracestate to the root span/global context; context_headers is also released again at function exit. Fresh evidence is that the current block releases these fields without nulling them or returning, so restart can store freed pointers and later double-free them instead of just dropping the upstream context.
Useful? React with 👍 / 👎.
| $span = $parentSpanContext->isValid() ? null : \DDTrace\start_trace_span($this->startEpochNanos); | ||
| $traceId = $parentSpanContext->isValid() ? $parentSpanContext->getTraceId() : \DDTrace\root_span()->traceId; | ||
| $behaviorExtract = \dd_trace_env_config('DD_TRACE_PROPAGATION_BEHAVIOR_EXTRACT'); | ||
| $restartOrIgnore = $parentSpanContext->isValid() && ($behaviorExtract === 1 || $behaviorExtract === 2); |
There was a problem hiding this comment.
Restrict extract behavior to remote OTel parents
With DD_TRACE_PROPAGATION_BEHAVIOR_EXTRACT=restart or ignore, this treats every valid OTel parent as extracted context. For ordinary in-process OTel children whose parent span is local, line 190 starts a new trace (and the restart branch can add a span link to the local parent) instead of preserving the parent/child relationship; the extract behavior should only apply when the parent context is remote/extracted, not for local spans created later in the same trace.
Useful? React with 👍 / 👎.
| $traceParent = "00-{$parentSpanContext->getTraceId()}-$parentId-01"; | ||
| \DDTrace\consume_distributed_tracing_headers([ | ||
| 'traceparent' => $traceParent, | ||
| 'tracestate' => (string) $samplingResultTraceState, |
There was a problem hiding this comment.
Preserve upstream OTel tracestate in restart links
When restart is triggered from an extracted OTel W3C context that carries tracestate or an unsampled trace flag, this synthetic header hard-codes sampled (-01) and uses the local sampler's tracestate instead of the parent span context's trace flags/tracestate. The C restart path builds the span link from these headers, so the link loses upstream vendor tracestate and records local sampler state rather than the linked upstream context.
Useful? React with 👍 / 👎.
|
|
||
| ddtrace_set_root_span_properties(root); | ||
|
|
||
| if (primary_stack && Z_TYPE(DDTRACE_G(pending_upstream_span_link)) == IS_OBJECT) { |
There was a problem hiding this comment.
Attach queued restart links to explicit trace roots
When request-init queues pending_upstream_span_link and the application creates its first root with DDTrace\start_trace_span() under DD_TRACE_GENERATE_ROOT_SPAN=0, start_trace_span() switches to a child root stack before ddtrace_open_span(), so primary_stack is false and this condition skips the queued link. In that flow restart starts a fresh trace but loses the upstream span link (or leaves it pending for a later unrelated primary-stack span), so explicit trace roots do not get the restart link promised by the new behavior.
Useful? React with 👍 / 👎.
Summary
Implements
DD_TRACE_PROPAGATION_BEHAVIOR_EXTRACT(APMAPI-1941, feature-parity #353) for the PHP tracer, matching behavior already present in .NET, Python, Node, Java, Ruby, and Rust.Three values:
continue(default): inherit upstream trace context unchanged — no behavior changerestart: start a fresh trace; the upstream context is captured as a span link withreason=propagation_behavior_extractignore: drop all extracted context including baggage and sampling priorityDesign:
ddtrace_apply_distributed_tracing_resultindistributed_tracing_headers.c, covering both request-init andconsume_distributed_tracing_headersuserland callsrestartpath: when the root span doesn't yet exist at request-init time, the span link is queued inDDTRACE_G(pending_upstream_span_link)and consumed inddtrace_open_spanwhen the root span is createdddtrace_build_span_link_from_result()fromSpanLink::fromHeadersfor reuseTest plan
tests/ext/distributed_tracing/propagation_behavior_extract_continue.phpt— verifies trace_id inherited, no span link, baggage preservedtests/ext/distributed_tracing/propagation_behavior_extract_restart.phpt— verifies fresh trace_id, span link with correct upstream ids and reason, baggage preserved,_dd.p.*not in link attributestests/ext/distributed_tracing/propagation_behavior_extract_ignore.phpt— verifies fresh trace_id, no span link, baggage dropped, sampling priority droppedtests/ext/distributed_tracing/propagation_behavior_extract_config.phpt— verifies case-insensitive parsing, invalid value falls back tocontinueRelated
🤖 Generated with Claude Code