Skip to content

Commit 0477d38

Browse files
xiepuhuanlaurittrask
authored
Add @WithSpan option to break from existing context (#13112)
Signed-off-by: xiepuhuan <[email protected]> Co-authored-by: Lauri Tulmin <[email protected]> Co-authored-by: Trask Stalnaker <[email protected]>
1 parent b54b200 commit 0477d38

File tree

9 files changed

+138
-7
lines changed

9 files changed

+138
-7
lines changed
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,4 @@
11
Comparing source compatibility of opentelemetry-instrumentation-annotations-2.14.0-SNAPSHOT.jar against opentelemetry-instrumentation-annotations-2.13.3.jar
2-
No changes.
2+
**** MODIFIED ANNOTATION: PUBLIC ABSTRACT io.opentelemetry.instrumentation.annotations.WithSpan (not serializable)
3+
=== CLASS FILE FORMAT VERSION: 52.0 <- 52.0
4+
+++* NEW METHOD: PUBLIC(+) ABSTRACT(+) boolean inheritContext()

instrumentation-annotations/src/main/java/io/opentelemetry/instrumentation/annotations/WithSpan.java

+12
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
package io.opentelemetry.instrumentation.annotations;
77

88
import io.opentelemetry.api.trace.SpanKind;
9+
import io.opentelemetry.context.Context;
910
import java.lang.annotation.ElementType;
1011
import java.lang.annotation.Retention;
1112
import java.lang.annotation.RetentionPolicy;
@@ -34,4 +35,15 @@
3435

3536
/** Specify the {@link SpanKind} of span to be created. Defaults to {@link SpanKind#INTERNAL}. */
3637
SpanKind kind() default SpanKind.INTERNAL;
38+
39+
/**
40+
* Specifies whether to inherit the current context when creating a span.
41+
*
42+
* <p>If set to {@code true} (default), the created span will use the current context as its
43+
* parent, remaining within the same trace.
44+
*
45+
* <p>If set to {@code false}, the created span will use {@link Context#root()} as its parent,
46+
* starting a new, independent trace.
47+
*/
48+
boolean inheritContext() default true;
3749
}

instrumentation/opentelemetry-instrumentation-annotations-1.16/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/instrumentationannotations/AnnotationSingletons.java

+38
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,15 @@
1010
import application.io.opentelemetry.instrumentation.annotations.WithSpan;
1111
import io.opentelemetry.api.GlobalOpenTelemetry;
1212
import io.opentelemetry.api.trace.SpanKind;
13+
import io.opentelemetry.context.Context;
1314
import io.opentelemetry.instrumentation.api.annotation.support.MethodSpanAttributesExtractor;
1415
import io.opentelemetry.instrumentation.api.annotation.support.SpanAttributesExtractor;
1516
import io.opentelemetry.instrumentation.api.incubator.semconv.code.CodeAttributesExtractor;
1617
import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter;
1718
import io.opentelemetry.instrumentation.api.semconv.util.SpanNames;
19+
import java.lang.invoke.MethodHandle;
20+
import java.lang.invoke.MethodHandles;
21+
import java.lang.invoke.MethodType;
1822
import java.lang.reflect.Method;
1923
import java.util.logging.Logger;
2024

@@ -29,6 +33,21 @@ public final class AnnotationSingletons {
2933
createInstrumenterWithAttributes();
3034
private static final SpanAttributesExtractor ATTRIBUTES = createAttributesExtractor();
3135

36+
// The reason for using reflection here is that it needs to be compatible with the old version of
37+
// @WithSpan annotation that does not include the inheritContext option to avoid failing the
38+
// muzzle check.
39+
private static MethodHandle inheritContextMethodHandle = null;
40+
41+
static {
42+
try {
43+
inheritContextMethodHandle =
44+
MethodHandles.publicLookup()
45+
.findVirtual(WithSpan.class, "inheritContext", MethodType.methodType(boolean.class));
46+
} catch (NoSuchMethodException | IllegalAccessException ignore) {
47+
// ignore
48+
}
49+
}
50+
3251
public static Instrumenter<Method, Object> instrumenter() {
3352
return INSTRUMENTER;
3453
}
@@ -104,5 +123,24 @@ private static String spanNameFromMethod(Method method) {
104123
return spanName;
105124
}
106125

126+
public static Context getContextForMethod(Method method) {
127+
return inheritContextFromMethod(method) ? Context.current() : Context.root();
128+
}
129+
130+
private static boolean inheritContextFromMethod(Method method) {
131+
if (inheritContextMethodHandle == null) {
132+
return true;
133+
}
134+
135+
WithSpan annotation = method.getDeclaredAnnotation(WithSpan.class);
136+
try {
137+
return (boolean) inheritContextMethodHandle.invoke(annotation);
138+
} catch (Throwable ignore) {
139+
// ignore
140+
}
141+
142+
return true;
143+
}
144+
107145
private AnnotationSingletons() {}
108146
}

