[SLES-2971] fix(traces): only trust the X-Ray Sampled flag on Datadog-planted headers - #1325
[SLES-2971] fix(traces): only trust the X-Ray Sampled flag on Datadog-planted headers#1325lym953 wants to merge 1 commit into
Conversation
|
🔗 Commit SHA: aca7b4a | Docs | View more details | Give us feedback! |
4950cd1 to
e93c41c
Compare
e93c41c to
a17b8d8
Compare
lucaspimentel
left a comment
There was a problem hiding this comment.
LGTM. This issue only affects SQS?
There was a problem hiding this comment.
Pull request overview
Prevents AWS-generated X-Ray drop decisions from suppressing Datadog Lambda traces.
Changes:
- Distinguishes Datadog-planted X-Ray root IDs.
- Propagates sampling priority selectively.
- Adds malformed-header and sampling tests.
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
|
Do we have a link to the equivalent version of this code in the old go extension, (might need to dig through history). If that was the approach being used there, then I agree we should match it with this approach. |
a17b8d8 to
c40be8b
Compare
…-planted headers The SQS AWSTraceHeader fallback accepted any `Root=`-prefixed value and mapped `Sampled != 1` to sampling priority 0, which the tracer then applied to its whole trace. On an AWS-generated root ID that flag is X-Ray's decision, not Datadog's -- and when X-Ray tracing is off it is not a decision at all, just the default the AWS SDK's recursion-detection header carries. Gate the priority on the ID shape instead: Datadog libraries zero the high 32 bits of the ID section (dd-trace-java's `XRayHttpCodec.TRACE_ID_PADDING`, and the Go extension's `rootRegex` asserted the same), so only then is `Sampled` ours to honor. For an AWS-generated ID keep the IDs for correlation and leave the priority unset so the tracer decides. Also switch the root-ID slice to a checked `get`, which the surrounding code needs anyway: a truncated `Root=` part would panic on the old fixed index.
c40be8b to
aca7b4a
Compare
@DarcyRaynerDD Yes, here's the code: https://github.com/DataDog/datadog-agent/blob/02d4183e967e1ba5dbc419cca202b8dcae78f583/pkg/serverless/trace/propagation/carriers.go#L56
|
@lucaspimentel Yes. Claude's response below: The X-Ray header fallback runs for SQS records and nothing else. That header is an SQS system attribute, so no other event type carries one, and the old Go extension read it only for SQS messages too. No other path lets X-Ray affect Datadog sampling. bottlecap has no X-Ray propagation style, so the Two caveats on how narrow "SQS only" really is:
|
Problem
A customer's Lambda→SQS→Lambda pipeline lost most of its trace. The consuming function, a Go Lambda, dropped from 14 spans per invocation to 2 after switching from compatibility mode to bottlecap. The application spans disappeared; the extension's own
aws.sqsandaws.lambdaspans remained.In the customer's
DD_TRACE_DEBUGflare, the tracer emitted its full 12-span tree per invocation, with every span carrying_sampling_priority_v1:0— the trace was assembled correctly, then dropped as sampled-out.Background
On each invocation, the tracer calls the extension's
/lambda/start-invocation, and the extension replies withx-datadog-trace-idandx-datadog-sampling-prioritydrawn from upstream trace context in the invocation payload. For an SQS trigger, that context normally arrives in the record's_datadogmessage attribute; when it is absent, the extension falls back to the X-RayAWSTraceHeadersystem attribute — a path added in #452 for Java→SQS, wheredd-trace-javaplants Datadog context in that header.A message can carry that X-Ray header without anyone using X-Ray. The AWS SDKs' recursion-detection middleware copies
_X_AMZN_TRACE_IDonto every outbound request inside Lambda, and SQS stores it asAWSTraceHeader. Any Lambda→SQS→Lambda chain therefore carries one, with no X-Ray SDK and no tracing configuration, and itsSampledflag is0whenever the producing function has X-Ray tracing off — the default.Cause
These records had no readable
_datadogattribute, so the fallback ran and mapped the X-RaySampledflag onto a Datadog sampling priority:Sampledwas0— the producer had X-Ray tracing off — so the extension handed the tracer priority0, and dd-trace-go applied it to its whole trace. The extension's own inferred and invocation spans escaped, because it reads a priority for those only when it found no upstream context at all, leaving the 2 spans above.Compatibility mode runs the legacy Go extension, which guards this same fallback with a regex accepting only root ids whose high 32 bits are zeroed —
Root=1-[0-9a-fA-F]{8}-00000000[0-9a-fA-F]{16}, the shapedd-trace-javaplants. An AWS-generated id fails it, so the Go extension returned no headers and the tracer sampled for itself. bottlecap kept the parsing without the guard.Fix
Trust the flag only when
dd-trace-javawrote the header. It marks its own with00000000padding and ignores headers lacking it; the Go extension's regex asserted the same shape.Sampled=11— keepSampled=00— dropSampled=11— keepSampled=0Sampledfield, either origin"Datadog-planted" means the root id carries the
…-00000000<16 hex>padding; anything else is AWS-generated.dd-trace-javais the only tracer with an X-Ray propagator, so it is the only source of a padded root — which is why the rows that change are the ones a non-Java producer hits, this customer's Go pipeline among them.The bolded rows are the ones that move: a priority is now sent only for an explicit
Sampled=1, or an explicitSampled=0on a padded root.dd-trace-javaomitsSampledwhen the priority isn't set yet, so a padded header can arrive without one.Trace ids still come from the header in every row, so the producer→consumer link is preserved.
Also replaces the fixed-index slice of the root id with a checked one: a truncated
Root=segment would have panicked.Impact
Sampled=1still sets priority1. That over-retains at worst, so it stays.DD_MERGE_XRAY_TRACESdeliberately plays no part here. Merging governs whether X-Ray's spans join a Datadog trace; it does not make X-Ray's sampler responsible for Datadog's sampling, so the drop is wrong to propagate whether merging is on or off.Testing
Verified live on a Go/arm64
provided.al2023SQS repro matching the customer's setup (datadog-lambda-go v1.32.0,dd-trace-go/v2 v2.9.2), sending each header shape with no_datadogattribute. Every span in the trace carried the priority shown.Sampled=00— dropped0— droppedSampled=11— kept1— keptSampled=00— dropped1— keptSampled=11— kept1— keptThe third row is the reported bug. All four rows take the X-Ray fallback, and the trace id still equals the header's low 64 bits, so correlation is unchanged.
A header with no
Sampledfield is covered by a unit test rather than a live run.These runs measure the priority the tracer receives. That a trace at priority
0is then dropped and its spans stop appearing in the UI is established behavior, which I did not re-measure.🤖 Generated with Claude Code