From 73d6e179f2f649208ddab9bea919dc20f2beed52 Mon Sep 17 00:00:00 2001 From: Matthew Li Date: Tue, 15 Sep 2026 16:36:50 -0400 Subject: [PATCH 1/7] introducing new tags/otlp resource attributes for otlp adoption metrics --- .../main/java/datadog/trace/api/DDTags.java | 1 + .../java/datadog/trace/core/CoreTracer.java | 4 ++- .../otlp/common/OtlpResourceAttributes.java | 8 ++++-- .../trace/core/CoreSpanBuilderTest.java | 25 +++++++++++++++++++ .../datadog/trace/core/CoreTracerTest.java | 11 ++++++-- .../datadog/trace/core/DDSpanContextTest.java | 9 +++++++ .../otlp/common/OtlpResourceJsonTest.java | 16 +++++++++++- .../otlp/common/OtlpResourceProtoTest.java | 16 +++++++++++- 8 files changed, 83 insertions(+), 7 deletions(-) diff --git a/dd-trace-api/src/main/java/datadog/trace/api/DDTags.java b/dd-trace-api/src/main/java/datadog/trace/api/DDTags.java index b7ca19232c6..756048551c9 100644 --- a/dd-trace-api/src/main/java/datadog/trace/api/DDTags.java +++ b/dd-trace-api/src/main/java/datadog/trace/api/DDTags.java @@ -105,4 +105,5 @@ public class DDTags { public static final String PROCESS_TAGS = "_dd.tags.process"; public static final String DD_INTEGRATION = "_dd.integration"; public static final String DD_SVC_SRC = "_dd.svc_src"; + public static final String SDK_OTLP_EXPORT = "_dd.sdk.otlp_export"; } 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 c280000f252..9ac1c1c1dc7 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 @@ -4,6 +4,7 @@ import static datadog.trace.api.DDTags.DJM_ENABLED; import static datadog.trace.api.DDTags.DSM_ENABLED; import static datadog.trace.api.DDTags.PROFILING_CONTEXT_ENGINE; +import static datadog.trace.api.DDTags.SDK_OTLP_EXPORT; import static datadog.trace.api.TracePropagationBehaviorExtract.IGNORE; import static datadog.trace.bootstrap.instrumentation.api.AgentPropagation.BAGGAGE_CONCERN; import static datadog.trace.bootstrap.instrumentation.api.AgentPropagation.DSM_CONCERN; @@ -2494,7 +2495,7 @@ protected ConfigSnapshot( */ static TagMap withTracerTags( Map userSpanTags, Config config, TraceConfig traceConfig) { - final TagMap result = TagMap.create(userSpanTags.size() + 5); + final TagMap result = TagMap.create(userSpanTags.size() + 6); result.putAll(userSpanTags); // Version is conditionally managed by InternalTagsAdder (added only when service == DD_SERVICE // and not set during the request), so keep it OUT of the trace-level bundle. This matters under @@ -2525,6 +2526,7 @@ static TagMap withTracerTags( if (config.isDataStreamsEnabled()) { result.set(DSM_ENABLED, 1); } + result.set(SDK_OTLP_EXPORT, config.isOtlpTracesExportEnabled() ? "true" : "false"); } if (null != traceConfig) { // dynamic if (traceConfig.isDataStreamsEnabled()) { diff --git a/dd-trace-core/src/main/java/datadog/trace/core/otlp/common/OtlpResourceAttributes.java b/dd-trace-core/src/main/java/datadog/trace/core/otlp/common/OtlpResourceAttributes.java index 988307c6b03..6ee69ee4b98 100644 --- a/dd-trace-core/src/main/java/datadog/trace/core/otlp/common/OtlpResourceAttributes.java +++ b/dd-trace-core/src/main/java/datadog/trace/core/otlp/common/OtlpResourceAttributes.java @@ -34,7 +34,8 @@ private OtlpResourceAttributes() {} "service.version", "telemetry.sdk.name", "telemetry.sdk.version", - "telemetry.sdk.language")); + "telemetry.sdk.language", + "datadog.sdk.semantics")); /** * {@code value} is a {@link String}, except {@code datadog.process_tags}: a {@code List}. @@ -81,10 +82,13 @@ static void visitResourceAttributes( /** * Builds the extra resource attributes for the OTLP trace export: the {@code _dd.stats_computed} * marker when the SDK is computing OTLP span metrics, so a downstream Agent does not recompute - * them from the exported spans. + * them from the exported spans; {@code datadog.sdk.semantics} marker when the SDK to note whether + * Datadog or OTel semantics are used. */ static Map traceResourceAttributes(Config config) { Map attributes = new LinkedHashMap<>(); + attributes.put( + "datadog.sdk.semantics", config.isTraceOtelSemanticsEnabled() ? "otel" : "datadog"); if (config.isOtelTracesSpanMetricsEnabled()) { attributes.put(STATS_COMPUTED_KEY, "true"); } 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 9d9367551a7..763150fdb09 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 @@ -9,10 +9,14 @@ import static datadog.trace.api.DDTags.PROFILING_ENABLED; import static datadog.trace.api.DDTags.RUNTIME_ID_TAG; import static datadog.trace.api.DDTags.SCHEMA_VERSION_TAG_KEY; +import static datadog.trace.api.DDTags.SDK_OTLP_EXPORT; import static datadog.trace.api.DDTags.THREAD_ID; import static datadog.trace.api.DDTags.THREAD_NAME; import static datadog.trace.api.TracePropagationStyle.DATADOG; +import static datadog.trace.api.config.GeneralConfig.TAGS; +import static datadog.trace.api.config.TracerConfig.WRITER_TYPE; import static datadog.trace.bootstrap.instrumentation.api.AgentTracer.noopSpan; +import static datadog.trace.bootstrap.instrumentation.api.WriterConstants.OTLP_WRITER_TYPE; import static datadog.trace.test.junit.utils.config.WithConfigExtension.injectSysConfig; import static java.util.concurrent.TimeUnit.MILLISECONDS; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -70,6 +74,26 @@ void buildSimpleSpan() { assertEquals("op name", span.getOperationName()); } + @Test + void nativeWriterSetsOtlpExportMarkerToFalse() { + DDSpan span = (DDSpan) tracer.buildSpan("test", "operation").start(); + assertEquals("false", span.getTag(SDK_OTLP_EXPORT)); + } + + @Test + @WithConfig(key = WRITER_TYPE, value = OTLP_WRITER_TYPE) + void otlpWriterSetsOtlpExportMarkerToTrue() { + DDSpan span = (DDSpan) tracer.buildSpan("test", "operation").start(); + assertEquals("true", span.getTag(SDK_OTLP_EXPORT)); + } + + @Test + @WithConfig(key = TAGS, value = "_dd.sdk.otlp_export:true") + void userTagCannotOverrideOtlpExportMarker() { + DDSpan span = (DDSpan) tracer.buildSpan("test", "operation").start(); + assertEquals("false", span.getTag(SDK_OTLP_EXPORT)); + } + @Test void buildComplexSpan() { String expectedName = "fakeName"; @@ -587,6 +611,7 @@ private Map productTags() { if (Config.get().isDataJobsEnabled()) { productTags.put(DJM_ENABLED, 1); } + productTags.put(SDK_OTLP_EXPORT, Config.get().isOtlpTracesExportEnabled() ? "true" : "false"); return productTags; } diff --git a/dd-trace-core/src/test/java/datadog/trace/core/CoreTracerTest.java b/dd-trace-core/src/test/java/datadog/trace/core/CoreTracerTest.java index db850470da0..9f46a79c7fb 100644 --- a/dd-trace-core/src/test/java/datadog/trace/core/CoreTracerTest.java +++ b/dd-trace-core/src/test/java/datadog/trace/core/CoreTracerTest.java @@ -262,7 +262,7 @@ void verifyMappingConfigsOnTracer(String scenario, String mapString, Map withOtlpExportMarker(Map tags) { + Map expectedTags = new LinkedHashMap<>(tags); + expectedTags.put(DDTags.SDK_OTLP_EXPORT, "false"); + return expectedTags; + } + static final String ACTION_JSON = "'{\"action\": \"enable\", \"lib_config\":" + "{\"tracing_sampling_rate\": null," diff --git a/dd-trace-core/src/test/java/datadog/trace/core/DDSpanContextTest.java b/dd-trace-core/src/test/java/datadog/trace/core/DDSpanContextTest.java index 030687c06ec..78c7544b7e9 100644 --- a/dd-trace-core/src/test/java/datadog/trace/core/DDSpanContextTest.java +++ b/dd-trace-core/src/test/java/datadog/trace/core/DDSpanContextTest.java @@ -99,12 +99,18 @@ void nullValuesForTagsDeleteExistingTags(String name) throws Exception { assertEquals("fakeType", context.getSpanType().toString()); } + /** + * Every caller of this helper exports the span before asserting, so the expected tags include the + * markers CoreTracer stamps at write time. {@code _dd.sdk.otlp_export} is {@code "false"} here + * because these tests use the default (native) writer — see {@link CoreSpanBuilderTest}. + */ private static Map createExpectedTagsFromCurrentThread() { Thread thread = Thread.currentThread(); Map expectedTags = new HashMap<>(); expectedTags.put(THREAD_NAME, thread.getName()); expectedTags.put(THREAD_ID, thread.getId()); expectedTags.put(DDTags.DD_SVC_SRC, ServiceNameSources.MANUAL); + expectedTags.put(DDTags.SDK_OTLP_EXPORT, "false"); return expectedTags; } @@ -276,11 +282,14 @@ void setTraceSegmentTagsAndDataOnCorrectSpan() { expectedTopTags.put("ttt", "t3"); expectedTopTags.put(dataTag("tcd"), "[4]"); expectedTopTags.put("tct", "t4"); + // Declared on every span at creation — see CoreSpanBuilderTest. + expectedTopTags.put(DDTags.SDK_OTLP_EXPORT, "false"); assertTagmap(topC.getTags(), expectedTopTags, true); Map expectedCurrentTags = new HashMap<>(); expectedCurrentTags.put(dataTag("ccd"), "[2]"); expectedCurrentTags.put("cct", "t2"); + expectedCurrentTags.put(DDTags.SDK_OTLP_EXPORT, "false"); assertTagmap(currentC.getTags(), expectedCurrentTags, true); current.finish(); diff --git a/dd-trace-core/src/test/java/datadog/trace/core/otlp/common/OtlpResourceJsonTest.java b/dd-trace-core/src/test/java/datadog/trace/core/otlp/common/OtlpResourceJsonTest.java index e73311993cf..0661ff3eee3 100644 --- a/dd-trace-core/src/test/java/datadog/trace/core/otlp/common/OtlpResourceJsonTest.java +++ b/dd-trace-core/src/test/java/datadog/trace/core/otlp/common/OtlpResourceJsonTest.java @@ -5,6 +5,7 @@ import static datadog.trace.api.config.GeneralConfig.EXPERIMENTAL_PROPAGATE_PROCESS_TAGS_ENABLED; import static datadog.trace.api.config.GeneralConfig.SERVICE_NAME; import static datadog.trace.api.config.GeneralConfig.TAGS; +import static datadog.trace.api.config.GeneralConfig.TRACE_OTEL_SEMANTICS_ENABLED; import static datadog.trace.api.config.GeneralConfig.VERSION; import static datadog.trace.api.config.OtlpConfig.OTEL_TRACES_SPAN_METRICS_ENABLED; import static datadog.trace.api.config.TracerConfig.TRACE_REPORT_HOSTNAME; @@ -125,7 +126,8 @@ static Stream resourceFragmentCases() { + "SERVICE.VERSION:ignored-version," + "telemetry.sdk.name:ignored-sdk," + "telemetry.sdk.version:ignored-version," - + "telemetry.sdk.language:ignored-language"), + + "telemetry.sdk.language:ignored-language," + + "datadog.sdk.semantics:ignored-semantics"), attrs( "service.name", "my-service", "deployment.environment.name", "staging", @@ -147,6 +149,17 @@ void testBuildResourceFragment( assertEquals(expectedAttributes, actualAttributes, "For case: " + caseName); } + @Test + void usesOtelSdkSemanticsWhenEnabled() throws IOException { + Config config = Config.get(props(TRACE_OTEL_SEMANTICS_ENABLED, "true")); + + Map attributes = + parseResourceAttributes( + OtlpResourceJson.buildResourceFragment(config, traceResourceAttributes(config))); + + assertEquals("otel", attributes.get("datadog.sdk.semantics")); + } + /** The datadog-attrs variant carries {@code datadog.runtime_id}; the plain variant omits it. */ @Test void datadogResourceAttributesVariantCarriesRuntimeId() throws IOException { @@ -211,6 +224,7 @@ void statsComputedVariantCarriesMarker() throws IOException { assertEquals( "true", withMarker.get("_dd.stats_computed"), "marker present when stats computed"); assertFalse(without.containsKey("_dd.stats_computed"), "marker absent when stats not computed"); + assertEquals("datadog", without.get("datadog.sdk.semantics")); } @Test diff --git a/dd-trace-core/src/test/java/datadog/trace/core/otlp/common/OtlpResourceProtoTest.java b/dd-trace-core/src/test/java/datadog/trace/core/otlp/common/OtlpResourceProtoTest.java index 24dda2ac60a..a061bcce635 100644 --- a/dd-trace-core/src/test/java/datadog/trace/core/otlp/common/OtlpResourceProtoTest.java +++ b/dd-trace-core/src/test/java/datadog/trace/core/otlp/common/OtlpResourceProtoTest.java @@ -5,6 +5,7 @@ import static datadog.trace.api.config.GeneralConfig.EXPERIMENTAL_PROPAGATE_PROCESS_TAGS_ENABLED; import static datadog.trace.api.config.GeneralConfig.SERVICE_NAME; import static datadog.trace.api.config.GeneralConfig.TAGS; +import static datadog.trace.api.config.GeneralConfig.TRACE_OTEL_SEMANTICS_ENABLED; import static datadog.trace.api.config.GeneralConfig.VERSION; import static datadog.trace.api.config.OtlpConfig.OTEL_TRACES_SPAN_METRICS_ENABLED; import static datadog.trace.api.config.TracerConfig.TRACE_REPORT_HOSTNAME; @@ -147,7 +148,8 @@ static Stream resourceMessageCases() { + "SERVICE.VERSION:ignored-version," + "telemetry.sdk.name:ignored-sdk," + "telemetry.sdk.version:ignored-version," - + "telemetry.sdk.language:ignored-language"), + + "telemetry.sdk.language:ignored-language," + + "datadog.sdk.semantics:ignored-semantics"), attrs( "service.name", "my-service", "deployment.environment.name", "staging", @@ -169,6 +171,17 @@ void testBuildResourceMessage( assertEquals(expectedAttributes, actualAttributes, "For case: " + caseName); } + @Test + void usesOtelSdkSemanticsWhenEnabled() throws IOException { + Config config = Config.get(props(TRACE_OTEL_SEMANTICS_ENABLED, "true")); + + Map attributes = + parseResourceAttributes( + OtlpResourceProto.buildResourceMessage(config, traceResourceAttributes(config))); + + assertEquals("otel", attributes.get("datadog.sdk.semantics")); + } + /** * The datadog-attrs variant ({@code buildResourceMessage(config, datadogResourceAttributes)}) * carries {@code datadog.runtime_id}; the plain variant omits it. @@ -236,6 +249,7 @@ void statsComputedVariantCarriesMarker() throws IOException { assertEquals( "true", withMarker.get("_dd.stats_computed"), "marker present when stats computed"); assertFalse(without.containsKey("_dd.stats_computed"), "marker absent when stats not computed"); + assertEquals("datadog", without.get("datadog.sdk.semantics")); } // ── parsing helpers ─────────────────────────────────────────────────────── From aab708fa43286d9b85a9702549c6efa6575e8950 Mon Sep 17 00:00:00 2001 From: Matthew Li Date: Wed, 16 Sep 2026 14:34:48 -0400 Subject: [PATCH 2/7] update tests to include SDK_OTLP_EXPORT --- .../java/datadog/trace/agent/test/assertions/TagsMatcher.java | 2 ++ .../trace/instrumentation/ognl/OgnlInstrumentationSpec.groovy | 1 + .../src/test/groovy/test/boot/SpringBootBasedTest.groovy | 1 + .../groovy/datadog/trace/agent/test/asserts/TagsAssert.groovy | 1 + 4 files changed, 5 insertions(+) diff --git a/dd-java-agent/instrumentation-testing/src/main/java/datadog/trace/agent/test/assertions/TagsMatcher.java b/dd-java-agent/instrumentation-testing/src/main/java/datadog/trace/agent/test/assertions/TagsMatcher.java index 7fe7cd0645e..aabd0bbcc13 100644 --- a/dd-java-agent/instrumentation-testing/src/main/java/datadog/trace/agent/test/assertions/TagsMatcher.java +++ b/dd-java-agent/instrumentation-testing/src/main/java/datadog/trace/agent/test/assertions/TagsMatcher.java @@ -15,6 +15,7 @@ import static datadog.trace.api.DDTags.REQUIRED_CODE_ORIGIN_TAGS; import static datadog.trace.api.DDTags.RUNTIME_ID_TAG; import static datadog.trace.api.DDTags.SCHEMA_VERSION_TAG_KEY; +import static datadog.trace.api.DDTags.SDK_OTLP_EXPORT; import static datadog.trace.api.DDTags.SPAN_LINKS; import static datadog.trace.api.DDTags.THREAD_ID; import static datadog.trace.api.DDTags.THREAD_NAME; @@ -58,6 +59,7 @@ public static TagsMatcher defaultTags() { tagMatchers.put(SPAN_LINKS, any()); // this is checked by LinksAsserter tagMatchers.put(DD_INTEGRATION, any()); tagMatchers.put(TRACER_HOST, any()); + tagMatchers.put(SDK_OTLP_EXPORT, any()); for (String tagName : REQUIRED_CODE_ORIGIN_TAGS) { tagMatchers.put(tagName, any()); diff --git a/dd-java-agent/instrumentation/ognl-appsec-3.3.2/src/test/groovy/datadog/trace/instrumentation/ognl/OgnlInstrumentationSpec.groovy b/dd-java-agent/instrumentation/ognl-appsec-3.3.2/src/test/groovy/datadog/trace/instrumentation/ognl/OgnlInstrumentationSpec.groovy index d2d77722b24..5ccffe3a34d 100644 --- a/dd-java-agent/instrumentation/ognl-appsec-3.3.2/src/test/groovy/datadog/trace/instrumentation/ognl/OgnlInstrumentationSpec.groovy +++ b/dd-java-agent/instrumentation/ognl-appsec-3.3.2/src/test/groovy/datadog/trace/instrumentation/ognl/OgnlInstrumentationSpec.groovy @@ -29,6 +29,7 @@ class OgnlInstrumentationSpec extends InstrumentationSpecification { 'ognl.expression' 'foo' assertedTags << 'thread.name' assertedTags << 'thread.id' + assertedTags << '_dd.sdk.otlp_export' } } } diff --git a/dd-java-agent/instrumentation/spring/spring-webmvc/spring-webmvc-3.1/src/test/groovy/test/boot/SpringBootBasedTest.groovy b/dd-java-agent/instrumentation/spring/spring-webmvc/spring-webmvc-3.1/src/test/groovy/test/boot/SpringBootBasedTest.groovy index 89b0389c3da..77bce7d4c02 100644 --- a/dd-java-agent/instrumentation/spring/spring-webmvc/spring-webmvc-3.1/src/test/groovy/test/boot/SpringBootBasedTest.groovy +++ b/dd-java-agent/instrumentation/spring/spring-webmvc/spring-webmvc-3.1/src/test/groovy/test/boot/SpringBootBasedTest.groovy @@ -544,6 +544,7 @@ class SpringBootBasedTest extends HttpServerTest "runtime-id" String "thread.id" Number "thread.name" String + "_dd.sdk.otlp_export" "false" serviceNameSource "inferred_proxy" } } diff --git a/dd-java-agent/testing/src/main/groovy/datadog/trace/agent/test/asserts/TagsAssert.groovy b/dd-java-agent/testing/src/main/groovy/datadog/trace/agent/test/asserts/TagsAssert.groovy index b8d5d355b6c..f789081f7f9 100644 --- a/dd-java-agent/testing/src/main/groovy/datadog/trace/agent/test/asserts/TagsAssert.groovy +++ b/dd-java-agent/testing/src/main/groovy/datadog/trace/agent/test/asserts/TagsAssert.groovy @@ -107,6 +107,7 @@ class TagsAssert { assertedTags.add(DDTags.DJM_ENABLED) assertedTags.add(DDTags.PARENT_ID) assertedTags.add(DDTags.SPAN_LINKS) // this is checked by LinksAsserter + assertedTags.add(DDTags.SDK_OTLP_EXPORT) DDTags.REQUIRED_CODE_ORIGIN_TAGS.each { assertedTags.add(it) } From 0ca3f4b801cb6ef475c855b7f9c1829e725bcd5c Mon Sep 17 00:00:00 2001 From: Matthew Li Date: Tue, 22 Sep 2026 14:41:55 -0400 Subject: [PATCH 3/7] replace span tag w/ OtlpResourceAttribute, payload tags, and chunk tags --- .../agent/test/assertions/TagsMatcher.java | 2 - .../ognl/OgnlInstrumentationSpec.groovy | 1 - .../test/boot/SpringBootBasedTest.groovy | 1 - .../agent/test/asserts/TagsAssert.groovy | 1 - .../common/writer/ddagent/TraceMapper.java | 9 + .../writer/ddagent/TraceMapperV0_4.java | 14 +- .../writer/ddagent/TraceMapperV0_5.java | 10 ++ .../common/writer/ddagent/TraceMapperV1.java | 10 +- .../java/datadog/trace/core/CoreTracer.java | 4 +- .../otlp/common/OtlpResourceAttributes.java | 10 +- .../trace/common/writer/DDAgentApiTest.java | 5 + .../writer/DDAgentWriterCombinedTest.java | 23 ++- .../ddagent/TraceMapperV04PayloadTest.java | 162 ++++++++++++------ .../ddagent/TraceMapperV05PayloadTest.java | 150 +++++++++++++--- .../ddagent/TraceMapperV1PayloadTest.java | 27 ++- .../writer/ddagent/V1PayloadReader.java | 15 ++ .../trace/core/CoreSpanBuilderTest.java | 25 --- .../datadog/trace/core/CoreTracerTest.java | 11 +- .../datadog/trace/core/DDSpanContextTest.java | 9 - .../trace/core/DDSpanSerializationTest.java | 17 +- .../otlp/common/OtlpResourceJsonTest.java | 19 +- .../otlp/common/OtlpResourceProtoTest.java | 19 +- 22 files changed, 397 insertions(+), 147 deletions(-) diff --git a/dd-java-agent/instrumentation-testing/src/main/java/datadog/trace/agent/test/assertions/TagsMatcher.java b/dd-java-agent/instrumentation-testing/src/main/java/datadog/trace/agent/test/assertions/TagsMatcher.java index aabd0bbcc13..7fe7cd0645e 100644 --- a/dd-java-agent/instrumentation-testing/src/main/java/datadog/trace/agent/test/assertions/TagsMatcher.java +++ b/dd-java-agent/instrumentation-testing/src/main/java/datadog/trace/agent/test/assertions/TagsMatcher.java @@ -15,7 +15,6 @@ import static datadog.trace.api.DDTags.REQUIRED_CODE_ORIGIN_TAGS; import static datadog.trace.api.DDTags.RUNTIME_ID_TAG; import static datadog.trace.api.DDTags.SCHEMA_VERSION_TAG_KEY; -import static datadog.trace.api.DDTags.SDK_OTLP_EXPORT; import static datadog.trace.api.DDTags.SPAN_LINKS; import static datadog.trace.api.DDTags.THREAD_ID; import static datadog.trace.api.DDTags.THREAD_NAME; @@ -59,7 +58,6 @@ public static TagsMatcher defaultTags() { tagMatchers.put(SPAN_LINKS, any()); // this is checked by LinksAsserter tagMatchers.put(DD_INTEGRATION, any()); tagMatchers.put(TRACER_HOST, any()); - tagMatchers.put(SDK_OTLP_EXPORT, any()); for (String tagName : REQUIRED_CODE_ORIGIN_TAGS) { tagMatchers.put(tagName, any()); diff --git a/dd-java-agent/instrumentation/ognl-appsec-3.3.2/src/test/groovy/datadog/trace/instrumentation/ognl/OgnlInstrumentationSpec.groovy b/dd-java-agent/instrumentation/ognl-appsec-3.3.2/src/test/groovy/datadog/trace/instrumentation/ognl/OgnlInstrumentationSpec.groovy index 5ccffe3a34d..d2d77722b24 100644 --- a/dd-java-agent/instrumentation/ognl-appsec-3.3.2/src/test/groovy/datadog/trace/instrumentation/ognl/OgnlInstrumentationSpec.groovy +++ b/dd-java-agent/instrumentation/ognl-appsec-3.3.2/src/test/groovy/datadog/trace/instrumentation/ognl/OgnlInstrumentationSpec.groovy @@ -29,7 +29,6 @@ class OgnlInstrumentationSpec extends InstrumentationSpecification { 'ognl.expression' 'foo' assertedTags << 'thread.name' assertedTags << 'thread.id' - assertedTags << '_dd.sdk.otlp_export' } } } diff --git a/dd-java-agent/instrumentation/spring/spring-webmvc/spring-webmvc-3.1/src/test/groovy/test/boot/SpringBootBasedTest.groovy b/dd-java-agent/instrumentation/spring/spring-webmvc/spring-webmvc-3.1/src/test/groovy/test/boot/SpringBootBasedTest.groovy index 77bce7d4c02..89b0389c3da 100644 --- a/dd-java-agent/instrumentation/spring/spring-webmvc/spring-webmvc-3.1/src/test/groovy/test/boot/SpringBootBasedTest.groovy +++ b/dd-java-agent/instrumentation/spring/spring-webmvc/spring-webmvc-3.1/src/test/groovy/test/boot/SpringBootBasedTest.groovy @@ -544,7 +544,6 @@ class SpringBootBasedTest extends HttpServerTest "runtime-id" String "thread.id" Number "thread.name" String - "_dd.sdk.otlp_export" "false" serviceNameSource "inferred_proxy" } } diff --git a/dd-java-agent/testing/src/main/groovy/datadog/trace/agent/test/asserts/TagsAssert.groovy b/dd-java-agent/testing/src/main/groovy/datadog/trace/agent/test/asserts/TagsAssert.groovy index f789081f7f9..b8d5d355b6c 100644 --- a/dd-java-agent/testing/src/main/groovy/datadog/trace/agent/test/asserts/TagsAssert.groovy +++ b/dd-java-agent/testing/src/main/groovy/datadog/trace/agent/test/asserts/TagsAssert.groovy @@ -107,7 +107,6 @@ class TagsAssert { assertedTags.add(DDTags.DJM_ENABLED) assertedTags.add(DDTags.PARENT_ID) assertedTags.add(DDTags.SPAN_LINKS) // this is checked by LinksAsserter - assertedTags.add(DDTags.SDK_OTLP_EXPORT) DDTags.REQUIRED_CODE_ORIGIN_TAGS.each { assertedTags.add(it) } diff --git a/dd-trace-core/src/main/java/datadog/trace/common/writer/ddagent/TraceMapper.java b/dd-trace-core/src/main/java/datadog/trace/common/writer/ddagent/TraceMapper.java index 0dd3eb5e41a..83f4b0af2a8 100644 --- a/dd-trace-core/src/main/java/datadog/trace/common/writer/ddagent/TraceMapper.java +++ b/dd-trace-core/src/main/java/datadog/trace/common/writer/ddagent/TraceMapper.java @@ -1,5 +1,6 @@ package datadog.trace.common.writer.ddagent; +import datadog.trace.api.Config; import datadog.trace.api.DDTags; import datadog.trace.bootstrap.instrumentation.api.UTF8BytesString; import datadog.trace.common.writer.RemoteMapper; @@ -12,4 +13,12 @@ public interface TraceMapper extends RemoteMapper { UTF8BytesString.create(DDSpanContext.PRIORITY_SAMPLING_KEY); static final UTF8BytesString ORIGIN_KEY = UTF8BytesString.create(DDTags.ORIGIN_KEY); static final UTF8BytesString PROCESS_TAGS_KEY = UTF8BytesString.create(DDTags.PROCESS_TAGS); + + static final UTF8BytesString SDK_OTLP_EXPORT_KEY = UTF8BytesString.create(DDTags.SDK_OTLP_EXPORT); + static final UTF8BytesString SDK_OTLP_EXPORT_TRUE = UTF8BytesString.create("true"); + static final UTF8BytesString SDK_OTLP_EXPORT_FALSE = UTF8BytesString.create("false"); + + static UTF8BytesString otlpExportMarker(Config config) { + return config.isOtlpTracesExportEnabled() ? SDK_OTLP_EXPORT_TRUE : SDK_OTLP_EXPORT_FALSE; + } } diff --git a/dd-trace-core/src/main/java/datadog/trace/common/writer/ddagent/TraceMapperV0_4.java b/dd-trace-core/src/main/java/datadog/trace/common/writer/ddagent/TraceMapperV0_4.java index 58fd278cf43..72b3ac47e9f 100644 --- a/dd-trace-core/src/main/java/datadog/trace/common/writer/ddagent/TraceMapperV0_4.java +++ b/dd-trace-core/src/main/java/datadog/trace/common/writer/ddagent/TraceMapperV0_4.java @@ -70,11 +70,17 @@ public TraceMapperV0_4() { private static final class MetaWriter implements MetadataConsumer { + private final UTF8BytesString otlpExportMarker; + private Writable writable; private boolean firstSpanInTrace; private boolean lastSpanInTrace; private boolean firstSpanInPayload; + MetaWriter(UTF8BytesString otlpExportMarker) { + this.otlpExportMarker = otlpExportMarker; + } + MetaWriter withWritable(Writable writable) { this.writable = writable; return this; @@ -101,12 +107,14 @@ public void accept(Metadata metadata) { final boolean writeSamplingPriority = firstSpanInTrace || lastSpanInTrace || metadata.topLevel(); final UTF8BytesString processTags = firstSpanInPayload ? metadata.processTags() : null; + final UTF8BytesString otlpExport = firstSpanInPayload ? otlpExportMarker : null; int metaSize = metadata.getBaggage().size() + tags.size() + (UNSET_STATUS == metadata.getHttpStatusCode() ? 0 : 1) + (null == metadata.getOrigin() ? 0 : 1) + (null == processTags ? 0 : 1) + + (null == otlpExport ? 0 : 1) + 1; int metricsSize = (writeSamplingPriority && metadata.hasSamplingPriority() ? 1 : 0) @@ -206,6 +214,10 @@ public void accept(Metadata metadata) { writable.writeUTF8(PROCESS_TAGS_KEY); writable.writeUTF8(processTags); } + if (otlpExport != null) { + writable.writeUTF8(SDK_OTLP_EXPORT_KEY); + writable.writeUTF8(otlpExport); + } tags.forEach( writable, @@ -316,7 +328,7 @@ private void writeMetaStructEntry( } } - private final MetaWriter metaWriter = new MetaWriter(); + private final MetaWriter metaWriter = new MetaWriter(TraceMapper.otlpExportMarker(Config.get())); private final MetaStructWriter metaStructWriter = new MetaStructWriter(); @Override diff --git a/dd-trace-core/src/main/java/datadog/trace/common/writer/ddagent/TraceMapperV0_5.java b/dd-trace-core/src/main/java/datadog/trace/common/writer/ddagent/TraceMapperV0_5.java index 60f221402d5..9de23e16003 100644 --- a/dd-trace-core/src/main/java/datadog/trace/common/writer/ddagent/TraceMapperV0_5.java +++ b/dd-trace-core/src/main/java/datadog/trace/common/writer/ddagent/TraceMapperV0_5.java @@ -8,6 +8,7 @@ import datadog.communication.serialization.Writable; import datadog.communication.serialization.WritableFormatter; import datadog.communication.serialization.msgpack.MsgPackWriter; +import datadog.trace.api.Config; import datadog.trace.api.TagMap; import datadog.trace.api.TagMap.EntryReader; import datadog.trace.api.internal.VisibleForTesting; @@ -35,6 +36,9 @@ public final class TraceMapperV0_5 implements TraceMapper { private final GrowableBuffer dictionary; private final MetaWriter metaWriter = new MetaWriter(); + + private final UTF8BytesString otlpExportMarker = TraceMapper.otlpExportMarker(Config.get()); + private final int size; private boolean firstSpanWritten; @@ -220,6 +224,7 @@ public void accept(Metadata metadata) { final boolean writeSamplingPriority = firstSpanInTrace || lastSpanInTrace || metadata.topLevel(); final UTF8BytesString processTags = firstSpanInPayload ? metadata.processTags() : null; + final UTF8BytesString otlpExport = firstSpanInPayload ? otlpExportMarker : null; TagMap tags = metadata.getTags(); @@ -229,6 +234,7 @@ public void accept(Metadata metadata) { + (UNSET_STATUS == metadata.getHttpStatusCode() ? 0 : 1) + (null == metadata.getOrigin() ? 0 : 1) + (null == processTags ? 0 : 1) + + (null == otlpExport ? 0 : 1) + 1; int metricsSize = (writeSamplingPriority && metadata.hasSamplingPriority() ? 1 : 0) @@ -272,6 +278,10 @@ public void accept(Metadata metadata) { writeDictionaryEncoded(writable, PROCESS_TAGS_KEY); writeDictionaryEncoded(writable, processTags); } + if (null != otlpExport) { + writeDictionaryEncoded(writable, SDK_OTLP_EXPORT_KEY); + writeDictionaryEncoded(writable, otlpExport); + } for (TagMap.EntryReader entry : tags) { if (entry.isNumber()) continue; diff --git a/dd-trace-core/src/main/java/datadog/trace/common/writer/ddagent/TraceMapperV1.java b/dd-trace-core/src/main/java/datadog/trace/common/writer/ddagent/TraceMapperV1.java index 6a0ac4d7b3b..c311ed2902e 100644 --- a/dd-trace-core/src/main/java/datadog/trace/common/writer/ddagent/TraceMapperV1.java +++ b/dd-trace-core/src/main/java/datadog/trace/common/writer/ddagent/TraceMapperV1.java @@ -71,6 +71,9 @@ public final class TraceMapperV1 implements TraceMapper { private final MsgPackWriter metaStructWriter; private final ByteBuffer header; + /** Snapshot taken at construction, the way {@code TraceMapperV0_4}/{@code V0_5} do it. */ + private final String otlpExportMarker = TraceMapper.otlpExportMarker(Config.get()).toString(); + public TraceMapperV1(int bufferSize) { this.bufferSize = bufferSize; this.stringTable = new StringTable(); @@ -665,8 +668,11 @@ private ByteBuffer buildHeader() { // attributes = 10, a collection of key to value pairs common in all `chunks` CharSequence processTags = ProcessTags.getTagsForSerialization(); - Map tags = - processTags != null ? singletonMap(DDTags.PROCESS_TAGS, processTags) : emptyMap(); + Map tags = new HashMap<>(4); + tags.put(DDTags.SDK_OTLP_EXPORT, otlpExportMarker); + if (processTags != null) { + tags.put(DDTags.PROCESS_TAGS, processTags); + } encodeAttributes(headerWriter, 10, tags); // chunks = 11, a list of trace `chunks`, value is written by PayloadV1 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 608c84f1101..e9e130847b9 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 @@ -4,7 +4,6 @@ import static datadog.trace.api.DDTags.DJM_ENABLED; import static datadog.trace.api.DDTags.DSM_ENABLED; import static datadog.trace.api.DDTags.PROFILING_CONTEXT_ENGINE; -import static datadog.trace.api.DDTags.SDK_OTLP_EXPORT; import static datadog.trace.api.TracePropagationBehaviorExtract.IGNORE; import static datadog.trace.bootstrap.instrumentation.api.AgentPropagation.BAGGAGE_CONCERN; import static datadog.trace.bootstrap.instrumentation.api.AgentPropagation.DSM_CONCERN; @@ -2580,7 +2579,7 @@ protected ConfigSnapshot( */ static TagMap withTracerTags( Map userSpanTags, Config config, TraceConfig traceConfig) { - final TagMap result = TagMap.create(userSpanTags.size() + 6); + final TagMap result = TagMap.create(userSpanTags.size() + 5); result.putAll(userSpanTags); // Version is conditionally managed by InternalTagsAdder (added only when service == DD_SERVICE // and not set during the request), so keep it OUT of the trace-level bundle. This matters under @@ -2611,7 +2610,6 @@ static TagMap withTracerTags( if (config.isDataStreamsEnabled()) { result.set(DSM_ENABLED, 1); } - result.set(SDK_OTLP_EXPORT, config.isOtlpTracesExportEnabled() ? "true" : "false"); } if (null != traceConfig) { // dynamic if (traceConfig.isDataStreamsEnabled()) { diff --git a/dd-trace-core/src/main/java/datadog/trace/core/otlp/common/OtlpResourceAttributes.java b/dd-trace-core/src/main/java/datadog/trace/core/otlp/common/OtlpResourceAttributes.java index 6ee69ee4b98..0da3b903dc1 100644 --- a/dd-trace-core/src/main/java/datadog/trace/core/otlp/common/OtlpResourceAttributes.java +++ b/dd-trace-core/src/main/java/datadog/trace/core/otlp/common/OtlpResourceAttributes.java @@ -1,6 +1,7 @@ package datadog.trace.core.otlp.common; import static datadog.communication.ddagent.TracerVersion.TRACER_VERSION; +import static datadog.trace.api.DDTags.SDK_OTLP_EXPORT; import static java.util.Arrays.asList; import datadog.trace.api.Config; @@ -35,7 +36,8 @@ private OtlpResourceAttributes() {} "telemetry.sdk.name", "telemetry.sdk.version", "telemetry.sdk.language", - "datadog.sdk.semantics")); + "datadog.sdk.semantics", + SDK_OTLP_EXPORT)); /** * {@code value} is a {@link String}, except {@code datadog.process_tags}: a {@code List}. @@ -82,13 +84,15 @@ static void visitResourceAttributes( /** * Builds the extra resource attributes for the OTLP trace export: the {@code _dd.stats_computed} * marker when the SDK is computing OTLP span metrics, so a downstream Agent does not recompute - * them from the exported spans; {@code datadog.sdk.semantics} marker when the SDK to note whether - * Datadog or OTel semantics are used. + * them from the exported spans; {@code datadog.sdk.semantics}, recording whether the SDK applied + * Datadog or OTel semantics; and {@code _dd.sdk.otlp_export}, which is always {@code "true"} here + * because reaching this encoder means the payload is leaving over OTLP. */ static Map traceResourceAttributes(Config config) { Map attributes = new LinkedHashMap<>(); attributes.put( "datadog.sdk.semantics", config.isTraceOtelSemanticsEnabled() ? "otel" : "datadog"); + attributes.put(SDK_OTLP_EXPORT, "true"); if (config.isOtelTracesSpanMetricsEnabled()) { attributes.put(STATS_COMPUTED_KEY, "true"); } diff --git a/dd-trace-core/src/test/java/datadog/trace/common/writer/DDAgentApiTest.java b/dd-trace-core/src/test/java/datadog/trace/common/writer/DDAgentApiTest.java index 4e0abf5fbab..47f23ea119a 100644 --- a/dd-trace-core/src/test/java/datadog/trace/common/writer/DDAgentApiTest.java +++ b/dd-trace-core/src/test/java/datadog/trace/common/writer/DDAgentApiTest.java @@ -25,6 +25,7 @@ import datadog.metrics.impl.MonitoringImpl; import datadog.trace.agent.test.server.http.JavaTestHttpServer; import datadog.trace.api.Config; +import datadog.trace.api.DDTags; import datadog.trace.api.ProcessTags; import datadog.trace.api.ProtocolVersion; import datadog.trace.api.config.OtlpConfig; @@ -252,6 +253,8 @@ void testContentIsSentAsMsgpackServiceSpan() throws IOException { && ProcessTags.getTagsForSerialization() != null) { meta.put("_dd.tags.process", ProcessTags.getTagsForSerialization().toString()); } + // payload-scoped marker, written on the first span of the first non-empty chunk + meta.put(DDTags.SDK_OTLP_EXPORT, "false"); Map metrics = new TreeMap<>(); metrics.put(DDSpanContext.PRIORITY_SAMPLING_KEY, 1); metrics.put(InstrumentationTags.DD_TOP_LEVEL.toString(), 1); @@ -328,6 +331,8 @@ void testContentIsSentAsMsgpackResourceSpan() throws IOException { && ProcessTags.getTagsForSerialization() != null) { meta.put("_dd.tags.process", ProcessTags.getTagsForSerialization().toString()); } + // payload-scoped marker, written on the first span of the first non-empty chunk + meta.put(DDTags.SDK_OTLP_EXPORT, "false"); Map metrics = new TreeMap<>(); metrics.put(DDSpanContext.PRIORITY_SAMPLING_KEY, 1); metrics.put(InstrumentationTags.DD_TOP_LEVEL.toString(), 1); diff --git a/dd-trace-core/src/test/java/datadog/trace/common/writer/DDAgentWriterCombinedTest.java b/dd-trace-core/src/test/java/datadog/trace/common/writer/DDAgentWriterCombinedTest.java index ae9741fe827..3f1bc9d1324 100644 --- a/dd-trace-core/src/test/java/datadog/trace/common/writer/DDAgentWriterCombinedTest.java +++ b/dd-trace-core/src/test/java/datadog/trace/common/writer/DDAgentWriterCombinedTest.java @@ -269,8 +269,11 @@ void testDefaultBufferSizeFor(String agentVersion) { TraceMapper mapper = agentVersion.equals("v0.5/traces") ? new TraceMapperV0_5() : new TraceMapperV0_4(); - int traceSize = calculateSize(minimalTrace, mapper); - int maxedPayloadTraceCount = (mapper.messageBufferSize() / traceSize); + // The first trace of a payload is larger: it carries the payload-scoped _dd.sdk.otlp_export + // marker on its first span. Size both cases so the overflow point is exact. + int firstTraceSize = calculateSize(minimalTrace, mapper, true); + int traceSize = calculateSize(minimalTrace, mapper, false); + int maxedPayloadTraceCount = 1 + (mapper.messageBufferSize() - firstTraceSize) / traceSize; when(discovery.getTraceEndpoint()).thenReturn(agentVersion); when(api.sendSerializedTraces( @@ -824,13 +827,25 @@ void statsdCommFailure() throws Exception { healthMetrics.close(); } - static int calculateSize(List trace, TraceMapper mapper) { + /** + * Serialized size of {@code trace}, either as the first trace of a payload (which carries the + * payload-scoped markers on its first span) or as any later trace. Uses a throwaway mapper of the + * same kind so the caller's mapper keeps its state. + */ + static int calculateSize(List trace, TraceMapper mapper, boolean firstInPayload) { AtomicInteger size = new AtomicInteger(); MsgPackWriter packer = new MsgPackWriter( new FlushingBuffer( 1024, (messageCount, buffer) -> size.set(buffer.limit() - buffer.position()))); - packer.format(trace, mapper); + TraceMapper sizingMapper = + mapper instanceof TraceMapperV0_5 ? new TraceMapperV0_5() : new TraceMapperV0_4(); + if (!firstInPayload) { + // burn the payload-scoped markers on a throwaway trace + packer.format(trace, sizingMapper); + packer.flush(); + } + packer.format(trace, sizingMapper); packer.flush(); return size.get(); } diff --git a/dd-trace-core/src/test/java/datadog/trace/common/writer/ddagent/TraceMapperV04PayloadTest.java b/dd-trace-core/src/test/java/datadog/trace/common/writer/ddagent/TraceMapperV04PayloadTest.java index 15483b94c95..226d85f2653 100644 --- a/dd-trace-core/src/test/java/datadog/trace/common/writer/ddagent/TraceMapperV04PayloadTest.java +++ b/dd-trace-core/src/test/java/datadog/trace/common/writer/ddagent/TraceMapperV04PayloadTest.java @@ -1,6 +1,9 @@ package datadog.trace.common.writer.ddagent; +import static datadog.trace.api.config.TracerConfig.WRITER_TYPE; import static datadog.trace.bootstrap.instrumentation.api.InstrumentationTags.DD_MEASURED; +import static datadog.trace.bootstrap.instrumentation.api.WriterConstants.MULTI_WRITER_TYPE; +import static datadog.trace.bootstrap.instrumentation.api.WriterConstants.OTLP_WRITER_TYPE; import static datadog.trace.common.writer.TraceGenerator.generateRandomTraces; import static datadog.trace.common.writer.ddagent.PayloadVerifiers.assertEqualsWithNullAsEmpty; import static datadog.trace.common.writer.ddagent.PayloadVerifiers.unpackNumber; @@ -23,10 +26,12 @@ import datadog.trace.common.writer.Payload; import datadog.trace.common.writer.TraceGenerator.PojoSpan; import datadog.trace.core.DDSpanContext; +import datadog.trace.test.junit.utils.config.WithConfig; import datadog.trace.test.junit.utils.config.WithConfigExtension; import java.io.IOException; import java.nio.ByteBuffer; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.List; @@ -101,24 +106,7 @@ private static Stream tracesWrittenCorrectlyArguments() { @MethodSource("fullSixtyFourBitTraceAndSpanIdentifiersArguments") void fullSixtyFourBitTraceAndSpanIdentifiers( String scenario, DDTraceId traceId, long spanId, long parentId) { - PojoSpan span = - new PojoSpan( - "service", - "operation", - "resource", - traceId, - spanId, - parentId, - 123L, - 456L, - 0, - Collections.emptyMap(), - Collections.emptyMap(), - "type", - false, - 0, - 0, - "origin"); + PojoSpan span = plainSpan(traceId, spanId, parentId); List> traces = Collections.singletonList(Collections.singletonList(span)); TraceMapperV0_4 traceMapper = new TraceMapperV0_4(); PayloadVerifier verifier = new PayloadVerifier(traces, traceMapper); @@ -139,24 +127,7 @@ private static Stream fullSixtyFourBitTraceAndSpanIdentifiersArgument @Test void metaStructSupport() { - PojoSpan span = - new PojoSpan( - "service", - "operation", - "resource", - DDTraceId.ONE, - 1L, - -1L, - 123L, - 456L, - 0, - Collections.emptyMap(), - Collections.emptyMap(), - "type", - false, - 0, - 0, - "origin"); + PojoSpan span = plainSpan(1L); List> stack = new ArrayList<>(); for (StackTraceElement element : Thread.currentThread().getStackTrace()) { Map frame = new HashMap<>(); @@ -200,24 +171,7 @@ void processTagsSerialization() { assertNotNull(ProcessTags.getTagsForSerialization()); List spans = new ArrayList<>(); for (long spanId = 1; spanId <= 2; ++spanId) { - spans.add( - new PojoSpan( - "service", - "operation", - "resource", - DDTraceId.ONE, - spanId, - -1L, - 123L, - 456L, - 0, - Collections.emptyMap(), - Collections.emptyMap(), - "type", - false, - 0, - 0, - "origin")); + spans.add(plainSpan(spanId)); } List> traces = Collections.singletonList(spans); @@ -231,6 +185,75 @@ void processTagsSerialization() { verifier.verifyTracesConsumed(); } + /** + * v0.4 has no payload-level field, so the export-mode marker rides on the first span of the first + * non-empty chunk and the Agent hoists it onto {@code TracerPayload.tags}. + */ + @Test + void otlpExportMarkerOnlyOnFirstSpanOfFirstNonEmptyChunk() { + List> traces = + Arrays.asList( + Collections.emptyList(), + Arrays.asList(plainSpan(1), plainSpan(2)), + Collections.singletonList(plainSpan(3))); + TraceMapperV0_4 traceMapper = new TraceMapperV0_4(); + PayloadVerifier verifier = new PayloadVerifier(traces, traceMapper); + MsgPackWriter packer = new MsgPackWriter(new FlushingBuffer(200 << 10, verifier)); + + for (List trace : traces) { + assertTrue(packer.format(trace, traceMapper)); + } + packer.flush(); + + verifier.verifyTracesConsumed(); + // The verifier already asserts exactly one marker per payload, that it sits on span 0 and that + // it holds the expected value; pin down *which chunk* it landed on: the first NON-EMPTY one, + // not the leading empty one and not a later one. + assertEquals(1, verifier.otlpExportTraceIndex()); + } + + @Test + @WithConfig( + key = WRITER_TYPE, + value = MULTI_WRITER_TYPE + ":" + OTLP_WRITER_TYPE + ",DDAgentWriter") + void otlpExportMarkerIsTrueWhenAlsoExportingOverOtlp() { + List> traces = + Collections.singletonList(Collections.singletonList(plainSpan(1))); + TraceMapperV0_4 traceMapper = new TraceMapperV0_4(); + PayloadVerifier verifier = new PayloadVerifier(traces, traceMapper).expectOtlpExport("true"); + MsgPackWriter packer = new MsgPackWriter(new FlushingBuffer(200 << 10, verifier)); + + packer.format(traces.get(0), traceMapper); + packer.flush(); + + verifier.verifyTracesConsumed(); + assertEquals(0, verifier.otlpExportTraceIndex()); + } + + private static PojoSpan plainSpan(long spanId) { + return plainSpan(DDTraceId.ONE, spanId, -1L); + } + + private static PojoSpan plainSpan(DDTraceId traceId, long spanId, long parentId) { + return new PojoSpan( + "service", + "operation", + "resource", + traceId, + spanId, + parentId, + 123L, + 456L, + 0, + Collections.emptyMap(), + Collections.emptyMap(), + "type", + false, + 0, + 0, + "origin"); + } + private static final class PayloadVerifier implements ByteBufferConsumer { private final List> expectedTraces; @@ -241,6 +264,16 @@ private static final class PayloadVerifier implements ByteBufferConsumer { private int position = 0; + /** Expected value of the payload-scoped {@code _dd.sdk.otlp_export} marker. */ + private String expectedOtlpExport = "false"; + + /** + * Payload-spanning index of the chunk the marker was last seen on, or {@code -1} if it was + * never seen. The span index within that chunk is not tracked: {@link #accept} already asserts + * the marker only ever rides span 0. + */ + private int otlpExportTraceIndex = -1; + private PayloadVerifier(List> traces, TraceMapperV0_4 mapper) { this(traces, mapper, null); } @@ -254,6 +287,16 @@ private PayloadVerifier( this.metaStructVerifier = metaStructVerifier; } + /** Sets the expected {@code _dd.sdk.otlp_export} value (defaults to {@code "false"}). */ + PayloadVerifier expectOtlpExport(String value) { + this.expectedOtlpExport = value; + return this; + } + + int otlpExportTraceIndex() { + return otlpExportTraceIndex; + } + void skipLargeTrace() { ++position; } @@ -264,6 +307,7 @@ public void accept(int messageCount, ByteBuffer buffer) { return; } int processTagsCount = 0; + int otlpExportCount = 0; try { Payload payload = mapper.newPayload().withBody(messageCount, buffer); payload.writeTo(channel); @@ -358,6 +402,14 @@ public void accept(int messageCount, ByteBuffer buffer) { assertEquals(0, k); assertEquals(ProcessTags.getTagsForSerialization().toString(), entry.getValue()); processTagsCount++; + } else if (DDTags.SDK_OTLP_EXPORT.equals(entry.getKey())) { + // Payload-scoped: only the first span of the first non-empty chunk carries it. + otlpExportCount++; + assertEquals(0, k); + assertEquals(expectedOtlpExport, entry.getValue()); + // `position` was post-incremented when this trace was picked up, so the current + // trace's payload-spanning index is `position - 1`. + otlpExportTraceIndex = position - 1; } else { Object tag = expectedSpan.getTag(entry.getKey()); if (tag != null) { @@ -389,6 +441,8 @@ public void accept(int messageCount, ByteBuffer buffer) { channel.resetForWriting(); assertEquals( Config.get().isExperimentalPropagateProcessTagsEnabled() ? 1 : 0, processTagsCount); + // exactly one _dd.sdk.otlp_export per payload, never per span + assertEquals(1, otlpExportCount); } } diff --git a/dd-trace-core/src/test/java/datadog/trace/common/writer/ddagent/TraceMapperV05PayloadTest.java b/dd-trace-core/src/test/java/datadog/trace/common/writer/ddagent/TraceMapperV05PayloadTest.java index 6dedd2e9ef6..aada5c9bb7b 100644 --- a/dd-trace-core/src/test/java/datadog/trace/common/writer/ddagent/TraceMapperV05PayloadTest.java +++ b/dd-trace-core/src/test/java/datadog/trace/common/writer/ddagent/TraceMapperV05PayloadTest.java @@ -1,7 +1,10 @@ package datadog.trace.common.writer.ddagent; import static datadog.trace.api.config.GeneralConfig.EXPERIMENTAL_PROPAGATE_PROCESS_TAGS_ENABLED; +import static datadog.trace.api.config.TracerConfig.WRITER_TYPE; import static datadog.trace.bootstrap.instrumentation.api.InstrumentationTags.DD_MEASURED; +import static datadog.trace.bootstrap.instrumentation.api.WriterConstants.MULTI_WRITER_TYPE; +import static datadog.trace.bootstrap.instrumentation.api.WriterConstants.OTLP_WRITER_TYPE; import static datadog.trace.common.writer.TraceGenerator.generateRandomTraces; import static datadog.trace.common.writer.ddagent.PayloadVerifiers.assertEqualsWithNullAsEmpty; import static datadog.trace.common.writer.ddagent.PayloadVerifiers.unpackNumber; @@ -30,6 +33,7 @@ import java.io.IOException; import java.nio.ByteBuffer; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.List; @@ -95,10 +99,14 @@ void bodyOverflowCausesAFlush() { PrioritySampling.UNSET, 0, null)); - int traceSize = calculateSize(repeatedTrace); + // The first trace of a payload is larger: it carries the payload-scoped _dd.sdk.otlp_export + // marker on its first span. Size both cases so the overflow point is exact. + int firstTraceSize = calculateSize(repeatedTrace, true); + int traceSize = calculateSize(repeatedTrace, false); // 30KB body int bufferSize = 30 << 10; - int tracesRequiredToOverflowBody = (int) Math.ceil((double) bufferSize / traceSize) + 1; + int tracesThatFitInBody = 1 + (bufferSize - firstTraceSize) / traceSize; + int tracesRequiredToOverflowBody = tracesThatFitInBody + 1; List> traces = new ArrayList<>(tracesRequiredToOverflowBody); for (int i = 0; i < tracesRequiredToOverflowBody; ++i) { traces.add(repeatedTrace); @@ -174,24 +182,7 @@ void processTagsSerialization() { assertNotNull(ProcessTags.getTagsForSerialization()); List spans = new ArrayList<>(); for (long spanId = 1; spanId <= 2; ++spanId) { - spans.add( - new PojoSpan( - "service", - "operation", - "resource", - DDTraceId.ONE, - spanId, - -1L, - 123L, - 456L, - 0, - Collections.emptyMap(), - Collections.emptyMap(), - "type", - false, - 0, - 0, - "origin")); + spans.add(plainSpan(spanId)); } List> traces = Collections.singletonList(spans); @@ -205,6 +196,71 @@ void processTagsSerialization() { verifier.verifyTracesConsumed(); } + /** + * v0.5 has no payload-level field, so the export-mode marker rides on the first span of the first + * non-empty chunk and the Agent hoists it onto {@code TracerPayload.tags}. + */ + @Test + void otlpExportMarkerOnlyOnFirstSpanOfFirstNonEmptyChunk() { + List> traces = + Arrays.asList( + Collections.emptyList(), + Arrays.asList(plainSpan(1), plainSpan(2)), + Collections.singletonList(plainSpan(3))); + TraceMapperV0_5 traceMapper = new TraceMapperV0_5(); + PayloadVerifier verifier = new PayloadVerifier(traces, traceMapper); + MsgPackWriter packer = new MsgPackWriter(new FlushingBuffer(200 << 10, verifier)); + + for (List trace : traces) { + assertTrue(packer.format(trace, traceMapper)); + } + packer.flush(); + + verifier.verifyTracesConsumed(); + // The verifier already asserts exactly one marker per payload, that it sits on span 0 and that + // it holds the expected value; pin down *which chunk* it landed on: the first NON-EMPTY one, + // not the leading empty one and not a later one. + assertEquals(1, verifier.otlpExportTraceIndex()); + } + + @Test + @WithConfig( + key = WRITER_TYPE, + value = MULTI_WRITER_TYPE + ":" + OTLP_WRITER_TYPE + ",DDAgentWriter") + void otlpExportMarkerIsTrueWhenAlsoExportingOverOtlp() { + List> traces = + Collections.singletonList(Collections.singletonList(plainSpan(1))); + TraceMapperV0_5 traceMapper = new TraceMapperV0_5(); + PayloadVerifier verifier = new PayloadVerifier(traces, traceMapper).expectOtlpExport("true"); + MsgPackWriter packer = new MsgPackWriter(new FlushingBuffer(200 << 10, verifier)); + + packer.format(traces.get(0), traceMapper); + packer.flush(); + + verifier.verifyTracesConsumed(); + assertEquals(0, verifier.otlpExportTraceIndex()); + } + + private static PojoSpan plainSpan(long spanId) { + return new PojoSpan( + "service", + "operation", + "resource", + DDTraceId.ONE, + spanId, + -1L, + 123L, + 456L, + 0, + Collections.emptyMap(), + Collections.emptyMap(), + "type", + false, + 0, + 0, + "origin"); + } + private static final class PayloadVerifier implements ByteBufferConsumer { private final List> expectedTraces; @@ -213,6 +269,16 @@ private static final class PayloadVerifier implements ByteBufferConsumer { private int position = 0; + /** Expected value of the payload-scoped {@code _dd.sdk.otlp_export} marker. */ + private String expectedOtlpExport = "false"; + + /** + * Payload-spanning index of the chunk the marker was last seen on, or {@code -1} if it was + * never seen. The span index within that chunk is not tracked: {@link #accept} already asserts + * the marker only ever rides span 0. + */ + private int otlpExportTraceIndex = -1; + private PayloadVerifier(List> traces, TraceMapperV0_5 mapper) { this(traces, mapper, 200 << 10); } @@ -223,13 +289,27 @@ private PayloadVerifier(List> traces, TraceMapperV0_5 mapper, int this.channel = new PayloadVerifiers.CapturingChannel(size); } + /** Sets the expected {@code _dd.sdk.otlp_export} value (defaults to {@code "false"}). */ + PayloadVerifier expectOtlpExport(String value) { + this.expectedOtlpExport = value; + return this; + } + + int otlpExportTraceIndex() { + return otlpExportTraceIndex; + } + void skipLargeTrace() { ++position; } @Override public void accept(int messageCount, ByteBuffer buffer) { + if (expectedTraces.isEmpty() && messageCount == 0) { + return; + } int processTagsCount = 0; + int otlpExportCount = 0; try { Payload payload = mapper.newPayload().withBody(messageCount, buffer); payload.writeTo(channel); @@ -283,6 +363,14 @@ public void accept(int messageCount, ByteBuffer buffer) { assertTrue(Config.get().isExperimentalPropagateProcessTagsEnabled()); assertEquals(0, k); assertEquals(ProcessTags.getTagsForSerialization().toString(), entry.getValue()); + } else if (DDTags.SDK_OTLP_EXPORT.equals(entry.getKey())) { + // Payload-scoped: only the first span of the first non-empty chunk carries it. + otlpExportCount++; + assertEquals(0, k); + assertEquals(expectedOtlpExport, entry.getValue()); + // `position` was post-incremented when this trace was picked up, so the current + // trace's payload-spanning index is `position - 1`. + otlpExportTraceIndex = position - 1; } else { Object tag = expectedSpan.getTag(entry.getKey()); if (tag != null) { @@ -334,10 +422,14 @@ public void accept(int messageCount, ByteBuffer buffer) { } catch (IOException e) { fail(e.getMessage()); } finally { - assertEquals( - Config.get().isExperimentalPropagateProcessTagsEnabled() ? 1 : 0, processTagsCount); + // Reset before asserting: a failing assertion here must not leave the mapper and channel + // dirty for the next payload of a multi-payload test. mapper.reset(); channel.resetForWriting(); + assertEquals( + Config.get().isExperimentalPropagateProcessTagsEnabled() ? 1 : 0, processTagsCount); + // exactly one _dd.sdk.otlp_export per payload, never per span + assertEquals(1, otlpExportCount); } } @@ -346,7 +438,11 @@ void verifyTracesConsumed() { } } - private static int calculateSize(List trace) { + /** + * Serialized size of {@code trace}, either as the first trace of a payload (which carries the + * payload-scoped markers on its first span) or as any later trace. + */ + private static int calculateSize(List trace, boolean firstInPayload) { AtomicInteger size = new AtomicInteger(); MsgPackWriter packer = new MsgPackWriter( @@ -358,7 +454,13 @@ public void accept(int messageCount, ByteBuffer buffer) { size.set(buffer.limit() - buffer.position()); } })); - packer.format(trace, new TraceMapperV0_5(1024)); + TraceMapperV0_5 mapper = new TraceMapperV0_5(1024); + if (!firstInPayload) { + // burn the payload-scoped markers on a throwaway trace + packer.format(trace, mapper); + packer.flush(); + } + packer.format(trace, mapper); packer.flush(); return size.get(); } diff --git a/dd-trace-core/src/test/java/datadog/trace/common/writer/ddagent/TraceMapperV1PayloadTest.java b/dd-trace-core/src/test/java/datadog/trace/common/writer/ddagent/TraceMapperV1PayloadTest.java index 1eacd903b96..9c8786b4642 100644 --- a/dd-trace-core/src/test/java/datadog/trace/common/writer/ddagent/TraceMapperV1PayloadTest.java +++ b/dd-trace-core/src/test/java/datadog/trace/common/writer/ddagent/TraceMapperV1PayloadTest.java @@ -1,9 +1,11 @@ package datadog.trace.common.writer.ddagent; import static datadog.trace.api.DDTags.PROCESS_TAGS; +import static datadog.trace.api.DDTags.SDK_OTLP_EXPORT; import static datadog.trace.api.DDTags.SPAN_EVENTS; import static datadog.trace.api.DDTags.THREAD_ID; import static datadog.trace.api.DDTags.THREAD_NAME; +import static datadog.trace.api.config.TracerConfig.WRITER_TYPE; import static datadog.trace.api.sampling.PrioritySampling.SAMPLER_KEEP; import static datadog.trace.bootstrap.instrumentation.api.Tags.COMPONENT; import static datadog.trace.bootstrap.instrumentation.api.Tags.ENV; @@ -12,6 +14,8 @@ import static datadog.trace.bootstrap.instrumentation.api.Tags.SPAN_KIND; import static datadog.trace.bootstrap.instrumentation.api.Tags.SPAN_KIND_CLIENT; import static datadog.trace.bootstrap.instrumentation.api.Tags.VERSION; +import static datadog.trace.bootstrap.instrumentation.api.WriterConstants.MULTI_WRITER_TYPE; +import static datadog.trace.bootstrap.instrumentation.api.WriterConstants.OTLP_WRITER_TYPE; import static datadog.trace.common.writer.TraceGenerator.generateRandomTraces; import static datadog.trace.common.writer.ddagent.PayloadVerifiers.assertEqualsWithNullAsEmpty; import static datadog.trace.common.writer.ddagent.V1PayloadReader.newStringTable; @@ -19,6 +23,7 @@ import static datadog.trace.common.writer.ddagent.V1PayloadReader.readBinary; import static datadog.trace.common.writer.ddagent.V1PayloadReader.readFirstChunk; import static datadog.trace.common.writer.ddagent.V1PayloadReader.readFirstSpan; +import static datadog.trace.common.writer.ddagent.V1PayloadReader.readPayloadAttributes; import static datadog.trace.common.writer.ddagent.V1PayloadReader.readStreamingString; import static datadog.trace.common.writer.ddagent.V1PayloadReader.skipChunkField; import static datadog.trace.common.writer.ddagent.V1PayloadReader.skipPayloadField; @@ -58,6 +63,7 @@ import datadog.trace.common.writer.ddagent.V1PayloadReader.V1SpanEvent; import datadog.trace.common.writer.ddagent.V1PayloadReader.V1SpanLink; import datadog.trace.core.MetadataConsumer; +import datadog.trace.test.junit.utils.config.WithConfig; import datadog.trace.test.junit.utils.config.WithConfigExtension; import java.io.ByteArrayOutputStream; import java.io.IOException; @@ -235,16 +241,33 @@ void payloadContainsExpectedHeaderAndChunkFields() throws IOException { assertEquals(EXPECTED_PAYLOAD_FIELD_IDS, payloadFieldsSeen); assertEquals(1, chunkCount); assertNotNull(payloadAttributes); + // The export-mode marker is payload-scoped and always written; process tags are conditional. + assertEquals("false", payloadAttributes.get(SDK_OTLP_EXPORT)); CharSequence processTags = ProcessTags.getTagsForSerialization(); if (processTags == null) { - assertEquals(0, payloadAttributes.size()); - } else { assertEquals(1, payloadAttributes.size()); + } else { + assertEquals(2, payloadAttributes.size()); assertEquals(processTags.toString(), payloadAttributes.get(PROCESS_TAGS)); } } } + /** + * v1 has a payload-level attribute map, so the export-mode marker lives there rather than on the + * first span the way v0.4/v0.5 must place it. The {@code "false"} default is covered by {@link + * #payloadContainsExpectedHeaderAndChunkFields()}, which also pins the attribute-map size. + */ + @Test + @WithConfig( + key = WRITER_TYPE, + value = MULTI_WRITER_TYPE + ":" + OTLP_WRITER_TYPE + ",DDAgentWriter") + void otlpExportMarkerIsTrueWhenAlsoExportingOverOtlp() throws IOException { + Map attributes = readPayloadAttributes(serializeV1Payload(span(emptyMap()))); + + assertEquals("true", attributes.get(SDK_OTLP_EXPORT)); + } + // expectedSamplingMechanism 0 is SamplingMechanism.DEFAULT. @TableTest({ "scenario | decisionMakerTag | expectedSamplingMechanism", diff --git a/dd-trace-core/src/test/java/datadog/trace/common/writer/ddagent/V1PayloadReader.java b/dd-trace-core/src/test/java/datadog/trace/common/writer/ddagent/V1PayloadReader.java index 4dfdaf4bbd7..82ef0b2df65 100644 --- a/dd-trace-core/src/test/java/datadog/trace/common/writer/ddagent/V1PayloadReader.java +++ b/dd-trace-core/src/test/java/datadog/trace/common/writer/ddagent/V1PayloadReader.java @@ -150,6 +150,21 @@ public static V1Chunk readFirstChunk(byte[] encoded) throws IOException { throw new AssertionError("Could not find first chunk in v1 payload"); } + /** Decodes the payload-level attribute map (field 10) of an encoded V1 payload. */ + public static Map readPayloadAttributes(byte[] encoded) throws IOException { + MessageUnpacker unpacker = MessagePack.newDefaultUnpacker(new ArrayBufferInput(encoded)); + List stringTable = newStringTable(); + int payloadFieldCount = unpacker.unpackMapHeader(); + for (int i = 0; i < payloadFieldCount; i++) { + int payloadFieldId = unpacker.unpackInt(); + if (payloadFieldId == PayloadField.ATTRIBUTES) { + return readAttributes(unpacker, stringTable); + } + skipPayloadField(unpacker, payloadFieldId, stringTable); + } + throw new AssertionError("Could not find payload attributes in v1 payload"); + } + /** Creates a string table seeded with the empty string at index 0, as the writer expects. */ public static List newStringTable() { List stringTable = new ArrayList<>(); 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 763150fdb09..9d9367551a7 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 @@ -9,14 +9,10 @@ import static datadog.trace.api.DDTags.PROFILING_ENABLED; import static datadog.trace.api.DDTags.RUNTIME_ID_TAG; import static datadog.trace.api.DDTags.SCHEMA_VERSION_TAG_KEY; -import static datadog.trace.api.DDTags.SDK_OTLP_EXPORT; import static datadog.trace.api.DDTags.THREAD_ID; import static datadog.trace.api.DDTags.THREAD_NAME; import static datadog.trace.api.TracePropagationStyle.DATADOG; -import static datadog.trace.api.config.GeneralConfig.TAGS; -import static datadog.trace.api.config.TracerConfig.WRITER_TYPE; import static datadog.trace.bootstrap.instrumentation.api.AgentTracer.noopSpan; -import static datadog.trace.bootstrap.instrumentation.api.WriterConstants.OTLP_WRITER_TYPE; import static datadog.trace.test.junit.utils.config.WithConfigExtension.injectSysConfig; import static java.util.concurrent.TimeUnit.MILLISECONDS; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -74,26 +70,6 @@ void buildSimpleSpan() { assertEquals("op name", span.getOperationName()); } - @Test - void nativeWriterSetsOtlpExportMarkerToFalse() { - DDSpan span = (DDSpan) tracer.buildSpan("test", "operation").start(); - assertEquals("false", span.getTag(SDK_OTLP_EXPORT)); - } - - @Test - @WithConfig(key = WRITER_TYPE, value = OTLP_WRITER_TYPE) - void otlpWriterSetsOtlpExportMarkerToTrue() { - DDSpan span = (DDSpan) tracer.buildSpan("test", "operation").start(); - assertEquals("true", span.getTag(SDK_OTLP_EXPORT)); - } - - @Test - @WithConfig(key = TAGS, value = "_dd.sdk.otlp_export:true") - void userTagCannotOverrideOtlpExportMarker() { - DDSpan span = (DDSpan) tracer.buildSpan("test", "operation").start(); - assertEquals("false", span.getTag(SDK_OTLP_EXPORT)); - } - @Test void buildComplexSpan() { String expectedName = "fakeName"; @@ -611,7 +587,6 @@ private Map productTags() { if (Config.get().isDataJobsEnabled()) { productTags.put(DJM_ENABLED, 1); } - productTags.put(SDK_OTLP_EXPORT, Config.get().isOtlpTracesExportEnabled() ? "true" : "false"); return productTags; } diff --git a/dd-trace-core/src/test/java/datadog/trace/core/CoreTracerTest.java b/dd-trace-core/src/test/java/datadog/trace/core/CoreTracerTest.java index 9f46a79c7fb..db850470da0 100644 --- a/dd-trace-core/src/test/java/datadog/trace/core/CoreTracerTest.java +++ b/dd-trace-core/src/test/java/datadog/trace/core/CoreTracerTest.java @@ -262,7 +262,7 @@ void verifyMappingConfigsOnTracer(String scenario, String mapString, Map withOtlpExportMarker(Map tags) { - Map expectedTags = new LinkedHashMap<>(tags); - expectedTags.put(DDTags.SDK_OTLP_EXPORT, "false"); - return expectedTags; - } - static final String ACTION_JSON = "'{\"action\": \"enable\", \"lib_config\":" + "{\"tracing_sampling_rate\": null," diff --git a/dd-trace-core/src/test/java/datadog/trace/core/DDSpanContextTest.java b/dd-trace-core/src/test/java/datadog/trace/core/DDSpanContextTest.java index 78c7544b7e9..030687c06ec 100644 --- a/dd-trace-core/src/test/java/datadog/trace/core/DDSpanContextTest.java +++ b/dd-trace-core/src/test/java/datadog/trace/core/DDSpanContextTest.java @@ -99,18 +99,12 @@ void nullValuesForTagsDeleteExistingTags(String name) throws Exception { assertEquals("fakeType", context.getSpanType().toString()); } - /** - * Every caller of this helper exports the span before asserting, so the expected tags include the - * markers CoreTracer stamps at write time. {@code _dd.sdk.otlp_export} is {@code "false"} here - * because these tests use the default (native) writer — see {@link CoreSpanBuilderTest}. - */ private static Map createExpectedTagsFromCurrentThread() { Thread thread = Thread.currentThread(); Map expectedTags = new HashMap<>(); expectedTags.put(THREAD_NAME, thread.getName()); expectedTags.put(THREAD_ID, thread.getId()); expectedTags.put(DDTags.DD_SVC_SRC, ServiceNameSources.MANUAL); - expectedTags.put(DDTags.SDK_OTLP_EXPORT, "false"); return expectedTags; } @@ -282,14 +276,11 @@ void setTraceSegmentTagsAndDataOnCorrectSpan() { expectedTopTags.put("ttt", "t3"); expectedTopTags.put(dataTag("tcd"), "[4]"); expectedTopTags.put("tct", "t4"); - // Declared on every span at creation — see CoreSpanBuilderTest. - expectedTopTags.put(DDTags.SDK_OTLP_EXPORT, "false"); assertTagmap(topC.getTags(), expectedTopTags, true); Map expectedCurrentTags = new HashMap<>(); expectedCurrentTags.put(dataTag("ccd"), "[2]"); expectedCurrentTags.put("cct", "t2"); - expectedCurrentTags.put(DDTags.SDK_OTLP_EXPORT, "false"); assertTagmap(currentC.getTags(), expectedCurrentTags, true); current.finish(); diff --git a/dd-trace-core/src/test/java/datadog/trace/core/DDSpanSerializationTest.java b/dd-trace-core/src/test/java/datadog/trace/core/DDSpanSerializationTest.java index 839abe61a39..10512f3c582 100644 --- a/dd-trace-core/src/test/java/datadog/trace/core/DDSpanSerializationTest.java +++ b/dd-trace-core/src/test/java/datadog/trace/core/DDSpanSerializationTest.java @@ -1,5 +1,6 @@ package datadog.trace.core; +import static datadog.trace.api.DDTags.SDK_OTLP_EXPORT; import static datadog.trace.api.DDTags.SPAN_EVENTS; import static datadog.trace.api.DDTags.SPAN_LINKS; import static datadog.trace.api.TracePropagationStyle.DATADOG; @@ -245,7 +246,9 @@ void serializeTraceWithBaggageAndTagsCorrectlyV04( for (int j = 0; j < packedSize; j++) { String k = unpacker.unpackString(); String v = unpacker.unpackString(); - if (!"thread.name".equals(k) && !"thread.id".equals(k)) { + // thread.* and the payload-scoped _dd.sdk.otlp_export marker are written by the + // mapper, not by the span under test (see TraceMapperV04/V05PayloadTest). + if (!"thread.name".equals(k) && !"thread.id".equals(k) && !SDK_OTLP_EXPORT.equals(k)) { unpackedMeta.put(k, v); } } @@ -324,7 +327,9 @@ void serializeTraceWithBaggageAndTagsCorrectlyV05( for (int j = 0; j < packedSize; j++) { String k = dictionary[unpacker.unpackInt()]; String v = dictionary[unpacker.unpackInt()]; - if (!"thread.name".equals(k) && !"thread.id".equals(k)) { + // thread.* and the payload-scoped _dd.sdk.otlp_export marker are written by the + // mapper, not by the span under test (see TraceMapperV04/V05PayloadTest). + if (!"thread.name".equals(k) && !"thread.id".equals(k) && !SDK_OTLP_EXPORT.equals(k)) { unpackedMeta.put(k, v); } } @@ -562,7 +567,9 @@ void serializeTraceWithFlatMapTagV04() throws Exception { for (int j = 0; j < packedSize; j++) { String k = unpacker.unpackString(); String v = unpacker.unpackString(); - if (!"thread.name".equals(k) && !"thread.id".equals(k)) { + // thread.* and the payload-scoped _dd.sdk.otlp_export marker are written by the + // mapper, not by the span under test (see TraceMapperV04/V05PayloadTest). + if (!"thread.name".equals(k) && !"thread.id".equals(k) && !SDK_OTLP_EXPORT.equals(k)) { unpackedMeta.put(k, v); } } @@ -632,7 +639,9 @@ void serializeTraceWithFlatMapTagV05() throws Exception { for (int j = 0; j < packedSize; j++) { String k = dictionary[unpacker.unpackInt()]; String v = dictionary[unpacker.unpackInt()]; - if (!"thread.name".equals(k) && !"thread.id".equals(k)) { + // thread.* and the payload-scoped _dd.sdk.otlp_export marker are written by the + // mapper, not by the span under test (see TraceMapperV04/V05PayloadTest). + if (!"thread.name".equals(k) && !"thread.id".equals(k) && !SDK_OTLP_EXPORT.equals(k)) { unpackedMeta.put(k, v); } } diff --git a/dd-trace-core/src/test/java/datadog/trace/core/otlp/common/OtlpResourceJsonTest.java b/dd-trace-core/src/test/java/datadog/trace/core/otlp/common/OtlpResourceJsonTest.java index 0661ff3eee3..102027cf668 100644 --- a/dd-trace-core/src/test/java/datadog/trace/core/otlp/common/OtlpResourceJsonTest.java +++ b/dd-trace-core/src/test/java/datadog/trace/core/otlp/common/OtlpResourceJsonTest.java @@ -127,7 +127,8 @@ static Stream resourceFragmentCases() { + "telemetry.sdk.name:ignored-sdk," + "telemetry.sdk.version:ignored-version," + "telemetry.sdk.language:ignored-language," - + "datadog.sdk.semantics:ignored-semantics"), + + "datadog.sdk.semantics:ignored-semantics," + + "_dd.sdk.otlp_export:ignored-export"), attrs( "service.name", "my-service", "deployment.environment.name", "staging", @@ -149,6 +150,21 @@ void testBuildResourceFragment( assertEquals(expectedAttributes, actualAttributes, "For case: " + caseName); } + /** + * On the OTLP path the export-mode marker is a resource attribute and is always {@code "true"} -- + * reaching this encoder means the payload is leaving over OTLP. + */ + @Test + void traceResourceAttributesCarryOtlpExportMarker() throws IOException { + Config config = Config.get(props(SERVICE_NAME, "my-service")); + + Map attributes = + parseResourceAttributes( + OtlpResourceJson.buildResourceFragment(config, traceResourceAttributes(config))); + + assertEquals("true", attributes.get("_dd.sdk.otlp_export")); + } + @Test void usesOtelSdkSemanticsWhenEnabled() throws IOException { Config config = Config.get(props(TRACE_OTEL_SEMANTICS_ENABLED, "true")); @@ -225,6 +241,7 @@ void statsComputedVariantCarriesMarker() throws IOException { "true", withMarker.get("_dd.stats_computed"), "marker present when stats computed"); assertFalse(without.containsKey("_dd.stats_computed"), "marker absent when stats not computed"); assertEquals("datadog", without.get("datadog.sdk.semantics")); + assertEquals("true", without.get("_dd.sdk.otlp_export")); } @Test diff --git a/dd-trace-core/src/test/java/datadog/trace/core/otlp/common/OtlpResourceProtoTest.java b/dd-trace-core/src/test/java/datadog/trace/core/otlp/common/OtlpResourceProtoTest.java index a061bcce635..f2831b3bd19 100644 --- a/dd-trace-core/src/test/java/datadog/trace/core/otlp/common/OtlpResourceProtoTest.java +++ b/dd-trace-core/src/test/java/datadog/trace/core/otlp/common/OtlpResourceProtoTest.java @@ -149,7 +149,8 @@ static Stream resourceMessageCases() { + "telemetry.sdk.name:ignored-sdk," + "telemetry.sdk.version:ignored-version," + "telemetry.sdk.language:ignored-language," - + "datadog.sdk.semantics:ignored-semantics"), + + "datadog.sdk.semantics:ignored-semantics," + + "_dd.sdk.otlp_export:ignored-export"), attrs( "service.name", "my-service", "deployment.environment.name", "staging", @@ -171,6 +172,21 @@ void testBuildResourceMessage( assertEquals(expectedAttributes, actualAttributes, "For case: " + caseName); } + /** + * On the OTLP path the export-mode marker is a resource attribute and is always {@code "true"} -- + * reaching this encoder means the payload is leaving over OTLP. + */ + @Test + void traceResourceAttributesCarryOtlpExportMarker() throws IOException { + Config config = Config.get(props(SERVICE_NAME, "my-service")); + + Map attributes = + parseResourceAttributes( + OtlpResourceProto.buildResourceMessage(config, traceResourceAttributes(config))); + + assertEquals("true", attributes.get("_dd.sdk.otlp_export")); + } + @Test void usesOtelSdkSemanticsWhenEnabled() throws IOException { Config config = Config.get(props(TRACE_OTEL_SEMANTICS_ENABLED, "true")); @@ -250,6 +266,7 @@ void statsComputedVariantCarriesMarker() throws IOException { "true", withMarker.get("_dd.stats_computed"), "marker present when stats computed"); assertFalse(without.containsKey("_dd.stats_computed"), "marker absent when stats not computed"); assertEquals("datadog", without.get("datadog.sdk.semantics")); + assertEquals("true", without.get("_dd.sdk.otlp_export")); } // ── parsing helpers ─────────────────────────────────────────────────────── From e3a4b8ec02f4f95f40fe0e0fb40b7a822c119310 Mon Sep 17 00:00:00 2001 From: Matthew Li Date: Tue, 22 Sep 2026 16:17:56 -0400 Subject: [PATCH 4/7] remove _dd.sdk.otlp_export from DDTags --- dd-trace-api/src/main/java/datadog/trace/api/DDTags.java | 1 - .../datadog/trace/common/writer/ddagent/TraceMapper.java | 5 ++++- .../trace/common/writer/ddagent/TraceMapperV1.java | 2 +- .../trace/core/otlp/common/OtlpResourceAttributes.java | 8 +++++--- .../java/datadog/trace/common/writer/DDAgentApiTest.java | 5 ++--- .../common/writer/ddagent/TraceMapperV04PayloadTest.java | 2 +- .../common/writer/ddagent/TraceMapperV05PayloadTest.java | 2 +- .../common/writer/ddagent/TraceMapperV1PayloadTest.java | 2 +- .../java/datadog/trace/core/DDSpanSerializationTest.java | 2 +- 9 files changed, 16 insertions(+), 13 deletions(-) diff --git a/dd-trace-api/src/main/java/datadog/trace/api/DDTags.java b/dd-trace-api/src/main/java/datadog/trace/api/DDTags.java index 756048551c9..b7ca19232c6 100644 --- a/dd-trace-api/src/main/java/datadog/trace/api/DDTags.java +++ b/dd-trace-api/src/main/java/datadog/trace/api/DDTags.java @@ -105,5 +105,4 @@ public class DDTags { public static final String PROCESS_TAGS = "_dd.tags.process"; public static final String DD_INTEGRATION = "_dd.integration"; public static final String DD_SVC_SRC = "_dd.svc_src"; - public static final String SDK_OTLP_EXPORT = "_dd.sdk.otlp_export"; } diff --git a/dd-trace-core/src/main/java/datadog/trace/common/writer/ddagent/TraceMapper.java b/dd-trace-core/src/main/java/datadog/trace/common/writer/ddagent/TraceMapper.java index 83f4b0af2a8..8caa5cdf0a2 100644 --- a/dd-trace-core/src/main/java/datadog/trace/common/writer/ddagent/TraceMapper.java +++ b/dd-trace-core/src/main/java/datadog/trace/common/writer/ddagent/TraceMapper.java @@ -14,7 +14,10 @@ public interface TraceMapper extends RemoteMapper { static final UTF8BytesString ORIGIN_KEY = UTF8BytesString.create(DDTags.ORIGIN_KEY); static final UTF8BytesString PROCESS_TAGS_KEY = UTF8BytesString.create(DDTags.PROCESS_TAGS); - static final UTF8BytesString SDK_OTLP_EXPORT_KEY = UTF8BytesString.create(DDTags.SDK_OTLP_EXPORT); + /** Payload-scoped marker written at serialization time; never stored as a span tag. */ + static final String SDK_OTLP_EXPORT = "_dd.sdk.otlp_export"; + + static final UTF8BytesString SDK_OTLP_EXPORT_KEY = UTF8BytesString.create(SDK_OTLP_EXPORT); static final UTF8BytesString SDK_OTLP_EXPORT_TRUE = UTF8BytesString.create("true"); static final UTF8BytesString SDK_OTLP_EXPORT_FALSE = UTF8BytesString.create("false"); diff --git a/dd-trace-core/src/main/java/datadog/trace/common/writer/ddagent/TraceMapperV1.java b/dd-trace-core/src/main/java/datadog/trace/common/writer/ddagent/TraceMapperV1.java index c311ed2902e..a92cc7b5aaa 100644 --- a/dd-trace-core/src/main/java/datadog/trace/common/writer/ddagent/TraceMapperV1.java +++ b/dd-trace-core/src/main/java/datadog/trace/common/writer/ddagent/TraceMapperV1.java @@ -669,7 +669,7 @@ private ByteBuffer buildHeader() { // attributes = 10, a collection of key to value pairs common in all `chunks` CharSequence processTags = ProcessTags.getTagsForSerialization(); Map tags = new HashMap<>(4); - tags.put(DDTags.SDK_OTLP_EXPORT, otlpExportMarker); + tags.put(SDK_OTLP_EXPORT, otlpExportMarker); if (processTags != null) { tags.put(DDTags.PROCESS_TAGS, processTags); } diff --git a/dd-trace-core/src/main/java/datadog/trace/core/otlp/common/OtlpResourceAttributes.java b/dd-trace-core/src/main/java/datadog/trace/core/otlp/common/OtlpResourceAttributes.java index 0da3b903dc1..9fc2d408b46 100644 --- a/dd-trace-core/src/main/java/datadog/trace/core/otlp/common/OtlpResourceAttributes.java +++ b/dd-trace-core/src/main/java/datadog/trace/core/otlp/common/OtlpResourceAttributes.java @@ -1,7 +1,6 @@ package datadog.trace.core.otlp.common; import static datadog.communication.ddagent.TracerVersion.TRACER_VERSION; -import static datadog.trace.api.DDTags.SDK_OTLP_EXPORT; import static java.util.Arrays.asList; import datadog.trace.api.Config; @@ -24,6 +23,9 @@ private OtlpResourceAttributes() {} /** Marks that the Agent should not recompute trace metrics from the exported spans. */ private static final String STATS_COMPUTED_KEY = "_dd.stats_computed"; + /** Marks that the payload was exported by the SDK over OTLP. */ + private static final String SDK_OTLP_EXPORT_KEY = "_dd.sdk.otlp_export"; + private static final Set IGNORED_GLOBAL_TAGS = new HashSet<>( asList( @@ -37,7 +39,7 @@ private OtlpResourceAttributes() {} "telemetry.sdk.version", "telemetry.sdk.language", "datadog.sdk.semantics", - SDK_OTLP_EXPORT)); + SDK_OTLP_EXPORT_KEY)); /** * {@code value} is a {@link String}, except {@code datadog.process_tags}: a {@code List}. @@ -92,7 +94,7 @@ static Map traceResourceAttributes(Config config) { Map attributes = new LinkedHashMap<>(); attributes.put( "datadog.sdk.semantics", config.isTraceOtelSemanticsEnabled() ? "otel" : "datadog"); - attributes.put(SDK_OTLP_EXPORT, "true"); + attributes.put(SDK_OTLP_EXPORT_KEY, "true"); if (config.isOtelTracesSpanMetricsEnabled()) { attributes.put(STATS_COMPUTED_KEY, "true"); } diff --git a/dd-trace-core/src/test/java/datadog/trace/common/writer/DDAgentApiTest.java b/dd-trace-core/src/test/java/datadog/trace/common/writer/DDAgentApiTest.java index 47f23ea119a..0ac98c69285 100644 --- a/dd-trace-core/src/test/java/datadog/trace/common/writer/DDAgentApiTest.java +++ b/dd-trace-core/src/test/java/datadog/trace/common/writer/DDAgentApiTest.java @@ -25,7 +25,6 @@ import datadog.metrics.impl.MonitoringImpl; import datadog.trace.agent.test.server.http.JavaTestHttpServer; import datadog.trace.api.Config; -import datadog.trace.api.DDTags; import datadog.trace.api.ProcessTags; import datadog.trace.api.ProtocolVersion; import datadog.trace.api.config.OtlpConfig; @@ -254,7 +253,7 @@ void testContentIsSentAsMsgpackServiceSpan() throws IOException { meta.put("_dd.tags.process", ProcessTags.getTagsForSerialization().toString()); } // payload-scoped marker, written on the first span of the first non-empty chunk - meta.put(DDTags.SDK_OTLP_EXPORT, "false"); + meta.put(TraceMapper.SDK_OTLP_EXPORT, "false"); Map metrics = new TreeMap<>(); metrics.put(DDSpanContext.PRIORITY_SAMPLING_KEY, 1); metrics.put(InstrumentationTags.DD_TOP_LEVEL.toString(), 1); @@ -332,7 +331,7 @@ void testContentIsSentAsMsgpackResourceSpan() throws IOException { meta.put("_dd.tags.process", ProcessTags.getTagsForSerialization().toString()); } // payload-scoped marker, written on the first span of the first non-empty chunk - meta.put(DDTags.SDK_OTLP_EXPORT, "false"); + meta.put(TraceMapper.SDK_OTLP_EXPORT, "false"); Map metrics = new TreeMap<>(); metrics.put(DDSpanContext.PRIORITY_SAMPLING_KEY, 1); metrics.put(InstrumentationTags.DD_TOP_LEVEL.toString(), 1); diff --git a/dd-trace-core/src/test/java/datadog/trace/common/writer/ddagent/TraceMapperV04PayloadTest.java b/dd-trace-core/src/test/java/datadog/trace/common/writer/ddagent/TraceMapperV04PayloadTest.java index 226d85f2653..ad9d9889284 100644 --- a/dd-trace-core/src/test/java/datadog/trace/common/writer/ddagent/TraceMapperV04PayloadTest.java +++ b/dd-trace-core/src/test/java/datadog/trace/common/writer/ddagent/TraceMapperV04PayloadTest.java @@ -402,7 +402,7 @@ public void accept(int messageCount, ByteBuffer buffer) { assertEquals(0, k); assertEquals(ProcessTags.getTagsForSerialization().toString(), entry.getValue()); processTagsCount++; - } else if (DDTags.SDK_OTLP_EXPORT.equals(entry.getKey())) { + } else if (TraceMapper.SDK_OTLP_EXPORT.equals(entry.getKey())) { // Payload-scoped: only the first span of the first non-empty chunk carries it. otlpExportCount++; assertEquals(0, k); diff --git a/dd-trace-core/src/test/java/datadog/trace/common/writer/ddagent/TraceMapperV05PayloadTest.java b/dd-trace-core/src/test/java/datadog/trace/common/writer/ddagent/TraceMapperV05PayloadTest.java index aada5c9bb7b..dedc1f834e1 100644 --- a/dd-trace-core/src/test/java/datadog/trace/common/writer/ddagent/TraceMapperV05PayloadTest.java +++ b/dd-trace-core/src/test/java/datadog/trace/common/writer/ddagent/TraceMapperV05PayloadTest.java @@ -363,7 +363,7 @@ public void accept(int messageCount, ByteBuffer buffer) { assertTrue(Config.get().isExperimentalPropagateProcessTagsEnabled()); assertEquals(0, k); assertEquals(ProcessTags.getTagsForSerialization().toString(), entry.getValue()); - } else if (DDTags.SDK_OTLP_EXPORT.equals(entry.getKey())) { + } else if (TraceMapper.SDK_OTLP_EXPORT.equals(entry.getKey())) { // Payload-scoped: only the first span of the first non-empty chunk carries it. otlpExportCount++; assertEquals(0, k); diff --git a/dd-trace-core/src/test/java/datadog/trace/common/writer/ddagent/TraceMapperV1PayloadTest.java b/dd-trace-core/src/test/java/datadog/trace/common/writer/ddagent/TraceMapperV1PayloadTest.java index 9c8786b4642..3d88ec41953 100644 --- a/dd-trace-core/src/test/java/datadog/trace/common/writer/ddagent/TraceMapperV1PayloadTest.java +++ b/dd-trace-core/src/test/java/datadog/trace/common/writer/ddagent/TraceMapperV1PayloadTest.java @@ -1,7 +1,6 @@ package datadog.trace.common.writer.ddagent; import static datadog.trace.api.DDTags.PROCESS_TAGS; -import static datadog.trace.api.DDTags.SDK_OTLP_EXPORT; import static datadog.trace.api.DDTags.SPAN_EVENTS; import static datadog.trace.api.DDTags.THREAD_ID; import static datadog.trace.api.DDTags.THREAD_NAME; @@ -18,6 +17,7 @@ import static datadog.trace.bootstrap.instrumentation.api.WriterConstants.OTLP_WRITER_TYPE; import static datadog.trace.common.writer.TraceGenerator.generateRandomTraces; import static datadog.trace.common.writer.ddagent.PayloadVerifiers.assertEqualsWithNullAsEmpty; +import static datadog.trace.common.writer.ddagent.TraceMapper.SDK_OTLP_EXPORT; import static datadog.trace.common.writer.ddagent.V1PayloadReader.newStringTable; import static datadog.trace.common.writer.ddagent.V1PayloadReader.readAttributes; import static datadog.trace.common.writer.ddagent.V1PayloadReader.readBinary; diff --git a/dd-trace-core/src/test/java/datadog/trace/core/DDSpanSerializationTest.java b/dd-trace-core/src/test/java/datadog/trace/core/DDSpanSerializationTest.java index 10512f3c582..dd8d10ee1cf 100644 --- a/dd-trace-core/src/test/java/datadog/trace/core/DDSpanSerializationTest.java +++ b/dd-trace-core/src/test/java/datadog/trace/core/DDSpanSerializationTest.java @@ -1,11 +1,11 @@ package datadog.trace.core; -import static datadog.trace.api.DDTags.SDK_OTLP_EXPORT; import static datadog.trace.api.DDTags.SPAN_EVENTS; import static datadog.trace.api.DDTags.SPAN_LINKS; import static datadog.trace.api.TracePropagationStyle.DATADOG; import static datadog.trace.api.config.GeneralConfig.EXPERIMENTAL_PROPAGATE_PROCESS_TAGS_ENABLED; import static datadog.trace.api.config.TracerConfig.TRACE_BAGGAGE_TAG_KEYS; +import static datadog.trace.common.writer.ddagent.TraceMapper.SDK_OTLP_EXPORT; import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; From b0af55866618d72bba6b7dc726e953279a970408 Mon Sep 17 00:00:00 2001 From: Matthew Li Date: Wed, 23 Sep 2026 14:55:51 -0400 Subject: [PATCH 5/7] reset mapper upon buffer overflow --- .../serialization/msgpack/MsgPackWriter.java | 2 ++ .../msgpack/MsgPackWriterTest.java | 33 +++++++++++++++++++ .../ddagent/TraceMapperV04PayloadTest.java | 3 -- .../ddagent/TraceMapperV1PayloadTest.java | 1 - 4 files changed, 35 insertions(+), 4 deletions(-) diff --git a/communication/src/main/java/datadog/communication/serialization/msgpack/MsgPackWriter.java b/communication/src/main/java/datadog/communication/serialization/msgpack/MsgPackWriter.java index 4fc9e8f967a..c86ce35bcac 100644 --- a/communication/src/main/java/datadog/communication/serialization/msgpack/MsgPackWriter.java +++ b/communication/src/main/java/datadog/communication/serialization/msgpack/MsgPackWriter.java @@ -101,6 +101,8 @@ public boolean format(T message, Mapper mapper) { } } buffer.reset(); + // the buffer is now empty, so drop any mapper state from the rejected message + mapper.reset(); return false; } } diff --git a/communication/src/test/java/datadog/communication/serialization/msgpack/MsgPackWriterTest.java b/communication/src/test/java/datadog/communication/serialization/msgpack/MsgPackWriterTest.java index fca302dd3d1..ecd57f010e5 100644 --- a/communication/src/test/java/datadog/communication/serialization/msgpack/MsgPackWriterTest.java +++ b/communication/src/test/java/datadog/communication/serialization/msgpack/MsgPackWriterTest.java @@ -14,6 +14,7 @@ import datadog.communication.serialization.Mapper; import datadog.communication.serialization.MessageFormatter; import datadog.communication.serialization.StreamingBuffer; +import datadog.communication.serialization.Writable; import datadog.trace.bootstrap.instrumentation.api.UTF8BytesString; import datadog.trace.util.stacktrace.StackTraceEvent; import datadog.trace.util.stacktrace.StackTraceFrame; @@ -65,6 +66,38 @@ public void testInsertAfterOverflow() { packer.format("abcdefghijklmnopqrstuvwxy", mapper), "data fits in buffer after overflow"); } + @Test + public void testMapperResetWhenOversizedMessageRejectedFromEmptyBuffer() { + CountingResetMapper mapper = new CountingResetMapper(); + MessageFormatter packer = new MsgPackWriter(newBuffer(2 + 25, (messageCount, buffer) -> {})); + assertFalse(packer.format("abcdefghijklmnopqrstuvwxyz", mapper)); + assertEquals(1, mapper.resets); + } + + @Test + public void testMapperResetWhenOversizedMessageRejectedAfterFlush() { + CountingResetMapper mapper = new CountingResetMapper(); + MessageFormatter packer = new MsgPackWriter(newBuffer(2 + 25, (messageCount, buffer) -> {})); + assertTrue(packer.format("abc", mapper)); + assertFalse(packer.format("abcdefghijklmnopqrstuvwxyz", mapper)); + // once before the retry, once after the retry is rejected + assertEquals(2, mapper.resets); + } + + private static final class CountingResetMapper implements Mapper { + int resets; + + @Override + public void map(String data, Writable writable) { + writable.writeString(data, null); + } + + @Override + public void reset() { + resets++; + } + } + @Test public void testFlushOfOverflow() { final List flushed = new ArrayList<>(); diff --git a/dd-trace-core/src/test/java/datadog/trace/common/writer/ddagent/TraceMapperV04PayloadTest.java b/dd-trace-core/src/test/java/datadog/trace/common/writer/ddagent/TraceMapperV04PayloadTest.java index ad9d9889284..12dbb5beaef 100644 --- a/dd-trace-core/src/test/java/datadog/trace/common/writer/ddagent/TraceMapperV04PayloadTest.java +++ b/dd-trace-core/src/test/java/datadog/trace/common/writer/ddagent/TraceMapperV04PayloadTest.java @@ -68,9 +68,6 @@ void tracesWrittenCorrectly(int bufferSize, int traceCount, boolean lowCardinali if (!packer.format(trace, traceMapper)) { verifier.skipLargeTrace(); tracesFitInBuffer = false; - // in the real like the mapper is always reset each trace. - // here we need to force it when we fail since the buffer will be reset as well - traceMapper.reset(); } } packer.flush(); diff --git a/dd-trace-core/src/test/java/datadog/trace/common/writer/ddagent/TraceMapperV1PayloadTest.java b/dd-trace-core/src/test/java/datadog/trace/common/writer/ddagent/TraceMapperV1PayloadTest.java index 3d88ec41953..00f26761456 100644 --- a/dd-trace-core/src/test/java/datadog/trace/common/writer/ddagent/TraceMapperV1PayloadTest.java +++ b/dd-trace-core/src/test/java/datadog/trace/common/writer/ddagent/TraceMapperV1PayloadTest.java @@ -155,7 +155,6 @@ void tracesWrittenCorrectly( if (!packer.format(trace, traceMapper)) { verifier.skipLargeTrace(); tracesFitInBuffer = false; - traceMapper.reset(); } } packer.flush(); From 93150876a2b4fbd82dbbda41972707f5446f26b7 Mon Sep 17 00:00:00 2001 From: Matthew Li Date: Wed, 23 Sep 2026 16:07:12 -0400 Subject: [PATCH 6/7] migrate OTLP Export marker to Metadata and add keys for OTLP Resource Attributes --- .../src/main/java/datadog/trace/api/DDTags.java | 1 + .../trace/common/writer/ddagent/TraceMapper.java | 13 +------------ .../common/writer/ddagent/TraceMapperV0_4.java | 10 ++-------- .../common/writer/ddagent/TraceMapperV0_5.java | 5 +---- .../trace/common/writer/ddagent/TraceMapperV1.java | 5 +---- .../main/java/datadog/trace/core/DDSpanContext.java | 4 ++++ .../src/main/java/datadog/trace/core/Metadata.java | 7 +++++++ .../core/otlp/common/OtlpResourceAttributes.java | 10 +++++----- .../datadog/trace/common/writer/DDAgentApiTest.java | 5 +++-- .../writer/FileBasedPayloadDispatcherTest.java | 1 + .../datadog/trace/common/writer/TraceGenerator.java | 2 ++ .../writer/ddagent/TraceMapperV04PayloadTest.java | 2 +- .../writer/ddagent/TraceMapperV05PayloadTest.java | 2 +- .../writer/ddagent/TraceMapperV1PayloadTest.java | 2 +- .../datadog/trace/core/DDSpanSerializationTest.java | 2 +- .../test/java/datadog/trace/core/MetadataTest.java | 1 + .../src/traceAgentTest/java/TraceGenerator.java | 2 ++ 17 files changed, 35 insertions(+), 39 deletions(-) diff --git a/dd-trace-api/src/main/java/datadog/trace/api/DDTags.java b/dd-trace-api/src/main/java/datadog/trace/api/DDTags.java index b7ca19232c6..756048551c9 100644 --- a/dd-trace-api/src/main/java/datadog/trace/api/DDTags.java +++ b/dd-trace-api/src/main/java/datadog/trace/api/DDTags.java @@ -105,4 +105,5 @@ public class DDTags { public static final String PROCESS_TAGS = "_dd.tags.process"; public static final String DD_INTEGRATION = "_dd.integration"; public static final String DD_SVC_SRC = "_dd.svc_src"; + public static final String SDK_OTLP_EXPORT = "_dd.sdk.otlp_export"; } diff --git a/dd-trace-core/src/main/java/datadog/trace/common/writer/ddagent/TraceMapper.java b/dd-trace-core/src/main/java/datadog/trace/common/writer/ddagent/TraceMapper.java index 8caa5cdf0a2..e6abb09bb3c 100644 --- a/dd-trace-core/src/main/java/datadog/trace/common/writer/ddagent/TraceMapper.java +++ b/dd-trace-core/src/main/java/datadog/trace/common/writer/ddagent/TraceMapper.java @@ -1,6 +1,5 @@ package datadog.trace.common.writer.ddagent; -import datadog.trace.api.Config; import datadog.trace.api.DDTags; import datadog.trace.bootstrap.instrumentation.api.UTF8BytesString; import datadog.trace.common.writer.RemoteMapper; @@ -13,15 +12,5 @@ public interface TraceMapper extends RemoteMapper { UTF8BytesString.create(DDSpanContext.PRIORITY_SAMPLING_KEY); static final UTF8BytesString ORIGIN_KEY = UTF8BytesString.create(DDTags.ORIGIN_KEY); static final UTF8BytesString PROCESS_TAGS_KEY = UTF8BytesString.create(DDTags.PROCESS_TAGS); - - /** Payload-scoped marker written at serialization time; never stored as a span tag. */ - static final String SDK_OTLP_EXPORT = "_dd.sdk.otlp_export"; - - static final UTF8BytesString SDK_OTLP_EXPORT_KEY = UTF8BytesString.create(SDK_OTLP_EXPORT); - static final UTF8BytesString SDK_OTLP_EXPORT_TRUE = UTF8BytesString.create("true"); - static final UTF8BytesString SDK_OTLP_EXPORT_FALSE = UTF8BytesString.create("false"); - - static UTF8BytesString otlpExportMarker(Config config) { - return config.isOtlpTracesExportEnabled() ? SDK_OTLP_EXPORT_TRUE : SDK_OTLP_EXPORT_FALSE; - } + static final UTF8BytesString SDK_OTLP_EXPORT_KEY = UTF8BytesString.create(DDTags.SDK_OTLP_EXPORT); } diff --git a/dd-trace-core/src/main/java/datadog/trace/common/writer/ddagent/TraceMapperV0_4.java b/dd-trace-core/src/main/java/datadog/trace/common/writer/ddagent/TraceMapperV0_4.java index 72b3ac47e9f..cbdcbd41433 100644 --- a/dd-trace-core/src/main/java/datadog/trace/common/writer/ddagent/TraceMapperV0_4.java +++ b/dd-trace-core/src/main/java/datadog/trace/common/writer/ddagent/TraceMapperV0_4.java @@ -70,17 +70,11 @@ public TraceMapperV0_4() { private static final class MetaWriter implements MetadataConsumer { - private final UTF8BytesString otlpExportMarker; - private Writable writable; private boolean firstSpanInTrace; private boolean lastSpanInTrace; private boolean firstSpanInPayload; - MetaWriter(UTF8BytesString otlpExportMarker) { - this.otlpExportMarker = otlpExportMarker; - } - MetaWriter withWritable(Writable writable) { this.writable = writable; return this; @@ -107,7 +101,7 @@ public void accept(Metadata metadata) { final boolean writeSamplingPriority = firstSpanInTrace || lastSpanInTrace || metadata.topLevel(); final UTF8BytesString processTags = firstSpanInPayload ? metadata.processTags() : null; - final UTF8BytesString otlpExport = firstSpanInPayload ? otlpExportMarker : null; + final UTF8BytesString otlpExport = firstSpanInPayload ? metadata.otlpExportMarker() : null; int metaSize = metadata.getBaggage().size() + tags.size() @@ -328,7 +322,7 @@ private void writeMetaStructEntry( } } - private final MetaWriter metaWriter = new MetaWriter(TraceMapper.otlpExportMarker(Config.get())); + private final MetaWriter metaWriter = new MetaWriter(); private final MetaStructWriter metaStructWriter = new MetaStructWriter(); @Override diff --git a/dd-trace-core/src/main/java/datadog/trace/common/writer/ddagent/TraceMapperV0_5.java b/dd-trace-core/src/main/java/datadog/trace/common/writer/ddagent/TraceMapperV0_5.java index 9de23e16003..51a7f1bdb8c 100644 --- a/dd-trace-core/src/main/java/datadog/trace/common/writer/ddagent/TraceMapperV0_5.java +++ b/dd-trace-core/src/main/java/datadog/trace/common/writer/ddagent/TraceMapperV0_5.java @@ -8,7 +8,6 @@ import datadog.communication.serialization.Writable; import datadog.communication.serialization.WritableFormatter; import datadog.communication.serialization.msgpack.MsgPackWriter; -import datadog.trace.api.Config; import datadog.trace.api.TagMap; import datadog.trace.api.TagMap.EntryReader; import datadog.trace.api.internal.VisibleForTesting; @@ -37,8 +36,6 @@ public final class TraceMapperV0_5 implements TraceMapper { private final MetaWriter metaWriter = new MetaWriter(); - private final UTF8BytesString otlpExportMarker = TraceMapper.otlpExportMarker(Config.get()); - private final int size; private boolean firstSpanWritten; @@ -224,7 +221,7 @@ public void accept(Metadata metadata) { final boolean writeSamplingPriority = firstSpanInTrace || lastSpanInTrace || metadata.topLevel(); final UTF8BytesString processTags = firstSpanInPayload ? metadata.processTags() : null; - final UTF8BytesString otlpExport = firstSpanInPayload ? otlpExportMarker : null; + final UTF8BytesString otlpExport = firstSpanInPayload ? metadata.otlpExportMarker() : null; TagMap tags = metadata.getTags(); diff --git a/dd-trace-core/src/main/java/datadog/trace/common/writer/ddagent/TraceMapperV1.java b/dd-trace-core/src/main/java/datadog/trace/common/writer/ddagent/TraceMapperV1.java index a92cc7b5aaa..bf80e29637b 100644 --- a/dd-trace-core/src/main/java/datadog/trace/common/writer/ddagent/TraceMapperV1.java +++ b/dd-trace-core/src/main/java/datadog/trace/common/writer/ddagent/TraceMapperV1.java @@ -71,9 +71,6 @@ public final class TraceMapperV1 implements TraceMapper { private final MsgPackWriter metaStructWriter; private final ByteBuffer header; - /** Snapshot taken at construction, the way {@code TraceMapperV0_4}/{@code V0_5} do it. */ - private final String otlpExportMarker = TraceMapper.otlpExportMarker(Config.get()).toString(); - public TraceMapperV1(int bufferSize) { this.bufferSize = bufferSize; this.stringTable = new StringTable(); @@ -669,7 +666,7 @@ private ByteBuffer buildHeader() { // attributes = 10, a collection of key to value pairs common in all `chunks` CharSequence processTags = ProcessTags.getTagsForSerialization(); Map tags = new HashMap<>(4); - tags.put(SDK_OTLP_EXPORT, otlpExportMarker); + tags.put(DDTags.SDK_OTLP_EXPORT, String.valueOf(cfg.isOtlpTracesExportEnabled())); if (processTags != null) { tags.put(DDTags.PROCESS_TAGS, processTags); } 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 1b211b5fae1..d5cb80b286a 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 @@ -71,6 +71,9 @@ public class DDSpanContext public static final String SPAN_SAMPLING_RULE_RATE_TAG = "_dd.span_sampling.rule_rate"; public static final String SPAN_SAMPLING_MAX_PER_SECOND_TAG = "_dd.span_sampling.max_per_second"; + private static final UTF8BytesString OTLP_EXPORT_TRUE = UTF8BytesString.create("true"); + private static final UTF8BytesString OTLP_EXPORT_FALSE = UTF8BytesString.create("false"); + private static final DDCache THREAD_NAMES = DDCaches.newFixedSizeCache(256); @@ -1416,6 +1419,7 @@ void processTagsAndBaggage( getOrigin(), longRunningVersion, ProcessTags.getTagsForSerialization(), + Config.get().isOtlpTracesExportEnabled() ? OTLP_EXPORT_TRUE : OTLP_EXPORT_FALSE, restrictedSpan.getLinks())); } } diff --git a/dd-trace-core/src/main/java/datadog/trace/core/Metadata.java b/dd-trace-core/src/main/java/datadog/trace/core/Metadata.java index d957358d73a..909e9d6dff2 100644 --- a/dd-trace-core/src/main/java/datadog/trace/core/Metadata.java +++ b/dd-trace-core/src/main/java/datadog/trace/core/Metadata.java @@ -23,6 +23,7 @@ public final class Metadata { private final CharSequence origin; private final int longRunningVersion; private final UTF8BytesString processTags; + private final UTF8BytesString otlpExportMarker; private final List spanLinks; public Metadata( @@ -37,6 +38,7 @@ public Metadata( CharSequence origin, int longRunningVersion, UTF8BytesString processTags, + UTF8BytesString otlpExportMarker, List spanLinks) { this.threadId = threadId; this.threadName = threadName; @@ -49,6 +51,7 @@ public Metadata( this.origin = origin; this.longRunningVersion = longRunningVersion; this.processTags = processTags; + this.otlpExportMarker = otlpExportMarker; this.spanLinks = spanLinks == null ? emptyList() : spanLinks; } @@ -121,6 +124,10 @@ public UTF8BytesString processTags() { return processTags; } + public UTF8BytesString otlpExportMarker() { + return otlpExportMarker; + } + public List getSpanLinks() { return spanLinks; } diff --git a/dd-trace-core/src/main/java/datadog/trace/core/otlp/common/OtlpResourceAttributes.java b/dd-trace-core/src/main/java/datadog/trace/core/otlp/common/OtlpResourceAttributes.java index 9fc2d408b46..6489c7ae5bf 100644 --- a/dd-trace-core/src/main/java/datadog/trace/core/otlp/common/OtlpResourceAttributes.java +++ b/dd-trace-core/src/main/java/datadog/trace/core/otlp/common/OtlpResourceAttributes.java @@ -1,6 +1,7 @@ package datadog.trace.core.otlp.common; import static datadog.communication.ddagent.TracerVersion.TRACER_VERSION; +import static datadog.trace.api.DDTags.SDK_OTLP_EXPORT; import static java.util.Arrays.asList; import datadog.trace.api.Config; @@ -23,8 +24,7 @@ private OtlpResourceAttributes() {} /** Marks that the Agent should not recompute trace metrics from the exported spans. */ private static final String STATS_COMPUTED_KEY = "_dd.stats_computed"; - /** Marks that the payload was exported by the SDK over OTLP. */ - private static final String SDK_OTLP_EXPORT_KEY = "_dd.sdk.otlp_export"; + private static final String SDK_SEMANTICS_KEY = "datadog.sdk.semantics"; private static final Set IGNORED_GLOBAL_TAGS = new HashSet<>( @@ -38,8 +38,8 @@ private OtlpResourceAttributes() {} "telemetry.sdk.name", "telemetry.sdk.version", "telemetry.sdk.language", - "datadog.sdk.semantics", - SDK_OTLP_EXPORT_KEY)); + SDK_SEMANTICS_KEY, + SDK_OTLP_EXPORT)); /** * {@code value} is a {@link String}, except {@code datadog.process_tags}: a {@code List}. @@ -94,7 +94,7 @@ static Map traceResourceAttributes(Config config) { Map attributes = new LinkedHashMap<>(); attributes.put( "datadog.sdk.semantics", config.isTraceOtelSemanticsEnabled() ? "otel" : "datadog"); - attributes.put(SDK_OTLP_EXPORT_KEY, "true"); + attributes.put(SDK_OTLP_EXPORT, "true"); if (config.isOtelTracesSpanMetricsEnabled()) { attributes.put(STATS_COMPUTED_KEY, "true"); } diff --git a/dd-trace-core/src/test/java/datadog/trace/common/writer/DDAgentApiTest.java b/dd-trace-core/src/test/java/datadog/trace/common/writer/DDAgentApiTest.java index 0ac98c69285..66b50ff1dde 100644 --- a/dd-trace-core/src/test/java/datadog/trace/common/writer/DDAgentApiTest.java +++ b/dd-trace-core/src/test/java/datadog/trace/common/writer/DDAgentApiTest.java @@ -1,5 +1,6 @@ package datadog.trace.common.writer; +import static datadog.trace.api.DDTags.SDK_OTLP_EXPORT; import static datadog.trace.api.ProtocolVersion.V0_5; import static java.util.Collections.emptyList; import static java.util.Collections.emptyMap; @@ -253,7 +254,7 @@ void testContentIsSentAsMsgpackServiceSpan() throws IOException { meta.put("_dd.tags.process", ProcessTags.getTagsForSerialization().toString()); } // payload-scoped marker, written on the first span of the first non-empty chunk - meta.put(TraceMapper.SDK_OTLP_EXPORT, "false"); + meta.put(SDK_OTLP_EXPORT, "false"); Map metrics = new TreeMap<>(); metrics.put(DDSpanContext.PRIORITY_SAMPLING_KEY, 1); metrics.put(InstrumentationTags.DD_TOP_LEVEL.toString(), 1); @@ -331,7 +332,7 @@ void testContentIsSentAsMsgpackResourceSpan() throws IOException { meta.put("_dd.tags.process", ProcessTags.getTagsForSerialization().toString()); } // payload-scoped marker, written on the first span of the first non-empty chunk - meta.put(TraceMapper.SDK_OTLP_EXPORT, "false"); + meta.put(SDK_OTLP_EXPORT, "false"); Map metrics = new TreeMap<>(); metrics.put(DDSpanContext.PRIORITY_SAMPLING_KEY, 1); metrics.put(InstrumentationTags.DD_TOP_LEVEL.toString(), 1); diff --git a/dd-trace-core/src/test/java/datadog/trace/common/writer/FileBasedPayloadDispatcherTest.java b/dd-trace-core/src/test/java/datadog/trace/common/writer/FileBasedPayloadDispatcherTest.java index 25c26bcd10a..2eb5e86f34b 100644 --- a/dd-trace-core/src/test/java/datadog/trace/common/writer/FileBasedPayloadDispatcherTest.java +++ b/dd-trace-core/src/test/java/datadog/trace/common/writer/FileBasedPayloadDispatcherTest.java @@ -313,6 +313,7 @@ private static CoreSpan mockSpan(CharSequence type, Map tags) null, 0, null, + null, null); doAnswer( inv -> { diff --git a/dd-trace-core/src/test/java/datadog/trace/common/writer/TraceGenerator.java b/dd-trace-core/src/test/java/datadog/trace/common/writer/TraceGenerator.java index e777235fef3..93d6130df92 100644 --- a/dd-trace-core/src/test/java/datadog/trace/common/writer/TraceGenerator.java +++ b/dd-trace-core/src/test/java/datadog/trace/common/writer/TraceGenerator.java @@ -3,6 +3,7 @@ import static datadog.trace.api.sampling.PrioritySampling.UNSET; import static java.util.Collections.emptyList; +import datadog.trace.api.Config; import datadog.trace.api.DDSpanId; import datadog.trace.api.DDTags; import datadog.trace.api.DDTraceId; @@ -242,6 +243,7 @@ public PojoSpan( origin, 0, ProcessTags.getTagsForSerialization(), + UTF8BytesString.create(String.valueOf(Config.get().isOtlpTracesExportEnabled())), spanLinks); } diff --git a/dd-trace-core/src/test/java/datadog/trace/common/writer/ddagent/TraceMapperV04PayloadTest.java b/dd-trace-core/src/test/java/datadog/trace/common/writer/ddagent/TraceMapperV04PayloadTest.java index 12dbb5beaef..6663c7c9501 100644 --- a/dd-trace-core/src/test/java/datadog/trace/common/writer/ddagent/TraceMapperV04PayloadTest.java +++ b/dd-trace-core/src/test/java/datadog/trace/common/writer/ddagent/TraceMapperV04PayloadTest.java @@ -399,7 +399,7 @@ public void accept(int messageCount, ByteBuffer buffer) { assertEquals(0, k); assertEquals(ProcessTags.getTagsForSerialization().toString(), entry.getValue()); processTagsCount++; - } else if (TraceMapper.SDK_OTLP_EXPORT.equals(entry.getKey())) { + } else if (DDTags.SDK_OTLP_EXPORT.equals(entry.getKey())) { // Payload-scoped: only the first span of the first non-empty chunk carries it. otlpExportCount++; assertEquals(0, k); diff --git a/dd-trace-core/src/test/java/datadog/trace/common/writer/ddagent/TraceMapperV05PayloadTest.java b/dd-trace-core/src/test/java/datadog/trace/common/writer/ddagent/TraceMapperV05PayloadTest.java index dedc1f834e1..aada5c9bb7b 100644 --- a/dd-trace-core/src/test/java/datadog/trace/common/writer/ddagent/TraceMapperV05PayloadTest.java +++ b/dd-trace-core/src/test/java/datadog/trace/common/writer/ddagent/TraceMapperV05PayloadTest.java @@ -363,7 +363,7 @@ public void accept(int messageCount, ByteBuffer buffer) { assertTrue(Config.get().isExperimentalPropagateProcessTagsEnabled()); assertEquals(0, k); assertEquals(ProcessTags.getTagsForSerialization().toString(), entry.getValue()); - } else if (TraceMapper.SDK_OTLP_EXPORT.equals(entry.getKey())) { + } else if (DDTags.SDK_OTLP_EXPORT.equals(entry.getKey())) { // Payload-scoped: only the first span of the first non-empty chunk carries it. otlpExportCount++; assertEquals(0, k); diff --git a/dd-trace-core/src/test/java/datadog/trace/common/writer/ddagent/TraceMapperV1PayloadTest.java b/dd-trace-core/src/test/java/datadog/trace/common/writer/ddagent/TraceMapperV1PayloadTest.java index 00f26761456..0da528e2eb4 100644 --- a/dd-trace-core/src/test/java/datadog/trace/common/writer/ddagent/TraceMapperV1PayloadTest.java +++ b/dd-trace-core/src/test/java/datadog/trace/common/writer/ddagent/TraceMapperV1PayloadTest.java @@ -1,6 +1,7 @@ package datadog.trace.common.writer.ddagent; import static datadog.trace.api.DDTags.PROCESS_TAGS; +import static datadog.trace.api.DDTags.SDK_OTLP_EXPORT; import static datadog.trace.api.DDTags.SPAN_EVENTS; import static datadog.trace.api.DDTags.THREAD_ID; import static datadog.trace.api.DDTags.THREAD_NAME; @@ -17,7 +18,6 @@ import static datadog.trace.bootstrap.instrumentation.api.WriterConstants.OTLP_WRITER_TYPE; import static datadog.trace.common.writer.TraceGenerator.generateRandomTraces; import static datadog.trace.common.writer.ddagent.PayloadVerifiers.assertEqualsWithNullAsEmpty; -import static datadog.trace.common.writer.ddagent.TraceMapper.SDK_OTLP_EXPORT; import static datadog.trace.common.writer.ddagent.V1PayloadReader.newStringTable; import static datadog.trace.common.writer.ddagent.V1PayloadReader.readAttributes; import static datadog.trace.common.writer.ddagent.V1PayloadReader.readBinary; diff --git a/dd-trace-core/src/test/java/datadog/trace/core/DDSpanSerializationTest.java b/dd-trace-core/src/test/java/datadog/trace/core/DDSpanSerializationTest.java index dd8d10ee1cf..10512f3c582 100644 --- a/dd-trace-core/src/test/java/datadog/trace/core/DDSpanSerializationTest.java +++ b/dd-trace-core/src/test/java/datadog/trace/core/DDSpanSerializationTest.java @@ -1,11 +1,11 @@ package datadog.trace.core; +import static datadog.trace.api.DDTags.SDK_OTLP_EXPORT; import static datadog.trace.api.DDTags.SPAN_EVENTS; import static datadog.trace.api.DDTags.SPAN_LINKS; import static datadog.trace.api.TracePropagationStyle.DATADOG; import static datadog.trace.api.config.GeneralConfig.EXPERIMENTAL_PROPAGATE_PROCESS_TAGS_ENABLED; import static datadog.trace.api.config.TracerConfig.TRACE_BAGGAGE_TAG_KEYS; -import static datadog.trace.common.writer.ddagent.TraceMapper.SDK_OTLP_EXPORT; import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; diff --git a/dd-trace-core/src/test/java/datadog/trace/core/MetadataTest.java b/dd-trace-core/src/test/java/datadog/trace/core/MetadataTest.java index 2815cac6730..eab3afd2812 100644 --- a/dd-trace-core/src/test/java/datadog/trace/core/MetadataTest.java +++ b/dd-trace-core/src/test/java/datadog/trace/core/MetadataTest.java @@ -62,6 +62,7 @@ private static Metadata metadataWithStatus(int status) { null, 0, null, + null, emptyList()); } } diff --git a/dd-trace-core/src/traceAgentTest/java/TraceGenerator.java b/dd-trace-core/src/traceAgentTest/java/TraceGenerator.java index 1e1a9c58fe3..da0911022e9 100644 --- a/dd-trace-core/src/traceAgentTest/java/TraceGenerator.java +++ b/dd-trace-core/src/traceAgentTest/java/TraceGenerator.java @@ -6,6 +6,7 @@ import static java.util.Collections.emptyList; import static java.util.Collections.emptyMap; +import datadog.trace.api.Config; import datadog.trace.api.DDSpanId; import datadog.trace.api.DDTags; import datadog.trace.api.DDTraceId; @@ -182,6 +183,7 @@ static class PojoSpan implements CoreSpan { null, 0, getTagsForSerialization(), + UTF8BytesString.create(String.valueOf(Config.get().isOtlpTracesExportEnabled())), emptyList()); } From 06e13575324a688e491d26df0ac6b944d47c2da0 Mon Sep 17 00:00:00 2001 From: Matthew Li Date: Wed, 23 Sep 2026 16:12:11 -0400 Subject: [PATCH 7/7] extract repeated test condition to helper function --- .../trace/core/DDSpanSerializationTest.java | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/dd-trace-core/src/test/java/datadog/trace/core/DDSpanSerializationTest.java b/dd-trace-core/src/test/java/datadog/trace/core/DDSpanSerializationTest.java index 10512f3c582..cf08059ea9f 100644 --- a/dd-trace-core/src/test/java/datadog/trace/core/DDSpanSerializationTest.java +++ b/dd-trace-core/src/test/java/datadog/trace/core/DDSpanSerializationTest.java @@ -246,9 +246,7 @@ void serializeTraceWithBaggageAndTagsCorrectlyV04( for (int j = 0; j < packedSize; j++) { String k = unpacker.unpackString(); String v = unpacker.unpackString(); - // thread.* and the payload-scoped _dd.sdk.otlp_export marker are written by the - // mapper, not by the span under test (see TraceMapperV04/V05PayloadTest). - if (!"thread.name".equals(k) && !"thread.id".equals(k) && !SDK_OTLP_EXPORT.equals(k)) { + if (!isWrittenByMapper(k)) { unpackedMeta.put(k, v); } } @@ -327,9 +325,7 @@ void serializeTraceWithBaggageAndTagsCorrectlyV05( for (int j = 0; j < packedSize; j++) { String k = dictionary[unpacker.unpackInt()]; String v = dictionary[unpacker.unpackInt()]; - // thread.* and the payload-scoped _dd.sdk.otlp_export marker are written by the - // mapper, not by the span under test (see TraceMapperV04/V05PayloadTest). - if (!"thread.name".equals(k) && !"thread.id".equals(k) && !SDK_OTLP_EXPORT.equals(k)) { + if (!isWrittenByMapper(k)) { unpackedMeta.put(k, v); } } @@ -567,9 +563,7 @@ void serializeTraceWithFlatMapTagV04() throws Exception { for (int j = 0; j < packedSize; j++) { String k = unpacker.unpackString(); String v = unpacker.unpackString(); - // thread.* and the payload-scoped _dd.sdk.otlp_export marker are written by the - // mapper, not by the span under test (see TraceMapperV04/V05PayloadTest). - if (!"thread.name".equals(k) && !"thread.id".equals(k) && !SDK_OTLP_EXPORT.equals(k)) { + if (!isWrittenByMapper(k)) { unpackedMeta.put(k, v); } } @@ -639,9 +633,7 @@ void serializeTraceWithFlatMapTagV05() throws Exception { for (int j = 0; j < packedSize; j++) { String k = dictionary[unpacker.unpackInt()]; String v = dictionary[unpacker.unpackInt()]; - // thread.* and the payload-scoped _dd.sdk.otlp_export marker are written by the - // mapper, not by the span under test (see TraceMapperV04/V05PayloadTest). - if (!"thread.name".equals(k) && !"thread.id".equals(k) && !SDK_OTLP_EXPORT.equals(k)) { + if (!isWrittenByMapper(k)) { unpackedMeta.put(k, v); } } @@ -649,6 +641,14 @@ void serializeTraceWithFlatMapTagV05() throws Exception { tracer.close(); } + /** + * thread.* and the payload-scoped _dd.sdk.otlp_export marker are written by the mapper, not by + * the span under test (see TraceMapperV04/V05PayloadTest). + */ + private static boolean isWrittenByMapper(String key) { + return "thread.name".equals(key) || "thread.id".equals(key) || SDK_OTLP_EXPORT.equals(key); + } + private static class CaptureBuffer implements ByteBufferConsumer { private byte[] bytes; int messageCount;