instrumentation/opentelemetry-instrumentation-annotations-1.16/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/instrumentationannotations/WithSpanInstrumentation.java

+2-3
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@
1919
import io.opentelemetry.context.Scope;
2020
import io.opentelemetry.instrumentation.api.annotation.support.async.AsyncOperationEndSupport;
2121
import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter;
22-
import io.opentelemetry.javaagent.bootstrap.Java8BytecodeBridge;
2322
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
2423
import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer;
2524
import java.lang.reflect.Method;
@@ -91,7 +90,7 @@ public static void onEnter(
9190
method = originMethod;
9291

9392
Instrumenter<Method, Object> instrumenter = instrumenter();
94-
Context current = Java8BytecodeBridge.currentContext();
93+
Context current = AnnotationSingletons.getContextForMethod(method);
9594

9695
if (instrumenter.shouldStart(current, method)) {
9796
context = instrumenter.start(current, method);
@@ -134,7 +133,7 @@ public static void onEnter(
134133
method = originMethod;
135134

136135
Instrumenter<MethodRequest, Object> instrumenter = instrumenterWithAttributes();
137-
Context current = Java8BytecodeBridge.currentContext();
136+
Context current = AnnotationSingletons.getContextForMethod(method);
138137
request = new MethodRequest(method, args);
139138

140139
if (instrumenter.shouldStart(current, request)) {

instrumentation/opentelemetry-instrumentation-annotations-1.16/javaagent/src/test/java/io/opentelemetry/test/annotation/TracedWithSpan.java

+10
Original file line numberDiff line numberDiff line change
@@ -57,4 +57,14 @@ public CompletionStage<String> completionStage(CompletableFuture<String> future)
5757
public CompletableFuture<String> completableFuture(CompletableFuture<String> future) {
5858
return future;
5959
}
60+
61+
@WithSpan(inheritContext = false)
62+
public String withoutParent() {
63+
return "hello!";
64+
}
65+
66+
@WithSpan(kind = SpanKind.CONSUMER)
67+
public String consumer() {
68+
return withoutParent();
69+
}
6070
}

instrumentation/opentelemetry-instrumentation-annotations-1.16/javaagent/src/test/java/io/opentelemetry/test/annotation/WithSpanInstrumentationTest.java

+25
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,31 @@ void multipleSpans() {
108108
equalTo(CODE_FUNCTION, "otel"))));
109109
}
110110

