From 2703719ea5dcbc1c00c20a3390bbbf4c817fc8b9 Mon Sep 17 00:00:00 2001 From: Douglas Q Hawkins Date: Fri, 23 Oct 2026 00:45:51 -0400 Subject: [PATCH 1/3] Start extracted Datadog propagation tags from the extracted priority The Datadog extractor parsed x-datadog-tags as soon as it saw the header, before the sampling priority was necessarily known, so the tags started at UNSET and the root span's constructor installed the priority as a second sampling state. The raw header is now kept and parsed when the context is built, starting from the priority the trace will inherit, so the root span's reconcile is a no-op. When APM tracing is disabled, the upstream priority may not be inherited, so the tags still start at UNSET there. Co-Authored-By: Claude Opus 5.5 --- .../core/propagation/ContextInterpreter.java | 8 +++++ .../core/propagation/DatadogHttpCodec.java | 21 ++++++++++++- .../core/propagation/PropagationTags.java | 6 ++++ .../propagation/ptags/DatadogPTagsCodec.java | 7 +++++ .../core/propagation/ptags/PTagsFactory.java | 31 ++++++++++++++++++- 5 files changed, 71 insertions(+), 2 deletions(-) diff --git a/dd-trace-core/src/main/java/datadog/trace/core/propagation/ContextInterpreter.java b/dd-trace-core/src/main/java/datadog/trace/core/propagation/ContextInterpreter.java index a1467a014e1..f8afe9f238d 100644 --- a/dd-trace-core/src/main/java/datadog/trace/core/propagation/ContextInterpreter.java +++ b/dd-trace-core/src/main/java/datadog/trace/core/propagation/ContextInterpreter.java @@ -336,6 +336,14 @@ protected void onlyTagContext() { this.fullContext = false; } + /** + * The extracted sampling priority that the trace will inherit, so propagation tags can start from + * it, or {@link PrioritySampling#UNSET} when it may not be inherited. + */ + protected int inheritedSamplingPriority() { + return apmTracingEnabled ? samplingPriority : PrioritySampling.UNSET; + } + protected int defaultSamplingPriority() { return PrioritySampling.UNSET; } diff --git a/dd-trace-core/src/main/java/datadog/trace/core/propagation/DatadogHttpCodec.java b/dd-trace-core/src/main/java/datadog/trace/core/propagation/DatadogHttpCodec.java index d9875db6779..23d32f48949 100644 --- a/dd-trace-core/src/main/java/datadog/trace/core/propagation/DatadogHttpCodec.java +++ b/dd-trace-core/src/main/java/datadog/trace/core/propagation/DatadogHttpCodec.java @@ -111,6 +111,9 @@ private static class DatadogContextInterpreter extends ContextInterpreter { private final boolean isAwsPropagationEnabled; + /** The raw x-datadog-tags value, parsed in {@link #build()} once the priority is known. */ + private String datadogTags; + private DatadogContextInterpreter(Config config) { super(config); isAwsPropagationEnabled = config.isAwsPropagationEnabled(); @@ -121,6 +124,12 @@ public TracePropagationStyle style() { return DATADOG; } + @Override + public ContextInterpreter reset(TraceConfig traceConfig) { + datadogTags = null; + return super.reset(traceConfig); + } + @Override public boolean accept(String key, String value) { if (null == key || key.isEmpty()) { @@ -187,7 +196,7 @@ public boolean accept(String key, String value) { endToEndStartTime = extractEndToEndStartTime(firstHeaderValue(value)); break; case DD_TAGS: - propagationTags = propagationTagsFactory.fromHeaderValue(HeaderType.DATADOG, value); + datadogTags = value; break; case OT_BAGGAGE: addBaggageItem(lowerCaseKey.substring(OT_BAGGAGE_PREFIX.length()), value); @@ -216,6 +225,16 @@ public boolean accept(String key, String value) { @Override protected TagContext build() { + if (datadogTags != null) { + try { + propagationTags = + propagationTagsFactory.fromDatadogHeaderValue( + datadogTags, inheritedSamplingPriority()); + } catch (RuntimeException e) { + invalidateContext(); + log.debug("Exception when extracting context", e); + } + } restore128bTraceId(); return super.build(); } diff --git a/dd-trace-core/src/main/java/datadog/trace/core/propagation/PropagationTags.java b/dd-trace-core/src/main/java/datadog/trace/core/propagation/PropagationTags.java index 86552fdf1cf..2e88d0becee 100644 --- a/dd-trace-core/src/main/java/datadog/trace/core/propagation/PropagationTags.java +++ b/dd-trace-core/src/main/java/datadog/trace/core/propagation/PropagationTags.java @@ -122,6 +122,12 @@ public interface Factory { PropagationTags fromHeaderValue(HeaderType headerType, String value); + /** + * Like {@link #fromHeaderValue} for {@link HeaderType#DATADOG}, starting from the trace's + * extracted {@code samplingPriority} so it doesn't have to be applied as a later update. + */ + PropagationTags fromDatadogHeaderValue(String value, int samplingPriority); + /** * Returns a fresh PropagationTags that re-encodes only the non-{@code dd} vendor sections of * the supplied W3C tracestate. All Datadog-side state (sampling priority, origin, {@code diff --git a/dd-trace-core/src/main/java/datadog/trace/core/propagation/ptags/DatadogPTagsCodec.java b/dd-trace-core/src/main/java/datadog/trace/core/propagation/ptags/DatadogPTagsCodec.java index 61b564c0215..4d86f94fec6 100644 --- a/dd-trace-core/src/main/java/datadog/trace/core/propagation/ptags/DatadogPTagsCodec.java +++ b/dd-trace-core/src/main/java/datadog/trace/core/propagation/ptags/DatadogPTagsCodec.java @@ -4,6 +4,7 @@ import datadog.logging.RatelimitedLogger; import datadog.trace.api.ProductTraceSource; +import datadog.trace.api.sampling.PrioritySampling; import datadog.trace.core.propagation.PropagationTags; import datadog.trace.core.propagation.PropagationTags.SamplingState; import datadog.trace.core.propagation.ptags.PTagsFactory.PTags; @@ -55,6 +56,11 @@ final class DatadogPTagsCodec extends PTagsCodec { */ @Override PropagationTags fromHeaderValue(PTagsFactory tagsFactory, String value) { + return fromHeaderValue(tagsFactory, value, PrioritySampling.UNSET); + } + + /** Parses {@code value} into propagation tags that start at {@code samplingPriority}. */ + PropagationTags fromHeaderValue(PTagsFactory tagsFactory, String value, int samplingPriority) { if (value == null) { return tagsFactory.empty(); } @@ -150,6 +156,7 @@ PropagationTags fromHeaderValue(PTagsFactory tagsFactory, String value) { decisionMakerTagValue, traceIdTagValue, traceSource, + samplingPriority, orgPropagationMarkerTagValue, LLMObsTagValues.of( llmObsTraceIdTagValue, diff --git a/dd-trace-core/src/main/java/datadog/trace/core/propagation/ptags/PTagsFactory.java b/dd-trace-core/src/main/java/datadog/trace/core/propagation/ptags/PTagsFactory.java index fb197efa226..fc5db6768cc 100644 --- a/dd-trace-core/src/main/java/datadog/trace/core/propagation/ptags/PTagsFactory.java +++ b/dd-trace-core/src/main/java/datadog/trace/core/propagation/ptags/PTagsFactory.java @@ -59,6 +59,12 @@ public final PropagationTags fromHeaderValue(@Nonnull HeaderType headerType, Str return DEC_ENC_MAP.get(headerType).fromHeaderValue(this, value); } + @Override + public final PropagationTags fromDatadogHeaderValue(String value, int samplingPriority) { + return ((DatadogPTagsCodec) DEC_ENC_MAP.get(DATADOG)) + .fromHeaderValue(this, value, samplingPriority); + } + @Override public final PropagationTags emptyW3C(String originalTracestate) { if (originalTracestate == null || originalTracestate.isEmpty()) { @@ -74,14 +80,37 @@ PropagationTags createValid( int productTraceSource, TagValue orgPropagationMarkerTagValue, @Nonnull LLMObsTagValues llmObsTagValues) { + return createValid( + tagPairs, + decisionMakerTagValue, + traceIdTagValue, + productTraceSource, + PrioritySampling.UNSET, + orgPropagationMarkerTagValue, + llmObsTagValues); + } + + PropagationTags createValid( + List tagPairs, + TagValue decisionMakerTagValue, + TagValue traceIdTagValue, + int productTraceSource, + int samplingPriority, + TagValue orgPropagationMarkerTagValue, + @Nonnull LLMObsTagValues llmObsTagValues) { return new PTags( this, tagPairs, decisionMakerTagValue, traceIdTagValue, productTraceSource, + samplingPriority, + null, + null, orgPropagationMarkerTagValue, - llmObsTagValues); + llmObsTagValues, + null, + null); } PropagationTags createInvalid(String error) { From 914edc2e11da03958260d5098560aa5759be81c3 Mon Sep 17 00:00:00 2001 From: Douglas Q Hawkins Date: Fri, 23 Oct 2026 00:45:53 -0400 Subject: [PATCH 2/3] Test that extracted Datadog propagation tags carry the priority Co-Authored-By: Claude Opus 5.5 --- .../DDSpanContextPropagationTagsTest.java | 17 ++++++++++ .../propagation/DatadogHttpExtractorTest.java | 34 +++++++++++++++++++ 2 files changed, 51 insertions(+) diff --git a/dd-trace-core/src/test/java/datadog/trace/core/DDSpanContextPropagationTagsTest.java b/dd-trace-core/src/test/java/datadog/trace/core/DDSpanContextPropagationTagsTest.java index 522b0da3463..9a376d38d0c 100644 --- a/dd-trace-core/src/test/java/datadog/trace/core/DDSpanContextPropagationTagsTest.java +++ b/dd-trace-core/src/test/java/datadog/trace/core/DDSpanContextPropagationTagsTest.java @@ -9,6 +9,7 @@ import static datadog.trace.api.sampling.SamplingMechanism.DEFAULT; import static datadog.trace.api.sampling.SamplingMechanism.MANUAL; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertSame; import datadog.trace.api.DDTraceId; import datadog.trace.bootstrap.instrumentation.api.AgentSpanContext; @@ -18,6 +19,7 @@ import datadog.trace.test.junit.utils.tabletest.TableTestTypeConverters; import java.util.Map; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; import org.tabletest.junit.TableTest; import org.tabletest.junit.TypeConverter; import org.tabletest.junit.TypeConverterSources; @@ -178,4 +180,19 @@ public static int toInt(String value) { throw new IllegalArgumentException("Value is invalid for " + value); } } + + @Test + void rootSpanKeepsExtractedSamplingState() { + PropagationTags propagationTags = + tracer.getPropagationTagsFactory().fromDatadogHeaderValue("_dd.p.dm=-1", SAMPLER_KEEP); + PropagationTags.SamplingState extractedState = propagationTags.samplingState(); + AgentSpanContext extracted = + new ExtractedContext( + DDTraceId.from(123), 456, SAMPLER_KEEP, null, propagationTags, DATADOG); + + DDSpan span = (DDSpan) tracer.buildSpan("datadog", "top").asChildOf(extracted).start(); + + assertSame(extractedState, span.spanContext().getPropagationTags().samplingState()); + assertEquals(SAMPLER_KEEP, span.spanContext().getSamplingPriority()); + } } diff --git a/dd-trace-core/src/test/java/datadog/trace/core/propagation/DatadogHttpExtractorTest.java b/dd-trace-core/src/test/java/datadog/trace/core/propagation/DatadogHttpExtractorTest.java index fb4a5e3bda2..743380c1e5a 100644 --- a/dd-trace-core/src/test/java/datadog/trace/core/propagation/DatadogHttpExtractorTest.java +++ b/dd-trace-core/src/test/java/datadog/trace/core/propagation/DatadogHttpExtractorTest.java @@ -383,6 +383,27 @@ void extractMappedBaggageIsSubjectToTheItemLimit() { assertEquals(singletonMap(SOME_BAGGAGE, "mappedBaggageValue"), context.getBaggage()); } + @ParameterizedTest + @ValueSource(booleans = {true, false}) + void propagationTagsStartFromExtractedPriority(boolean tagsFirst) { + Map headers = new LinkedHashMap<>(); + headers.put(TRACE_ID_KEY, "1"); + headers.put(SPAN_ID_KEY, "2"); + if (tagsFirst) { + headers.put(DATADOG_TAGS_KEY, "_dd.p.dm=-1"); + headers.put(SAMPLING_PRIORITY_KEY, String.valueOf(SAMPLER_KEEP)); + } else { + headers.put(SAMPLING_PRIORITY_KEY, String.valueOf(SAMPLER_KEEP)); + headers.put(DATADOG_TAGS_KEY, "_dd.p.dm=-1"); + } + + ExtractedContext context = (ExtractedContext) extractor.extract(headers, stringValuesMap()); + + PropagationTags propagationTags = context.getPropagationTags(); + assertEquals(SAMPLER_KEEP, propagationTags.getSamplingPriority()); + assertEquals("_dd.p.dm=-1", propagationTags.headerValue(PropagationTags.HeaderType.DATADOG)); + } + /** * With APM tracing disabled, an upstream sampling decision is only kept when {@code _dd.p.ts} * marks the trace for ASM or AI Guard. Otherwise the local sampler sets the priority. @@ -415,6 +436,19 @@ void upstreamPriorityKeptWhenProductMarkedTheTrace() { assertEquals(SAMPLER_KEEP, context.getSamplingPriority()); } + @Test + void propagationTagsDontStartFromUninheritedPriority() { + Map headers = new HashMap<>(); + headers.put(TRACE_ID_KEY, "1"); + headers.put(SPAN_ID_KEY, "2"); + headers.put(SAMPLING_PRIORITY_KEY, String.valueOf(SAMPLER_KEEP)); + headers.put(DATADOG_TAGS_KEY, "_dd.p.dm=-1"); + + ExtractedContext context = (ExtractedContext) extractor.extract(headers, stringValuesMap()); + + assertEquals(UNSET, context.getPropagationTags().getSamplingPriority()); + } + @Test void upstreamPriorityDroppedWhenOnlyDsmMarkedTheTrace() { Map headers = new HashMap<>(); From 67e1349d5d1c70f25260467872421c387728b353 Mon Sep 17 00:00:00 2001 From: Douglas Q Hawkins Date: Fri, 23 Oct 2026 00:49:46 -0400 Subject: [PATCH 3/3] Remove propagation tags factory overloads left unused Co-Authored-By: Claude Opus 5.5 --- .../core/propagation/ptags/PTagsFactory.java | 49 +++---------------- 1 file changed, 8 insertions(+), 41 deletions(-) diff --git a/dd-trace-core/src/main/java/datadog/trace/core/propagation/ptags/PTagsFactory.java b/dd-trace-core/src/main/java/datadog/trace/core/propagation/ptags/PTagsFactory.java index fc5db6768cc..13dc8da6eee 100644 --- a/dd-trace-core/src/main/java/datadog/trace/core/propagation/ptags/PTagsFactory.java +++ b/dd-trace-core/src/main/java/datadog/trace/core/propagation/ptags/PTagsFactory.java @@ -51,7 +51,14 @@ PTagsCodec getDecoderEncoder(@Nonnull HeaderType headerType) { @Override public final PropagationTags empty() { - return createValid(null, null, null, ProductTraceSource.UNSET, null, LLMObsTagValues.EMPTY); + return createValid( + null, + null, + null, + ProductTraceSource.UNSET, + PrioritySampling.UNSET, + null, + LLMObsTagValues.EMPTY); } @Override @@ -73,23 +80,6 @@ public final PropagationTags emptyW3C(String originalTracestate) { return W3CPTagsCodec.empty(this, originalTracestate); } - PropagationTags createValid( - List tagPairs, - TagValue decisionMakerTagValue, - TagValue traceIdTagValue, - int productTraceSource, - TagValue orgPropagationMarkerTagValue, - @Nonnull LLMObsTagValues llmObsTagValues) { - return createValid( - tagPairs, - decisionMakerTagValue, - traceIdTagValue, - productTraceSource, - PrioritySampling.UNSET, - orgPropagationMarkerTagValue, - llmObsTagValues); - } - PropagationTags createValid( List tagPairs, TagValue decisionMakerTagValue, @@ -211,29 +201,6 @@ static class PTags extends PropagationTags { */ private volatile CharSequence lastParentId; - PTags( - PTagsFactory factory, - List tagPairs, - TagValue decisionMakerTagValue, - TagValue traceIdTagValue, - int traceSource, - TagValue orgPropagationMarkerTagValue, - @Nonnull LLMObsTagValues llmObsTagValues) { - this( - factory, - tagPairs, - decisionMakerTagValue, - traceIdTagValue, - traceSource, - PrioritySampling.UNSET, - null, - null, - orgPropagationMarkerTagValue, - llmObsTagValues, - null, - null); - } - // Takes tracestate/otelTraceState up-front to build SamplingState in one allocation. PTags( PTagsFactory factory,