Add @WithSpan option to break from existing context (#13112)
Signed-off-by: xiepuhuan <xiepuhuan@didachuxing.com> Co-authored-by: Lauri Tulmin <ltulmin@splunk.com> Co-authored-by: Trask Stalnaker <trask.stalnaker@gmail.com>
This commit is contained in:
parent
b54b2000e7
commit
0477d38897
|
@ -1,2 +1,4 @@
|
|||
Comparing source compatibility of opentelemetry-instrumentation-annotations-2.14.0-SNAPSHOT.jar against opentelemetry-instrumentation-annotations-2.13.3.jar
|
||||
No changes.
|
||||
**** 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()
|
||||
|
|
|
@ -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 @@ public @interface WithSpan {
|
|||
|
||||
/** 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.
|
||||
*
|
||||
* <p>If set to {@code true} (default), the created span will use the current context as its
|
||||
* parent, remaining within the same trace.
|
||||
*
|
||||
* <p>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;
|
||||
}
|
||||
|
|
|
@ -10,11 +10,15 @@ import static java.util.logging.Level.FINE;
|
|||
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<Method, Object> instrumenter() {
|
||||
return INSTRUMENTER;
|
||||
}
|
||||
|
@ -104,5 +123,24 @@ public final class AnnotationSingletons {
|
|||
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() {}
|
||||
}
|
||||
|
|
|
@ -19,7 +19,6 @@ import io.opentelemetry.context.Context;
|
|||
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 @@ class WithSpanInstrumentation implements TypeInstrumentation {
|
|||
method = originMethod;
|
||||
|
||||
Instrumenter<Method, Object> 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 @@ class WithSpanInstrumentation implements TypeInstrumentation {
|
|||
method = originMethod;
|
||||
|
||||
Instrumenter<MethodRequest, Object> instrumenter = instrumenterWithAttributes();
|
||||
Context current = Java8BytecodeBridge.currentContext();
|
||||
Context current = AnnotationSingletons.getContextForMethod(method);
|
||||
request = new MethodRequest(method, args);
|
||||
|
||||
if (instrumenter.shouldStart(current, request)) {
|
||||
|
|
|
@ -57,4 +57,14 @@ public class TracedWithSpan {
|
|||
public CompletableFuture<String> completableFuture(CompletableFuture<String> future) {
|
||||
return future;
|
||||
}
|
||||
|
||||
@WithSpan(inheritContext = false)
|
||||
public String withoutParent() {
|
||||
return "hello!";
|
||||
}
|
||||
|
||||
@WithSpan(kind = SpanKind.CONSUMER)
|
||||
public String consumer() {
|
||||
return withoutParent();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -108,6 +108,31 @@ class WithSpanInstrumentationTest {
|
|||
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();
|
||||
|
|
|
@ -18,8 +18,14 @@ final class JoinPointRequest {
|
|||
private final Method method;
|
||||
private final String spanName;
|
||||
private final SpanKind spanKind;
|
||||
private final boolean inheritContext;
|
||||
|
||||
private JoinPointRequest(JoinPoint joinPoint, Method method, String spanName, SpanKind spanKind) {
|
||||
private JoinPointRequest(
|
||||
JoinPoint joinPoint,
|
||||
Method method,
|
||||
String spanName,
|
||||
SpanKind spanKind,
|
||||
boolean inheritContext) {
|
||||
if (spanName.isEmpty()) {
|
||||
spanName = SpanNames.fromMethod(method);
|
||||
}
|
||||
|
@ -28,6 +34,7 @@ final class JoinPointRequest {
|
|||
this.method = method;
|
||||
this.spanName = spanName;
|
||||
this.spanKind = spanKind;
|
||||
this.inheritContext = inheritContext;
|
||||
}
|
||||
|
||||
String spanName() {
|
||||
|
@ -46,6 +53,10 @@ final class JoinPointRequest {
|
|||
return joinPoint.getArgs();
|
||||
}
|
||||
|
||||
boolean inheritContext() {
|
||||
return inheritContext;
|
||||
}
|
||||
|
||||
interface Factory {
|
||||
|
||||
JoinPointRequest create(JoinPoint joinPoint);
|
||||
|
@ -65,8 +76,9 @@ final class JoinPointRequest {
|
|||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -181,6 +181,27 @@ class InstrumentationWithSpanAspectTest {
|
|||
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 @@ class InstrumentationWithSpanAspectTest {
|
|||
|
||||
return "hello!";
|
||||
}
|
||||
|
||||
@WithSpan(inheritContext = false)
|
||||
public String testWithoutParentSpan() {
|
||||
return "Span without parent span was created";
|
||||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
|
|
Loading…
Reference in New Issue