111+
@Test
112+
void multipleSpansWithoutParent() {
113+
new TracedWithSpan().consumer();
114+
115+
testing.waitAndAssertTraces(
116+
trace ->
117+
trace.hasSpansSatisfyingExactly(
118+
span ->
119+
span.hasName("TracedWithSpan.consumer")
120+
.hasKind(SpanKind.CONSUMER)
121+
.hasNoParent()
122+
.hasAttributesSatisfyingExactly(
123+
equalTo(CODE_NAMESPACE, TracedWithSpan.class.getName()),
124+
equalTo(CODE_FUNCTION, "consumer"))),
125+
trace ->
126+
trace.hasSpansSatisfyingExactly(
127+
span ->
128+
span.hasName("TracedWithSpan.withoutParent")
129+
.hasKind(SpanKind.INTERNAL)
130+
.hasNoParent()
131+
.hasAttributesSatisfyingExactly(
132+
equalTo(CODE_NAMESPACE, TracedWithSpan.class.getName()),
133+
equalTo(CODE_FUNCTION, "withoutParent"))));
134+
}
135+
111136
@Test
112137
void excludedMethod() throws Exception {
113138
new TracedWithSpan().ignored();

instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/annotations/JoinPointRequest.java

+15-3
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,14 @@ final class JoinPointRequest {
1818
private final Method method;
1919
private final String spanName;
2020
private final SpanKind spanKind;
21-
22-
private JoinPointRequest(JoinPoint joinPoint, Method method, String spanName, SpanKind spanKind) {
21+
private final boolean inheritContext;
22+
23+
private JoinPointRequest(
24+
JoinPoint joinPoint,
25+
Method method,
26+
String spanName,
27+
SpanKind spanKind,
28+
boolean inheritContext) {
2329
if (spanName.isEmpty()) {
2430
spanName = SpanNames.fromMethod(method);
2531
}
@@ -28,6 +34,7 @@ private JoinPointRequest(JoinPoint joinPoint, Method method, String spanName, Sp
2834
this.method = method;
2935
this.spanName = spanName;
3036
this.spanKind = spanKind;
37+
this.inheritContext = inheritContext;
3138
}
3239

3340
String spanName() {
@@ -46,6 +53,10 @@ Object[] args() {
4653
return joinPoint.getArgs();
4754
}
4855

56+
boolean inheritContext() {
57+
return inheritContext;
58+
}
59+
4960
interface Factory {
5061

5162
JoinPointRequest create(JoinPoint joinPoint);
@@ -65,8 +76,9 @@ public JoinPointRequest create(JoinPoint joinPoint) {
6576
WithSpan annotation = method.getDeclaredAnnotation(WithSpan.class);
6677
String spanName = annotation != null ? annotation.value() : "";
6778
SpanKind spanKind = annotation != null ? annotation.kind() : SpanKind.INTERNAL;
79+
boolean inheritContext = annotation == null || annotation.inheritContext();
6880

69-
return new JoinPointRequest(joinPoint, method, spanName, spanKind);
81+
return new JoinPointRequest(joinPoint, method, spanName, spanKind, inheritContext);
7082
}
7183
}
7284
}

instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/annotations/WithSpanAspect.java

+7
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
package io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.annotations;
77

88
import io.opentelemetry.api.OpenTelemetry;
9+
import io.opentelemetry.api.common.Attributes;
910
import io.opentelemetry.api.trace.Span;
1011
import io.opentelemetry.context.Context;
1112
import io.opentelemetry.context.Scope;
@@ -53,10 +54,16 @@ abstract class WithSpanAspect {
5354
JoinPointRequest::method,
5455
parameterAttributeNamesExtractor,
5556
JoinPointRequest::args))
57+
.addContextCustomizer(WithSpanAspect::parentContext)
5658
.buildInstrumenter(JoinPointRequest::spanKind);
5759
this.requestFactory = requestFactory;
5860
}
5961

62+
private static Context parentContext(
63+
Context parentContext, JoinPointRequest request, Attributes unused) {
64+
return request.inheritContext() ? parentContext : Context.root();
65+
}
66+
6067
public Object traceMethod(ProceedingJoinPoint pjp) throws Throwable {
6168
JoinPointRequest request = requestFactory.create(pjp);
6269
Context parentContext = Context.current();

instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/annotations/InstrumentationWithSpanAspectTest.java

+26
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,27 @@ void withSpanAttributes() {
181181
equalTo(stringKey("explicitName"), "baz"))));
182182
}
183183

184+
@Test
185+
@DisplayName(
186+
"when method is annotated with @WithSpan(inheritContext=false) should build span without parent")
187+
void withSpanWithoutParent() {
188+
// when
189+
testing.runWithSpan("parent", withSpanTester::testWithoutParentSpan);
190+
191+
// then
192+
testing.waitAndAssertTraces(
193+
trace -> trace.hasSpansSatisfyingExactly(span -> span.hasName("parent").hasKind(INTERNAL)),
194+
trace ->
195+
trace.hasSpansSatisfyingExactly(
196+
span ->
197+
span.hasName(unproxiedTesterSimpleClassName + ".testWithoutParentSpan")
198+
.hasKind(INTERNAL)
199+
.hasNoParent()
200+
.hasAttributesSatisfyingExactly(
201+
equalTo(CODE_NAMESPACE, unproxiedTesterClassName),
202+
equalTo(CODE_FUNCTION, "testWithoutParentSpan"))));
203+
}
204+
184205
static class InstrumentationWithSpanTester {
185206
@WithSpan
186207
public String testWithSpan() {
@@ -222,6 +243,11 @@ public String withSpanAttributes(
222243

223244
return "hello!";
224245
}
246+
247+
@WithSpan(inheritContext = false)
248+
public String testWithoutParentSpan() {
249+
return "Span without parent span was created";
250+
}
225251
}
226252

227253
@Nested

0 commit comments

Comments
 (0)