From 8e57e8754adc99ddc8ef22940ea6acda8424e3f0 Mon Sep 17 00:00:00 2001 From: Douglas Q Hawkins Date: Thu, 22 Oct 2026 22:26:36 -0400 Subject: [PATCH 1/8] Introduce SpanStorage for eager tag post-processors Eager post-processors run on the application thread as a span finishes. They now take a SpanStorage (a span's tags as stored) instead of a TagMap, and DDSpanContext implements SpanStorage directly, so no TagMap view needs to be built for them once span storage stops being a single TagMap. SpanStorage has its own verbs, load and store, apart from TagMap's get/set and the span's intercepted getTag/setTag, so DDSpanContext can offer the raw and the intercepted operations without a clash. Stores bypass interception; thread safety is left to each implementation (DDSpanContext's are unsynchronized, called under the tag lock). TagMap implements it too, through its own get and set, for standalone maps. This is the first slice: load, loadString and two stores. Co-Authored-By: Claude Opus 5.5 --- .../datadog/trace/core/DDSpanContext.java | 30 +++++++++++++++++-- .../tagprocessor/EagerPostProcessorChain.java | 24 +++++++++++++++ .../tagprocessor/EagerTagsPostProcessor.java | 21 +++++++++++++ .../HttpEndpointPostProcessor.java | 12 ++++---- .../core/tagprocessor/InternalTagsAdder.java | 11 +++---- .../tagprocessor/PeerServiceCalculator.java | 16 +++++----- .../TagsPostProcessorFactory.java | 10 +++---- .../java/datadog/trace/api/SpanStorage.java | 27 +++++++++++++++++ .../main/java/datadog/trace/api/TagMap.java | 25 +++++++++++++++- .../trace/api/endpoint/EndpointResolver.java | 9 +++--- .../trace/api/naming/NamingSchema.java | 4 +-- .../api/naming/v0/PeerServiceNamingV0.java | 4 +-- .../api/naming/v1/PeerServiceNamingV1.java | 21 ++++++------- .../api/endpoint/EndpointResolverTest.java | 23 +++++++------- 14 files changed, 179 insertions(+), 58 deletions(-) create mode 100644 dd-trace-core/src/main/java/datadog/trace/core/tagprocessor/EagerPostProcessorChain.java create mode 100644 dd-trace-core/src/main/java/datadog/trace/core/tagprocessor/EagerTagsPostProcessor.java create mode 100644 internal-api/src/main/java/datadog/trace/api/SpanStorage.java 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 1e236d3a120..8fdf11b8a85 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 @@ -18,6 +18,7 @@ import datadog.trace.api.KnownTagCodec; import datadog.trace.api.KnownTags; import datadog.trace.api.ProcessTags; +import datadog.trace.api.SpanStorage; import datadog.trace.api.TagMap; import datadog.trace.api.cache.DDCache; import datadog.trace.api.cache.DDCaches; @@ -68,7 +69,7 @@ * the associated Span instance */ public class DDSpanContext - implements AgentSpanContext, RequestContext, TraceSegment, ProfilerContext { + implements AgentSpanContext, RequestContext, TraceSegment, ProfilerContext, SpanStorage { private static final Logger log = LoggerFactory.getLogger(DDSpanContext.class); public static final String PRIORITY_SAMPLING_KEY = "_sampling_priority_v1"; @@ -1395,6 +1396,31 @@ Object getTag(final String key) { } } + /* + * SpanStorage: the span's tags, loaded and stored with no interception. + * Unsynchronized, like unsafeGetTag: callers hold the span's tag lock, as post-processing does. + */ + + @Override + public Object load(String tag) { + return unsafeTags.getObject(tag); + } + + @Override + public String loadString(String tag) { + return unsafeTags.getString(tag); + } + + @Override + public void store(String tag, Object value) { + unsafeTags.set(tag, value); + } + + @Override + public void store(TagMap.EntryReader entry) { + unsafeTags.set(entry); + } + /** * This is not thread-safe and must only be used when it can be guaranteed that the context will * not be mutated. This is internal API and must not be exposed to users. @@ -1500,7 +1526,7 @@ public void setMetaStruct(final String field, final T value) { void earlyProcessTags(AppendableSpanLinks links) { synchronized (unsafeTags) { - TagsPostProcessorFactory.eagerProcessor().processTags(unsafeTags, this, links); + TagsPostProcessorFactory.eagerProcessor().processTags(this, this, links); } } diff --git a/dd-trace-core/src/main/java/datadog/trace/core/tagprocessor/EagerPostProcessorChain.java b/dd-trace-core/src/main/java/datadog/trace/core/tagprocessor/EagerPostProcessorChain.java new file mode 100644 index 00000000000..f5ac6765293 --- /dev/null +++ b/dd-trace-core/src/main/java/datadog/trace/core/tagprocessor/EagerPostProcessorChain.java @@ -0,0 +1,24 @@ +package datadog.trace.core.tagprocessor; + +import datadog.trace.api.SpanStorage; +import datadog.trace.bootstrap.instrumentation.api.AppendableSpanLinks; +import datadog.trace.core.DDSpanContext; +import java.util.Objects; +import javax.annotation.Nonnull; + +/** Runs eager post-processors in order. */ +public final class EagerPostProcessorChain extends EagerTagsPostProcessor { + private final EagerTagsPostProcessor[] chain; + + public EagerPostProcessorChain(@Nonnull final EagerTagsPostProcessor... processors) { + chain = Objects.requireNonNull(processors); + } + + @Override + public void processTags( + SpanStorage storage, DDSpanContext spanContext, AppendableSpanLinks spanLinks) { + for (final EagerTagsPostProcessor processor : chain) { + processor.processTags(storage, spanContext, spanLinks); + } + } +} diff --git a/dd-trace-core/src/main/java/datadog/trace/core/tagprocessor/EagerTagsPostProcessor.java b/dd-trace-core/src/main/java/datadog/trace/core/tagprocessor/EagerTagsPostProcessor.java new file mode 100644 index 00000000000..54ace639c80 --- /dev/null +++ b/dd-trace-core/src/main/java/datadog/trace/core/tagprocessor/EagerTagsPostProcessor.java @@ -0,0 +1,21 @@ +package datadog.trace.core.tagprocessor; + +import datadog.trace.api.SpanStorage; +import datadog.trace.bootstrap.instrumentation.api.AppendableSpanLinks; +import datadog.trace.core.DDSpanContext; + +/** + * A tag post-processor run on the application thread as a span finishes. It works on the span's + * {@link SpanStorage} rather than a {@code TagMap}, so the span needn't build one for it. + */ +public abstract class EagerTagsPostProcessor { + /** + * Processes the span's tags. + * + * @param contents the span's contents to read and write; the caller holds the span's tag lock + * @param spanContext the span, for its other properties + * @param spanLinks the span's links + */ + public abstract void processTags( + SpanStorage storage, DDSpanContext spanContext, AppendableSpanLinks spanLinks); +} diff --git a/dd-trace-core/src/main/java/datadog/trace/core/tagprocessor/HttpEndpointPostProcessor.java b/dd-trace-core/src/main/java/datadog/trace/core/tagprocessor/HttpEndpointPostProcessor.java index c2e0dd72761..6e6f9cfd829 100644 --- a/dd-trace-core/src/main/java/datadog/trace/core/tagprocessor/HttpEndpointPostProcessor.java +++ b/dd-trace-core/src/main/java/datadog/trace/core/tagprocessor/HttpEndpointPostProcessor.java @@ -4,7 +4,7 @@ import static datadog.trace.bootstrap.instrumentation.api.Tags.HTTP_ROUTE; import static datadog.trace.bootstrap.instrumentation.api.Tags.HTTP_URL; -import datadog.trace.api.TagMap; +import datadog.trace.api.SpanStorage; import datadog.trace.api.endpoint.EndpointResolver; import datadog.trace.api.internal.VisibleForTesting; import datadog.trace.bootstrap.instrumentation.api.AppendableSpanLinks; @@ -30,7 +30,7 @@ *
  • If {@code alwaysSimplifiedEndpoint=true}, always compute from URL regardless of route * */ -public class HttpEndpointPostProcessor extends TagsPostProcessor { +public class HttpEndpointPostProcessor extends EagerTagsPostProcessor { private static final Logger log = LoggerFactory.getLogger(HttpEndpointPostProcessor.class); private final EndpointResolver endpointResolver; @@ -55,19 +55,19 @@ public HttpEndpointPostProcessor() { @Override public void processTags( - TagMap unsafeTags, DDSpanContext spanContext, AppendableSpanLinks spanLinks) { + SpanStorage unsafeTags, DDSpanContext spanContext, AppendableSpanLinks spanLinks) { if (!endpointResolver.isEnabled()) { log.debug("EndpointResolver is not enabled, skipping HTTP endpoint post processing"); return; } - if (unsafeTags.getObject(HTTP_METHOD) == null) { + if (unsafeTags.load(HTTP_METHOD) == null) { return; } try { - String httpRoute = unsafeTags.getString(HTTP_ROUTE); - String httpUrl = unsafeTags.getString(HTTP_URL); + String httpRoute = unsafeTags.loadString(HTTP_ROUTE); + String httpUrl = unsafeTags.loadString(HTTP_URL); endpointResolver.resolveEndpoint(unsafeTags, httpRoute, httpUrl); } catch (Throwable t) { log.debug("Error processing HTTP endpoint for span {}", spanContext.getSpanId(), t); diff --git a/dd-trace-core/src/main/java/datadog/trace/core/tagprocessor/InternalTagsAdder.java b/dd-trace-core/src/main/java/datadog/trace/core/tagprocessor/InternalTagsAdder.java index 3e6b8b13192..afcf8c30438 100644 --- a/dd-trace-core/src/main/java/datadog/trace/core/tagprocessor/InternalTagsAdder.java +++ b/dd-trace-core/src/main/java/datadog/trace/core/tagprocessor/InternalTagsAdder.java @@ -3,6 +3,7 @@ import static datadog.trace.bootstrap.instrumentation.api.Tags.VERSION; import datadog.trace.api.DDTags; +import datadog.trace.api.SpanStorage; import datadog.trace.api.TagMap; import datadog.trace.bootstrap.instrumentation.api.AppendableSpanLinks; import datadog.trace.bootstrap.instrumentation.api.UTF8BytesString; @@ -10,7 +11,7 @@ import javax.annotation.Nonnull; import javax.annotation.Nullable; -public final class InternalTagsAdder extends TagsPostProcessor { +public final class InternalTagsAdder extends EagerTagsPostProcessor { private final UTF8BytesString ddService; // Prebuilt once to avoid per-span Entry allocation. @@ -28,7 +29,7 @@ public InternalTagsAdder(@Nonnull final String ddService, @Nullable final String @Override public void processTags( - TagMap unsafeTags, DDSpanContext spanContext, AppendableSpanLinks spanLinks) { + SpanStorage unsafeTags, DDSpanContext spanContext, AppendableSpanLinks spanLinks) { if (spanContext == null) { return; } @@ -36,13 +37,13 @@ public void processTags( if (!ddService.toString().equalsIgnoreCase(spanContext.getServiceName())) { if (baseServiceEntry != null) { // service name != DD_SERVICE - unsafeTags.set(baseServiceEntry); + unsafeTags.store(baseServiceEntry); } } else { // as per config consistency, the version tag is added across tracers only if // the service name is DD_SERVICE and version tag is not manually set - if (versionEntry != null && !unsafeTags.containsKey(VERSION)) { - unsafeTags.set(versionEntry); + if (versionEntry != null && unsafeTags.load(VERSION) == null) { + unsafeTags.store(versionEntry); } } } diff --git a/dd-trace-core/src/main/java/datadog/trace/core/tagprocessor/PeerServiceCalculator.java b/dd-trace-core/src/main/java/datadog/trace/core/tagprocessor/PeerServiceCalculator.java index 198e2c78f1c..8d5d0513962 100644 --- a/dd-trace-core/src/main/java/datadog/trace/core/tagprocessor/PeerServiceCalculator.java +++ b/dd-trace-core/src/main/java/datadog/trace/core/tagprocessor/PeerServiceCalculator.java @@ -2,7 +2,7 @@ import datadog.trace.api.Config; import datadog.trace.api.DDTags; -import datadog.trace.api.TagMap; +import datadog.trace.api.SpanStorage; import datadog.trace.api.internal.VisibleForTesting; import datadog.trace.api.naming.NamingSchema; import datadog.trace.api.naming.SpanNaming; @@ -12,7 +12,7 @@ import java.util.Map; import javax.annotation.Nonnull; -public final class PeerServiceCalculator extends TagsPostProcessor { +public final class PeerServiceCalculator extends EagerTagsPostProcessor { private final NamingSchema.ForPeerService peerServiceNaming; private final Map peerServiceMapping; @@ -34,8 +34,8 @@ public PeerServiceCalculator() { @Override public void processTags( - TagMap unsafeTags, DDSpanContext spanContext, AppendableSpanLinks spanLinks) { - Object peerService = unsafeTags.getObject(Tags.PEER_SERVICE); + SpanStorage unsafeTags, DDSpanContext spanContext, AppendableSpanLinks spanLinks) { + Object peerService = unsafeTags.load(Tags.PEER_SERVICE); // the user set it if (peerService != null) { if (canRemap) { @@ -46,18 +46,18 @@ public void processTags( // calculate the defaults (if any) peerServiceNaming.tags(unsafeTags); // only remap if the mapping is not empty (saves one get) - remapPeerService(unsafeTags, canRemap ? unsafeTags.getObject(Tags.PEER_SERVICE) : null); + remapPeerService(unsafeTags, canRemap ? unsafeTags.load(Tags.PEER_SERVICE) : null); return; } // we have no peer.service and we do not compute defaults. Leave the map untouched } - private void remapPeerService(TagMap unsafeTags, Object value) { + private void remapPeerService(SpanStorage unsafeTags, Object value) { if (value != null) { String mapped = peerServiceMapping.get(value); if (mapped != null) { - unsafeTags.put(Tags.PEER_SERVICE, mapped); - unsafeTags.put(DDTags.PEER_SERVICE_REMAPPED_FROM, value); + unsafeTags.store(Tags.PEER_SERVICE, mapped); + unsafeTags.store(DDTags.PEER_SERVICE_REMAPPED_FROM, value); } } } diff --git a/dd-trace-core/src/main/java/datadog/trace/core/tagprocessor/TagsPostProcessorFactory.java b/dd-trace-core/src/main/java/datadog/trace/core/tagprocessor/TagsPostProcessorFactory.java index 08c52a2658a..2e3cd4ca345 100644 --- a/dd-trace-core/src/main/java/datadog/trace/core/tagprocessor/TagsPostProcessorFactory.java +++ b/dd-trace-core/src/main/java/datadog/trace/core/tagprocessor/TagsPostProcessorFactory.java @@ -9,11 +9,11 @@ public final class TagsPostProcessorFactory { private static boolean addRemoteHostname = true; private static class Lazy { - private static TagsPostProcessor eagerProcessor = createEagerChain(); + private static EagerTagsPostProcessor eagerProcessor = createEagerChain(); private static TagsPostProcessor lazyProcessor = createLazyChain(); - private static TagsPostProcessor createEagerChain() { - final List processors = new ArrayList<>(3); + private static EagerTagsPostProcessor createEagerChain() { + final List processors = new ArrayList<>(3); processors.add(new PeerServiceCalculator()); if (addInternalTags) { processors.add( @@ -24,7 +24,7 @@ private static TagsPostProcessor createEagerChain() { if (Config.get().isTraceResourceRenamingEnabled()) { processors.add(new HttpEndpointPostProcessor()); } - return new PostProcessorChain(processors.toArray(new TagsPostProcessor[0])); + return new EagerPostProcessorChain(processors.toArray(new EagerTagsPostProcessor[0])); } private static TagsPostProcessor createLazyChain() { @@ -54,7 +54,7 @@ private static TagsPostProcessor createLazyChain() { } } - public static TagsPostProcessor eagerProcessor() { + public static EagerTagsPostProcessor eagerProcessor() { return Lazy.eagerProcessor; } diff --git a/internal-api/src/main/java/datadog/trace/api/SpanStorage.java b/internal-api/src/main/java/datadog/trace/api/SpanStorage.java new file mode 100644 index 00000000000..09b2fab946d --- /dev/null +++ b/internal-api/src/main/java/datadog/trace/api/SpanStorage.java @@ -0,0 +1,27 @@ +package datadog.trace.api; + +/** + * A span's tags as stored, for the tracer core's processing pipeline: loads and stores go straight + * to storage, with no interception. Thread safety is up to the implementation; {@code + * DDSpanContext}'s expects the span's tag lock to be held. + * + *

    Its own verbs, {@code load} and {@code store}, keep it apart from {@link TagMap}'s {@code + * get}/{@code set} and the span's intercepted {@code getTag}/{@code setTag}, so a type can offer + * several of these without a clash. + * + *

    This is the first slice: only what the eager tag post-processors need. Id variants, typed + * loads and primitive stores come as more of the pipeline moves to it. + */ +public interface SpanStorage { + /** The tag's value, or null when it's absent. */ + Object load(String tag); + + /** The tag's value as a string, or null when it's absent. */ + String loadString(String tag); + + /** Stores the tag; a null value removes it. */ + void store(String tag, Object value); + + /** Stores the entry's tag; a null entry is a no-op. */ + void store(TagMap.EntryReader entry); +} diff --git a/internal-api/src/main/java/datadog/trace/api/TagMap.java b/internal-api/src/main/java/datadog/trace/api/TagMap.java index 296d52399a6..b4cc9997222 100644 --- a/internal-api/src/main/java/datadog/trace/api/TagMap.java +++ b/internal-api/src/main/java/datadog/trace/api/TagMap.java @@ -48,7 +48,8 @@ *

  • adaptive collision * */ -public final class TagMap implements Map, Iterable { +public final class TagMap + implements Map, Iterable, SpanStorage { /** Immutable empty TagMap - similar to {@link Collections#emptyMap()} */ // Frozen view over a length-1 array: bucket masking needs a power-of-two array length (size 0 // would fail with ArrayIndexOutOfBoundsException, size 1 works), and the private constructor @@ -1346,6 +1347,28 @@ public String getString(String tag) { return entry == null ? null : entry.stringValue(); } + /* SpanStorage, for standalone maps handed to the pipeline: TagMap's own get and set. */ + + @Override + public Object load(String tag) { + return this.getObject(tag); + } + + @Override + public String loadString(String tag) { + return this.getString(tag); + } + + @Override + public void store(String tag, Object value) { + this.set(tag, value); + } + + @Override + public void store(EntryReader entry) { + this.set(entry); + } + public boolean getBoolean(String tag) { return this.getBooleanOrDefault(tag, false); } diff --git a/internal-api/src/main/java/datadog/trace/api/endpoint/EndpointResolver.java b/internal-api/src/main/java/datadog/trace/api/endpoint/EndpointResolver.java index c5e47328734..30114ca2372 100644 --- a/internal-api/src/main/java/datadog/trace/api/endpoint/EndpointResolver.java +++ b/internal-api/src/main/java/datadog/trace/api/endpoint/EndpointResolver.java @@ -2,6 +2,7 @@ import static datadog.trace.bootstrap.instrumentation.api.Tags.HTTP_ENDPOINT; +import datadog.trace.api.SpanStorage; import javax.annotation.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -50,9 +51,7 @@ public EndpointResolver(boolean enabled, boolean alwaysSimplifiedEndpoint) { */ @Nullable public String resolveEndpoint( - java.util.Map unsafeTags, - @Nullable String httpRoute, - @Nullable String httpUrl) { + SpanStorage unsafeTags, @Nullable String httpRoute, @Nullable String httpUrl) { if (!enabled) { return null; } @@ -61,7 +60,7 @@ public String resolveEndpoint( if (alwaysSimplifiedEndpoint) { String endpoint = computeEndpoint(httpUrl); if (endpoint != null) { - unsafeTags.put(HTTP_ENDPOINT, endpoint); + unsafeTags.store(HTTP_ENDPOINT, endpoint); } return endpoint; } @@ -74,7 +73,7 @@ public String resolveEndpoint( // Compute endpoint from URL and tag the span String endpoint = computeEndpoint(httpUrl); if (endpoint != null) { - unsafeTags.put(HTTP_ENDPOINT, endpoint); + unsafeTags.store(HTTP_ENDPOINT, endpoint); } return endpoint; } diff --git a/internal-api/src/main/java/datadog/trace/api/naming/NamingSchema.java b/internal-api/src/main/java/datadog/trace/api/naming/NamingSchema.java index 9ddf3178f3b..b17b52bc58b 100644 --- a/internal-api/src/main/java/datadog/trace/api/naming/NamingSchema.java +++ b/internal-api/src/main/java/datadog/trace/api/naming/NamingSchema.java @@ -1,6 +1,6 @@ package datadog.trace.api.naming; -import datadog.trace.api.TagMap; +import datadog.trace.api.SpanStorage; import java.util.function.Supplier; import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -232,7 +232,7 @@ interface ForPeerService { * @param unsafeTags the span tags. Map to be mutated */ @Nonnull - void tags(@Nonnull TagMap unsafeTags); + void tags(@Nonnull SpanStorage unsafeTags); } interface ForServer { diff --git a/internal-api/src/main/java/datadog/trace/api/naming/v0/PeerServiceNamingV0.java b/internal-api/src/main/java/datadog/trace/api/naming/v0/PeerServiceNamingV0.java index 3e76d657069..d802622205a 100644 --- a/internal-api/src/main/java/datadog/trace/api/naming/v0/PeerServiceNamingV0.java +++ b/internal-api/src/main/java/datadog/trace/api/naming/v0/PeerServiceNamingV0.java @@ -1,6 +1,6 @@ package datadog.trace.api.naming.v0; -import datadog.trace.api.TagMap; +import datadog.trace.api.SpanStorage; import datadog.trace.api.naming.NamingSchema; import javax.annotation.Nonnull; @@ -12,5 +12,5 @@ public boolean supports() { @Nonnull @Override - public void tags(@Nonnull final TagMap unsafeTags) {} + public void tags(@Nonnull final SpanStorage unsafeTags) {} } diff --git a/internal-api/src/main/java/datadog/trace/api/naming/v1/PeerServiceNamingV1.java b/internal-api/src/main/java/datadog/trace/api/naming/v1/PeerServiceNamingV1.java index 827a7489e09..021dbda7aee 100644 --- a/internal-api/src/main/java/datadog/trace/api/naming/v1/PeerServiceNamingV1.java +++ b/internal-api/src/main/java/datadog/trace/api/naming/v1/PeerServiceNamingV1.java @@ -1,7 +1,7 @@ package datadog.trace.api.naming.v1; import datadog.trace.api.DDTags; -import datadog.trace.api.TagMap; +import datadog.trace.api.SpanStorage; import datadog.trace.api.naming.NamingSchema; import datadog.trace.bootstrap.instrumentation.api.InstrumentationTags; import datadog.trace.bootstrap.instrumentation.api.Tags; @@ -53,8 +53,8 @@ public boolean supports() { return true; } - private void resolve(@Nonnull final TagMap unsafeTags) { - final Object component = unsafeTags.getObject(Tags.COMPONENT); + private void resolve(@Nonnull final SpanStorage unsafeTags) { + final Object component = unsafeTags.load(Tags.COMPONENT); // avoid issues with UTF8ByteString or others final String componentString = component == null ? null : component.toString(); final String override = overridesByComponent.get(componentString); @@ -71,14 +71,15 @@ private void resolve(@Nonnull final TagMap unsafeTags) { resolveBy(unsafeTags, DEFAULT_PRECURSORS); } - private boolean resolveBy(@Nonnull final TagMap unsafeTags, @Nullable final String[] precursors) { + private boolean resolveBy( + @Nonnull final SpanStorage unsafeTags, @Nullable final String[] precursors) { if (precursors == null) { return false; } Object value = null; String source = null; for (String precursor : precursors) { - value = unsafeTags.getObject(precursor); + value = unsafeTags.load(precursor); if (value != null) { // we have a match. Use the tag name for the source source = precursor; @@ -90,18 +91,18 @@ private boolean resolveBy(@Nonnull final TagMap unsafeTags, @Nullable final Stri return true; } - private void set(@Nonnull final TagMap unsafeTags, Object value, String source) { + private void set(@Nonnull final SpanStorage unsafeTags, Object value, String source) { if (value != null) { - unsafeTags.put(Tags.PEER_SERVICE, value); - unsafeTags.put(DDTags.PEER_SERVICE_SOURCE, source); + unsafeTags.store(Tags.PEER_SERVICE, value); + unsafeTags.store(DDTags.PEER_SERVICE_SOURCE, source); } } @Nonnull @Override - public void tags(@Nonnull final TagMap unsafeTags) { + public void tags(@Nonnull final SpanStorage unsafeTags) { // check span.kind eligibility - final Object kind = unsafeTags.getObject(Tags.SPAN_KIND); + final Object kind = unsafeTags.load(Tags.SPAN_KIND); if (Tags.SPAN_KIND_CLIENT.equals(kind) || Tags.SPAN_KIND_PRODUCER.equals(kind)) { // we can calculate the peer service now resolve(unsafeTags); diff --git a/internal-api/src/test/java/datadog/trace/api/endpoint/EndpointResolverTest.java b/internal-api/src/test/java/datadog/trace/api/endpoint/EndpointResolverTest.java index 10d6ad5b940..eeec260b9eb 100644 --- a/internal-api/src/test/java/datadog/trace/api/endpoint/EndpointResolverTest.java +++ b/internal-api/src/test/java/datadog/trace/api/endpoint/EndpointResolverTest.java @@ -6,8 +6,7 @@ import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertTrue; -import java.util.HashMap; -import java.util.Map; +import datadog.trace.api.TagMap; import org.junit.jupiter.api.Test; import org.tabletest.junit.TableTest; @@ -47,7 +46,7 @@ void computeEndpointDelegatesToEndpointSimplifier(String input, String expected) @Test void disabledResolverReturnsNull() { EndpointResolver resolver = new EndpointResolver(false, false); - Map unsafeTags = new HashMap<>(); + TagMap unsafeTags = TagMap.create(); String result = resolver.resolveEndpoint(unsafeTags, "/users", "http://example.com/users/123"); @@ -59,7 +58,7 @@ void disabledResolverReturnsNull() { @Test void alwaysSimplifiedEndpointComputesFromUrlEvenWithValidRoute() { EndpointResolver resolver = new EndpointResolver(true, true); - Map unsafeTags = new HashMap<>(); + TagMap unsafeTags = TagMap.create(); String result = resolver.resolveEndpoint(unsafeTags, "/users", "http://example.com/users/123"); @@ -71,7 +70,7 @@ void alwaysSimplifiedEndpointComputesFromUrlEvenWithValidRoute() { @Test void usesEligibleRouteWithoutTagging() { EndpointResolver resolver = new EndpointResolver(true, false); - Map unsafeTags = new HashMap<>(); + TagMap unsafeTags = TagMap.create(); String result = resolver.resolveEndpoint(unsafeTags, "/api/v1/users", "http://example.com/users/123"); @@ -91,7 +90,7 @@ void usesEligibleRouteWithoutTagging() { void computesEndpointAndTagsWhenRouteIsIneligible( String ineligibleRoute, String url, String expectedEndpoint) { EndpointResolver resolver = new EndpointResolver(true, false); - Map unsafeTags = new HashMap<>(); + TagMap unsafeTags = TagMap.create(); String result = resolver.resolveEndpoint(unsafeTags, ineligibleRoute, url); @@ -115,7 +114,7 @@ void isAlwaysSimplifiedEndpointReturnsConfigurationValue() { @Test void realWorldScenarioServiceWithRoute() { EndpointResolver resolver = new EndpointResolver(true, false); - Map unsafeTags = new HashMap<>(); + TagMap unsafeTags = TagMap.create(); // framework provides accurate route String result = @@ -132,7 +131,7 @@ void realWorldScenarioServiceWithRoute() { @Test void realWorldScenarioProxyWithoutRoute() { EndpointResolver resolver = new EndpointResolver(true, false); - Map unsafeTags = new HashMap<>(); + TagMap unsafeTags = TagMap.create(); // no route available (tracer in proxy) String result = @@ -147,7 +146,7 @@ void realWorldScenarioProxyWithoutRoute() { @Test void realWorldScenarioBadInstrumentationWithCatchAll() { EndpointResolver resolver = new EndpointResolver(true, false); - Map unsafeTags = new HashMap<>(); + TagMap unsafeTags = TagMap.create(); // instrumentation provides unhelpful catch-all route String result = @@ -161,7 +160,7 @@ void realWorldScenarioBadInstrumentationWithCatchAll() { @Test void realWorldScenarioTestingWithAlwaysSimplified() { EndpointResolver resolver = new EndpointResolver(true, true); - Map unsafeTags = new HashMap<>(); + TagMap unsafeTags = TagMap.create(); // testing mode to compare with backend inference String result = @@ -176,7 +175,7 @@ void realWorldScenarioTestingWithAlwaysSimplified() { @Test void edgeCaseMalformedUrlReturnsDefaultEndpoint() { EndpointResolver resolver = new EndpointResolver(true, false); - Map unsafeTags = new HashMap<>(); + TagMap unsafeTags = TagMap.create(); String result = resolver.resolveEndpoint(unsafeTags, null, "not-a-url"); @@ -189,7 +188,7 @@ void edgeCaseMalformedUrlReturnsDefaultEndpoint() { @Test void edgeCaseBothRouteAndUrlMissing() { EndpointResolver resolver = new EndpointResolver(true, false); - Map unsafeTags = new HashMap<>(); + TagMap unsafeTags = TagMap.create(); String result = resolver.resolveEndpoint(unsafeTags, null, null); From 884c508dacf68901f7f4c3ec7103ee81940de20a Mon Sep 17 00:00:00 2001 From: Douglas Q Hawkins Date: Mon, 26 Oct 2026 12:56:12 -0400 Subject: [PATCH 2/8] Load and store known tags by id through SpanStorage The eager post-processors only touch known tags, yet loaded and stored them by name, so every call classified the name first. SpanStorage gains id variants of load, loadString and store, and the processors, the peer service naming and the endpoint resolver use KnownTags ids. A known tag's id and name address the same tag. The peer service precursor lists stay names: they include tags outside the registry. Co-Authored-By: Claude Opus 5.5 --- .../java/datadog/trace/core/DDSpanContext.java | 15 +++++++++++++++ .../tagprocessor/HttpEndpointPostProcessor.java | 12 ++++++------ .../core/tagprocessor/InternalTagsAdder.java | 10 +++++----- .../tagprocessor/PeerServiceCalculator.java | 13 +++++++------ .../java/datadog/trace/api/SpanStorage.java | 16 ++++++++++++++-- .../src/main/java/datadog/trace/api/TagMap.java | 17 +++++++++++++++++ .../trace/api/endpoint/EndpointResolver.java | 6 +++--- .../api/naming/v1/PeerServiceNamingV1.java | 14 +++++++++----- 8 files changed, 76 insertions(+), 27 deletions(-) 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 8fdf11b8a85..044314cecf4 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 @@ -1411,11 +1411,26 @@ public String loadString(String tag) { return unsafeTags.getString(tag); } + @Override + public Object load(long tagId) { + return unsafeTags.load(tagId); + } + + @Override + public String loadString(long tagId) { + return unsafeTags.loadString(tagId); + } + @Override public void store(String tag, Object value) { unsafeTags.set(tag, value); } + @Override + public void store(long tagId, Object value) { + unsafeTags.set(tagId, value); + } + @Override public void store(TagMap.EntryReader entry) { unsafeTags.set(entry); diff --git a/dd-trace-core/src/main/java/datadog/trace/core/tagprocessor/HttpEndpointPostProcessor.java b/dd-trace-core/src/main/java/datadog/trace/core/tagprocessor/HttpEndpointPostProcessor.java index 6e6f9cfd829..9f1a9b8a51e 100644 --- a/dd-trace-core/src/main/java/datadog/trace/core/tagprocessor/HttpEndpointPostProcessor.java +++ b/dd-trace-core/src/main/java/datadog/trace/core/tagprocessor/HttpEndpointPostProcessor.java @@ -1,8 +1,8 @@ package datadog.trace.core.tagprocessor; -import static datadog.trace.bootstrap.instrumentation.api.Tags.HTTP_METHOD; -import static datadog.trace.bootstrap.instrumentation.api.Tags.HTTP_ROUTE; -import static datadog.trace.bootstrap.instrumentation.api.Tags.HTTP_URL; +import static datadog.trace.api.KnownTags.HTTP_METHOD_ID; +import static datadog.trace.api.KnownTags.HTTP_ROUTE_ID; +import static datadog.trace.api.KnownTags.HTTP_URL_ID; import datadog.trace.api.SpanStorage; import datadog.trace.api.endpoint.EndpointResolver; @@ -61,13 +61,13 @@ public void processTags( return; } - if (unsafeTags.load(HTTP_METHOD) == null) { + if (unsafeTags.load(HTTP_METHOD_ID) == null) { return; } try { - String httpRoute = unsafeTags.loadString(HTTP_ROUTE); - String httpUrl = unsafeTags.loadString(HTTP_URL); + String httpRoute = unsafeTags.loadString(HTTP_ROUTE_ID); + String httpUrl = unsafeTags.loadString(HTTP_URL_ID); endpointResolver.resolveEndpoint(unsafeTags, httpRoute, httpUrl); } catch (Throwable t) { log.debug("Error processing HTTP endpoint for span {}", spanContext.getSpanId(), t); diff --git a/dd-trace-core/src/main/java/datadog/trace/core/tagprocessor/InternalTagsAdder.java b/dd-trace-core/src/main/java/datadog/trace/core/tagprocessor/InternalTagsAdder.java index afcf8c30438..8847b833c44 100644 --- a/dd-trace-core/src/main/java/datadog/trace/core/tagprocessor/InternalTagsAdder.java +++ b/dd-trace-core/src/main/java/datadog/trace/core/tagprocessor/InternalTagsAdder.java @@ -1,8 +1,8 @@ package datadog.trace.core.tagprocessor; -import static datadog.trace.bootstrap.instrumentation.api.Tags.VERSION; +import static datadog.trace.api.KnownTags.DD_BASE_SERVICE_ID; +import static datadog.trace.api.KnownTags.VERSION_ID; -import datadog.trace.api.DDTags; import datadog.trace.api.SpanStorage; import datadog.trace.api.TagMap; import datadog.trace.bootstrap.instrumentation.api.AppendableSpanLinks; @@ -20,10 +20,10 @@ public final class InternalTagsAdder extends EagerTagsPostProcessor { public InternalTagsAdder(@Nonnull final String ddService, @Nullable final String version) { this.ddService = UTF8BytesString.create(ddService); - this.baseServiceEntry = TagMap.Entry.create(DDTags.BASE_SERVICE, this.ddService); + this.baseServiceEntry = TagMap.Entry.create(DD_BASE_SERVICE_ID, this.ddService); this.versionEntry = version != null && !version.isEmpty() - ? TagMap.Entry.create(VERSION, UTF8BytesString.create(version)) + ? TagMap.Entry.create(VERSION_ID, UTF8BytesString.create(version)) : null; } @@ -42,7 +42,7 @@ public void processTags( } else { // as per config consistency, the version tag is added across tracers only if // the service name is DD_SERVICE and version tag is not manually set - if (versionEntry != null && unsafeTags.load(VERSION) == null) { + if (versionEntry != null && unsafeTags.load(VERSION_ID) == null) { unsafeTags.store(versionEntry); } } diff --git a/dd-trace-core/src/main/java/datadog/trace/core/tagprocessor/PeerServiceCalculator.java b/dd-trace-core/src/main/java/datadog/trace/core/tagprocessor/PeerServiceCalculator.java index 8d5d0513962..83a39c52002 100644 --- a/dd-trace-core/src/main/java/datadog/trace/core/tagprocessor/PeerServiceCalculator.java +++ b/dd-trace-core/src/main/java/datadog/trace/core/tagprocessor/PeerServiceCalculator.java @@ -1,13 +1,14 @@ package datadog.trace.core.tagprocessor; +import static datadog.trace.api.KnownTags.DD_PEER_SERVICE_REMAPPED_FROM_ID; +import static datadog.trace.api.KnownTags.PEER_SERVICE_ID; + import datadog.trace.api.Config; -import datadog.trace.api.DDTags; import datadog.trace.api.SpanStorage; import datadog.trace.api.internal.VisibleForTesting; import datadog.trace.api.naming.NamingSchema; import datadog.trace.api.naming.SpanNaming; import datadog.trace.bootstrap.instrumentation.api.AppendableSpanLinks; -import datadog.trace.bootstrap.instrumentation.api.Tags; import datadog.trace.core.DDSpanContext; import java.util.Map; import javax.annotation.Nonnull; @@ -35,7 +36,7 @@ public PeerServiceCalculator() { @Override public void processTags( SpanStorage unsafeTags, DDSpanContext spanContext, AppendableSpanLinks spanLinks) { - Object peerService = unsafeTags.load(Tags.PEER_SERVICE); + Object peerService = unsafeTags.load(PEER_SERVICE_ID); // the user set it if (peerService != null) { if (canRemap) { @@ -46,7 +47,7 @@ public void processTags( // calculate the defaults (if any) peerServiceNaming.tags(unsafeTags); // only remap if the mapping is not empty (saves one get) - remapPeerService(unsafeTags, canRemap ? unsafeTags.load(Tags.PEER_SERVICE) : null); + remapPeerService(unsafeTags, canRemap ? unsafeTags.load(PEER_SERVICE_ID) : null); return; } // we have no peer.service and we do not compute defaults. Leave the map untouched @@ -56,8 +57,8 @@ private void remapPeerService(SpanStorage unsafeTags, Object value) { if (value != null) { String mapped = peerServiceMapping.get(value); if (mapped != null) { - unsafeTags.store(Tags.PEER_SERVICE, mapped); - unsafeTags.store(DDTags.PEER_SERVICE_REMAPPED_FROM, value); + unsafeTags.store(PEER_SERVICE_ID, mapped); + unsafeTags.store(DD_PEER_SERVICE_REMAPPED_FROM_ID, value); } } } diff --git a/internal-api/src/main/java/datadog/trace/api/SpanStorage.java b/internal-api/src/main/java/datadog/trace/api/SpanStorage.java index 09b2fab946d..8a5f18b1b99 100644 --- a/internal-api/src/main/java/datadog/trace/api/SpanStorage.java +++ b/internal-api/src/main/java/datadog/trace/api/SpanStorage.java @@ -9,8 +9,11 @@ * get}/{@code set} and the span's intercepted {@code getTag}/{@code setTag}, so a type can offer * several of these without a clash. * - *

    This is the first slice: only what the eager tag post-processors need. Id variants, typed - * loads and primitive stores come as more of the pipeline moves to it. + *

    A known tag can be addressed by its {@link KnownTags} id or by its name, and both address the + * same tag. Prefer the id: it skips classifying the name on every call. A custom tag has no id. + * + *

    This is the first slice: only what the eager tag post-processors need. Typed loads and + * primitive stores come as more of the pipeline moves to it. */ public interface SpanStorage { /** The tag's value, or null when it's absent. */ @@ -19,9 +22,18 @@ public interface SpanStorage { /** The tag's value as a string, or null when it's absent. */ String loadString(String tag); + /** The known tag's value, or null when it's absent. */ + Object load(long tagId); + + /** The known tag's value as a string, or null when it's absent. */ + String loadString(long tagId); + /** Stores the tag; a null value removes it. */ void store(String tag, Object value); + /** Stores the known tag; a null value removes it. */ + void store(long tagId, Object value); + /** Stores the entry's tag; a null entry is a no-op. */ void store(TagMap.EntryReader entry); } diff --git a/internal-api/src/main/java/datadog/trace/api/TagMap.java b/internal-api/src/main/java/datadog/trace/api/TagMap.java index b4cc9997222..7de3856b002 100644 --- a/internal-api/src/main/java/datadog/trace/api/TagMap.java +++ b/internal-api/src/main/java/datadog/trace/api/TagMap.java @@ -1359,11 +1359,28 @@ public String loadString(String tag) { return this.getString(tag); } + @Override + public Object load(long tagId) { + Entry entry = this.getEntry(tagId); + return entry == null ? null : entry.objectValue(); + } + + @Override + public String loadString(long tagId) { + Entry entry = this.getEntry(tagId); + return entry == null ? null : entry.stringValue(); + } + @Override public void store(String tag, Object value) { this.set(tag, value); } + @Override + public void store(long tagId, Object value) { + this.set(tagId, value); + } + @Override public void store(EntryReader entry) { this.set(entry); diff --git a/internal-api/src/main/java/datadog/trace/api/endpoint/EndpointResolver.java b/internal-api/src/main/java/datadog/trace/api/endpoint/EndpointResolver.java index 30114ca2372..f9877d708bf 100644 --- a/internal-api/src/main/java/datadog/trace/api/endpoint/EndpointResolver.java +++ b/internal-api/src/main/java/datadog/trace/api/endpoint/EndpointResolver.java @@ -1,6 +1,6 @@ package datadog.trace.api.endpoint; -import static datadog.trace.bootstrap.instrumentation.api.Tags.HTTP_ENDPOINT; +import static datadog.trace.api.KnownTags.HTTP_ENDPOINT_ID; import datadog.trace.api.SpanStorage; import javax.annotation.Nullable; @@ -60,7 +60,7 @@ public String resolveEndpoint( if (alwaysSimplifiedEndpoint) { String endpoint = computeEndpoint(httpUrl); if (endpoint != null) { - unsafeTags.store(HTTP_ENDPOINT, endpoint); + unsafeTags.store(HTTP_ENDPOINT_ID, endpoint); } return endpoint; } @@ -73,7 +73,7 @@ public String resolveEndpoint( // Compute endpoint from URL and tag the span String endpoint = computeEndpoint(httpUrl); if (endpoint != null) { - unsafeTags.store(HTTP_ENDPOINT, endpoint); + unsafeTags.store(HTTP_ENDPOINT_ID, endpoint); } return endpoint; } diff --git a/internal-api/src/main/java/datadog/trace/api/naming/v1/PeerServiceNamingV1.java b/internal-api/src/main/java/datadog/trace/api/naming/v1/PeerServiceNamingV1.java index 021dbda7aee..ae1e7d6ed19 100644 --- a/internal-api/src/main/java/datadog/trace/api/naming/v1/PeerServiceNamingV1.java +++ b/internal-api/src/main/java/datadog/trace/api/naming/v1/PeerServiceNamingV1.java @@ -1,6 +1,10 @@ package datadog.trace.api.naming.v1; -import datadog.trace.api.DDTags; +import static datadog.trace.api.KnownTags.COMPONENT_ID; +import static datadog.trace.api.KnownTags.DD_PEER_SERVICE_SOURCE_ID; +import static datadog.trace.api.KnownTags.PEER_SERVICE_ID; +import static datadog.trace.api.KnownTags.SPAN_KIND_ID; + import datadog.trace.api.SpanStorage; import datadog.trace.api.naming.NamingSchema; import datadog.trace.bootstrap.instrumentation.api.InstrumentationTags; @@ -54,7 +58,7 @@ public boolean supports() { } private void resolve(@Nonnull final SpanStorage unsafeTags) { - final Object component = unsafeTags.load(Tags.COMPONENT); + final Object component = unsafeTags.load(COMPONENT_ID); // avoid issues with UTF8ByteString or others final String componentString = component == null ? null : component.toString(); final String override = overridesByComponent.get(componentString); @@ -93,8 +97,8 @@ private boolean resolveBy( private void set(@Nonnull final SpanStorage unsafeTags, Object value, String source) { if (value != null) { - unsafeTags.store(Tags.PEER_SERVICE, value); - unsafeTags.store(DDTags.PEER_SERVICE_SOURCE, source); + unsafeTags.store(PEER_SERVICE_ID, value); + unsafeTags.store(DD_PEER_SERVICE_SOURCE_ID, source); } } @@ -102,7 +106,7 @@ private void set(@Nonnull final SpanStorage unsafeTags, Object value, String sou @Override public void tags(@Nonnull final SpanStorage unsafeTags) { // check span.kind eligibility - final Object kind = unsafeTags.load(Tags.SPAN_KIND); + final Object kind = unsafeTags.load(SPAN_KIND_ID); if (Tags.SPAN_KIND_CLIENT.equals(kind) || Tags.SPAN_KIND_PRODUCER.equals(kind)) { // we can calculate the peer service now resolve(unsafeTags); From 590f6e7e1bf653b30081edd3e2b74ad997b18257 Mon Sep 17 00:00:00 2001 From: Douglas Q Hawkins Date: Mon, 26 Oct 2026 12:56:14 -0400 Subject: [PATCH 3/8] Test that a known tag's id and name address the same tag in SpanStorage Co-Authored-By: Claude Opus 5.5 --- .../datadog/trace/core/SpanStorageTest.java | 87 +++++++++++++++++++ 1 file changed, 87 insertions(+) create mode 100644 dd-trace-core/src/test/java/datadog/trace/core/SpanStorageTest.java diff --git a/dd-trace-core/src/test/java/datadog/trace/core/SpanStorageTest.java b/dd-trace-core/src/test/java/datadog/trace/core/SpanStorageTest.java new file mode 100644 index 00000000000..3c6c24277d1 --- /dev/null +++ b/dd-trace-core/src/test/java/datadog/trace/core/SpanStorageTest.java @@ -0,0 +1,87 @@ +package datadog.trace.core; + +import static datadog.trace.api.KnownTags.HTTP_ROUTE_ID; +import static datadog.trace.api.KnownTags.HTTP_STATUS_CODE_ID; +import static datadog.trace.bootstrap.instrumentation.api.Tags.HTTP_ROUTE; +import static datadog.trace.bootstrap.instrumentation.api.Tags.HTTP_STATUS; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; + +import datadog.trace.api.SpanStorage; +import datadog.trace.api.TagMap; +import datadog.trace.common.writer.ListWriter; +import java.util.function.Consumer; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +/** A known tag's id and name address the same tag, in every {@link SpanStorage}. */ +class SpanStorageTest extends DDCoreJavaSpecification { + private CoreTracer tracer; + + @BeforeEach + void setup() { + tracer = tracerBuilder().writer(new ListWriter()).build(); + } + + @AfterEach + void cleanup() { + tracer.close(); + } + + @Test + void storedByIdLoadsByName() { + forEachStorage( + storage -> { + storage.store(HTTP_ROUTE_ID, "/users/{id}"); + + assertEquals("/users/{id}", storage.load(HTTP_ROUTE)); + assertEquals("/users/{id}", storage.load(HTTP_ROUTE_ID)); + }); + } + + @Test + void storedByNameLoadsById() { + forEachStorage( + storage -> { + storage.store(HTTP_ROUTE, "/users/{id}"); + + assertEquals("/users/{id}", storage.load(HTTP_ROUTE_ID)); + }); + } + + @Test + void loadStringByIdConverts() { + forEachStorage( + storage -> { + storage.store(HTTP_STATUS_CODE_ID, 404); + + assertEquals("404", storage.loadString(HTTP_STATUS_CODE_ID)); + assertEquals("404", storage.loadString(HTTP_STATUS)); + }); + } + + @Test + void nullStoredByIdRemoves() { + forEachStorage( + storage -> { + storage.store(HTTP_ROUTE, "/users/{id}"); + + storage.store(HTTP_ROUTE_ID, null); + + assertNull(storage.load(HTTP_ROUTE)); + assertNull(storage.loadString(HTTP_ROUTE_ID)); + }); + } + + private void forEachStorage(Consumer check) { + check.accept(TagMap.create()); + + DDSpan span = (DDSpan) tracer.buildSpan("test", "span").start(); + try { + check.accept(span.spanContext()); + } finally { + span.finish(); + } + } +} From 38dc4b38cbac5ed63943db4ffa97451bbb9e3a40 Mon Sep 17 00:00:00 2001 From: Douglas Q Hawkins Date: Mon, 26 Oct 2026 15:42:18 -0400 Subject: [PATCH 4/8] Rename SpanStorage to TagStore Everything on it is a tag, and TagMap implements it too, so TagStore says what it is. Span-named types stay the layer with more to them: SpanData is the intercepted view, with span-level fields as well as tags. Co-Authored-By: Claude Opus 5.5 --- .../main/java/datadog/trace/core/DDSpanContext.java | 6 +++--- .../core/tagprocessor/EagerPostProcessorChain.java | 4 ++-- .../core/tagprocessor/EagerTagsPostProcessor.java | 6 +++--- .../core/tagprocessor/HttpEndpointPostProcessor.java | 4 ++-- .../trace/core/tagprocessor/InternalTagsAdder.java | 4 ++-- .../trace/core/tagprocessor/PeerServiceCalculator.java | 6 +++--- .../core/{SpanStorageTest.java => TagStoreTest.java} | 8 ++++---- .../src/main/java/datadog/trace/api/TagMap.java | 5 ++--- .../trace/api/{SpanStorage.java => TagStore.java} | 2 +- .../datadog/trace/api/endpoint/EndpointResolver.java | 4 ++-- .../java/datadog/trace/api/naming/NamingSchema.java | 4 ++-- .../trace/api/naming/v0/PeerServiceNamingV0.java | 4 ++-- .../trace/api/naming/v1/PeerServiceNamingV1.java | 10 +++++----- 13 files changed, 33 insertions(+), 34 deletions(-) rename dd-trace-core/src/test/java/datadog/trace/core/{SpanStorageTest.java => TagStoreTest.java} (92%) rename internal-api/src/main/java/datadog/trace/api/{SpanStorage.java => TagStore.java} (98%) 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 044314cecf4..8c77f2fbb39 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 @@ -18,8 +18,8 @@ import datadog.trace.api.KnownTagCodec; import datadog.trace.api.KnownTags; import datadog.trace.api.ProcessTags; -import datadog.trace.api.SpanStorage; import datadog.trace.api.TagMap; +import datadog.trace.api.TagStore; import datadog.trace.api.cache.DDCache; import datadog.trace.api.cache.DDCaches; import datadog.trace.api.config.TracerConfig; @@ -69,7 +69,7 @@ * the associated Span instance */ public class DDSpanContext - implements AgentSpanContext, RequestContext, TraceSegment, ProfilerContext, SpanStorage { + implements AgentSpanContext, RequestContext, TraceSegment, ProfilerContext, TagStore { private static final Logger log = LoggerFactory.getLogger(DDSpanContext.class); public static final String PRIORITY_SAMPLING_KEY = "_sampling_priority_v1"; @@ -1397,7 +1397,7 @@ Object getTag(final String key) { } /* - * SpanStorage: the span's tags, loaded and stored with no interception. + * TagStore: the span's tags, loaded and stored with no interception. * Unsynchronized, like unsafeGetTag: callers hold the span's tag lock, as post-processing does. */ diff --git a/dd-trace-core/src/main/java/datadog/trace/core/tagprocessor/EagerPostProcessorChain.java b/dd-trace-core/src/main/java/datadog/trace/core/tagprocessor/EagerPostProcessorChain.java index f5ac6765293..21cc4ed9be6 100644 --- a/dd-trace-core/src/main/java/datadog/trace/core/tagprocessor/EagerPostProcessorChain.java +++ b/dd-trace-core/src/main/java/datadog/trace/core/tagprocessor/EagerPostProcessorChain.java @@ -1,6 +1,6 @@ package datadog.trace.core.tagprocessor; -import datadog.trace.api.SpanStorage; +import datadog.trace.api.TagStore; import datadog.trace.bootstrap.instrumentation.api.AppendableSpanLinks; import datadog.trace.core.DDSpanContext; import java.util.Objects; @@ -16,7 +16,7 @@ public EagerPostProcessorChain(@Nonnull final EagerTagsPostProcessor... processo @Override public void processTags( - SpanStorage storage, DDSpanContext spanContext, AppendableSpanLinks spanLinks) { + TagStore storage, DDSpanContext spanContext, AppendableSpanLinks spanLinks) { for (final EagerTagsPostProcessor processor : chain) { processor.processTags(storage, spanContext, spanLinks); } diff --git a/dd-trace-core/src/main/java/datadog/trace/core/tagprocessor/EagerTagsPostProcessor.java b/dd-trace-core/src/main/java/datadog/trace/core/tagprocessor/EagerTagsPostProcessor.java index 54ace639c80..503d922e8a9 100644 --- a/dd-trace-core/src/main/java/datadog/trace/core/tagprocessor/EagerTagsPostProcessor.java +++ b/dd-trace-core/src/main/java/datadog/trace/core/tagprocessor/EagerTagsPostProcessor.java @@ -1,12 +1,12 @@ package datadog.trace.core.tagprocessor; -import datadog.trace.api.SpanStorage; +import datadog.trace.api.TagStore; import datadog.trace.bootstrap.instrumentation.api.AppendableSpanLinks; import datadog.trace.core.DDSpanContext; /** * A tag post-processor run on the application thread as a span finishes. It works on the span's - * {@link SpanStorage} rather than a {@code TagMap}, so the span needn't build one for it. + * {@link TagStore} rather than a {@code TagMap}, so the span needn't build one for it. */ public abstract class EagerTagsPostProcessor { /** @@ -17,5 +17,5 @@ public abstract class EagerTagsPostProcessor { * @param spanLinks the span's links */ public abstract void processTags( - SpanStorage storage, DDSpanContext spanContext, AppendableSpanLinks spanLinks); + TagStore storage, DDSpanContext spanContext, AppendableSpanLinks spanLinks); } diff --git a/dd-trace-core/src/main/java/datadog/trace/core/tagprocessor/HttpEndpointPostProcessor.java b/dd-trace-core/src/main/java/datadog/trace/core/tagprocessor/HttpEndpointPostProcessor.java index 9f1a9b8a51e..c447bc32ec6 100644 --- a/dd-trace-core/src/main/java/datadog/trace/core/tagprocessor/HttpEndpointPostProcessor.java +++ b/dd-trace-core/src/main/java/datadog/trace/core/tagprocessor/HttpEndpointPostProcessor.java @@ -4,7 +4,7 @@ import static datadog.trace.api.KnownTags.HTTP_ROUTE_ID; import static datadog.trace.api.KnownTags.HTTP_URL_ID; -import datadog.trace.api.SpanStorage; +import datadog.trace.api.TagStore; import datadog.trace.api.endpoint.EndpointResolver; import datadog.trace.api.internal.VisibleForTesting; import datadog.trace.bootstrap.instrumentation.api.AppendableSpanLinks; @@ -55,7 +55,7 @@ public HttpEndpointPostProcessor() { @Override public void processTags( - SpanStorage unsafeTags, DDSpanContext spanContext, AppendableSpanLinks spanLinks) { + TagStore unsafeTags, DDSpanContext spanContext, AppendableSpanLinks spanLinks) { if (!endpointResolver.isEnabled()) { log.debug("EndpointResolver is not enabled, skipping HTTP endpoint post processing"); return; diff --git a/dd-trace-core/src/main/java/datadog/trace/core/tagprocessor/InternalTagsAdder.java b/dd-trace-core/src/main/java/datadog/trace/core/tagprocessor/InternalTagsAdder.java index 8847b833c44..b0f8184e5d9 100644 --- a/dd-trace-core/src/main/java/datadog/trace/core/tagprocessor/InternalTagsAdder.java +++ b/dd-trace-core/src/main/java/datadog/trace/core/tagprocessor/InternalTagsAdder.java @@ -3,8 +3,8 @@ import static datadog.trace.api.KnownTags.DD_BASE_SERVICE_ID; import static datadog.trace.api.KnownTags.VERSION_ID; -import datadog.trace.api.SpanStorage; import datadog.trace.api.TagMap; +import datadog.trace.api.TagStore; import datadog.trace.bootstrap.instrumentation.api.AppendableSpanLinks; import datadog.trace.bootstrap.instrumentation.api.UTF8BytesString; import datadog.trace.core.DDSpanContext; @@ -29,7 +29,7 @@ public InternalTagsAdder(@Nonnull final String ddService, @Nullable final String @Override public void processTags( - SpanStorage unsafeTags, DDSpanContext spanContext, AppendableSpanLinks spanLinks) { + TagStore unsafeTags, DDSpanContext spanContext, AppendableSpanLinks spanLinks) { if (spanContext == null) { return; } diff --git a/dd-trace-core/src/main/java/datadog/trace/core/tagprocessor/PeerServiceCalculator.java b/dd-trace-core/src/main/java/datadog/trace/core/tagprocessor/PeerServiceCalculator.java index 83a39c52002..98fd6888d45 100644 --- a/dd-trace-core/src/main/java/datadog/trace/core/tagprocessor/PeerServiceCalculator.java +++ b/dd-trace-core/src/main/java/datadog/trace/core/tagprocessor/PeerServiceCalculator.java @@ -4,7 +4,7 @@ import static datadog.trace.api.KnownTags.PEER_SERVICE_ID; import datadog.trace.api.Config; -import datadog.trace.api.SpanStorage; +import datadog.trace.api.TagStore; import datadog.trace.api.internal.VisibleForTesting; import datadog.trace.api.naming.NamingSchema; import datadog.trace.api.naming.SpanNaming; @@ -35,7 +35,7 @@ public PeerServiceCalculator() { @Override public void processTags( - SpanStorage unsafeTags, DDSpanContext spanContext, AppendableSpanLinks spanLinks) { + TagStore unsafeTags, DDSpanContext spanContext, AppendableSpanLinks spanLinks) { Object peerService = unsafeTags.load(PEER_SERVICE_ID); // the user set it if (peerService != null) { @@ -53,7 +53,7 @@ public void processTags( // we have no peer.service and we do not compute defaults. Leave the map untouched } - private void remapPeerService(SpanStorage unsafeTags, Object value) { + private void remapPeerService(TagStore unsafeTags, Object value) { if (value != null) { String mapped = peerServiceMapping.get(value); if (mapped != null) { diff --git a/dd-trace-core/src/test/java/datadog/trace/core/SpanStorageTest.java b/dd-trace-core/src/test/java/datadog/trace/core/TagStoreTest.java similarity index 92% rename from dd-trace-core/src/test/java/datadog/trace/core/SpanStorageTest.java rename to dd-trace-core/src/test/java/datadog/trace/core/TagStoreTest.java index 3c6c24277d1..5f12d64300a 100644 --- a/dd-trace-core/src/test/java/datadog/trace/core/SpanStorageTest.java +++ b/dd-trace-core/src/test/java/datadog/trace/core/TagStoreTest.java @@ -7,16 +7,16 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNull; -import datadog.trace.api.SpanStorage; import datadog.trace.api.TagMap; +import datadog.trace.api.TagStore; import datadog.trace.common.writer.ListWriter; import java.util.function.Consumer; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -/** A known tag's id and name address the same tag, in every {@link SpanStorage}. */ -class SpanStorageTest extends DDCoreJavaSpecification { +/** A known tag's id and name address the same tag, in every {@link TagStore}. */ +class TagStoreTest extends DDCoreJavaSpecification { private CoreTracer tracer; @BeforeEach @@ -74,7 +74,7 @@ void nullStoredByIdRemoves() { }); } - private void forEachStorage(Consumer check) { + private void forEachStorage(Consumer check) { check.accept(TagMap.create()); DDSpan span = (DDSpan) tracer.buildSpan("test", "span").start(); diff --git a/internal-api/src/main/java/datadog/trace/api/TagMap.java b/internal-api/src/main/java/datadog/trace/api/TagMap.java index 7de3856b002..bb18907b390 100644 --- a/internal-api/src/main/java/datadog/trace/api/TagMap.java +++ b/internal-api/src/main/java/datadog/trace/api/TagMap.java @@ -48,8 +48,7 @@ *

  • adaptive collision * */ -public final class TagMap - implements Map, Iterable, SpanStorage { +public final class TagMap implements Map, Iterable, TagStore { /** Immutable empty TagMap - similar to {@link Collections#emptyMap()} */ // Frozen view over a length-1 array: bucket masking needs a power-of-two array length (size 0 // would fail with ArrayIndexOutOfBoundsException, size 1 works), and the private constructor @@ -1347,7 +1346,7 @@ public String getString(String tag) { return entry == null ? null : entry.stringValue(); } - /* SpanStorage, for standalone maps handed to the pipeline: TagMap's own get and set. */ + /* TagStore, for standalone maps handed to the pipeline: TagMap's own get and set. */ @Override public Object load(String tag) { diff --git a/internal-api/src/main/java/datadog/trace/api/SpanStorage.java b/internal-api/src/main/java/datadog/trace/api/TagStore.java similarity index 98% rename from internal-api/src/main/java/datadog/trace/api/SpanStorage.java rename to internal-api/src/main/java/datadog/trace/api/TagStore.java index 8a5f18b1b99..04e81c43e67 100644 --- a/internal-api/src/main/java/datadog/trace/api/SpanStorage.java +++ b/internal-api/src/main/java/datadog/trace/api/TagStore.java @@ -15,7 +15,7 @@ *

    This is the first slice: only what the eager tag post-processors need. Typed loads and * primitive stores come as more of the pipeline moves to it. */ -public interface SpanStorage { +public interface TagStore { /** The tag's value, or null when it's absent. */ Object load(String tag); diff --git a/internal-api/src/main/java/datadog/trace/api/endpoint/EndpointResolver.java b/internal-api/src/main/java/datadog/trace/api/endpoint/EndpointResolver.java index f9877d708bf..29533b14e18 100644 --- a/internal-api/src/main/java/datadog/trace/api/endpoint/EndpointResolver.java +++ b/internal-api/src/main/java/datadog/trace/api/endpoint/EndpointResolver.java @@ -2,7 +2,7 @@ import static datadog.trace.api.KnownTags.HTTP_ENDPOINT_ID; -import datadog.trace.api.SpanStorage; +import datadog.trace.api.TagStore; import javax.annotation.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -51,7 +51,7 @@ public EndpointResolver(boolean enabled, boolean alwaysSimplifiedEndpoint) { */ @Nullable public String resolveEndpoint( - SpanStorage unsafeTags, @Nullable String httpRoute, @Nullable String httpUrl) { + TagStore unsafeTags, @Nullable String httpRoute, @Nullable String httpUrl) { if (!enabled) { return null; } diff --git a/internal-api/src/main/java/datadog/trace/api/naming/NamingSchema.java b/internal-api/src/main/java/datadog/trace/api/naming/NamingSchema.java index b17b52bc58b..7876901a68d 100644 --- a/internal-api/src/main/java/datadog/trace/api/naming/NamingSchema.java +++ b/internal-api/src/main/java/datadog/trace/api/naming/NamingSchema.java @@ -1,6 +1,6 @@ package datadog.trace.api.naming; -import datadog.trace.api.SpanStorage; +import datadog.trace.api.TagStore; import java.util.function.Supplier; import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -232,7 +232,7 @@ interface ForPeerService { * @param unsafeTags the span tags. Map to be mutated */ @Nonnull - void tags(@Nonnull SpanStorage unsafeTags); + void tags(@Nonnull TagStore unsafeTags); } interface ForServer { diff --git a/internal-api/src/main/java/datadog/trace/api/naming/v0/PeerServiceNamingV0.java b/internal-api/src/main/java/datadog/trace/api/naming/v0/PeerServiceNamingV0.java index d802622205a..004a8e654bd 100644 --- a/internal-api/src/main/java/datadog/trace/api/naming/v0/PeerServiceNamingV0.java +++ b/internal-api/src/main/java/datadog/trace/api/naming/v0/PeerServiceNamingV0.java @@ -1,6 +1,6 @@ package datadog.trace.api.naming.v0; -import datadog.trace.api.SpanStorage; +import datadog.trace.api.TagStore; import datadog.trace.api.naming.NamingSchema; import javax.annotation.Nonnull; @@ -12,5 +12,5 @@ public boolean supports() { @Nonnull @Override - public void tags(@Nonnull final SpanStorage unsafeTags) {} + public void tags(@Nonnull final TagStore unsafeTags) {} } diff --git a/internal-api/src/main/java/datadog/trace/api/naming/v1/PeerServiceNamingV1.java b/internal-api/src/main/java/datadog/trace/api/naming/v1/PeerServiceNamingV1.java index ae1e7d6ed19..4f88c13aa46 100644 --- a/internal-api/src/main/java/datadog/trace/api/naming/v1/PeerServiceNamingV1.java +++ b/internal-api/src/main/java/datadog/trace/api/naming/v1/PeerServiceNamingV1.java @@ -5,7 +5,7 @@ import static datadog.trace.api.KnownTags.PEER_SERVICE_ID; import static datadog.trace.api.KnownTags.SPAN_KIND_ID; -import datadog.trace.api.SpanStorage; +import datadog.trace.api.TagStore; import datadog.trace.api.naming.NamingSchema; import datadog.trace.bootstrap.instrumentation.api.InstrumentationTags; import datadog.trace.bootstrap.instrumentation.api.Tags; @@ -57,7 +57,7 @@ public boolean supports() { return true; } - private void resolve(@Nonnull final SpanStorage unsafeTags) { + private void resolve(@Nonnull final TagStore unsafeTags) { final Object component = unsafeTags.load(COMPONENT_ID); // avoid issues with UTF8ByteString or others final String componentString = component == null ? null : component.toString(); @@ -76,7 +76,7 @@ private void resolve(@Nonnull final SpanStorage unsafeTags) { } private boolean resolveBy( - @Nonnull final SpanStorage unsafeTags, @Nullable final String[] precursors) { + @Nonnull final TagStore unsafeTags, @Nullable final String[] precursors) { if (precursors == null) { return false; } @@ -95,7 +95,7 @@ private boolean resolveBy( return true; } - private void set(@Nonnull final SpanStorage unsafeTags, Object value, String source) { + private void set(@Nonnull final TagStore unsafeTags, Object value, String source) { if (value != null) { unsafeTags.store(PEER_SERVICE_ID, value); unsafeTags.store(DD_PEER_SERVICE_SOURCE_ID, source); @@ -104,7 +104,7 @@ private void set(@Nonnull final SpanStorage unsafeTags, Object value, String sou @Nonnull @Override - public void tags(@Nonnull final SpanStorage unsafeTags) { + public void tags(@Nonnull final TagStore unsafeTags) { // check span.kind eligibility final Object kind = unsafeTags.load(SPAN_KIND_ID); if (Tags.SPAN_KIND_CLIENT.equals(kind) || Tags.SPAN_KIND_PRODUCER.equals(kind)) { From 773906530c7b130b2a012ddb8b6841a47af7e328 Mon Sep 17 00:00:00 2001 From: Douglas Q Hawkins Date: Mon, 26 Oct 2026 15:46:10 -0400 Subject: [PATCH 5/8] Say why the span implements TagStore A span isn't conceptually a TagStore: the store is the storage beneath it, as a Map was beneath a TagMap. It implements one only so post-processing can be handed its storage without allocating, since the processor chain's dispatch loop keeps escape analysis from removing a wrapper. Drop the TagStore Javadoc's note about this being a first slice. Co-Authored-By: Claude Opus 5.5 --- .../src/main/java/datadog/trace/core/DDSpanContext.java | 8 ++++++-- .../trace/core/tagprocessor/EagerTagsPostProcessor.java | 6 ++++-- .../src/main/java/datadog/trace/api/TagStore.java | 7 ++----- 3 files changed, 12 insertions(+), 9 deletions(-) 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 8c77f2fbb39..cb6fa415438 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 @@ -1397,8 +1397,12 @@ Object getTag(final String key) { } /* - * TagStore: the span's tags, loaded and stored with no interception. - * Unsynchronized, like unsafeGetTag: callers hold the span's tag lock, as post-processing does. + * TagStore: the span's tags as stored, loaded and stored with no locking, interception or + * interpretation -- the storage beneath the span, as a Map was beneath a TagMap. A span isn't + * conceptually a TagStore. It implements one so post-processing is handed the span's storage + * without allocating: a wrapper would be free only if escape analysis removed it, and the + * post-processor chain's dispatch loop defeats that. Callers hold the span's tag lock, as + * post-processing does. */ @Override diff --git a/dd-trace-core/src/main/java/datadog/trace/core/tagprocessor/EagerTagsPostProcessor.java b/dd-trace-core/src/main/java/datadog/trace/core/tagprocessor/EagerTagsPostProcessor.java index 503d922e8a9..55aa3579216 100644 --- a/dd-trace-core/src/main/java/datadog/trace/core/tagprocessor/EagerTagsPostProcessor.java +++ b/dd-trace-core/src/main/java/datadog/trace/core/tagprocessor/EagerTagsPostProcessor.java @@ -6,13 +6,15 @@ /** * A tag post-processor run on the application thread as a span finishes. It works on the span's - * {@link TagStore} rather than a {@code TagMap}, so the span needn't build one for it. + * tags as stored, through a {@link TagStore}, rather than a {@code TagMap} the span would have to + * build for it. The span passes itself as that store, which allocates nothing. */ public abstract class EagerTagsPostProcessor { /** * Processes the span's tags. * - * @param contents the span's contents to read and write; the caller holds the span's tag lock + * @param storage the span's tags as stored, to read and write; the caller holds the span's tag + * lock * @param spanContext the span, for its other properties * @param spanLinks the span's links */ diff --git a/internal-api/src/main/java/datadog/trace/api/TagStore.java b/internal-api/src/main/java/datadog/trace/api/TagStore.java index 04e81c43e67..b0e63cbf15f 100644 --- a/internal-api/src/main/java/datadog/trace/api/TagStore.java +++ b/internal-api/src/main/java/datadog/trace/api/TagStore.java @@ -2,8 +2,8 @@ /** * A span's tags as stored, for the tracer core's processing pipeline: loads and stores go straight - * to storage, with no interception. Thread safety is up to the implementation; {@code - * DDSpanContext}'s expects the span's tag lock to be held. + * to storage, with no interception or interpretation. Thread safety is up to the implementation; + * {@code DDSpanContext}'s expects the span's tag lock to be held. * *

    Its own verbs, {@code load} and {@code store}, keep it apart from {@link TagMap}'s {@code * get}/{@code set} and the span's intercepted {@code getTag}/{@code setTag}, so a type can offer @@ -11,9 +11,6 @@ * *

    A known tag can be addressed by its {@link KnownTags} id or by its name, and both address the * same tag. Prefer the id: it skips classifying the name on every call. A custom tag has no id. - * - *

    This is the first slice: only what the eager tag post-processors need. Typed loads and - * primitive stores come as more of the pipeline moves to it. */ public interface TagStore { /** The tag's value, or null when it's absent. */ From 36bbbd276c9e1425f107de30890329b918d04f53 Mon Sep 17 00:00:00 2001 From: Douglas Q Hawkins Date: Mon, 26 Oct 2026 15:56:53 -0400 Subject: [PATCH 6/8] Test that urlOrPath answers the same from a TagStore as from a map Co-Authored-By: Claude Opus 5.5 --- .../api/OtelHttpSemanticsTest.java | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/internal-api/src/test/java/datadog/trace/bootstrap/instrumentation/api/OtelHttpSemanticsTest.java b/internal-api/src/test/java/datadog/trace/bootstrap/instrumentation/api/OtelHttpSemanticsTest.java index bfbf0a748fe..ea6ec759ffa 100644 --- a/internal-api/src/test/java/datadog/trace/bootstrap/instrumentation/api/OtelHttpSemanticsTest.java +++ b/internal-api/src/test/java/datadog/trace/bootstrap/instrumentation/api/OtelHttpSemanticsTest.java @@ -13,6 +13,8 @@ import datadog.trace.api.DDTags; import datadog.trace.api.KnownTags; +import datadog.trace.api.TagMap; +import datadog.trace.api.TagStore; import java.net.URI; import java.util.HashMap; import java.util.Map; @@ -79,6 +81,26 @@ void urlOrPathFallsBackOnlyWhenUrlIsAbsent(String httpUrl, String path, String e assertEquals(expected, OtelHttpSemantics.urlOrPath(tags)); } + /** From a span's tags as stored, it answers as it does from the same tags as a map. */ + @TableTest({ + "Scenario | Http URL | URL path ", + "No URL or path | | ", + "URL only | https://example.com/full | ", + "Path only | | /orders ", + "Prefer URL | https://example.com/full | /different", + "Empty URL | '' | /orders ", + "Empty path | | '' " + }) + void urlOrPathFromTagStoreMatchesMap(String httpUrl, String path) { + TagMap tags = TagMap.create(); + tags.store(KnownTags.HTTP_URL_ID, httpUrl); + tags.store(KnownTags.URL_PATH_ID, path); + + assertEquals( + OtelHttpSemantics.urlOrPath((Map) tags), + OtelHttpSemantics.urlOrPath((TagStore) tags)); + } + @Test void urlOrPathDoesNotRenderLazyUrl() { URIUtils.LazyUrl url = URIUtils.lazyValidURL("https", "example.com", 443, "/orders"); From b9dbfa9704b1cea8310965589c20315b6b26828b Mon Sep 17 00:00:00 2001 From: Douglas Q Hawkins Date: Mon, 26 Oct 2026 16:43:10 -0400 Subject: [PATCH 7/8] Name the post-processor chain's TagStore parameter unsafeTags EagerTagsPostProcessor and EagerPostProcessorChain still called it storage after the rename to TagStore, while every processor calls it unsafeTags. Co-Authored-By: Claude Opus 5.5 --- .../trace/core/tagprocessor/EagerPostProcessorChain.java | 4 ++-- .../trace/core/tagprocessor/EagerTagsPostProcessor.java | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/dd-trace-core/src/main/java/datadog/trace/core/tagprocessor/EagerPostProcessorChain.java b/dd-trace-core/src/main/java/datadog/trace/core/tagprocessor/EagerPostProcessorChain.java index 21cc4ed9be6..df1ee6ea25d 100644 --- a/dd-trace-core/src/main/java/datadog/trace/core/tagprocessor/EagerPostProcessorChain.java +++ b/dd-trace-core/src/main/java/datadog/trace/core/tagprocessor/EagerPostProcessorChain.java @@ -16,9 +16,9 @@ public EagerPostProcessorChain(@Nonnull final EagerTagsPostProcessor... processo @Override public void processTags( - TagStore storage, DDSpanContext spanContext, AppendableSpanLinks spanLinks) { + TagStore unsafeTags, DDSpanContext spanContext, AppendableSpanLinks spanLinks) { for (final EagerTagsPostProcessor processor : chain) { - processor.processTags(storage, spanContext, spanLinks); + processor.processTags(unsafeTags, spanContext, spanLinks); } } } diff --git a/dd-trace-core/src/main/java/datadog/trace/core/tagprocessor/EagerTagsPostProcessor.java b/dd-trace-core/src/main/java/datadog/trace/core/tagprocessor/EagerTagsPostProcessor.java index 55aa3579216..15b1dcf4e1a 100644 --- a/dd-trace-core/src/main/java/datadog/trace/core/tagprocessor/EagerTagsPostProcessor.java +++ b/dd-trace-core/src/main/java/datadog/trace/core/tagprocessor/EagerTagsPostProcessor.java @@ -13,11 +13,11 @@ public abstract class EagerTagsPostProcessor { /** * Processes the span's tags. * - * @param storage the span's tags as stored, to read and write; the caller holds the span's tag + * @param unsafeTags the span's tags as stored, to read and write; the caller holds the span's tag * lock * @param spanContext the span, for its other properties * @param spanLinks the span's links */ public abstract void processTags( - TagStore storage, DDSpanContext spanContext, AppendableSpanLinks spanLinks); + TagStore unsafeTags, DDSpanContext spanContext, AppendableSpanLinks spanLinks); } From 3eed22059a3706f0ce6b1b4b071d45fa5c763b43 Mon Sep 17 00:00:00 2001 From: Douglas Q Hawkins Date: Mon, 26 Oct 2026 16:43:12 -0400 Subject: [PATCH 8/8] Rename TagStoreTest's storage helper and locals Co-Authored-By: Claude Opus 5.5 --- .../java/datadog/trace/core/TagStoreTest.java | 42 +++++++++---------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/dd-trace-core/src/test/java/datadog/trace/core/TagStoreTest.java b/dd-trace-core/src/test/java/datadog/trace/core/TagStoreTest.java index 5f12d64300a..fd6e413f4e8 100644 --- a/dd-trace-core/src/test/java/datadog/trace/core/TagStoreTest.java +++ b/dd-trace-core/src/test/java/datadog/trace/core/TagStoreTest.java @@ -31,50 +31,50 @@ void cleanup() { @Test void storedByIdLoadsByName() { - forEachStorage( - storage -> { - storage.store(HTTP_ROUTE_ID, "/users/{id}"); + forEachTagStore( + tags -> { + tags.store(HTTP_ROUTE_ID, "/users/{id}"); - assertEquals("/users/{id}", storage.load(HTTP_ROUTE)); - assertEquals("/users/{id}", storage.load(HTTP_ROUTE_ID)); + assertEquals("/users/{id}", tags.load(HTTP_ROUTE)); + assertEquals("/users/{id}", tags.load(HTTP_ROUTE_ID)); }); } @Test void storedByNameLoadsById() { - forEachStorage( - storage -> { - storage.store(HTTP_ROUTE, "/users/{id}"); + forEachTagStore( + tags -> { + tags.store(HTTP_ROUTE, "/users/{id}"); - assertEquals("/users/{id}", storage.load(HTTP_ROUTE_ID)); + assertEquals("/users/{id}", tags.load(HTTP_ROUTE_ID)); }); } @Test void loadStringByIdConverts() { - forEachStorage( - storage -> { - storage.store(HTTP_STATUS_CODE_ID, 404); + forEachTagStore( + tags -> { + tags.store(HTTP_STATUS_CODE_ID, 404); - assertEquals("404", storage.loadString(HTTP_STATUS_CODE_ID)); - assertEquals("404", storage.loadString(HTTP_STATUS)); + assertEquals("404", tags.loadString(HTTP_STATUS_CODE_ID)); + assertEquals("404", tags.loadString(HTTP_STATUS)); }); } @Test void nullStoredByIdRemoves() { - forEachStorage( - storage -> { - storage.store(HTTP_ROUTE, "/users/{id}"); + forEachTagStore( + tags -> { + tags.store(HTTP_ROUTE, "/users/{id}"); - storage.store(HTTP_ROUTE_ID, null); + tags.store(HTTP_ROUTE_ID, null); - assertNull(storage.load(HTTP_ROUTE)); - assertNull(storage.loadString(HTTP_ROUTE_ID)); + assertNull(tags.load(HTTP_ROUTE)); + assertNull(tags.loadString(HTTP_ROUTE_ID)); }); } - private void forEachStorage(Consumer check) { + private void forEachTagStore(Consumer check) { check.accept(TagMap.create()); DDSpan span = (DDSpan) tracer.buildSpan("test", "span").start();