diff --git a/dd-java-agent/agent-otel/otel-shim/src/main/java/datadog/opentelemetry/shim/context/OtelContext.java b/dd-java-agent/agent-otel/otel-shim/src/main/java/datadog/opentelemetry/shim/context/OtelContext.java index bea0f7a8b5c..a9d0064b75e 100644 --- a/dd-java-agent/agent-otel/otel-shim/src/main/java/datadog/opentelemetry/shim/context/OtelContext.java +++ b/dd-java-agent/agent-otel/otel-shim/src/main/java/datadog/opentelemetry/shim/context/OtelContext.java @@ -20,6 +20,9 @@ public class OtelContext implements Context { private static final String OTEL_CONTEXT_SPAN_KEY = "opentelemetry-trace-span-key"; private static final String OTEL_CONTEXT_ROOT_SPAN_KEY = "opentelemetry-traces-local-root-span"; + /** Keep track of propagated context that has not been captured on the scope stack. */ + private static final ThreadLocal lastPropagated = new ThreadLocal<>(); + private final Span currentSpan; private final Span rootSpan; @@ -51,15 +54,31 @@ public Context with(ContextKey k1, V v1) { @Override public Scope makeCurrent() { - Scope scope = Context.super.makeCurrent(); + final Scope scope = Context.super.makeCurrent(); if (this.currentSpan instanceof OtelSpan) { + // only keep propagated context until next span activation + lastPropagated.remove(); AgentScope agentScope = ((OtelSpan) this.currentSpan).activate(); - scope = new OtelScope(scope, agentScope); + return new OtelScope(scope, agentScope); + } else { + // propagated context not on the scope stack, capture it here + lastPropagated.set(this); + return new Scope() { + @Override + public void close() { + lastPropagated.remove(); + scope.close(); + } + }; } - return scope; } public static Context current() { + // Check for propagated context not on the scope stack + Context context = lastPropagated.get(); + if (null != context) { + return context; + } // Check empty context AgentSpan agentCurrentSpan = AgentTracer.activeSpan(); if (null == agentCurrentSpan) { @@ -91,6 +110,12 @@ public static Context current() { return new OtelContext(otelCurrentSpan, otelRootSpan); } + /** Last propagated context not on the scope stack; {@code null} if there's no such context. */ + @Nullable + public static Context lastPropagated() { + return lastPropagated.get(); + } + @Override public String toString() { return "OtelContext{" diff --git a/dd-java-agent/agent-otel/otel-shim/src/main/java/datadog/opentelemetry/shim/trace/OtelSpanBuilder.java b/dd-java-agent/agent-otel/otel-shim/src/main/java/datadog/opentelemetry/shim/trace/OtelSpanBuilder.java index 45a55f02ad9..c83e7f55018 100644 --- a/dd-java-agent/agent-otel/otel-shim/src/main/java/datadog/opentelemetry/shim/trace/OtelSpanBuilder.java +++ b/dd-java-agent/agent-otel/otel-shim/src/main/java/datadog/opentelemetry/shim/trace/OtelSpanBuilder.java @@ -10,6 +10,7 @@ import static java.lang.Boolean.parseBoolean; import static java.util.Locale.ROOT; +import datadog.opentelemetry.shim.context.OtelContext; import datadog.trace.bootstrap.instrumentation.api.AgentSpan; import datadog.trace.bootstrap.instrumentation.api.AgentTracer; import io.opentelemetry.api.common.AttributeKey; @@ -27,6 +28,7 @@ public class OtelSpanBuilder implements SpanBuilder { private final AgentTracer.SpanBuilder delegate; private boolean spanKindSet; + private boolean ignoreActiveSpan; /** * Operation name overridden value by {@link OtelConventions#OPERATION_NAME_SPECIFIC_ATTRIBUTE} * reserved attribute ({@code null} if not set). @@ -51,14 +53,15 @@ public SpanBuilder setParent(Context context) { AgentSpan.Context extractedContext = extract(context); if (extractedContext != null) { this.delegate.asChildOf(extractedContext); + this.ignoreActiveSpan = true; } return this; } @Override public SpanBuilder setNoParent() { - this.delegate.asChildOf(null); - this.delegate.ignoreActiveSpan(); + this.delegate.ignoreActiveSpan().asChildOf(null); + this.ignoreActiveSpan = true; return this; } @@ -163,6 +166,13 @@ public Span startSpan() { if (!this.spanKindSet) { setSpanKind(INTERNAL); } + if (!this.ignoreActiveSpan) { + // support automatic parenting from propagated context not on the scope stack + Context context = OtelContext.lastPropagated(); + if (null != context) { + setParent(context); + } + } AgentSpan delegate = this.delegate.start(); // Apply overrides if (this.overriddenOperationName != null) { diff --git a/dd-java-agent/instrumentation/opentelemetry/opentelemetry-1.4/src/main/java/datadog/trace/instrumentation/opentelemetry14/OpenTelemetryInstrumentation.java b/dd-java-agent/instrumentation/opentelemetry/opentelemetry-1.4/src/main/java/datadog/trace/instrumentation/opentelemetry14/OpenTelemetryInstrumentation.java index 23a6b92b488..37260659892 100644 --- a/dd-java-agent/instrumentation/opentelemetry/opentelemetry-1.4/src/main/java/datadog/trace/instrumentation/opentelemetry14/OpenTelemetryInstrumentation.java +++ b/dd-java-agent/instrumentation/opentelemetry/opentelemetry-1.4/src/main/java/datadog/trace/instrumentation/opentelemetry14/OpenTelemetryInstrumentation.java @@ -58,6 +58,7 @@ public boolean onlyMatchKnownTypes() { public String[] helperClassNames() { return new String[] { "datadog.opentelemetry.shim.context.OtelContext", + "datadog.opentelemetry.shim.context.OtelContext$1", "datadog.opentelemetry.shim.context.OtelScope", "datadog.opentelemetry.shim.context.propagation.AgentTextMapPropagator", "datadog.opentelemetry.shim.context.propagation.OtelContextPropagators", diff --git a/dd-java-agent/instrumentation/opentelemetry/opentelemetry-1.4/src/main/java/datadog/trace/instrumentation/opentelemetry14/context/OpenTelemetryContextInstrumentation.java b/dd-java-agent/instrumentation/opentelemetry/opentelemetry-1.4/src/main/java/datadog/trace/instrumentation/opentelemetry14/context/OpenTelemetryContextInstrumentation.java index 16bf739da0b..6c9b4323a3b 100644 --- a/dd-java-agent/instrumentation/opentelemetry/opentelemetry-1.4/src/main/java/datadog/trace/instrumentation/opentelemetry14/context/OpenTelemetryContextInstrumentation.java +++ b/dd-java-agent/instrumentation/opentelemetry/opentelemetry-1.4/src/main/java/datadog/trace/instrumentation/opentelemetry14/context/OpenTelemetryContextInstrumentation.java @@ -55,6 +55,7 @@ public boolean onlyMatchKnownTypes() { public String[] helperClassNames() { return new String[] { "datadog.opentelemetry.shim.context.OtelContext", + "datadog.opentelemetry.shim.context.OtelContext$1", "datadog.opentelemetry.shim.context.OtelScope", "datadog.opentelemetry.shim.trace.OtelExtractedContext", "datadog.opentelemetry.shim.trace.OtelConventions", diff --git a/dd-java-agent/instrumentation/opentelemetry/opentelemetry-1.4/src/main/java/datadog/trace/instrumentation/opentelemetry14/context/OpenTelemetryContextStorageInstrumentation.java b/dd-java-agent/instrumentation/opentelemetry/opentelemetry-1.4/src/main/java/datadog/trace/instrumentation/opentelemetry14/context/OpenTelemetryContextStorageInstrumentation.java index c84213266d2..3ae810bdc1c 100644 --- a/dd-java-agent/instrumentation/opentelemetry/opentelemetry-1.4/src/main/java/datadog/trace/instrumentation/opentelemetry14/context/OpenTelemetryContextStorageInstrumentation.java +++ b/dd-java-agent/instrumentation/opentelemetry/opentelemetry-1.4/src/main/java/datadog/trace/instrumentation/opentelemetry14/context/OpenTelemetryContextStorageInstrumentation.java @@ -56,6 +56,7 @@ public boolean onlyMatchKnownTypes() { public String[] helperClassNames() { return new String[] { "datadog.opentelemetry.shim.context.OtelContext", + "datadog.opentelemetry.shim.context.OtelContext$1", "datadog.opentelemetry.shim.context.OtelScope", "datadog.opentelemetry.shim.trace.OtelExtractedContext", "datadog.opentelemetry.shim.trace.OtelConventions", diff --git a/dd-java-agent/instrumentation/opentelemetry/opentelemetry-1.4/src/test/groovy/OpenTelemetry14Test.groovy b/dd-java-agent/instrumentation/opentelemetry/opentelemetry-1.4/src/test/groovy/OpenTelemetry14Test.groovy index 322ad5a1b62..7a0c4d7ee91 100644 --- a/dd-java-agent/instrumentation/opentelemetry/opentelemetry-1.4/src/test/groovy/OpenTelemetry14Test.groovy +++ b/dd-java-agent/instrumentation/opentelemetry/opentelemetry-1.4/src/test/groovy/OpenTelemetry14Test.groovy @@ -12,7 +12,6 @@ import io.opentelemetry.context.Context import io.opentelemetry.context.ThreadLocalContextStorage import opentelemetry14.context.propagation.TextMap import org.skyscreamer.jsonassert.JSONAssert -import spock.lang.Ignore import spock.lang.Subject import java.security.InvalidParameterException @@ -100,7 +99,6 @@ class OpenTelemetry14Test extends AgentTestRunner { } } - @Ignore("Core tracer is not picking incomplete span context from context") def "test parent span using propagation data"() { setup: def traceId = '00000000000000001111111111111111'