From 60e1300adde32173d218055ff30b4961f0032a7f Mon Sep 17 00:00:00 2001 From: Douglas Q Hawkins Date: Thu, 9 Jul 2026 05:35:53 -0400 Subject: [PATCH] Demonstrate SpanPrototype: bake constant span tags in decorators MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Wires BaseDecorator/ClientDecorator/ServerDecorator to bake a per-decorator SpanPrototype (identity + constant tags) — the concrete usage behind the slide examples. Decorator-coupled tests live here; the pure-API test moved to the introducing PR. Co-Authored-By: Claude Opus 4.8 --- .../decorator/BaseDecorator.java | 35 ++++++++ .../decorator/ClientDecorator.java | 6 ++ .../decorator/ServerDecorator.java | 8 ++ .../decorator/SpanPrototypeTest.java | 81 +++++++++++++++++++ 4 files changed, 130 insertions(+) create mode 100644 dd-java-agent/agent-bootstrap/src/test/java/datadog/trace/bootstrap/instrumentation/decorator/SpanPrototypeTest.java diff --git a/dd-java-agent/agent-bootstrap/src/main/java/datadog/trace/bootstrap/instrumentation/decorator/BaseDecorator.java b/dd-java-agent/agent-bootstrap/src/main/java/datadog/trace/bootstrap/instrumentation/decorator/BaseDecorator.java index 2628e9416cf..0ed7b470727 100644 --- a/dd-java-agent/agent-bootstrap/src/main/java/datadog/trace/bootstrap/instrumentation/decorator/BaseDecorator.java +++ b/dd-java-agent/agent-bootstrap/src/main/java/datadog/trace/bootstrap/instrumentation/decorator/BaseDecorator.java @@ -12,6 +12,7 @@ import datadog.trace.bootstrap.instrumentation.api.AgentScope; import datadog.trace.bootstrap.instrumentation.api.AgentSpan; import datadog.trace.bootstrap.instrumentation.api.ErrorPriorities; +import datadog.trace.bootstrap.instrumentation.api.SpanPrototype; import datadog.trace.bootstrap.instrumentation.api.Tags; import java.lang.reflect.Method; import java.net.Inet4Address; @@ -48,6 +49,10 @@ public String apply(Class clazz) { // Deliberately not volatile, reading null and repeating the calculation is safe private TagMap.Entry cachedComponentEntry = null; + // Deliberately not volatile, same benign-race reasoning as cachedComponentEntry: baking twice is + // safe because the constant tags are identical. + private SpanPrototype cachedPrototype = null; + protected BaseDecorator() { final Config config = Config.get(); final String[] instrumentationNames = instrumentationNames(); @@ -90,6 +95,36 @@ protected boolean traceAnalyticsDefault() { return false; } + /** + * The baked-once {@link SpanPrototype} for this decorator: the constant identity + tags {@link + * #afterStart} would otherwise stamp one at a time. Composed across the hierarchy via {@link + * #prototypeBuilder()} and cached (lazily, to respect the same static-init ordering caution as + * {@link #componentEntry()}). + * + *

Not yet wired into span creation — this is the provider surface; the seed hook comes next. + */ + public final SpanPrototype prototype() { + SpanPrototype prototype = cachedPrototype; + if (prototype == null) { + cachedPrototype = prototype = prototypeBuilder().build(); + } + return prototype; + } + + /** + * Contributes this decorator's constant identity + tags to the prototype under construction. + * Overrides must call {@code super.prototypeBuilder()} first, then add their own — mirroring the + * {@link #afterStart} super-chain, but run once at bake time rather than per span. Authors + * compose via the typed builder and never see {@link TagMap}. + */ + protected SpanPrototype.Builder prototypeBuilder() { + return SpanPrototype.builder() + .instrumentationName(instrumentationNames()) + .spanType(spanType()) + .initComponent(component()) + .initTag(traceAnalyticsEntry); + } + public AgentSpan afterStart(final AgentSpan span) { if (spanType() != null) { span.setSpanType(spanType()); diff --git a/dd-java-agent/agent-bootstrap/src/main/java/datadog/trace/bootstrap/instrumentation/decorator/ClientDecorator.java b/dd-java-agent/agent-bootstrap/src/main/java/datadog/trace/bootstrap/instrumentation/decorator/ClientDecorator.java index 99dec2dbc08..bc534e495fd 100644 --- a/dd-java-agent/agent-bootstrap/src/main/java/datadog/trace/bootstrap/instrumentation/decorator/ClientDecorator.java +++ b/dd-java-agent/agent-bootstrap/src/main/java/datadog/trace/bootstrap/instrumentation/decorator/ClientDecorator.java @@ -2,6 +2,7 @@ import datadog.trace.api.TagMap; import datadog.trace.bootstrap.instrumentation.api.AgentSpan; +import datadog.trace.bootstrap.instrumentation.api.SpanPrototype; import datadog.trace.bootstrap.instrumentation.api.Tags; public abstract class ClientDecorator extends BaseDecorator { @@ -43,4 +44,9 @@ public AgentSpan afterStart(final AgentSpan span) { span.setMeasured(true); return super.afterStart(span); } + + @Override + protected SpanPrototype.Builder prototypeBuilder() { + return super.prototypeBuilder().initKind(spanKind()); + } } diff --git a/dd-java-agent/agent-bootstrap/src/main/java/datadog/trace/bootstrap/instrumentation/decorator/ServerDecorator.java b/dd-java-agent/agent-bootstrap/src/main/java/datadog/trace/bootstrap/instrumentation/decorator/ServerDecorator.java index 20b11038ffd..d36dd73b866 100644 --- a/dd-java-agent/agent-bootstrap/src/main/java/datadog/trace/bootstrap/instrumentation/decorator/ServerDecorator.java +++ b/dd-java-agent/agent-bootstrap/src/main/java/datadog/trace/bootstrap/instrumentation/decorator/ServerDecorator.java @@ -3,6 +3,7 @@ import datadog.trace.api.DDTags; import datadog.trace.api.TagMap; import datadog.trace.bootstrap.instrumentation.api.AgentSpan; +import datadog.trace.bootstrap.instrumentation.api.SpanPrototype; import datadog.trace.bootstrap.instrumentation.api.Tags; public abstract class ServerDecorator extends BaseDecorator { @@ -18,4 +19,11 @@ public AgentSpan afterStart(final AgentSpan span) { return super.afterStart(span); } + + @Override + protected SpanPrototype.Builder prototypeBuilder() { + return super.prototypeBuilder() + .initKind(Tags.SPAN_KIND_SERVER) + .initTag(DDTags.LANGUAGE_TAG_KEY, DDTags.LANGUAGE_TAG_VALUE); + } } diff --git a/dd-java-agent/agent-bootstrap/src/test/java/datadog/trace/bootstrap/instrumentation/decorator/SpanPrototypeTest.java b/dd-java-agent/agent-bootstrap/src/test/java/datadog/trace/bootstrap/instrumentation/decorator/SpanPrototypeTest.java new file mode 100644 index 00000000000..d7bfecb29ec --- /dev/null +++ b/dd-java-agent/agent-bootstrap/src/test/java/datadog/trace/bootstrap/instrumentation/decorator/SpanPrototypeTest.java @@ -0,0 +1,81 @@ +package datadog.trace.bootstrap.instrumentation.decorator; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertSame; + +import datadog.trace.api.DDTags; +import datadog.trace.api.TagMap; +import datadog.trace.bootstrap.instrumentation.api.SpanPrototype; +import datadog.trace.bootstrap.instrumentation.api.Tags; +import org.junit.jupiter.api.Test; + +class SpanPrototypeTest { + + @Test + void serverPrototypeComposesBaseAndServerConstants() { + final SpanPrototype prototype = new TestServerDecorator().prototype(); + final TagMap tags = prototype.tags(); + + // Identity (BaseDecorator) + assertEquals("test", prototype.instrumentationName()); + assertEquals("test-type", prototype.spanType()); + // BaseDecorator contribution + assertEquals("test-component", tags.getString(Tags.COMPONENT)); + // ServerDecorator contribution + assertEquals(Tags.SPAN_KIND_SERVER, tags.getString(Tags.SPAN_KIND)); + assertEquals(DDTags.LANGUAGE_TAG_VALUE, tags.getString(DDTags.LANGUAGE_TAG_KEY)); + } + + @Test + void clientPrototypeComposesBaseAndClientConstants() { + final TagMap tags = new TestClientDecorator().prototype().tags(); + + assertEquals("test-component", tags.getString(Tags.COMPONENT)); + assertEquals(Tags.SPAN_KIND_CLIENT, tags.getString(Tags.SPAN_KIND)); + } + + @Test + void prototypeIsBakedOnce() { + final TestServerDecorator decorator = new TestServerDecorator(); + assertSame(decorator.prototype(), decorator.prototype()); + } + + static final class TestServerDecorator extends ServerDecorator { + @Override + protected String[] instrumentationNames() { + return new String[] {"test"}; + } + + @Override + protected CharSequence spanType() { + return "test-type"; + } + + @Override + protected CharSequence component() { + return "test-component"; + } + } + + static final class TestClientDecorator extends ClientDecorator { + @Override + protected String[] instrumentationNames() { + return new String[] {"test"}; + } + + @Override + protected CharSequence spanType() { + return "test-type"; + } + + @Override + protected CharSequence component() { + return "test-component"; + } + + @Override + protected String service() { + return "test-service"; + } + } +}