From ab3d841860a954b2c97683a96c764efc28f9f2b7 Mon Sep 17 00:00:00 2001 From: Douglas Q Hawkins Date: Fri, 9 Oct 2026 10:14:09 -0400 Subject: [PATCH 1/2] Fix split-by-tags for tags configured by OpenTelemetry name Entries set on the span builder are canonicalized to their Datadog name (#12354), so a split-by-tags entry configured by its OpenTelemetry name (e.g. db.system) no longer matched. Add the Datadog name of each configured tag at load. Co-Authored-By: Claude Opus 5.5 --- .../core/taginterceptor/TagInterceptor.java | 25 +++++++++++++++++-- .../taginterceptor/TagInterceptorTest.java | 19 ++++++++++++++ 2 files changed, 42 insertions(+), 2 deletions(-) diff --git a/dd-trace-core/src/main/java/datadog/trace/core/taginterceptor/TagInterceptor.java b/dd-trace-core/src/main/java/datadog/trace/core/taginterceptor/TagInterceptor.java index 89e5a01adb8..bed308b7e57 100644 --- a/dd-trace-core/src/main/java/datadog/trace/core/taginterceptor/TagInterceptor.java +++ b/dd-trace-core/src/main/java/datadog/trace/core/taginterceptor/TagInterceptor.java @@ -27,6 +27,7 @@ import datadog.trace.api.Config; import datadog.trace.api.ConfigDefaults; import datadog.trace.api.DDTags; +import datadog.trace.api.KnownTagCodec; import datadog.trace.api.Pair; import datadog.trace.api.TagMap; import datadog.trace.api.config.GeneralConfig; @@ -41,6 +42,7 @@ import datadog.trace.bootstrap.instrumentation.api.UTF8BytesString; import datadog.trace.core.DDSpanContext; import java.net.URI; +import java.util.HashSet; import java.util.Map; import java.util.Set; import javax.annotation.Nonnull; @@ -77,9 +79,9 @@ public TagInterceptor( boolean jeeSplitByDeployment) { this.isServiceNameSetByUser = isServiceNameSetByUser; this.inferredServiceName = inferredServiceName; - this.splitServiceTags = splitServiceTags; + this.splitServiceTags = withCanonicalNames(splitServiceTags); this.ruleFlags = ruleFlags; - splitByServletContext = splitServiceTags.contains(SERVLET_CONTEXT); + splitByServletContext = this.splitServiceTags.contains(SERVLET_CONTEXT); shouldSet404ResourceName = ruleFlags.isEnabled(URL_AS_RESOURCE_NAME) @@ -89,6 +91,25 @@ public TagInterceptor( this.jeeSplitByDeployment = jeeSplitByDeployment; } + /** + * Adds the Datadog name of each configured tag: entries set on the span builder arrive under it, + * so a tag configured by its OpenTelemetry name (e.g. {@code db.system}) would otherwise be + * missed. + */ + private static Set withCanonicalNames(Set tags) { + Set result = null; + for (String tag : tags) { + String canonical = KnownTagCodec.canonicalTagName(tag); + if (!canonical.equals(tag) && !tags.contains(canonical)) { + if (result == null) { + result = new HashSet<>(tags); + } + result.add(canonical); + } + } + return result == null ? tags : result; + } + public boolean needsIntercept(TagMap map) { for (TagMap.EntryReader entry : map) { if (needsIntercept(entry.tag())) return true; diff --git a/dd-trace-core/src/test/java/datadog/trace/core/taginterceptor/TagInterceptorTest.java b/dd-trace-core/src/test/java/datadog/trace/core/taginterceptor/TagInterceptorTest.java index 91f3da0910f..ef0f63deec9 100644 --- a/dd-trace-core/src/test/java/datadog/trace/core/taginterceptor/TagInterceptorTest.java +++ b/dd-trace-core/src/test/java/datadog/trace/core/taginterceptor/TagInterceptorTest.java @@ -190,6 +190,25 @@ void mappingCausesServletContextToNotChangeServiceName(String serviceName) { assertEquals("new-service", span.getServiceName()); } + @TableTest({ + "scenario | configured | tag ", + "configured by OTel name | db.system | db.system", + "configured by Datadog name | db.type | db.type ", + "Datadog name, set with OTel name | db.type | db.system" + }) + void splitByTagsMatchesTheCanonicalNameOfABuilderTag(String configured, String tag) { + CoreTracer tracer = createSplittingTracer(configured); + + AgentSpan builderSpan = tracer.buildSpan("datadog", "some span").withTag(tag, "split").start(); + AgentSpan setterSpan = tracer.buildSpan("datadog", "some span").start(); + setterSpan.setTag(tag, "split"); + + assertEquals("split", builderSpan.getServiceName()); + if (configured.equals(tag)) { + assertEquals("split", setterSpan.getServiceName()); + } + } + private CoreTracer createSplittingTracer(String tag) { return tracerBuilder() .serviceName("my-service") From 73a0d4bc2d4cd6df8d0c77ab87eb8ab0c94ad082 Mon Sep 17 00:00:00 2001 From: Douglas Q Hawkins Date: Fri, 9 Oct 2026 16:53:33 -0400 Subject: [PATCH 2/2] Expand split-by-tags canonical-name test coverage Cover reverse alias pairing, custom and unconfigured tags, empty configuration, both names configured, multiple renamed tags, and default span tags, with an explicit expected result for the setter. Co-Authored-By: Claude Opus 5.5 --- .../taginterceptor/TagInterceptorTest.java | 50 +++++++++++++------ 1 file changed, 36 insertions(+), 14 deletions(-) diff --git a/dd-trace-core/src/test/java/datadog/trace/core/taginterceptor/TagInterceptorTest.java b/dd-trace-core/src/test/java/datadog/trace/core/taginterceptor/TagInterceptorTest.java index ef0f63deec9..7430a747382 100644 --- a/dd-trace-core/src/test/java/datadog/trace/core/taginterceptor/TagInterceptorTest.java +++ b/dd-trace-core/src/test/java/datadog/trace/core/taginterceptor/TagInterceptorTest.java @@ -9,6 +9,7 @@ import static datadog.trace.bootstrap.instrumentation.api.Tags.HTTP_STATUS; import static datadog.trace.bootstrap.instrumentation.api.Tags.HTTP_URL; import static datadog.trace.test.junit.utils.config.WithConfigExtension.injectSysConfig; +import static java.util.Collections.emptyMap; import static java.util.Collections.emptySet; import static java.util.Collections.singletonMap; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -48,8 +49,11 @@ import datadog.trace.test.junit.utils.config.WithConfig; import datadog.trace.test.junit.utils.converter.ConfigDefaultsConverter; import datadog.trace.test.junit.utils.converter.TagsConverter; +import java.util.Arrays; import java.util.Collections; +import java.util.HashSet; import java.util.Map; +import java.util.Set; import java.util.function.Function; import java.util.stream.Stream; import org.junit.jupiter.api.Test; @@ -191,33 +195,51 @@ void mappingCausesServletContextToNotChangeServiceName(String serviceName) { } @TableTest({ - "scenario | configured | tag ", - "configured by OTel name | db.system | db.system", - "configured by Datadog name | db.type | db.type ", - "Datadog name, set with OTel name | db.type | db.system" + "scenario | configured | tag | expectedBuilder | expectedSetter", + "configured by OTel name | db.system | db.system | split | split ", + "configured by Datadog name | db.type | db.type | split | split ", + "Datadog config, OTel tag | db.type | db.system | split | my-service ", + "OTel config, Datadog tag | db.system | db.type | split | split ", + "custom tag | custom.tag | custom.tag | split | split ", + "unconfigured tag | db.system | other.tag | my-service | my-service ", + "empty configuration | '' | db.system | my-service | my-service ", + "both names, OTel tag | db.system,db.type | db.system | split | split ", + "both names, Datadog tag | db.system,db.type | db.type | split | split ", + "custom tag survives expansion | db.system,custom.tag | custom.tag | split | split ", + "first of two renamed tags | db.system,db.operation.name | db.type | split | split ", + "second of two renamed tags | db.system,db.operation.name | db.operation | split | split " }) - void splitByTagsMatchesTheCanonicalNameOfABuilderTag(String configured, String tag) { - CoreTracer tracer = createSplittingTracer(configured); + void splitByTagsMatchesCanonicalAndOriginalNames( + String configured, String tag, String expectedBuilder, String expectedSetter) { + Set configuredTags = + configured.isEmpty() ? emptySet() : new HashSet<>(Arrays.asList(configured.split(","))); + CoreTracer tracer = createSplittingTracer(configuredTags, emptyMap()); AgentSpan builderSpan = tracer.buildSpan("datadog", "some span").withTag(tag, "split").start(); AgentSpan setterSpan = tracer.buildSpan("datadog", "some span").start(); setterSpan.setTag(tag, "split"); - assertEquals("split", builderSpan.getServiceName()); - if (configured.equals(tag)) { - assertEquals("split", setterSpan.getServiceName()); - } + assertEquals(expectedBuilder, builderSpan.getServiceName()); + assertEquals(expectedSetter, setterSpan.getServiceName()); + + CoreTracer defaultTagTracer = createSplittingTracer(configuredTags, singletonMap(tag, "split")); + AgentSpan defaultTagSpan = defaultTagTracer.buildSpan("datadog", "some span").start(); + + assertEquals(expectedBuilder, defaultTagSpan.getServiceName()); } private CoreTracer createSplittingTracer(String tag) { + return createSplittingTracer(Collections.singleton(tag), emptyMap()); + } + + private CoreTracer createSplittingTracer(Set tags, Map defaultSpanTags) { return tracerBuilder() .serviceName("my-service") .writer(new LoggingWriter()) .sampler(new AllSampler()) - // equivalent to split-by-tags: tag - .tagInterceptor( - new TagInterceptor( - true, "my-service", Collections.singleton(tag), new RuleFlags(), false)) + .defaultSpanTags(defaultSpanTags) + // equivalent to split-by-tags: tags + .tagInterceptor(new TagInterceptor(true, "my-service", tags, new RuleFlags(), false)) .build(); }