From 6650b55fd54b9ed4610bb5a9592efe8ba6c9978c Mon Sep 17 00:00:00 2001 From: Douglas Q Hawkins Date: Fri, 8 May 2026 11:33:26 -0400 Subject: [PATCH] Read trace propagation behavior from tracer's config, not global singleton CoreSpanBuilder.startSpan was reading Config.get().getTracePropagationBehaviorExtract() from the global Config singleton on every remote-parent span build. The tracer's own configuration was already available via tracer.initialConfig, which is the consistent source used elsewhere in this file (line 841, ConfigSnapshot, etc.). Reading from the singleton has two consequences: - Span behavior depends on whatever the global Config is at build time, not on the tracer's configured behavior. Tracers built via withProperties(...) get a different Config from the singleton, and on master the override is silently ignored. - Extra getfield on a hot path. Marginal but unnecessary. Switch to tracer.initialConfig and add a regression test that builds a CoreTracer with a Properties override and verifies the resulting span reflects the override. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../java/datadog/trace/core/CoreTracer.java | 2 +- .../trace/core/CoreSpanBuilderTest.java | 44 +++++++++++++++++++ 2 files changed, 45 insertions(+), 1 deletion(-) diff --git a/dd-trace-core/src/main/java/datadog/trace/core/CoreTracer.java b/dd-trace-core/src/main/java/datadog/trace/core/CoreTracer.java index 6c5efb4a0d1..168e7a2399b 100644 --- a/dd-trace-core/src/main/java/datadog/trace/core/CoreTracer.java +++ b/dd-trace-core/src/main/java/datadog/trace/core/CoreTracer.java @@ -1778,7 +1778,7 @@ protected static final AgentSpan startSpan( // Handle remote terminated context as span links if (parentContext != null && parentContext.isRemote()) { - switch (Config.get().getTracePropagationBehaviorExtract()) { + switch (tracer.initialConfig.getTracePropagationBehaviorExtract()) { case RESTART: links = addParentContextLink(links, parentContext); parentContext = null; diff --git a/dd-trace-core/src/test/java/datadog/trace/core/CoreSpanBuilderTest.java b/dd-trace-core/src/test/java/datadog/trace/core/CoreSpanBuilderTest.java index 05c9cd87222..7ee001c5293 100644 --- a/dd-trace-core/src/test/java/datadog/trace/core/CoreSpanBuilderTest.java +++ b/dd-trace-core/src/test/java/datadog/trace/core/CoreSpanBuilderTest.java @@ -27,6 +27,7 @@ import datadog.trace.api.DDSpanId; import datadog.trace.api.DDTraceId; import datadog.trace.api.TagMap; +import datadog.trace.api.TracePropagationBehaviorExtract; import datadog.trace.api.datastreams.NoopPathwayContext; import datadog.trace.api.gateway.RequestContextSlot; import datadog.trace.api.naming.SpanNaming; @@ -45,6 +46,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Properties; import java.util.stream.Stream; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -419,6 +421,48 @@ void buildContextFromExtractedContextWithIgnoreBehavior() { assertTrue(span.getLinks().isEmpty()); } + @Test + void extractBehaviorReadsTracerConfigNotGlobalSingleton() { + // Build a tracer with a Properties-based config that selects RESTART. The global + // Config.get() singleton is unaffected (default CONTINUE). The span build path must + // read the tracer's own config — not Config.get() — so the resulting span should + // reflect RESTART behavior (parent dropped, parent context attached as a link). + Properties props = new Properties(); + props.setProperty("trace.propagation.behavior.extract", "restart"); + CoreTracer customTracer = tracerBuilder().withProperties(props).writer(writer).build(); + assertEquals( + TracePropagationBehaviorExtract.RESTART, + customTracer.initialConfig.getTracePropagationBehaviorExtract(), + "precondition: customTracer's config must reflect Properties override"); + + ExtractedContext extractedContext = + new ExtractedContext( + DDTraceId.ONE, + 2, + PrioritySampling.SAMPLER_DROP, + null, + 0, + Collections.emptyMap(), + Collections.emptyMap(), + null, + PropagationTags.factory() + .fromHeaderValue( + PropagationTags.HeaderType.DATADOG, "_dd.p.dm=934086a686-4,_dd.p.anytag=value"), + null, + DATADOG); + DDSpan span = + (DDSpan) customTracer.buildSpan("test", "op name").asChildOf(extractedContext).start(); + + assertNotEquals(extractedContext.getTraceId(), span.getTraceId()); + assertNotEquals(extractedContext.getSpanId(), span.getParentId()); + + List spanLinks = span.getLinks(); + assertEquals(1, spanLinks.size()); + AgentSpanLink link = spanLinks.get(0); + assertEquals(extractedContext.getTraceId(), link.traceId()); + assertEquals(extractedContext.getSpanId(), link.spanId()); + } + @TableTest({ "scenario | origin | tagMap ", "empty tag map | | [:] ",