diff --git a/docs/apidiffs/current_vs_latest/opentelemetry-instrumentation-annotations.txt b/docs/apidiffs/current_vs_latest/opentelemetry-instrumentation-annotations.txt
index 4593f46cc30f..bf71bc2a7e74 100644
--- a/docs/apidiffs/current_vs_latest/opentelemetry-instrumentation-annotations.txt
+++ b/docs/apidiffs/current_vs_latest/opentelemetry-instrumentation-annotations.txt
@@ -1,2 +1,4 @@
Comparing source compatibility of opentelemetry-instrumentation-annotations-2.14.0-SNAPSHOT.jar against opentelemetry-instrumentation-annotations-2.13.1.jar
-No changes.
\ No newline at end of file
+**** MODIFIED ANNOTATION: PUBLIC ABSTRACT io.opentelemetry.instrumentation.annotations.WithSpan (not serializable)
+ === CLASS FILE FORMAT VERSION: 52.0 <- 52.0
+ +++* NEW METHOD: PUBLIC(+) ABSTRACT(+) boolean inheritContext()
diff --git a/instrumentation-annotations/src/main/java/io/opentelemetry/instrumentation/annotations/WithSpan.java b/instrumentation-annotations/src/main/java/io/opentelemetry/instrumentation/annotations/WithSpan.java
index 6b4cf4fefb75..047a9f7a4dbe 100644
--- a/instrumentation-annotations/src/main/java/io/opentelemetry/instrumentation/annotations/WithSpan.java
+++ b/instrumentation-annotations/src/main/java/io/opentelemetry/instrumentation/annotations/WithSpan.java
@@ -6,6 +6,7 @@
package io.opentelemetry.instrumentation.annotations;
import io.opentelemetry.api.trace.SpanKind;
+import io.opentelemetry.context.Context;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -34,4 +35,15 @@
/** Specify the {@link SpanKind} of span to be created. Defaults to {@link SpanKind#INTERNAL}. */
SpanKind kind() default SpanKind.INTERNAL;
+
+ /**
+ * Specifies whether to inherit the current context when creating a span.
+ *
+ *
If set to {@code true} (default), the created span will use the current context as its
+ * parent, remaining within the same trace.
+ *
+ *
If set to {@code false}, the created span will use {@link Context#root()} as its parent,
+ * starting a new, independent trace.
+ */
+ boolean inheritContext() default true;
}
diff --git a/instrumentation/opentelemetry-instrumentation-annotations-1.16/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/instrumentationannotations/AnnotationSingletons.java b/instrumentation/opentelemetry-instrumentation-annotations-1.16/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/instrumentationannotations/AnnotationSingletons.java
index 4e268ebf496b..840ccf8bd6ab 100644
--- a/instrumentation/opentelemetry-instrumentation-annotations-1.16/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/instrumentationannotations/AnnotationSingletons.java
+++ b/instrumentation/opentelemetry-instrumentation-annotations-1.16/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/instrumentationannotations/AnnotationSingletons.java
@@ -10,11 +10,15 @@
import application.io.opentelemetry.instrumentation.annotations.WithSpan;
import io.opentelemetry.api.GlobalOpenTelemetry;
import io.opentelemetry.api.trace.SpanKind;
+import io.opentelemetry.context.Context;
import io.opentelemetry.instrumentation.api.annotation.support.MethodSpanAttributesExtractor;
import io.opentelemetry.instrumentation.api.annotation.support.SpanAttributesExtractor;
import io.opentelemetry.instrumentation.api.incubator.semconv.code.CodeAttributesExtractor;
import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter;
import io.opentelemetry.instrumentation.api.semconv.util.SpanNames;
+import java.lang.invoke.MethodHandle;
+import java.lang.invoke.MethodHandles;
+import java.lang.invoke.MethodType;
import java.lang.reflect.Method;
import java.util.logging.Logger;
@@ -29,6 +33,21 @@ public final class AnnotationSingletons {
createInstrumenterWithAttributes();
private static final SpanAttributesExtractor ATTRIBUTES = createAttributesExtractor();
+ // The reason for using reflection here is that it needs to be compatible with the old version of
+ // @WithSpan annotation that does not include the inheritContext option to avoid failing the
+ // muzzle check.
+ private static MethodHandle inheritContextMethodHandle = null;
+
+ static {
+ try {
+ inheritContextMethodHandle =
+ MethodHandles.publicLookup()
+ .findVirtual(WithSpan.class, "inheritContext", MethodType.methodType(boolean.class));
+ } catch (NoSuchMethodException | IllegalAccessException ignore) {
+ // ignore
+ }
+ }
+
public static Instrumenter instrumenter() {
return INSTRUMENTER;
}
@@ -104,5 +123,24 @@ private static String spanNameFromMethod(Method method) {
return spanName;
}
+ public static Context getContextForMethod(Method method) {
+ return inheritContextFromMethod(method) ? Context.current() : Context.root();
+ }
+
+ private static boolean inheritContextFromMethod(Method method) {
+ if (inheritContextMethodHandle == null) {
+ return true;
+ }
+
+ WithSpan annotation = method.getDeclaredAnnotation(WithSpan.class);
+ try {
+ return (boolean) inheritContextMethodHandle.invoke(annotation);
+ } catch (Throwable ignore) {
+ // ignore
+ }
+
+ return true;
+ }
+
private AnnotationSingletons() {}
}
diff --git a/instrumentation/opentelemetry-instrumentation-annotations-1.16/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/instrumentationannotations/WithSpanInstrumentation.java b/instrumentation/opentelemetry-instrumentation-annotations-1.16/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/instrumentationannotations/WithSpanInstrumentation.java
index 9cfcc18666aa..74d9d84c00d3 100644
--- a/instrumentation/opentelemetry-instrumentation-annotations-1.16/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/instrumentationannotations/WithSpanInstrumentation.java
+++ b/instrumentation/opentelemetry-instrumentation-annotations-1.16/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/instrumentationannotations/WithSpanInstrumentation.java
@@ -19,7 +19,6 @@
import io.opentelemetry.context.Scope;
import io.opentelemetry.instrumentation.api.annotation.support.async.AsyncOperationEndSupport;
import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter;
-import io.opentelemetry.javaagent.bootstrap.Java8BytecodeBridge;
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer;
import java.lang.reflect.Method;
@@ -91,7 +90,7 @@ public static void onEnter(
method = originMethod;
Instrumenter instrumenter = instrumenter();
- Context current = Java8BytecodeBridge.currentContext();
+ Context current = AnnotationSingletons.getContextForMethod(method);
if (instrumenter.shouldStart(current, method)) {
context = instrumenter.start(current, method);
@@ -134,7 +133,7 @@ public static void onEnter(
method = originMethod;
Instrumenter instrumenter = instrumenterWithAttributes();
- Context current = Java8BytecodeBridge.currentContext();
+ Context current = AnnotationSingletons.getContextForMethod(method);
request = new MethodRequest(method, args);
if (instrumenter.shouldStart(current, request)) {
diff --git a/instrumentation/opentelemetry-instrumentation-annotations-1.16/javaagent/src/test/java/io/opentelemetry/test/annotation/TracedWithSpan.java b/instrumentation/opentelemetry-instrumentation-annotations-1.16/javaagent/src/test/java/io/opentelemetry/test/annotation/TracedWithSpan.java
index 3fab82c2bcea..29e58a4212cc 100644
--- a/instrumentation/opentelemetry-instrumentation-annotations-1.16/javaagent/src/test/java/io/opentelemetry/test/annotation/TracedWithSpan.java
+++ b/instrumentation/opentelemetry-instrumentation-annotations-1.16/javaagent/src/test/java/io/opentelemetry/test/annotation/TracedWithSpan.java
@@ -57,4 +57,14 @@ public CompletionStage completionStage(CompletableFuture future)
public CompletableFuture completableFuture(CompletableFuture future) {
return future;
}
+
+ @WithSpan(inheritContext = false)
+ public String withoutParent() {
+ return "hello!";
+ }
+
+ @WithSpan(kind = SpanKind.CONSUMER)
+ public String consumer() {
+ return withoutParent();
+ }
}
diff --git a/instrumentation/opentelemetry-instrumentation-annotations-1.16/javaagent/src/test/java/io/opentelemetry/test/annotation/WithSpanInstrumentationTest.java b/instrumentation/opentelemetry-instrumentation-annotations-1.16/javaagent/src/test/java/io/opentelemetry/test/annotation/WithSpanInstrumentationTest.java
index d56fdc3f4028..961dd7098704 100644
--- a/instrumentation/opentelemetry-instrumentation-annotations-1.16/javaagent/src/test/java/io/opentelemetry/test/annotation/WithSpanInstrumentationTest.java
+++ b/instrumentation/opentelemetry-instrumentation-annotations-1.16/javaagent/src/test/java/io/opentelemetry/test/annotation/WithSpanInstrumentationTest.java
@@ -108,6 +108,31 @@ void multipleSpans() {
equalTo(CODE_FUNCTION, "otel"))));
}
+ @Test
+ void multipleSpansWithoutParent() {
+ new TracedWithSpan().consumer();
+
+ testing.waitAndAssertTraces(
+ trace ->
+ trace.hasSpansSatisfyingExactly(
+ span ->
+ span.hasName("TracedWithSpan.consumer")
+ .hasKind(SpanKind.CONSUMER)
+ .hasNoParent()
+ .hasAttributesSatisfyingExactly(
+ equalTo(CODE_NAMESPACE, TracedWithSpan.class.getName()),
+ equalTo(CODE_FUNCTION, "consumer"))),
+ trace ->
+ trace.hasSpansSatisfyingExactly(
+ span ->
+ span.hasName("TracedWithSpan.withoutParent")
+ .hasKind(SpanKind.INTERNAL)
+ .hasNoParent()
+ .hasAttributesSatisfyingExactly(
+ equalTo(CODE_NAMESPACE, TracedWithSpan.class.getName()),
+ equalTo(CODE_FUNCTION, "withoutParent"))));
+ }
+
@Test
void excludedMethod() throws Exception {
new TracedWithSpan().ignored();
diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/annotations/JoinPointRequest.java b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/annotations/JoinPointRequest.java
index 4189c6cb3060..6dbfa686e4d3 100644
--- a/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/annotations/JoinPointRequest.java
+++ b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/annotations/JoinPointRequest.java
@@ -18,8 +18,14 @@ final class JoinPointRequest {
private final Method method;
private final String spanName;
private final SpanKind spanKind;
-
- private JoinPointRequest(JoinPoint joinPoint, Method method, String spanName, SpanKind spanKind) {
+ private final boolean inheritContext;
+
+ private JoinPointRequest(
+ JoinPoint joinPoint,
+ Method method,
+ String spanName,
+ SpanKind spanKind,
+ boolean inheritContext) {
if (spanName.isEmpty()) {
spanName = SpanNames.fromMethod(method);
}
@@ -28,6 +34,7 @@ private JoinPointRequest(JoinPoint joinPoint, Method method, String spanName, Sp
this.method = method;
this.spanName = spanName;
this.spanKind = spanKind;
+ this.inheritContext = inheritContext;
}
String spanName() {
@@ -46,6 +53,10 @@ Object[] args() {
return joinPoint.getArgs();
}
+ boolean inheritContext() {
+ return inheritContext;
+ }
+
interface Factory {
JoinPointRequest create(JoinPoint joinPoint);
@@ -65,8 +76,9 @@ public JoinPointRequest create(JoinPoint joinPoint) {
WithSpan annotation = method.getDeclaredAnnotation(WithSpan.class);
String spanName = annotation != null ? annotation.value() : "";
SpanKind spanKind = annotation != null ? annotation.kind() : SpanKind.INTERNAL;
+ boolean inheritContext = annotation == null || annotation.inheritContext();
- return new JoinPointRequest(joinPoint, method, spanName, spanKind);
+ return new JoinPointRequest(joinPoint, method, spanName, spanKind, inheritContext);
}
}
}
diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/annotations/WithSpanAspect.java b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/annotations/WithSpanAspect.java
index f25506a0b780..89ff79dd6ab9 100644
--- a/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/annotations/WithSpanAspect.java
+++ b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/annotations/WithSpanAspect.java
@@ -6,6 +6,7 @@
package io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.annotations;
import io.opentelemetry.api.OpenTelemetry;
+import io.opentelemetry.api.common.Attributes;
import io.opentelemetry.api.trace.Span;
import io.opentelemetry.context.Context;
import io.opentelemetry.context.Scope;
@@ -53,10 +54,16 @@ abstract class WithSpanAspect {
JoinPointRequest::method,
parameterAttributeNamesExtractor,
JoinPointRequest::args))
+ .addContextCustomizer(WithSpanAspect::parentContext)
.buildInstrumenter(JoinPointRequest::spanKind);
this.requestFactory = requestFactory;
}
+ private static Context parentContext(
+ Context parentContext, JoinPointRequest request, Attributes unused) {
+ return request.inheritContext() ? parentContext : Context.root();
+ }
+
public Object traceMethod(ProceedingJoinPoint pjp) throws Throwable {
JoinPointRequest request = requestFactory.create(pjp);
Context parentContext = Context.current();
diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/annotations/InstrumentationWithSpanAspectTest.java b/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/annotations/InstrumentationWithSpanAspectTest.java
index 50c549b4aaa4..443bfdc5beb9 100644
--- a/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/annotations/InstrumentationWithSpanAspectTest.java
+++ b/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/annotations/InstrumentationWithSpanAspectTest.java
@@ -181,6 +181,27 @@ void withSpanAttributes() {
equalTo(stringKey("explicitName"), "baz"))));
}
+ @Test
+ @DisplayName(
+ "when method is annotated with @WithSpan(inheritContext=false) should build span without parent")
+ void withSpanWithoutParent() {
+ // when
+ testing.runWithSpan("parent", withSpanTester::testWithoutParentSpan);
+
+ // then
+ testing.waitAndAssertTraces(
+ trace -> trace.hasSpansSatisfyingExactly(span -> span.hasName("parent").hasKind(INTERNAL)),
+ trace ->
+ trace.hasSpansSatisfyingExactly(
+ span ->
+ span.hasName(unproxiedTesterSimpleClassName + ".testWithoutParentSpan")
+ .hasKind(INTERNAL)
+ .hasNoParent()
+ .hasAttributesSatisfyingExactly(
+ equalTo(CODE_NAMESPACE, unproxiedTesterClassName),
+ equalTo(CODE_FUNCTION, "testWithoutParentSpan"))));
+ }
+
static class InstrumentationWithSpanTester {
@WithSpan
public String testWithSpan() {
@@ -222,6 +243,11 @@ public String withSpanAttributes(
return "hello!";
}
+
+ @WithSpan(inheritContext = false)
+ public String testWithoutParentSpan() {
+ return "Span without parent span was created";
+ }
}
@Nested