From 56b4ffdd9f2c3ce8b14191a077974316b9862da6 Mon Sep 17 00:00:00 2001 From: Douglas Q Hawkins Date: Tue, 20 Oct 2026 21:45:47 -0400 Subject: [PATCH 1/2] Resolve direction-scoped OpenTelemetry names when reading span tags getTag and unsafeGetTag already resolve an OpenTelemetry name to its Datadog tag when the name means the same tag in every direction. Names whose tag depends on the span's direction (server.address is http.hostname inbound and peer.hostname outbound) found nothing, so a sampling rule on server.address never matched. After a plain lookup misses, resolve such a name by the span's kind, using the cached span-kind ordinal. A tag set under the literal name still wins, and spans with no direction resolve nothing. Co-Authored-By: Claude Opus 5.5 --- .../datadog/trace/core/DDSpanContext.java | 40 ++++++++++++++++++- 1 file changed, 39 insertions(+), 1 deletion(-) 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 9281c377afb..1e236d3a120 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 @@ -1403,7 +1403,45 @@ Object getTag(final String key) { * @return the value associated with the tag */ public Object unsafeGetTag(final String tag) { - return unsafeTags.getObject(tag); + Object value = unsafeTags.getObject(tag); + return value != null ? value : unsafeGetDirectionScopedTag(tag); + } + + /** + * Resolves an OpenTelemetry name whose tag depends on the span's direction, by this span's kind: + * {@code server.address} is {@code http.hostname} on inbound spans and {@code peer.hostname} on + * outbound ones. Tried only after a plain lookup misses, so a tag set under the literal name + * wins; null when the name isn't direction-scoped or the span has no direction. + */ + private Object unsafeGetDirectionScopedTag(final String tag) { + int direction = directionOfKind(spanKindOrdinal); + if (direction == KnownTagCodec.DIRECTION_UNKNOWN) { + return null; + } + long tagId = KnownTagCodec.directionalKeyOf(tag, direction); + if (tagId == 0) { + return null; + } + TagMap.Entry entry = unsafeTags.getEntry(tagId); + return entry == null ? null : entry.objectValue(); + } + + /** + * The {@link KnownTagCodec} direction of a span-kind ordinal; brokers and custom kinds have none. + */ + static int directionOfKind(byte spanKindOrdinal) { + switch (spanKindOrdinal) { + case SPAN_KIND_SERVER: + case SPAN_KIND_CONSUMER: + return KnownTagCodec.DIRECTION_INBOUND; + case SPAN_KIND_CLIENT: + case SPAN_KIND_PRODUCER: + return KnownTagCodec.DIRECTION_OUTBOUND; + case SPAN_KIND_INTERNAL: + return KnownTagCodec.DIRECTION_NONE; + default: + return KnownTagCodec.DIRECTION_UNKNOWN; + } } @Deprecated From 81429bf5447062a1419e782486efe92e7893eabb Mon Sep 17 00:00:00 2001 From: Douglas Q Hawkins Date: Tue, 20 Oct 2026 21:45:50 -0400 Subject: [PATCH 2/2] Test reading direction-scoped OpenTelemetry names by span kind Co-Authored-By: Claude Opus 5.5 --- .../datadog/trace/core/DDSpanContextTest.java | 67 +++++++++++++++++++ 1 file changed, 67 insertions(+) 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..221d350f032 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 @@ -20,10 +20,12 @@ import static datadog.trace.core.DDSpanContext.SPAN_SAMPLING_MAX_PER_SECOND_TAG; import static datadog.trace.core.DDSpanContext.SPAN_SAMPLING_MECHANISM_TAG; import static datadog.trace.core.DDSpanContext.SPAN_SAMPLING_RULE_RATE_TAG; +import static java.util.Collections.singletonMap; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertInstanceOf; import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; @@ -40,6 +42,7 @@ import datadog.trace.bootstrap.instrumentation.api.Tags; import datadog.trace.common.writer.ListWriter; import datadog.trace.core.propagation.ExtractedContext; +import datadog.trace.core.util.TagsMatcher; import datadog.trace.test.junit.utils.tabletest.TableTestTypeConverters; import java.util.HashMap; import java.util.Map; @@ -580,4 +583,68 @@ static void assertTagmap(Map source, Map comparison, boolean removeT private static String dataTag(String tag) { return "_dd." + tag + ".json"; } + + @TableTest({ + "scenario | kind | storedTag | readTag ", + "server.address on a server span | server | http.hostname | server.address ", + "server.address on a consumer span | consumer | http.hostname | server.address ", + "server.address on a client span | client | peer.hostname | server.address ", + "server.address on a producer span | producer | peer.hostname | server.address ", + "network.peer.address inbound | server | network.client.ip | network.peer.address" + }) + void readsDirectionScopedNameBySpanKind(String kind, String storedTag, String readTag) { + AgentSpan span = tracer.buildSpan("datadog", "fakeOperation").withTag(SPAN_KIND, kind).start(); + span.setTag(storedTag, "example.com"); + DDSpanContext context = (DDSpanContext) span.spanContext(); + + assertEquals("example.com", span.getTag(readTag)); + assertEquals("example.com", context.unsafeGetTag(readTag)); + span.finish(); + } + + @TableTest({ + "scenario | kind | storedTag ", + "outbound-only rename, server span | server | peer.hostname", + "inbound-only rename, client span | client | http.hostname", + "internal span | internal | http.hostname", + "broker span | broker | peer.hostname" + }) + void leavesDirectionScopedNameUnresolvedWhereItNamesNoTag(String kind, String storedTag) { + AgentSpan span = tracer.buildSpan("datadog", "fakeOperation").withTag(SPAN_KIND, kind).start(); + span.setTag(storedTag, "example.com"); + + assertNull(span.getTag("server.address")); + span.finish(); + } + + @Test + void leavesDirectionScopedNameUnresolvedWithoutSpanKind() { + AgentSpan span = tracer.buildSpan("datadog", "fakeOperation").start(); + span.setTag("http.hostname", "example.com"); + span.setTag("peer.hostname", "example.com"); + + assertNull(span.getTag("server.address")); + span.finish(); + } + + @Test + void prefersTagSetUnderTheLiteralDirectionScopedName() { + AgentSpan span = + tracer.buildSpan("datadog", "fakeOperation").withTag(SPAN_KIND, "client").start(); + span.setTag("peer.hostname", "resolved.example.com"); + span.setTag("server.address", "literal.example.com"); + + assertEquals("literal.example.com", span.getTag("server.address")); + span.finish(); + } + + @Test + void samplingRuleMatchesDirectionScopedName() { + AgentSpan span = + tracer.buildSpan("datadog", "fakeOperation").withTag(SPAN_KIND, "client").start(); + span.setTag("peer.hostname", "db.example.com"); + + assertTrue(TagsMatcher.create(singletonMap("server.address", "db.*")).matches((DDSpan) span)); + span.finish(); + } }