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