From d06652d71280af11a370c266d8b440e044b56852 Mon Sep 17 00:00:00 2001 From: Rachel Yang Date: Mon, 14 Jul 2025 11:29:40 -0400 Subject: [PATCH 01/15] WIP: baggage span tags --- .../datadog/trace/api/ConfigDefaults.java | 2 ++ .../trace/api/config/TracerConfig.java | 1 + .../java/datadog/trace/core/CoreTracer.java | 27 +++++++++++++++++++ .../main/java/datadog/trace/api/Config.java | 12 +++++++++ 4 files changed, 42 insertions(+) diff --git a/dd-trace-api/src/main/java/datadog/trace/api/ConfigDefaults.java b/dd-trace-api/src/main/java/datadog/trace/api/ConfigDefaults.java index 1f8d733e8ce..8ef3ff5f5bc 100644 --- a/dd-trace-api/src/main/java/datadog/trace/api/ConfigDefaults.java +++ b/dd-trace-api/src/main/java/datadog/trace/api/ConfigDefaults.java @@ -84,6 +84,8 @@ public final class ConfigDefaults { new LinkedHashSet<>(asList(PropagationStyle.DATADOG)); static final int DEFAULT_TRACE_BAGGAGE_MAX_ITEMS = 64; static final int DEFAULT_TRACE_BAGGAGE_MAX_BYTES = 8192; + static final List DEFAULT_TRACE_BAGGAGE_TAG_KEYS = + Arrays.asList("user.id", "session.id", "account.id"); static final boolean DEFAULT_JMX_FETCH_ENABLED = true; static final boolean DEFAULT_TRACE_AGENT_V05_ENABLED = false; diff --git a/dd-trace-api/src/main/java/datadog/trace/api/config/TracerConfig.java b/dd-trace-api/src/main/java/datadog/trace/api/config/TracerConfig.java index d817c88666e..0300cef2070 100644 --- a/dd-trace-api/src/main/java/datadog/trace/api/config/TracerConfig.java +++ b/dd-trace-api/src/main/java/datadog/trace/api/config/TracerConfig.java @@ -98,6 +98,7 @@ public final class TracerConfig { public static final String TRACE_PROPAGATION_EXTRACT_FIRST = "trace.propagation.extract.first"; public static final String TRACE_BAGGAGE_MAX_ITEMS = "trace.baggage.max.items"; public static final String TRACE_BAGGAGE_MAX_BYTES = "trace.baggage.max.bytes"; + public static final String TRACE_BAGGAGE_TAG_KEYS = "trace.baggage.tag.keys"; public static final String ENABLE_TRACE_AGENT_V05 = "trace.agent.v0.5.enabled"; 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 1e44edd3f89..1494735d1cf 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 @@ -1512,6 +1512,7 @@ private DDSpanContext buildSpanContext() { Object ciVisibilityContextData; final PathwayContext pathwayContext; final PropagationTags propagationTags; + final Map baggageTags; if (this.spanId == 0) { spanId = idGenerationStrategy.generateSpanId(); @@ -1558,6 +1559,7 @@ private DDSpanContext buildSpanContext() { origin = null; coreTags = null; rootSpanTags = null; + baggageTags = null; parentServiceName = ddsc.getServiceName(); if (serviceName == null) { serviceName = parentServiceName; @@ -1611,6 +1613,7 @@ private DDSpanContext buildSpanContext() { coreTags = tc.getTags(); origin = tc.getOrigin(); baggage = tc.getBaggage(); + baggageTags = mapBaggageTags(baggage); requestContextDataAppSec = tc.getRequestContextDataAppSec(); requestContextDataIast = tc.getRequestContextDataIast(); ciVisibilityContextData = tc.getCiVisibilityContextData(); @@ -1622,6 +1625,7 @@ private DDSpanContext buildSpanContext() { requestContextDataAppSec = null; requestContextDataIast = null; ciVisibilityContextData = null; + baggageTags = null; } rootSpanTags = localRootSpanTags; @@ -1728,6 +1732,7 @@ private DDSpanContext buildSpanContext() { context.setAllTags(coreTags); context.setAllTags(rootSpanTags); context.setAllTags(contextualTags); + context.setAllTags(baggageTags); return context; } } @@ -1807,4 +1812,26 @@ protected ConfigSnapshot( } return Collections.unmodifiableMap(result); } + + static Map mapBaggageTags(Map baggage) { + List baggageTagKeys = Config.get().getTraceBaggageTagKeys(); + if (baggageTagKeys.isEmpty()) { + return Collections.emptyMap(); + } + Map baggageTags = new HashMap<>(baggageTagKeys.size()); + for (String key : baggageTagKeys) { + if (key == "*") { + // If the key is "*", we add all baggage items + for (Map.Entry entry : baggage.entrySet()) { + baggageTags.put("baggage." + entry.getKey(), entry.getValue()); + } + break; + } + String value = baggage.get(key); + if (value != null) { + baggageTags.put("baggage." + key, value); + } + } + return Collections.unmodifiableMap(baggageTags); + } } diff --git a/internal-api/src/main/java/datadog/trace/api/Config.java b/internal-api/src/main/java/datadog/trace/api/Config.java index 3d4dc949c9a..57eb010a683 100644 --- a/internal-api/src/main/java/datadog/trace/api/Config.java +++ b/internal-api/src/main/java/datadog/trace/api/Config.java @@ -195,6 +195,7 @@ public static String getHostName() { private final boolean tracePropagationExtractFirst; private final int traceBaggageMaxItems; private final int traceBaggageMaxBytes; + private final List traceBaggageTagKeys; private final int clockSyncPeriod; private final boolean logsInjectionEnabled; @@ -1056,6 +1057,11 @@ private Config(final ConfigProvider configProvider, final InstrumenterConfig ins // If we have a new setting, we log a warning logOverriddenDeprecatedSettingWarning(PROPAGATION_STYLE_INJECT, injectOrigin, inject); } + + // Parse the baggage tag keys configuration + traceBaggageTagKeys = + configProvider.getList(TRACE_BAGGAGE_TAG_KEYS, DEFAULT_TRACE_BAGGAGE_TAG_KEYS); + // Now we can check if we should pick the default injection/extraction tracePropagationStylesToExtract = @@ -2262,6 +2268,10 @@ public Map getBaggageMapping() { return baggageMapping; } + public List getTraceBaggageTagKeys() { + return traceBaggageTagKeys; + } + public Map getHttpServerPathResourceNameMapping() { return httpServerPathResourceNameMapping; } @@ -4647,6 +4657,8 @@ public String toString() { + traceKeepLatencyThreshold + ", traceStrictWritesEnabled=" + traceStrictWritesEnabled + + ", traceBaggageTagKeys=" + + traceBaggageTagKeys + ", tracePropagationStylesToExtract=" + tracePropagationStylesToExtract + ", tracePropagationStylesToInject=" From bb005f53605b2a66fff8002df0cf0fa0befff70e Mon Sep 17 00:00:00 2001 From: Rachel Yang Date: Mon, 14 Jul 2025 13:02:22 -0400 Subject: [PATCH 02/15] simple test --- .../test/groovy/datadog/trace/core/CoreTracerTest.groovy | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/dd-trace-core/src/test/groovy/datadog/trace/core/CoreTracerTest.groovy b/dd-trace-core/src/test/groovy/datadog/trace/core/CoreTracerTest.groovy index ba781cf010c..e47651e05c6 100644 --- a/dd-trace-core/src/test/groovy/datadog/trace/core/CoreTracerTest.groovy +++ b/dd-trace-core/src/test/groovy/datadog/trace/core/CoreTracerTest.groovy @@ -639,6 +639,13 @@ class CoreTracerTest extends DDCoreSpecification { "service" | "env" | "service" | "env_1" "service" | "env" | "service_2" | "env_2" } + + def "test mapBaggageTags"() { + when: + def tags = CoreTracer.mapBaggageTags(["user.id": "doggo", "foo": "bar"]) + then: + tags == ["baggage.user.id": "doggo"] + } } class WriterWithExplicitFlush implements datadog.trace.common.writer.Writer { From 22631c59ef2ad0c50bcd8dbe5bf161054cd6c49b Mon Sep 17 00:00:00 2001 From: Rachel Yang Date: Tue, 15 Jul 2025 09:45:31 -0400 Subject: [PATCH 03/15] fix build errors --- internal-api/src/main/java/datadog/trace/api/Config.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/internal-api/src/main/java/datadog/trace/api/Config.java b/internal-api/src/main/java/datadog/trace/api/Config.java index 5d200642369..9b73e64aa9e 100644 --- a/internal-api/src/main/java/datadog/trace/api/Config.java +++ b/internal-api/src/main/java/datadog/trace/api/Config.java @@ -151,6 +151,7 @@ import static datadog.trace.api.ConfigDefaults.DEFAULT_TRACE_ANALYTICS_ENABLED; import static datadog.trace.api.ConfigDefaults.DEFAULT_TRACE_BAGGAGE_MAX_BYTES; import static datadog.trace.api.ConfigDefaults.DEFAULT_TRACE_BAGGAGE_MAX_ITEMS; +import static datadog.trace.api.ConfigDefaults.DEFAULT_TRACE_BAGGAGE_TAG_KEYS; import static datadog.trace.api.ConfigDefaults.DEFAULT_TRACE_CLOUD_PAYLOAD_TAGGING_SERVICES; import static datadog.trace.api.ConfigDefaults.DEFAULT_TRACE_EXPERIMENTAL_FEATURES_ENABLED; import static datadog.trace.api.ConfigDefaults.DEFAULT_TRACE_HTTP_RESOURCE_REMOVE_TRAILING_SLASH; @@ -583,6 +584,7 @@ import static datadog.trace.api.config.TracerConfig.TRACE_ANALYTICS_ENABLED; import static datadog.trace.api.config.TracerConfig.TRACE_BAGGAGE_MAX_BYTES; import static datadog.trace.api.config.TracerConfig.TRACE_BAGGAGE_MAX_ITEMS; +import static datadog.trace.api.config.TracerConfig.TRACE_BAGGAGE_TAG_KEYS; import static datadog.trace.api.config.TracerConfig.TRACE_CLIENT_IP_HEADER; import static datadog.trace.api.config.TracerConfig.TRACE_CLIENT_IP_RESOLVER_ENABLED; import static datadog.trace.api.config.TracerConfig.TRACE_CLOUD_PAYLOAD_TAGGING_MAX_DEPTH; From 87e2aaa553703b925f7049cf7928fab990ee9a5e Mon Sep 17 00:00:00 2001 From: Rachel Yang Date: Tue, 15 Jul 2025 16:25:24 -0400 Subject: [PATCH 04/15] adding more tests --- .../src/test/groovy/OkHttp3Test.groovy | 59 +++++++++++++++++++ .../trace/core/CoreSpanBuilderTest.groovy | 27 +++++++++ .../datadog/trace/core/CoreTracerTest.groovy | 27 ++++++++- 3 files changed, 110 insertions(+), 3 deletions(-) diff --git a/dd-java-agent/instrumentation/okhttp-3/src/test/groovy/OkHttp3Test.groovy b/dd-java-agent/instrumentation/okhttp-3/src/test/groovy/OkHttp3Test.groovy index eb7af3fe476..92761aeec7b 100644 --- a/dd-java-agent/instrumentation/okhttp-3/src/test/groovy/OkHttp3Test.groovy +++ b/dd-java-agent/instrumentation/okhttp-3/src/test/groovy/OkHttp3Test.groovy @@ -78,6 +78,65 @@ abstract class OkHttp3Test extends HttpClientTest { method = "GET" url = server.address.resolve(path) } + + def "baggage span tags are properly added"() { + setup: + // W3C Baggage header format: key1=value1,key2=value2,key3=value3 + def baggageHeader = "user.id=bark,session.id=test-sess1,account.id=fetch,language=en" + def sentHeaders = [:] + + when: + def status + // Capture the headers that OkHttp3 sends + def interceptor = new Interceptor() { + @Override + Response intercept(Chain chain) throws IOException { + def request = chain.request() + // Capture all headers sent + request.headers().names().each { headerName -> + sentHeaders[headerName] = request.header(headerName) + } + return chain.proceed(request) + } + } + + def testClient = new OkHttpClient.Builder() + .connectTimeout(CONNECT_TIMEOUT_MS, TimeUnit.MILLISECONDS) + .readTimeout(READ_TIMEOUT_MS, TimeUnit.MILLISECONDS) + .writeTimeout(READ_TIMEOUT_MS, TimeUnit.MILLISECONDS) + .addInterceptor(interceptor) + .build() + + def request = new Request.Builder() + .url(server.address.resolve("/success").toURL()) + .header("baggage", baggageHeader) // Pass baggage directly in header + .get() + .build() + def response = testClient.newCall(request).execute() + status = response.code() + + then: + status == 200 + // Verify baggage header was sent + sentHeaders["baggage"] == baggageHeader + sentHeaders["baggage"].contains("user.id=bark") + sentHeaders["baggage"].contains("session.id=test-sess1") + sentHeaders["baggage"].contains("account.id=fetch") + sentHeaders["baggage"].contains("language=en") + + // Verify the resulting span has the correct baggage tags (only default configured keys) + assertTraces(1) { + trace(1) { + clientSpan(it, null, "GET", false, false, server.address.resolve("/success"), 200, false, null, false, [ + // Should have baggage tags for keys in default configuration + "baggage.user.id": "bark", + "baggage.session.id": "test-sess1", + "baggage.account.id": "fetch", + // "baggage.language" should NOT be present since it's not in default config + ]) + } + } + } } @Timeout(5) diff --git a/dd-trace-core/src/test/groovy/datadog/trace/core/CoreSpanBuilderTest.groovy b/dd-trace-core/src/test/groovy/datadog/trace/core/CoreSpanBuilderTest.groovy index 59521b95ab1..13c71ecdbf0 100644 --- a/dd-trace-core/src/test/groovy/datadog/trace/core/CoreSpanBuilderTest.groovy +++ b/dd-trace-core/src/test/groovy/datadog/trace/core/CoreSpanBuilderTest.groovy @@ -4,11 +4,13 @@ import static datadog.trace.api.DDTags.DJM_ENABLED import static datadog.trace.api.DDTags.DSM_ENABLED import static datadog.trace.api.DDTags.PROFILING_ENABLED import static datadog.trace.api.DDTags.SCHEMA_VERSION_TAG_KEY +import static datadog.trace.api.config.TracerConfig.TRACE_BAGGAGE_TAG_KEYS import datadog.trace.api.Config import datadog.trace.api.DDSpanId import datadog.trace.api.DDTraceId import datadog.trace.api.gateway.RequestContextSlot +import datadog.trace.api.sampling.PrioritySampling import datadog.trace.api.naming.SpanNaming import datadog.trace.api.sampling.PrioritySampling import datadog.trace.bootstrap.instrumentation.api.AgentScope @@ -491,6 +493,31 @@ class CoreSpanBuilderTest extends DDCoreSpecification { span1.finish() } + def "buildSpan should add baggage tags with different configurations"() { + setup: + injectSysConfig(TRACE_BAGGAGE_TAG_KEYS, baggageTagKeysConfig) + def baggage = ["user.id": "alice", "session.id": "123", "region": "us-west-1", "env": "production"] + def tagContext = new TagContext(null, null, null, baggage, PrioritySampling.UNSET, null, DATADOG, DDTraceId.ZERO) + + when: + def span = tracer.buildSpan("test", "test-op") + .asChildOf(tagContext) + .start() + + then: + // Filter span tags to only check baggage tags (those starting with "baggage.") + def actualBaggageTags = span.tags.findAll { key, value -> key.startsWith("baggage.") } + actualBaggageTags == expectedBaggageTags + + cleanup: + span.finish() + + where: + baggageTagKeysConfig | expectedBaggageTags + "user.id" | ["baggage.user.id": "alice"] + "user.id,session.id" | ["baggage.user.id": "alice", "baggage.session.id": "123"] + } + def productTags() { def productTags = [ (PROFILING_ENABLED) : Config.get().isProfilingEnabled() ? 1 : 0 diff --git a/dd-trace-core/src/test/groovy/datadog/trace/core/CoreTracerTest.groovy b/dd-trace-core/src/test/groovy/datadog/trace/core/CoreTracerTest.groovy index e47651e05c6..d2070931e15 100644 --- a/dd-trace-core/src/test/groovy/datadog/trace/core/CoreTracerTest.groovy +++ b/dd-trace-core/src/test/groovy/datadog/trace/core/CoreTracerTest.groovy @@ -38,6 +38,7 @@ import static datadog.trace.api.config.TracerConfig.HEADER_TAGS import static datadog.trace.api.config.TracerConfig.PRIORITY_SAMPLING import static datadog.trace.api.config.TracerConfig.SERVICE_MAPPING import static datadog.trace.api.config.TracerConfig.SPAN_TAGS +import static datadog.trace.api.config.TracerConfig.TRACE_BAGGAGE_TAG_KEYS import static datadog.trace.api.config.TracerConfig.WRITER_TYPE @Timeout(10) @@ -640,11 +641,31 @@ class CoreTracerTest extends DDCoreSpecification { "service" | "env" | "service_2" | "env_2" } - def "test mapBaggageTags"() { + def "test mapBaggageTags default"() { when: - def tags = CoreTracer.mapBaggageTags(["user.id": "doggo", "foo": "bar"]) + def tags = CoreTracer.mapBaggageTags(["user.id": "doggo", "account.id": "test", "session.id":"1234", "region":"us-east-1"]) then: - tags == ["baggage.user.id": "doggo"] + tags == ["baggage.user.id": "doggo", "baggage.account.id": "test", "baggage.session.id": "1234"] + } + + def "test mapBaggageTags with different configurations"() { + setup: + injectSysConfig(TRACE_BAGGAGE_TAG_KEYS, baggageTagKeysConfig) + + when: + def tags = CoreTracer.mapBaggageTags(["user.id": "doggo", "foo": "bar", "language":"en"]) + + then: + tags == expectedTags + + where: + baggageTagKeysConfig | expectedTags + "*" | ["baggage.user.id": "doggo", "baggage.foo": "bar", "baggage.language": "en"] + " " | [:] + "system.id" | [:] + "user.id" | ["baggage.user.id": "doggo"] + "foo" | ["baggage.foo": "bar"] + "user.id,foo" | ["baggage.user.id": "doggo", "baggage.foo": "bar"] } } From 2ca1604b7f154802bfb547a3216ffda74703fcf9 Mon Sep 17 00:00:00 2001 From: Rachel Yang Date: Wed, 16 Jul 2025 16:31:06 -0400 Subject: [PATCH 05/15] Add baggage span tags functionality and tests --- .../src/test/groovy/OkHttp3Test.groovy | 59 ------------------- .../agent/test/base/HttpServerTest.groovy | 47 +++++++++++++++ .../trace/core/CoreSpanBuilderTest.groovy | 1 - 3 files changed, 47 insertions(+), 60 deletions(-) diff --git a/dd-java-agent/instrumentation/okhttp-3/src/test/groovy/OkHttp3Test.groovy b/dd-java-agent/instrumentation/okhttp-3/src/test/groovy/OkHttp3Test.groovy index 92761aeec7b..eb7af3fe476 100644 --- a/dd-java-agent/instrumentation/okhttp-3/src/test/groovy/OkHttp3Test.groovy +++ b/dd-java-agent/instrumentation/okhttp-3/src/test/groovy/OkHttp3Test.groovy @@ -78,65 +78,6 @@ abstract class OkHttp3Test extends HttpClientTest { method = "GET" url = server.address.resolve(path) } - - def "baggage span tags are properly added"() { - setup: - // W3C Baggage header format: key1=value1,key2=value2,key3=value3 - def baggageHeader = "user.id=bark,session.id=test-sess1,account.id=fetch,language=en" - def sentHeaders = [:] - - when: - def status - // Capture the headers that OkHttp3 sends - def interceptor = new Interceptor() { - @Override - Response intercept(Chain chain) throws IOException { - def request = chain.request() - // Capture all headers sent - request.headers().names().each { headerName -> - sentHeaders[headerName] = request.header(headerName) - } - return chain.proceed(request) - } - } - - def testClient = new OkHttpClient.Builder() - .connectTimeout(CONNECT_TIMEOUT_MS, TimeUnit.MILLISECONDS) - .readTimeout(READ_TIMEOUT_MS, TimeUnit.MILLISECONDS) - .writeTimeout(READ_TIMEOUT_MS, TimeUnit.MILLISECONDS) - .addInterceptor(interceptor) - .build() - - def request = new Request.Builder() - .url(server.address.resolve("/success").toURL()) - .header("baggage", baggageHeader) // Pass baggage directly in header - .get() - .build() - def response = testClient.newCall(request).execute() - status = response.code() - - then: - status == 200 - // Verify baggage header was sent - sentHeaders["baggage"] == baggageHeader - sentHeaders["baggage"].contains("user.id=bark") - sentHeaders["baggage"].contains("session.id=test-sess1") - sentHeaders["baggage"].contains("account.id=fetch") - sentHeaders["baggage"].contains("language=en") - - // Verify the resulting span has the correct baggage tags (only default configured keys) - assertTraces(1) { - trace(1) { - clientSpan(it, null, "GET", false, false, server.address.resolve("/success"), 200, false, null, false, [ - // Should have baggage tags for keys in default configuration - "baggage.user.id": "bark", - "baggage.session.id": "test-sess1", - "baggage.account.id": "fetch", - // "baggage.language" should NOT be present since it's not in default config - ]) - } - } - } } @Timeout(5) diff --git a/dd-java-agent/testing/src/main/groovy/datadog/trace/agent/test/base/HttpServerTest.groovy b/dd-java-agent/testing/src/main/groovy/datadog/trace/agent/test/base/HttpServerTest.groovy index 69760e48eb9..7eafc05ce93 100644 --- a/dd-java-agent/testing/src/main/groovy/datadog/trace/agent/test/base/HttpServerTest.groovy +++ b/dd-java-agent/testing/src/main/groovy/datadog/trace/agent/test/base/HttpServerTest.groovy @@ -785,6 +785,53 @@ abstract class HttpServerTest extends WithHttpServer { 'GET' | null | 'x-datadog-test-request-header' | 'bar' | ['request_header_tag': 'bar'] } + def "test baggage span tags are properly added"() { + setup: + // Use default configuration for TRACE_BAGGAGE_TAG_KEYS (user.id, session.id, account.id) + def baggageHeader = "user.id=test-user,session.id=test-session,account.id=test-account,language=en" + def request = request(SUCCESS, 'GET', null) + .header("baggage", baggageHeader) + .build() + def response = client.newCall(request).execute() + if (isDataStreamsEnabled()) { + TEST_DATA_STREAMS_WRITER.waitForGroups(1) + } + + expect: + response.code() == SUCCESS.status + response.body().string() == SUCCESS.body + + and: + assertTraces(1) { + trace(spanCount(SUCCESS)) { + sortSpansByStart() + // Verify baggage tags are added for default configured keys only + serverSpan(it, null, null, 'GET', SUCCESS, [ + "baggage.user.id": "test-user", + "baggage.session.id": "test-session", + "baggage.account.id": "test-account" + // "baggage.language" should NOT be present since it's not in default config + ]) + if (hasHandlerSpan()) { + handlerSpan(it) + } + controllerSpan(it) + if (hasResponseSpan(SUCCESS)) { + responseSpan(it, SUCCESS) + } + } + } + + and: + if (isDataStreamsEnabled()) { + StatsGroup first = TEST_DATA_STREAMS_WRITER.groups.find { it.parentHash == 0 } + verifyAll(first) { + edgeTags.containsAll(DSM_EDGE_TAGS) + edgeTags.size() == DSM_EDGE_TAGS.size() + } + } + } + @Flaky(value = "https://github.com/DataDog/dd-trace-java/issues/4690", suites = ["MuleHttpServerForkedTest"]) def "test QUERY_ENCODED_BOTH with response header x-ig-response-header tag mapping"() { setup: diff --git a/dd-trace-core/src/test/groovy/datadog/trace/core/CoreSpanBuilderTest.groovy b/dd-trace-core/src/test/groovy/datadog/trace/core/CoreSpanBuilderTest.groovy index 527b0c62db8..dbecbda6468 100644 --- a/dd-trace-core/src/test/groovy/datadog/trace/core/CoreSpanBuilderTest.groovy +++ b/dd-trace-core/src/test/groovy/datadog/trace/core/CoreSpanBuilderTest.groovy @@ -13,7 +13,6 @@ import datadog.trace.api.TagMap import datadog.trace.api.gateway.RequestContextSlot import datadog.trace.api.sampling.PrioritySampling import datadog.trace.api.naming.SpanNaming -import datadog.trace.api.sampling.PrioritySampling import datadog.trace.bootstrap.instrumentation.api.AgentScope import datadog.trace.api.datastreams.NoopPathwayContext import datadog.trace.bootstrap.instrumentation.api.TagContext From de8a55ae029d258d730193cb9e767efd3016794c Mon Sep 17 00:00:00 2001 From: Stuart McCulloch Date: Fri, 18 Jul 2025 10:44:03 +0100 Subject: [PATCH 06/15] Move baggage tags feature to HttpServerDecorator.getExtractedSpanContext and update it to check the W3C baggage held in the new Context API (the baggage map in CoreTracer is OpenTracing only, and won't contain W3C baggage) --- .../decorator/HttpServerDecorator.java | 36 ++++++++++++++++++- .../java/datadog/trace/core/CoreTracer.java | 27 -------------- 2 files changed, 35 insertions(+), 28 deletions(-) diff --git a/dd-java-agent/agent-bootstrap/src/main/java/datadog/trace/bootstrap/instrumentation/decorator/HttpServerDecorator.java b/dd-java-agent/agent-bootstrap/src/main/java/datadog/trace/bootstrap/instrumentation/decorator/HttpServerDecorator.java index 3119ecd2fe5..f480a2b83f8 100644 --- a/dd-java-agent/agent-bootstrap/src/main/java/datadog/trace/bootstrap/instrumentation/decorator/HttpServerDecorator.java +++ b/dd-java-agent/agent-bootstrap/src/main/java/datadog/trace/bootstrap/instrumentation/decorator/HttpServerDecorator.java @@ -12,6 +12,7 @@ import datadog.context.propagation.Propagators; import datadog.trace.api.Config; import datadog.trace.api.DDTags; +import datadog.trace.api.TagMap; import datadog.trace.api.function.TriConsumer; import datadog.trace.api.function.TriFunction; import datadog.trace.api.gateway.BlockResponseFunction; @@ -26,6 +27,7 @@ import datadog.trace.bootstrap.instrumentation.api.AgentSpan; import datadog.trace.bootstrap.instrumentation.api.AgentSpanContext; import datadog.trace.bootstrap.instrumentation.api.AgentTracer; +import datadog.trace.bootstrap.instrumentation.api.Baggage; import datadog.trace.bootstrap.instrumentation.api.ErrorPriorities; import datadog.trace.bootstrap.instrumentation.api.InternalSpanTypes; import datadog.trace.bootstrap.instrumentation.api.ResourceNamePriorities; @@ -37,6 +39,7 @@ import datadog.trace.bootstrap.instrumentation.decorator.http.ClientIpAddressResolver; import java.net.InetAddress; import java.util.BitSet; +import java.util.List; import java.util.Locale; import java.util.Map; import java.util.concurrent.ExecutionException; @@ -149,7 +152,17 @@ public AgentSpan startSpan(REQUEST_CARRIER carrier, Context context) { public AgentSpanContext.Extracted getExtractedSpanContext(Context context) { AgentSpan extractedSpan = AgentSpan.fromContext(context); - return extractedSpan == null ? null : (AgentSpanContext.Extracted) extractedSpan.context(); + AgentSpanContext.Extracted extractedContext = null; + if (extractedSpan != null) { + extractedContext = (AgentSpanContext.Extracted) extractedSpan.context(); + if (extractedContext instanceof TagContext) { + Baggage baggage = Baggage.fromContext(context); + if (baggage != null) { + setBaggageTags((TagContext) extractedContext, baggage.asMap()); + } + } + } + return extractedContext; } public AgentSpan onRequest( @@ -644,4 +657,25 @@ public boolean accept(String key, String value) { return true; } } + + private static void setBaggageTags(TagContext tagContext, Map baggage) { + List baggageTagKeys = Config.get().getTraceBaggageTagKeys(); + if (baggageTagKeys.isEmpty()) { + return; + } + TagMap tags = tagContext.getTags(); + for (String key : baggageTagKeys) { + if ("*".equals(key)) { + // If the key is "*", we add all baggage items + for (Map.Entry entry : baggage.entrySet()) { + tags.putIfAbsent("baggage." + entry.getKey(), entry.getValue()); + } + break; + } + String value = baggage.get(key); + if (value != null) { + tags.putIfAbsent("baggage." + key, value); + } + } + } } 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 48fbbfa5612..a5cc89f5011 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 @@ -1604,7 +1604,6 @@ private DDSpanContext buildSpanContext() { Object ciVisibilityContextData; final PathwayContext pathwayContext; final PropagationTags propagationTags; - final Map baggageTags; if (this.spanId == 0) { spanId = tracer.idGenerationStrategy.generateSpanId(); @@ -1652,7 +1651,6 @@ private DDSpanContext buildSpanContext() { coreTags = null; coreTagsNeedsIntercept = false; rootSpanTags = null; - baggageTags = null; rootSpanTagsNeedsIntercept = false; parentServiceName = ddsc.getServiceName(); if (serviceName == null) { @@ -1708,7 +1706,6 @@ private DDSpanContext buildSpanContext() { coreTagsNeedsIntercept = true; // maybe intercept isn't needed? origin = tc.getOrigin(); baggage = tc.getBaggage(); - baggageTags = mapBaggageTags(baggage); requestContextDataAppSec = tc.getRequestContextDataAppSec(); requestContextDataIast = tc.getRequestContextDataIast(); ciVisibilityContextData = tc.getCiVisibilityContextData(); @@ -1721,7 +1718,6 @@ private DDSpanContext buildSpanContext() { requestContextDataAppSec = null; requestContextDataIast = null; ciVisibilityContextData = null; - baggageTags = null; } rootSpanTags = tracer.localRootSpanTags; @@ -1830,7 +1826,6 @@ private DDSpanContext buildSpanContext() { context.setAllTags(coreTags, coreTagsNeedsIntercept); context.setAllTags(rootSpanTags, rootSpanTagsNeedsIntercept); context.setAllTags(contextualTags); - context.setAllTags(baggageTags); return context; } } @@ -1915,26 +1910,4 @@ static TagMap withTracerTags( } return result.freeze(); } - - static Map mapBaggageTags(Map baggage) { - List baggageTagKeys = Config.get().getTraceBaggageTagKeys(); - if (baggageTagKeys.isEmpty()) { - return Collections.emptyMap(); - } - Map baggageTags = new HashMap<>(baggageTagKeys.size()); - for (String key : baggageTagKeys) { - if (key == "*") { - // If the key is "*", we add all baggage items - for (Map.Entry entry : baggage.entrySet()) { - baggageTags.put("baggage." + entry.getKey(), entry.getValue()); - } - break; - } - String value = baggage.get(key); - if (value != null) { - baggageTags.put("baggage." + key, value); - } - } - return Collections.unmodifiableMap(baggageTags); - } } From 5b52ea44bf84bb3405f3a1e14381722e970f067a Mon Sep 17 00:00:00 2001 From: Stuart McCulloch Date: Fri, 18 Jul 2025 11:24:48 +0100 Subject: [PATCH 07/15] Fix potential corner-case where TagContext.getTags() may return an immutable EMPTY TagMap --- .../decorator/HttpServerDecorator.java | 6 ++-- .../datadog/trace/core/CoreTracerTest.groovy | 28 ------------------- .../instrumentation/api/TagContext.java | 9 ++++++ 3 files changed, 11 insertions(+), 32 deletions(-) diff --git a/dd-java-agent/agent-bootstrap/src/main/java/datadog/trace/bootstrap/instrumentation/decorator/HttpServerDecorator.java b/dd-java-agent/agent-bootstrap/src/main/java/datadog/trace/bootstrap/instrumentation/decorator/HttpServerDecorator.java index f480a2b83f8..38a8586fe55 100644 --- a/dd-java-agent/agent-bootstrap/src/main/java/datadog/trace/bootstrap/instrumentation/decorator/HttpServerDecorator.java +++ b/dd-java-agent/agent-bootstrap/src/main/java/datadog/trace/bootstrap/instrumentation/decorator/HttpServerDecorator.java @@ -12,7 +12,6 @@ import datadog.context.propagation.Propagators; import datadog.trace.api.Config; import datadog.trace.api.DDTags; -import datadog.trace.api.TagMap; import datadog.trace.api.function.TriConsumer; import datadog.trace.api.function.TriFunction; import datadog.trace.api.gateway.BlockResponseFunction; @@ -663,18 +662,17 @@ private static void setBaggageTags(TagContext tagContext, Map ba if (baggageTagKeys.isEmpty()) { return; } - TagMap tags = tagContext.getTags(); for (String key : baggageTagKeys) { if ("*".equals(key)) { // If the key is "*", we add all baggage items for (Map.Entry entry : baggage.entrySet()) { - tags.putIfAbsent("baggage." + entry.getKey(), entry.getValue()); + tagContext.putTagIfAbsent("baggage." + entry.getKey(), entry.getValue()); } break; } String value = baggage.get(key); if (value != null) { - tags.putIfAbsent("baggage." + key, value); + tagContext.putTagIfAbsent("baggage." + key, value); } } } diff --git a/dd-trace-core/src/test/groovy/datadog/trace/core/CoreTracerTest.groovy b/dd-trace-core/src/test/groovy/datadog/trace/core/CoreTracerTest.groovy index d2070931e15..ba781cf010c 100644 --- a/dd-trace-core/src/test/groovy/datadog/trace/core/CoreTracerTest.groovy +++ b/dd-trace-core/src/test/groovy/datadog/trace/core/CoreTracerTest.groovy @@ -38,7 +38,6 @@ import static datadog.trace.api.config.TracerConfig.HEADER_TAGS import static datadog.trace.api.config.TracerConfig.PRIORITY_SAMPLING import static datadog.trace.api.config.TracerConfig.SERVICE_MAPPING import static datadog.trace.api.config.TracerConfig.SPAN_TAGS -import static datadog.trace.api.config.TracerConfig.TRACE_BAGGAGE_TAG_KEYS import static datadog.trace.api.config.TracerConfig.WRITER_TYPE @Timeout(10) @@ -640,33 +639,6 @@ class CoreTracerTest extends DDCoreSpecification { "service" | "env" | "service" | "env_1" "service" | "env" | "service_2" | "env_2" } - - def "test mapBaggageTags default"() { - when: - def tags = CoreTracer.mapBaggageTags(["user.id": "doggo", "account.id": "test", "session.id":"1234", "region":"us-east-1"]) - then: - tags == ["baggage.user.id": "doggo", "baggage.account.id": "test", "baggage.session.id": "1234"] - } - - def "test mapBaggageTags with different configurations"() { - setup: - injectSysConfig(TRACE_BAGGAGE_TAG_KEYS, baggageTagKeysConfig) - - when: - def tags = CoreTracer.mapBaggageTags(["user.id": "doggo", "foo": "bar", "language":"en"]) - - then: - tags == expectedTags - - where: - baggageTagKeysConfig | expectedTags - "*" | ["baggage.user.id": "doggo", "baggage.foo": "bar", "baggage.language": "en"] - " " | [:] - "system.id" | [:] - "user.id" | ["baggage.user.id": "doggo"] - "foo" | ["baggage.foo": "bar"] - "user.id,foo" | ["baggage.user.id": "doggo", "baggage.foo": "bar"] - } } class WriterWithExplicitFlush implements datadog.trace.common.writer.Writer { diff --git a/internal-api/src/main/java/datadog/trace/bootstrap/instrumentation/api/TagContext.java b/internal-api/src/main/java/datadog/trace/bootstrap/instrumentation/api/TagContext.java index 9a22acaa8cb..5cb284d3102 100644 --- a/internal-api/src/main/java/datadog/trace/bootstrap/instrumentation/api/TagContext.java +++ b/internal-api/src/main/java/datadog/trace/bootstrap/instrumentation/api/TagContext.java @@ -175,6 +175,15 @@ public void putTag(final String key, final String value) { this.tags.set(key, value); } + public void putTagIfAbsent(final String key, final String value) { + if (this.tags == null) { + this.tags = TagMap.create(4); + } + if (!this.tags.containsKey(key)) { + this.tags.set(key, value); + } + } + @Override public final int getSamplingPriority() { return samplingPriority; From af0a6405645c15f24ee0186522c9292e9d43df3b Mon Sep 17 00:00:00 2001 From: Stuart McCulloch Date: Fri, 18 Jul 2025 11:38:55 +0100 Subject: [PATCH 08/15] Move baggage tags unit test to HttpServerDecoratorTest --- .../decorator/HttpServerDecorator.java | 2 +- .../decorator/HttpServerDecoratorTest.groovy | 52 +++++++++++++++++++ 2 files changed, 53 insertions(+), 1 deletion(-) diff --git a/dd-java-agent/agent-bootstrap/src/main/java/datadog/trace/bootstrap/instrumentation/decorator/HttpServerDecorator.java b/dd-java-agent/agent-bootstrap/src/main/java/datadog/trace/bootstrap/instrumentation/decorator/HttpServerDecorator.java index 38a8586fe55..954070ca83d 100644 --- a/dd-java-agent/agent-bootstrap/src/main/java/datadog/trace/bootstrap/instrumentation/decorator/HttpServerDecorator.java +++ b/dd-java-agent/agent-bootstrap/src/main/java/datadog/trace/bootstrap/instrumentation/decorator/HttpServerDecorator.java @@ -657,7 +657,7 @@ public boolean accept(String key, String value) { } } - private static void setBaggageTags(TagContext tagContext, Map baggage) { + static void setBaggageTags(TagContext tagContext, Map baggage) { List baggageTagKeys = Config.get().getTraceBaggageTagKeys(); if (baggageTagKeys.isEmpty()) { return; diff --git a/dd-java-agent/agent-bootstrap/src/test/groovy/datadog/trace/bootstrap/instrumentation/decorator/HttpServerDecoratorTest.groovy b/dd-java-agent/agent-bootstrap/src/test/groovy/datadog/trace/bootstrap/instrumentation/decorator/HttpServerDecoratorTest.groovy index 14261722ad1..0e5f0b9527e 100644 --- a/dd-java-agent/agent-bootstrap/src/test/groovy/datadog/trace/bootstrap/instrumentation/decorator/HttpServerDecoratorTest.groovy +++ b/dd-java-agent/agent-bootstrap/src/test/groovy/datadog/trace/bootstrap/instrumentation/decorator/HttpServerDecoratorTest.groovy @@ -17,6 +17,7 @@ import datadog.trace.bootstrap.instrumentation.api.AgentTracer.TracerAPI import datadog.trace.bootstrap.instrumentation.api.ContextVisitors import datadog.trace.bootstrap.instrumentation.api.ErrorPriorities import datadog.trace.bootstrap.instrumentation.api.ResourceNamePriorities +import datadog.trace.bootstrap.instrumentation.api.TagContext import datadog.trace.bootstrap.instrumentation.api.Tags import datadog.trace.bootstrap.instrumentation.api.URIDataAdapter import datadog.trace.bootstrap.instrumentation.api.URIDefaultDataAdapter @@ -29,6 +30,7 @@ import static datadog.trace.api.config.TraceInstrumentationConfig.HTTP_SERVER_DE import static datadog.trace.api.config.TraceInstrumentationConfig.HTTP_SERVER_RAW_QUERY_STRING import static datadog.trace.api.config.TraceInstrumentationConfig.HTTP_SERVER_RAW_RESOURCE import static datadog.trace.api.config.TraceInstrumentationConfig.HTTP_SERVER_TAG_QUERY_STRING +import static datadog.trace.api.config.TracerConfig.TRACE_BAGGAGE_TAG_KEYS import static datadog.trace.api.gateway.Events.EVENTS class HttpServerDecoratorTest extends ServerDecoratorTest { @@ -566,4 +568,54 @@ class HttpServerDecoratorTest extends ServerDecoratorTest { return reqHeaderDoneCount } } + + def "test setBaggageTags default"() { + setup: + def tags = [:] + + when: + def tagContext = new TagContext() + HttpServerDecorator.setBaggageTags(tagContext, ["user.id": "doggo", "account.id": "test", "session.id": "1234", "region": "us -east - 1"]) + tagContext.tags.fillMap(tags) + + then: + tags == ["baggage.user.id": "doggo", "baggage.account.id": "test", "baggage.session.id": "1234"] + } + + def "test setBaggageTags does not overwrite"() { + setup: + def tags = [:] + + when: + def tagContext = new TagContext() + tagContext.putTag("baggage.account.id", "staging") + HttpServerDecorator.setBaggageTags(tagContext, ["user.id": "doggo", "account.id": "test", "session.id": "1234", "region": "us -east - 1"]) + tagContext.tags.fillMap(tags) + + then: + tags == ["baggage.user.id": "doggo", "baggage.account.id": "staging", "baggage.session.id": "1234"] + } + + def "test setBaggageTags with different configurations"() { + setup: + injectSysConfig(TRACE_BAGGAGE_TAG_KEYS, baggageTagKeysConfig) + def tags = [:] + + when: + def tagContext = new TagContext() + HttpServerDecorator.setBaggageTags(tagContext, ["user.id": "doggo", "foo": "bar", "language": "en"]) + tagContext.tags.fillMap(tags) + + then: + tags == expectedTags + + where: + baggageTagKeysConfig | expectedTags + "*" | ["baggage.user.id": "doggo", "baggage.foo": "bar", "baggage.language": "en"] + " " | [:] + "system.id" | [:] + "user.id" | ["baggage.user.id": "doggo"] + "foo" | ["baggage.foo": "bar"] + "user.id,foo" | ["baggage.user.id": "doggo", "baggage.foo": "bar"] + } } From eca080eb87e05870461908aeba6133f1c785bdf4 Mon Sep 17 00:00:00 2001 From: Stuart McCulloch Date: Fri, 18 Jul 2025 12:16:43 +0100 Subject: [PATCH 09/15] Baggage tags unit tests have been moved to HttpServerDecoratorTest --- .../trace/core/CoreSpanBuilderTest.groovy | 28 +------------------ 1 file changed, 1 insertion(+), 27 deletions(-) diff --git a/dd-trace-core/src/test/groovy/datadog/trace/core/CoreSpanBuilderTest.groovy b/dd-trace-core/src/test/groovy/datadog/trace/core/CoreSpanBuilderTest.groovy index dbecbda6468..2e533583ea2 100644 --- a/dd-trace-core/src/test/groovy/datadog/trace/core/CoreSpanBuilderTest.groovy +++ b/dd-trace-core/src/test/groovy/datadog/trace/core/CoreSpanBuilderTest.groovy @@ -4,15 +4,14 @@ import static datadog.trace.api.DDTags.DJM_ENABLED import static datadog.trace.api.DDTags.DSM_ENABLED import static datadog.trace.api.DDTags.PROFILING_ENABLED import static datadog.trace.api.DDTags.SCHEMA_VERSION_TAG_KEY -import static datadog.trace.api.config.TracerConfig.TRACE_BAGGAGE_TAG_KEYS import datadog.trace.api.Config import datadog.trace.api.DDSpanId import datadog.trace.api.DDTraceId import datadog.trace.api.TagMap import datadog.trace.api.gateway.RequestContextSlot -import datadog.trace.api.sampling.PrioritySampling import datadog.trace.api.naming.SpanNaming +import datadog.trace.api.sampling.PrioritySampling import datadog.trace.bootstrap.instrumentation.api.AgentScope import datadog.trace.api.datastreams.NoopPathwayContext import datadog.trace.bootstrap.instrumentation.api.TagContext @@ -493,31 +492,6 @@ class CoreSpanBuilderTest extends DDCoreSpecification { span1.finish() } - def "buildSpan should add baggage tags with different configurations"() { - setup: - injectSysConfig(TRACE_BAGGAGE_TAG_KEYS, baggageTagKeysConfig) - def baggage = ["user.id": "alice", "session.id": "123", "region": "us-west-1", "env": "production"] - def tagContext = new TagContext(null, null, null, baggage, PrioritySampling.UNSET, null, DATADOG, DDTraceId.ZERO) - - when: - def span = tracer.buildSpan("test", "test-op") - .asChildOf(tagContext) - .start() - - then: - // Filter span tags to only check baggage tags (those starting with "baggage.") - def actualBaggageTags = span.tags.findAll { key, value -> key.startsWith("baggage.") } - actualBaggageTags == expectedBaggageTags - - cleanup: - span.finish() - - where: - baggageTagKeysConfig | expectedBaggageTags - "user.id" | ["baggage.user.id": "alice"] - "user.id,session.id" | ["baggage.user.id": "alice", "baggage.session.id": "123"] - } - def productTags() { def productTags = [ (PROFILING_ENABLED) : Config.get().isProfilingEnabled() ? 1 : 0 From 85d573fed99a6886aa5d0dd9d6fbdc6590826695 Mon Sep 17 00:00:00 2001 From: Stuart McCulloch Date: Tue, 22 Jul 2025 17:20:13 +0100 Subject: [PATCH 10/15] Move W3C baggage tag injection to just before the span is serialized --- .../decorator/HttpServerDecorator.java | 34 +----------- .../decorator/HttpServerDecoratorTest.groovy | 52 ------------------- .../java/datadog/trace/core/CoreTracer.java | 6 +++ .../datadog/trace/core/DDSpanContext.java | 33 ++++++++++++ .../trace/core/baggage/BaggagePropagator.java | 19 ++++++- .../instrumentation/api/AgentPropagation.java | 4 +- .../instrumentation/api/TagContext.java | 9 ++++ 7 files changed, 70 insertions(+), 87 deletions(-) diff --git a/dd-java-agent/agent-bootstrap/src/main/java/datadog/trace/bootstrap/instrumentation/decorator/HttpServerDecorator.java b/dd-java-agent/agent-bootstrap/src/main/java/datadog/trace/bootstrap/instrumentation/decorator/HttpServerDecorator.java index 954070ca83d..3119ecd2fe5 100644 --- a/dd-java-agent/agent-bootstrap/src/main/java/datadog/trace/bootstrap/instrumentation/decorator/HttpServerDecorator.java +++ b/dd-java-agent/agent-bootstrap/src/main/java/datadog/trace/bootstrap/instrumentation/decorator/HttpServerDecorator.java @@ -26,7 +26,6 @@ import datadog.trace.bootstrap.instrumentation.api.AgentSpan; import datadog.trace.bootstrap.instrumentation.api.AgentSpanContext; import datadog.trace.bootstrap.instrumentation.api.AgentTracer; -import datadog.trace.bootstrap.instrumentation.api.Baggage; import datadog.trace.bootstrap.instrumentation.api.ErrorPriorities; import datadog.trace.bootstrap.instrumentation.api.InternalSpanTypes; import datadog.trace.bootstrap.instrumentation.api.ResourceNamePriorities; @@ -38,7 +37,6 @@ import datadog.trace.bootstrap.instrumentation.decorator.http.ClientIpAddressResolver; import java.net.InetAddress; import java.util.BitSet; -import java.util.List; import java.util.Locale; import java.util.Map; import java.util.concurrent.ExecutionException; @@ -151,17 +149,7 @@ public AgentSpan startSpan(REQUEST_CARRIER carrier, Context context) { public AgentSpanContext.Extracted getExtractedSpanContext(Context context) { AgentSpan extractedSpan = AgentSpan.fromContext(context); - AgentSpanContext.Extracted extractedContext = null; - if (extractedSpan != null) { - extractedContext = (AgentSpanContext.Extracted) extractedSpan.context(); - if (extractedContext instanceof TagContext) { - Baggage baggage = Baggage.fromContext(context); - if (baggage != null) { - setBaggageTags((TagContext) extractedContext, baggage.asMap()); - } - } - } - return extractedContext; + return extractedSpan == null ? null : (AgentSpanContext.Extracted) extractedSpan.context(); } public AgentSpan onRequest( @@ -656,24 +644,4 @@ public boolean accept(String key, String value) { return true; } } - - static void setBaggageTags(TagContext tagContext, Map baggage) { - List baggageTagKeys = Config.get().getTraceBaggageTagKeys(); - if (baggageTagKeys.isEmpty()) { - return; - } - for (String key : baggageTagKeys) { - if ("*".equals(key)) { - // If the key is "*", we add all baggage items - for (Map.Entry entry : baggage.entrySet()) { - tagContext.putTagIfAbsent("baggage." + entry.getKey(), entry.getValue()); - } - break; - } - String value = baggage.get(key); - if (value != null) { - tagContext.putTagIfAbsent("baggage." + key, value); - } - } - } } diff --git a/dd-java-agent/agent-bootstrap/src/test/groovy/datadog/trace/bootstrap/instrumentation/decorator/HttpServerDecoratorTest.groovy b/dd-java-agent/agent-bootstrap/src/test/groovy/datadog/trace/bootstrap/instrumentation/decorator/HttpServerDecoratorTest.groovy index 0e5f0b9527e..14261722ad1 100644 --- a/dd-java-agent/agent-bootstrap/src/test/groovy/datadog/trace/bootstrap/instrumentation/decorator/HttpServerDecoratorTest.groovy +++ b/dd-java-agent/agent-bootstrap/src/test/groovy/datadog/trace/bootstrap/instrumentation/decorator/HttpServerDecoratorTest.groovy @@ -17,7 +17,6 @@ import datadog.trace.bootstrap.instrumentation.api.AgentTracer.TracerAPI import datadog.trace.bootstrap.instrumentation.api.ContextVisitors import datadog.trace.bootstrap.instrumentation.api.ErrorPriorities import datadog.trace.bootstrap.instrumentation.api.ResourceNamePriorities -import datadog.trace.bootstrap.instrumentation.api.TagContext import datadog.trace.bootstrap.instrumentation.api.Tags import datadog.trace.bootstrap.instrumentation.api.URIDataAdapter import datadog.trace.bootstrap.instrumentation.api.URIDefaultDataAdapter @@ -30,7 +29,6 @@ import static datadog.trace.api.config.TraceInstrumentationConfig.HTTP_SERVER_DE import static datadog.trace.api.config.TraceInstrumentationConfig.HTTP_SERVER_RAW_QUERY_STRING import static datadog.trace.api.config.TraceInstrumentationConfig.HTTP_SERVER_RAW_RESOURCE import static datadog.trace.api.config.TraceInstrumentationConfig.HTTP_SERVER_TAG_QUERY_STRING -import static datadog.trace.api.config.TracerConfig.TRACE_BAGGAGE_TAG_KEYS import static datadog.trace.api.gateway.Events.EVENTS class HttpServerDecoratorTest extends ServerDecoratorTest { @@ -568,54 +566,4 @@ class HttpServerDecoratorTest extends ServerDecoratorTest { return reqHeaderDoneCount } } - - def "test setBaggageTags default"() { - setup: - def tags = [:] - - when: - def tagContext = new TagContext() - HttpServerDecorator.setBaggageTags(tagContext, ["user.id": "doggo", "account.id": "test", "session.id": "1234", "region": "us -east - 1"]) - tagContext.tags.fillMap(tags) - - then: - tags == ["baggage.user.id": "doggo", "baggage.account.id": "test", "baggage.session.id": "1234"] - } - - def "test setBaggageTags does not overwrite"() { - setup: - def tags = [:] - - when: - def tagContext = new TagContext() - tagContext.putTag("baggage.account.id", "staging") - HttpServerDecorator.setBaggageTags(tagContext, ["user.id": "doggo", "account.id": "test", "session.id": "1234", "region": "us -east - 1"]) - tagContext.tags.fillMap(tags) - - then: - tags == ["baggage.user.id": "doggo", "baggage.account.id": "staging", "baggage.session.id": "1234"] - } - - def "test setBaggageTags with different configurations"() { - setup: - injectSysConfig(TRACE_BAGGAGE_TAG_KEYS, baggageTagKeysConfig) - def tags = [:] - - when: - def tagContext = new TagContext() - HttpServerDecorator.setBaggageTags(tagContext, ["user.id": "doggo", "foo": "bar", "language": "en"]) - tagContext.tags.fillMap(tags) - - then: - tags == expectedTags - - where: - baggageTagKeysConfig | expectedTags - "*" | ["baggage.user.id": "doggo", "baggage.foo": "bar", "baggage.language": "en"] - " " | [:] - "system.id" | [:] - "user.id" | ["baggage.user.id": "doggo"] - "foo" | ["baggage.foo": "bar"] - "user.id,foo" | ["baggage.user.id": "doggo", "baggage.foo": "bar"] - } } 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 a5cc89f5011..6b5ea0b5f10 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 @@ -59,6 +59,7 @@ import datadog.trace.bootstrap.instrumentation.api.AgentSpanContext; import datadog.trace.bootstrap.instrumentation.api.AgentSpanLink; import datadog.trace.bootstrap.instrumentation.api.AgentTracer; +import datadog.trace.bootstrap.instrumentation.api.Baggage; import datadog.trace.bootstrap.instrumentation.api.BlackHoleSpan; import datadog.trace.bootstrap.instrumentation.api.ProfilingContextIntegration; import datadog.trace.bootstrap.instrumentation.api.SpanAttributes; @@ -1591,6 +1592,7 @@ private DDSpanContext buildSpanContext() { final long spanId; final long parentSpanId; final Map baggage; + final Baggage w3cBaggage; final TraceCollector parentTraceCollector; final int samplingPriority; final CharSequence origin; @@ -1645,6 +1647,7 @@ private DDSpanContext buildSpanContext() { traceId = ddsc.getTraceId(); parentSpanId = ddsc.getSpanId(); baggage = ddsc.getBaggageItems(); + w3cBaggage = null; parentTraceCollector = ddsc.getTraceCollector(); samplingPriority = PrioritySampling.UNSET; origin = null; @@ -1706,6 +1709,7 @@ private DDSpanContext buildSpanContext() { coreTagsNeedsIntercept = true; // maybe intercept isn't needed? origin = tc.getOrigin(); baggage = tc.getBaggage(); + w3cBaggage = tc.getW3CBaggage(); requestContextDataAppSec = tc.getRequestContextDataAppSec(); requestContextDataIast = tc.getRequestContextDataIast(); ciVisibilityContextData = tc.getCiVisibilityContextData(); @@ -1715,6 +1719,7 @@ private DDSpanContext buildSpanContext() { coreTagsNeedsIntercept = false; origin = null; baggage = null; + w3cBaggage = null; requestContextDataAppSec = null; requestContextDataIast = null; ciVisibilityContextData = null; @@ -1805,6 +1810,7 @@ private DDSpanContext buildSpanContext() { samplingPriority, origin, baggage, + w3cBaggage, errorFlag, spanType, tagsSize, diff --git a/dd-trace-core/src/main/java/datadog/trace/core/DDSpanContext.java b/dd-trace-core/src/main/java/datadog/trace/core/DDSpanContext.java index 482d1efe09e..ed18aa1efc8 100644 --- a/dd-trace-core/src/main/java/datadog/trace/core/DDSpanContext.java +++ b/dd-trace-core/src/main/java/datadog/trace/core/DDSpanContext.java @@ -5,6 +5,7 @@ import static datadog.trace.api.cache.RadixTreeCache.HTTP_STATUSES; import static datadog.trace.bootstrap.instrumentation.api.ErrorPriorities.UNSET; +import datadog.trace.api.Config; import datadog.trace.api.DDSpanId; import datadog.trace.api.DDTags; import datadog.trace.api.DDTraceId; @@ -23,6 +24,7 @@ import datadog.trace.api.sampling.SamplingMechanism; import datadog.trace.bootstrap.instrumentation.api.AgentSpanContext; import datadog.trace.bootstrap.instrumentation.api.AgentSpanLink; +import datadog.trace.bootstrap.instrumentation.api.Baggage; import datadog.trace.bootstrap.instrumentation.api.ProfilerContext; import datadog.trace.bootstrap.instrumentation.api.ProfilingContextIntegration; import datadog.trace.bootstrap.instrumentation.api.ResourceNamePriorities; @@ -77,6 +79,8 @@ public class DDSpanContext /** Baggage is associated with the whole trace and shared with other spans */ private volatile Map baggageItems; + private final Baggage w3cBaggage; + // Not Shared with other span contexts private final DDTraceId traceId; private final long spanId; @@ -187,6 +191,7 @@ public DDSpanContext( samplingPriority, origin, baggageItems, + null, errorFlag, spanType, tagsSize, @@ -233,6 +238,7 @@ public DDSpanContext( samplingPriority, origin, baggageItems, + null, errorFlag, spanType, tagsSize, @@ -279,6 +285,7 @@ public DDSpanContext( samplingPriority, origin, baggageItems, + null, errorFlag, spanType, tagsSize, @@ -304,6 +311,7 @@ public DDSpanContext( final int samplingPriority, final CharSequence origin, final Map baggageItems, + final Baggage w3cBaggage, final boolean errorFlag, final CharSequence spanType, final int tagsSize, @@ -331,6 +339,7 @@ public DDSpanContext( } else { this.baggageItems = new ConcurrentHashMap<>(baggageItems); } + this.w3cBaggage = w3cBaggage; this.requestContextDataAppSec = requestContextDataAppSec; this.requestContextDataIast = requestContextDataIast; @@ -926,6 +935,9 @@ public void processTagsAndBaggage( Map baggageItemsWithPropagationTags; if (injectBaggageAsTags) { baggageItemsWithPropagationTags = new HashMap<>(baggageItems); + if (w3cBaggage != null) { + injectW3CBaggageTags(baggageItemsWithPropagationTags); + } propagationTags.fillTagMap(baggageItemsWithPropagationTags); } else { baggageItemsWithPropagationTags = propagationTags.createTagMap(); @@ -948,6 +960,27 @@ public void processTagsAndBaggage( } } + void injectW3CBaggageTags(Map baggageItemsWithPropagationTags) { + List baggageTagKeys = Config.get().getTraceBaggageTagKeys(); + if (baggageTagKeys.isEmpty()) { + return; + } + Map w3cBaggageItems = w3cBaggage.asMap(); + for (String key : baggageTagKeys) { + if ("*".equals(key)) { + // If the key is "*", we add all baggage items + for (Map.Entry entry : w3cBaggageItems.entrySet()) { + baggageItemsWithPropagationTags.put("baggage." + entry.getKey(), entry.getValue()); + } + break; + } + String value = w3cBaggageItems.get(key); + if (value != null) { + baggageItemsWithPropagationTags.put("baggage." + key, value); + } + } + } + @Override public void setIntegrationName(CharSequence integrationName) { this.integrationName = integrationName; diff --git a/dd-trace-core/src/main/java/datadog/trace/core/baggage/BaggagePropagator.java b/dd-trace-core/src/main/java/datadog/trace/core/baggage/BaggagePropagator.java index 3f3d00c373f..f3957dbc003 100644 --- a/dd-trace-core/src/main/java/datadog/trace/core/baggage/BaggagePropagator.java +++ b/dd-trace-core/src/main/java/datadog/trace/core/baggage/BaggagePropagator.java @@ -7,7 +7,10 @@ import datadog.context.propagation.CarrierVisitor; import datadog.context.propagation.Propagator; import datadog.trace.api.Config; +import datadog.trace.bootstrap.instrumentation.api.AgentSpan; +import datadog.trace.bootstrap.instrumentation.api.AgentSpanContext; import datadog.trace.bootstrap.instrumentation.api.Baggage; +import datadog.trace.bootstrap.instrumentation.api.TagContext; import datadog.trace.core.util.PercentEscaper; import datadog.trace.core.util.PercentEscaper.Escaped; import java.io.UnsupportedEncodingException; @@ -109,7 +112,21 @@ public Context extract(Context context, C carrier, CarrierVisitor visitor } BaggageExtractor baggageExtractor = new BaggageExtractor(); visitor.forEachKeyValue(carrier, baggageExtractor); - return baggageExtractor.extracted == null ? context : context.with(baggageExtractor.extracted); + Baggage baggage = baggageExtractor.extracted; + if (baggage == null) { + return context; + } + + // TODO: consider a better way to link baggage with the extracted (legacy) TagContext + AgentSpan extractedSpan = AgentSpan.fromContext(context); + if (extractedSpan != null) { + AgentSpanContext extractedSpanContext = extractedSpan.context(); + if (extractedSpanContext instanceof TagContext) { + ((TagContext) extractedSpanContext).setW3CBaggage(baggage); + } + } + + return context.with(baggage); } private class BaggageExtractor implements BiConsumer { diff --git a/internal-api/src/main/java/datadog/trace/bootstrap/instrumentation/api/AgentPropagation.java b/internal-api/src/main/java/datadog/trace/bootstrap/instrumentation/api/AgentPropagation.java index 3dea68cd5b2..fba659bea74 100644 --- a/internal-api/src/main/java/datadog/trace/bootstrap/instrumentation/api/AgentPropagation.java +++ b/internal-api/src/main/java/datadog/trace/bootstrap/instrumentation/api/AgentPropagation.java @@ -14,7 +14,9 @@ public final class AgentPropagation { public static final Concern TRACING_CONCERN = named("tracing"); - public static final Concern BAGGAGE_CONCERN = named("baggage"); + // TODO: Baggage propagator should run after tracing so it can link baggage with the span context + // TODO: remove this priority once we have a story for replacing TagContext with the Context API + public static final Concern BAGGAGE_CONCERN = withPriority("baggage", 105); public static final Concern XRAY_TRACING_CONCERN = named("tracing-xray"); // TODO DSM propagator should run after the other propagators as it stores the pathway context diff --git a/internal-api/src/main/java/datadog/trace/bootstrap/instrumentation/api/TagContext.java b/internal-api/src/main/java/datadog/trace/bootstrap/instrumentation/api/TagContext.java index 5cb284d3102..5981dca4bfd 100644 --- a/internal-api/src/main/java/datadog/trace/bootstrap/instrumentation/api/TagContext.java +++ b/internal-api/src/main/java/datadog/trace/bootstrap/instrumentation/api/TagContext.java @@ -32,6 +32,7 @@ public class TagContext implements AgentSpanContext.Extracted { private PathwayContext pathwayContext; private final HttpHeaders httpHeaders; private final Map baggage; + private Baggage w3cBaggage; private final int samplingPriority; private final TraceConfig traceConfig; private final TracePropagationStyle propagationStyle; @@ -198,6 +199,14 @@ public Iterable> baggageItems() { return baggage.entrySet(); } + public final Baggage getW3CBaggage() { + return w3cBaggage; + } + + public void setW3CBaggage(Baggage w3cBaggage) { + this.w3cBaggage = w3cBaggage; + } + @Override public DDTraceId getTraceId() { return traceId; From 33fe83e2aa6eb2dfa653a492509d3432295d040a Mon Sep 17 00:00:00 2001 From: Stuart McCulloch Date: Tue, 22 Jul 2025 17:46:11 +0100 Subject: [PATCH 11/15] Update test trace writer so injected baggage metadata can be asserted as tags --- .../datadog/trace/agent/test/base/HttpServerTest.groovy | 3 +-- .../java/datadog/trace/common/writer/ListWriter.java | 9 ++++++++- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/dd-java-agent/testing/src/main/groovy/datadog/trace/agent/test/base/HttpServerTest.groovy b/dd-java-agent/testing/src/main/groovy/datadog/trace/agent/test/base/HttpServerTest.groovy index 4a5f5c76858..0b9eb484f2b 100644 --- a/dd-java-agent/testing/src/main/groovy/datadog/trace/agent/test/base/HttpServerTest.groovy +++ b/dd-java-agent/testing/src/main/groovy/datadog/trace/agent/test/base/HttpServerTest.groovy @@ -819,8 +819,7 @@ abstract class HttpServerTest extends WithHttpServer { if (isDataStreamsEnabled()) { StatsGroup first = TEST_DATA_STREAMS_WRITER.groups.find { it.parentHash == 0 } verifyAll(first) { - edgeTags.containsAll(DSM_EDGE_TAGS) - edgeTags.size() == DSM_EDGE_TAGS.size() + tags == DSM_EDGE_TAGS } } } diff --git a/dd-trace-core/src/main/java/datadog/trace/common/writer/ListWriter.java b/dd-trace-core/src/main/java/datadog/trace/common/writer/ListWriter.java index 03bed3c7174..36fbedd6c0c 100644 --- a/dd-trace-core/src/main/java/datadog/trace/common/writer/ListWriter.java +++ b/dd-trace-core/src/main/java/datadog/trace/common/writer/ListWriter.java @@ -38,7 +38,14 @@ public void write(List trace) { for (DDSpan span : trace) { // This is needed to properly do all delayed processing to make this writer even // remotely realistic so the test actually test something - span.processTagsAndBaggage(MetadataConsumer.NO_OP); + span.processTagsAndBaggage(metadata -> { + // surface injected baggage metadata as span tags so they can be asserted + metadata.getBaggage().forEach((k, v) -> { + if (!k.startsWith("_dd.") && span.getTag(k) == null) { + span.setTag(k, v); + } + }); + }); } add(trace); From 3a8d4a8007817eb706ca1df74160b2c1ac6c554a Mon Sep 17 00:00:00 2001 From: Stuart McCulloch Date: Tue, 22 Jul 2025 17:48:00 +0100 Subject: [PATCH 12/15] spotless format --- .../trace/common/writer/ListWriter.java | 21 +++++++++++-------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/dd-trace-core/src/main/java/datadog/trace/common/writer/ListWriter.java b/dd-trace-core/src/main/java/datadog/trace/common/writer/ListWriter.java index 36fbedd6c0c..55fc2cf1cbf 100644 --- a/dd-trace-core/src/main/java/datadog/trace/common/writer/ListWriter.java +++ b/dd-trace-core/src/main/java/datadog/trace/common/writer/ListWriter.java @@ -5,7 +5,6 @@ import static java.util.concurrent.TimeUnit.SECONDS; import datadog.trace.core.DDSpan; -import datadog.trace.core.MetadataConsumer; import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.TimeUnit; @@ -38,14 +37,18 @@ public void write(List trace) { for (DDSpan span : trace) { // This is needed to properly do all delayed processing to make this writer even // remotely realistic so the test actually test something - span.processTagsAndBaggage(metadata -> { - // surface injected baggage metadata as span tags so they can be asserted - metadata.getBaggage().forEach((k, v) -> { - if (!k.startsWith("_dd.") && span.getTag(k) == null) { - span.setTag(k, v); - } - }); - }); + span.processTagsAndBaggage( + metadata -> { + // surface injected baggage metadata as span tags so they can be asserted + metadata + .getBaggage() + .forEach( + (k, v) -> { + if (!k.startsWith("_dd.") && span.getTag(k) == null) { + span.setTag(k, v); + } + }); + }); } add(trace); From a4fb5a633960ecbd4e1cdc76141357198ba87fc1 Mon Sep 17 00:00:00 2001 From: Stuart McCulloch Date: Tue, 22 Jul 2025 18:00:51 +0100 Subject: [PATCH 13/15] Remove unused method --- .../trace/bootstrap/instrumentation/api/TagContext.java | 9 --------- 1 file changed, 9 deletions(-) diff --git a/internal-api/src/main/java/datadog/trace/bootstrap/instrumentation/api/TagContext.java b/internal-api/src/main/java/datadog/trace/bootstrap/instrumentation/api/TagContext.java index 5981dca4bfd..6cc277e3b22 100644 --- a/internal-api/src/main/java/datadog/trace/bootstrap/instrumentation/api/TagContext.java +++ b/internal-api/src/main/java/datadog/trace/bootstrap/instrumentation/api/TagContext.java @@ -176,15 +176,6 @@ public void putTag(final String key, final String value) { this.tags.set(key, value); } - public void putTagIfAbsent(final String key, final String value) { - if (this.tags == null) { - this.tags = TagMap.create(4); - } - if (!this.tags.containsKey(key)) { - this.tags.set(key, value); - } - } - @Override public final int getSamplingPriority() { return samplingPriority; From 988bed851319584c5a994ebc913d561cb89b2408 Mon Sep 17 00:00:00 2001 From: Stuart McCulloch Date: Tue, 22 Jul 2025 18:16:05 +0100 Subject: [PATCH 14/15] Fix test code --- .../groovy/datadog/trace/core/test/DDCoreSpecification.groovy | 1 + 1 file changed, 1 insertion(+) diff --git a/dd-trace-core/src/test/groovy/datadog/trace/core/test/DDCoreSpecification.groovy b/dd-trace-core/src/test/groovy/datadog/trace/core/test/DDCoreSpecification.groovy index 7060b7a5084..5318c9b93c6 100644 --- a/dd-trace-core/src/test/groovy/datadog/trace/core/test/DDCoreSpecification.groovy +++ b/dd-trace-core/src/test/groovy/datadog/trace/core/test/DDCoreSpecification.groovy @@ -70,6 +70,7 @@ abstract class DDCoreSpecification extends DDSpecification { prioritySampling, null, [:], + null, false, spanType, 0, From a22edabb0c2f59bca33a426b3cafd022bb8ac7bd Mon Sep 17 00:00:00 2001 From: Stuart McCulloch Date: Tue, 22 Jul 2025 19:28:28 +0100 Subject: [PATCH 15/15] Support recording of baggage, that would be sent to agent as trace metadata, during tests --- .../agent/test/base/HttpServerTest.groovy | 23 ++++++++++++++----- .../trace/common/writer/ListWriter.java | 21 ++++++++--------- 2 files changed, 26 insertions(+), 18 deletions(-) diff --git a/dd-java-agent/testing/src/main/groovy/datadog/trace/agent/test/base/HttpServerTest.groovy b/dd-java-agent/testing/src/main/groovy/datadog/trace/agent/test/base/HttpServerTest.groovy index 0b9eb484f2b..fba553fe199 100644 --- a/dd-java-agent/testing/src/main/groovy/datadog/trace/agent/test/base/HttpServerTest.groovy +++ b/dd-java-agent/testing/src/main/groovy/datadog/trace/agent/test/base/HttpServerTest.groovy @@ -39,6 +39,7 @@ import datadog.trace.bootstrap.instrumentation.api.Tags import datadog.trace.bootstrap.instrumentation.api.URIDataAdapter import datadog.trace.bootstrap.instrumentation.api.URIUtils import datadog.trace.core.DDSpan +import datadog.trace.core.Metadata import datadog.trace.core.datastreams.StatsGroup import datadog.trace.test.util.Flaky import groovy.json.JsonOutput @@ -780,6 +781,15 @@ abstract class HttpServerTest extends WithHttpServer { def "test baggage span tags are properly added"() { setup: + def recordedBaggageTags = [:] + TEST_WRITER.metadataConsumer = { Metadata md -> + md.baggage.forEach { k, v -> + // record non-internal baggage sent to agent as trace metadata + if (!k.startsWith("_dd.")) { + recordedBaggageTags.put(k, v) + } + } + } // Use default configuration for TRACE_BAGGAGE_TAG_KEYS (user.id, session.id, account.id) def baggageHeader = "user.id=test-user,session.id=test-session,account.id=test-account,language=en" def request = request(SUCCESS, 'GET', null) @@ -799,12 +809,7 @@ abstract class HttpServerTest extends WithHttpServer { trace(spanCount(SUCCESS)) { sortSpansByStart() // Verify baggage tags are added for default configured keys only - serverSpan(it, null, null, 'GET', SUCCESS, [ - "baggage.user.id": "test-user", - "baggage.session.id": "test-session", - "baggage.account.id": "test-account" - // "baggage.language" should NOT be present since it's not in default config - ]) + serverSpan(it, null, null, 'GET', SUCCESS) if (hasHandlerSpan()) { handlerSpan(it) } @@ -814,6 +819,12 @@ abstract class HttpServerTest extends WithHttpServer { } } } + recordedBaggageTags == [ + "baggage.user.id": "test-user", + "baggage.session.id": "test-session", + "baggage.account.id": "test-account" + // "baggage.language" should NOT be present since it's not in default config + ] and: if (isDataStreamsEnabled()) { diff --git a/dd-trace-core/src/main/java/datadog/trace/common/writer/ListWriter.java b/dd-trace-core/src/main/java/datadog/trace/common/writer/ListWriter.java index 55fc2cf1cbf..66f9f468624 100644 --- a/dd-trace-core/src/main/java/datadog/trace/common/writer/ListWriter.java +++ b/dd-trace-core/src/main/java/datadog/trace/common/writer/ListWriter.java @@ -5,6 +5,7 @@ import static java.util.concurrent.TimeUnit.SECONDS; import datadog.trace.core.DDSpan; +import datadog.trace.core.MetadataConsumer; import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.TimeUnit; @@ -25,6 +26,8 @@ public class ListWriter extends CopyOnWriteArrayList> implements Wr private Filter filter = ACCEPT_ALL; + private MetadataConsumer metadataConsumer = MetadataConsumer.NO_OP; + public List firstTrace() { return get(0); } @@ -37,18 +40,7 @@ public void write(List trace) { for (DDSpan span : trace) { // This is needed to properly do all delayed processing to make this writer even // remotely realistic so the test actually test something - span.processTagsAndBaggage( - metadata -> { - // surface injected baggage metadata as span tags so they can be asserted - metadata - .getBaggage() - .forEach( - (k, v) -> { - if (!k.startsWith("_dd.") && span.getTag(k) == null) { - span.setTag(k, v); - } - }); - }); + span.processTagsAndBaggage(metadataConsumer); } add(trace); @@ -127,6 +119,11 @@ public void setFilter(Filter filter) { this.filter = filter; } + /** Set a {@link MetadataConsumer} to capture what trace metadata would be sent to the agent. */ + public void setMetadataConsumer(MetadataConsumer metadataConsumer) { + this.metadataConsumer = metadataConsumer; + } + private boolean isReported(DDSpan span) { for (List trace : this) { for (DDSpan aSpan : trace) {