From c288cdaca4280a00642daf2ea9d7ab35499150d4 Mon Sep 17 00:00:00 2001 From: Mateusz Rzeszutek Date: Thu, 28 Jul 2022 11:48:59 +0200 Subject: [PATCH] Support new @WithSpan annotation in spring-boot-autoconfigure (#6378) --- .../build.gradle.kts | 6 +- .../aspects/JoinPointRequest.java | 48 +++++- .../autoconfigure/aspects/WithSpanAspect.java | 32 ++-- ...spectParameterAttributeNamesExtractor.java | 20 ++- ...t.java => AbstractWithSpanAspectTest.java} | 147 ++++++++---------- .../InstrumentationWithSpanAspectTest.java | 71 +++++++++ .../SdkExtensionWithSpanAspectTest.java | 71 +++++++++ .../starters/spring-starter/build.gradle.kts | 2 +- 8 files changed, 280 insertions(+), 117 deletions(-) rename instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/aspects/{WithSpanAspectTest.java => AbstractWithSpanAspectTest.java} (78%) create mode 100644 instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/aspects/InstrumentationWithSpanAspectTest.java create mode 100644 instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/aspects/SdkExtensionWithSpanAspectTest.java diff --git a/instrumentation/spring/spring-boot-autoconfigure/build.gradle.kts b/instrumentation/spring/spring-boot-autoconfigure/build.gradle.kts index 01541752c7a6..5e5381c8ac51 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/build.gradle.kts +++ b/instrumentation/spring/spring-boot-autoconfigure/build.gradle.kts @@ -9,12 +9,11 @@ val versions: Map by project val springBootVersion = versions["org.springframework.boot"] dependencies { - implementation(project(":instrumentation-annotations-support")) - implementation("org.springframework.boot:spring-boot-autoconfigure:$springBootVersion") annotationProcessor("org.springframework.boot:spring-boot-autoconfigure-processor:$springBootVersion") implementation("javax.validation:validation-api:2.0.1.Final") + implementation(project(":instrumentation-annotations-support")) implementation(project(":instrumentation:spring:spring-web-3.1:library")) implementation(project(":instrumentation:spring:spring-webmvc-3.1:library")) implementation(project(":instrumentation:spring:spring-webflux-5.0:library")) @@ -37,6 +36,7 @@ dependencies { compileOnly("io.opentelemetry:opentelemetry-exporter-jaeger") compileOnly("io.opentelemetry:opentelemetry-exporter-otlp") compileOnly("io.opentelemetry:opentelemetry-exporter-zipkin") + compileOnly(project(":instrumentation-annotations")) testImplementation("org.springframework.boot:spring-boot-starter-actuator:$springBootVersion") testImplementation("org.springframework.boot:spring-boot-starter-aop:$springBootVersion") @@ -58,7 +58,7 @@ dependencies { testImplementation("io.opentelemetry:opentelemetry-exporter-jaeger") testImplementation("io.opentelemetry:opentelemetry-exporter-otlp") testImplementation("io.opentelemetry:opentelemetry-exporter-zipkin") - testImplementation(project(":instrumentation-annotations-support")) + testImplementation(project(":instrumentation-annotations")) } tasks.compileTestJava { diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/aspects/JoinPointRequest.java b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/aspects/JoinPointRequest.java index 4cd4bc28a84c..50089844262e 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/aspects/JoinPointRequest.java +++ b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/aspects/JoinPointRequest.java @@ -5,29 +5,63 @@ package io.opentelemetry.instrumentation.spring.autoconfigure.aspects; -import io.opentelemetry.extension.annotations.WithSpan; +import io.opentelemetry.api.trace.SpanKind; +import io.opentelemetry.instrumentation.annotations.WithSpan; +import io.opentelemetry.instrumentation.api.util.SpanNames; import java.lang.reflect.Method; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.reflect.MethodSignature; final class JoinPointRequest { + private final JoinPoint joinPoint; private final Method method; - private final WithSpan annotation; + private final String spanName; + private final SpanKind spanKind; JoinPointRequest(JoinPoint joinPoint) { this.joinPoint = joinPoint; MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature(); this.method = methodSignature.getMethod(); - this.annotation = this.method.getDeclaredAnnotation(WithSpan.class); + + // in rare cases, when interface method does not have annotations but the implementation does, + // and the AspectJ factory is configured to proxy interfaces, this class will receive the + // abstract interface method (without annotations) instead of the implementation method (with + // annotations); these defaults prevent NPEs in this scenario + String spanName = ""; + SpanKind spanKind = SpanKind.INTERNAL; + + io.opentelemetry.extension.annotations.WithSpan oldAnnotation = + this.method.getDeclaredAnnotation(io.opentelemetry.extension.annotations.WithSpan.class); + if (oldAnnotation != null) { + spanName = oldAnnotation.value(); + spanKind = oldAnnotation.kind(); + } + + WithSpan annotation = this.method.getDeclaredAnnotation(WithSpan.class); + if (annotation != null) { + spanName = annotation.value(); + spanKind = annotation.kind(); + } + + if (spanName.isEmpty()) { + spanName = SpanNames.fromMethod(method); + } + + this.spanName = spanName; + this.spanKind = spanKind; } - Method method() { - return method; + String spanName() { + return spanName; + } + + SpanKind spanKind() { + return spanKind; } - WithSpan annotation() { - return annotation; + Method method() { + return method; } Object[] args() { diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/aspects/WithSpanAspect.java b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/aspects/WithSpanAspect.java index 56a597d01add..31ec3eaa9112 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/aspects/WithSpanAspect.java +++ b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/aspects/WithSpanAspect.java @@ -6,29 +6,28 @@ package io.opentelemetry.instrumentation.spring.autoconfigure.aspects; import io.opentelemetry.api.OpenTelemetry; -import io.opentelemetry.api.trace.SpanKind; +import io.opentelemetry.api.trace.Span; import io.opentelemetry.context.Context; import io.opentelemetry.context.Scope; -import io.opentelemetry.extension.annotations.WithSpan; +import io.opentelemetry.instrumentation.annotations.WithSpan; import io.opentelemetry.instrumentation.api.annotation.support.MethodSpanAttributesExtractor; import io.opentelemetry.instrumentation.api.annotation.support.ParameterAttributeNamesExtractor; import io.opentelemetry.instrumentation.api.annotation.support.async.AsyncOperationEndSupport; import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; import io.opentelemetry.instrumentation.api.instrumenter.code.CodeAttributesExtractor; -import io.opentelemetry.instrumentation.api.util.SpanNames; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.springframework.core.ParameterNameDiscoverer; /** - * Uses Spring-AOP to wrap methods marked by {@link WithSpan} in a {@link - * io.opentelemetry.api.trace.Span}. + * Uses Spring-AOP to wrap methods marked by {@link WithSpan} (or the deprecated {@link + * io.opentelemetry.extension.annotations.WithSpan}) in a {@link Span}. * *

Ensure methods annotated with {@link WithSpan} are implemented on beans managed by the Spring * container. * - *

Note: This Aspect uses spring-aop to proxy beans. Therefore the {@link WithSpan} annotation + *

Note: This Aspect uses spring-aop to proxy beans. Therefore, the {@link WithSpan} annotation * can not be applied to constructors. */ @Aspect @@ -44,7 +43,7 @@ public WithSpanAspect( new WithSpanAspectParameterAttributeNamesExtractor(parameterNameDiscoverer); instrumenter = - Instrumenter.builder(openTelemetry, INSTRUMENTATION_NAME, WithSpanAspect::spanName) + Instrumenter.builder(openTelemetry, INSTRUMENTATION_NAME, JoinPointRequest::spanName) .addAttributesExtractor( CodeAttributesExtractor.create(JointPointCodeAttributesExtractor.INSTANCE)) .addAttributesExtractor( @@ -52,23 +51,12 @@ public WithSpanAspect( JoinPointRequest::method, parameterAttributeNamesExtractor, JoinPointRequest::args)) - .buildInstrumenter(WithSpanAspect::spanKind); + .buildInstrumenter(JoinPointRequest::spanKind); } - private static String spanName(JoinPointRequest request) { - WithSpan annotation = request.annotation(); - String spanName = annotation.value(); - if (spanName.isEmpty()) { - return SpanNames.fromMethod(request.method()); - } - return spanName; - } - - private static SpanKind spanKind(JoinPointRequest request) { - return request.annotation().kind(); - } - - @Around("@annotation(io.opentelemetry.extension.annotations.WithSpan)") + @Around( + "@annotation(io.opentelemetry.extension.annotations.WithSpan) || " + + "@annotation(io.opentelemetry.instrumentation.annotations.WithSpan)") public Object traceMethod(ProceedingJoinPoint pjp) throws Throwable { JoinPointRequest request = new JoinPointRequest(pjp); diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/aspects/WithSpanAspectParameterAttributeNamesExtractor.java b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/aspects/WithSpanAspectParameterAttributeNamesExtractor.java index c6c2d96f0da7..8dbef92d9a4e 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/aspects/WithSpanAspectParameterAttributeNamesExtractor.java +++ b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/aspects/WithSpanAspectParameterAttributeNamesExtractor.java @@ -5,7 +5,7 @@ package io.opentelemetry.instrumentation.spring.autoconfigure.aspects; -import io.opentelemetry.extension.annotations.SpanAttribute; +import io.opentelemetry.instrumentation.annotations.SpanAttribute; import io.opentelemetry.instrumentation.api.annotation.support.ParameterAttributeNamesExtractor; import java.lang.reflect.Method; import java.lang.reflect.Parameter; @@ -34,13 +34,23 @@ public String[] extract(Method method, Parameter[] parameters) { @Nullable private static String attributeName(Parameter parameter, String[] parameterNames, int index) { + io.opentelemetry.extension.annotations.SpanAttribute oldAnnotation = + parameter.getDeclaredAnnotation(io.opentelemetry.extension.annotations.SpanAttribute.class); SpanAttribute annotation = parameter.getDeclaredAnnotation(SpanAttribute.class); - if (annotation == null) { + if (oldAnnotation == null && annotation == null) { return null; } - String value = annotation.value(); - if (!value.isEmpty()) { - return value; + if (annotation != null) { + String value = annotation.value(); + if (!value.isEmpty()) { + return value; + } + } + if (oldAnnotation != null) { + String value = oldAnnotation.value(); + if (!value.isEmpty()) { + return value; + } } if (parameterNames != null && index < parameterNames.length) { String parameterName = parameterNames[index]; diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/aspects/WithSpanAspectTest.java b/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/aspects/AbstractWithSpanAspectTest.java similarity index 78% rename from instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/aspects/WithSpanAspectTest.java rename to instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/aspects/AbstractWithSpanAspectTest.java index 314c9e39edd1..c58610a3c3f1 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/aspects/WithSpanAspectTest.java +++ b/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/aspects/AbstractWithSpanAspectTest.java @@ -14,8 +14,6 @@ import static io.opentelemetry.semconv.trace.attributes.SemanticAttributes.CODE_NAMESPACE; import static org.assertj.core.api.Assertions.assertThatThrownBy; -import io.opentelemetry.extension.annotations.SpanAttribute; -import io.opentelemetry.extension.annotations.WithSpan; import io.opentelemetry.instrumentation.testing.junit.LibraryInstrumentationExtension; import io.opentelemetry.sdk.trace.data.SpanData; import io.opentelemetry.sdk.trace.data.StatusData; @@ -33,58 +31,45 @@ import org.springframework.core.ParameterNameDiscoverer; /** Spring AOP Test for {@link WithSpanAspect}. */ -public class WithSpanAspectTest { +abstract class AbstractWithSpanAspectTest { @RegisterExtension static final LibraryInstrumentationExtension testing = LibraryInstrumentationExtension.create(); - static class WithSpanTester { - @WithSpan - public String testWithSpan() { - return "Span with name testWithSpan was created"; - } + private WithSpanTester withSpanTester; + private String unproxiedTesterSimpleClassName; + private String unproxiedTesterClassName; - @WithSpan("greatestSpanEver") - public String testWithSpanWithValue() { - return "Span with name greatestSpanEver was created"; - } + abstract WithSpanTester newWithSpanTester(); - @WithSpan - public String testWithSpanWithException() throws Exception { - throw new Exception("Test @WithSpan With Exception"); - } + public interface WithSpanTester { + String testWithSpan(); - @WithSpan(kind = CLIENT) - public String testWithClientSpan() { - return "Span with name testWithClientSpan and SpanKind.CLIENT was created"; - } + String testWithSpanWithValue(); - @WithSpan - public CompletionStage testAsyncCompletionStage(CompletionStage stage) { - return stage; - } + String testWithSpanWithException() throws Exception; - @WithSpan - public CompletableFuture testAsyncCompletableFuture(CompletableFuture stage) { - return stage; - } + String testWithClientSpan(); - @WithSpan - public String withSpanAttributes( - @SpanAttribute String discoveredName, - @SpanAttribute String implicitName, - @SpanAttribute("explicitName") String parameter, - @SpanAttribute("nullAttribute") String nullAttribute, - String notTraced) { + CompletionStage testAsyncCompletionStage(CompletionStage stage); - return "hello!"; - } - } + CompletableFuture testAsyncCompletableFuture(CompletableFuture stage); - private WithSpanTester withSpanTester; + String withSpanAttributes( + String discoveredName, + String implicitName, + String parameter, + String nullAttribute, + String notTraced); + } @BeforeEach void setup() { - AspectJProxyFactory factory = new AspectJProxyFactory(new WithSpanTester()); + WithSpanTester unproxiedTester = newWithSpanTester(); + unproxiedTesterSimpleClassName = unproxiedTester.getClass().getSimpleName(); + unproxiedTesterClassName = unproxiedTester.getClass().getName(); + + AspectJProxyFactory factory = new AspectJProxyFactory(); + factory.setTarget(unproxiedTester); ParameterNameDiscoverer parameterNameDiscoverer = new ParameterNameDiscoverer() { @Override @@ -106,7 +91,7 @@ public String[] getParameterNames(Constructor constructor) { @Test @DisplayName("when method is annotated with @WithSpan should wrap method execution in a Span") - void withSpanWithDefaults() throws Throwable { + void withSpanWithDefaults() { // when testing.runWithSpan("parent", withSpanTester::testWithSpan); @@ -118,18 +103,18 @@ void withSpanWithDefaults() throws Throwable { trace.hasSpansSatisfyingExactly( parentSpan -> parentSpan.hasName("parent").hasKind(INTERNAL), span -> - span.hasName("WithSpanTester.testWithSpan") + span.hasName(unproxiedTesterSimpleClassName + ".testWithSpan") .hasKind(INTERNAL) .hasParent(trace.getSpan(0)) .hasAttributesSatisfyingExactly( - equalTo(CODE_NAMESPACE, WithSpanTester.class.getName()), + equalTo(CODE_NAMESPACE, unproxiedTesterClassName), equalTo(CODE_FUNCTION, "testWithSpan")))); } @Test @DisplayName( "when @WithSpan value is set should wrap method execution in a Span with custom name") - void withSpanName() throws Throwable { + void withSpanName() { // when testing.runWithSpan("parent", () -> withSpanTester.testWithSpanWithValue()); @@ -145,14 +130,14 @@ void withSpanName() throws Throwable { .hasKind(INTERNAL) .hasParent(trace.getSpan(0)) .hasAttributesSatisfyingExactly( - equalTo(CODE_NAMESPACE, WithSpanTester.class.getName()), + equalTo(CODE_NAMESPACE, unproxiedTesterClassName), equalTo(CODE_FUNCTION, "testWithSpanWithValue")))); } @Test @DisplayName( "when method is annotated with @WithSpan AND an exception is thrown span should record the exception") - void withSpanError() throws Throwable { + void withSpanError() { assertThatThrownBy(() -> withSpanTester.testWithSpanWithException()) .isInstanceOf(Exception.class); @@ -162,18 +147,18 @@ void withSpanError() throws Throwable { trace -> trace.hasSpansSatisfyingExactly( span -> - span.hasName("WithSpanTester.testWithSpanWithException") + span.hasName(unproxiedTesterSimpleClassName + ".testWithSpanWithException") .hasKind(INTERNAL) .hasStatus(StatusData.error()) .hasAttributesSatisfyingExactly( - equalTo(CODE_NAMESPACE, WithSpanTester.class.getName()), + equalTo(CODE_NAMESPACE, unproxiedTesterClassName), equalTo(CODE_FUNCTION, "testWithSpanWithException")))); } @Test @DisplayName( "when method is annotated with @WithSpan(kind=CLIENT) should build span with the declared SpanKind") - void withSpanKind() throws Throwable { + void withSpanKind() { // when testing.runWithSpan("parent", () -> withSpanTester.testWithClientSpan()); @@ -185,17 +170,16 @@ void withSpanKind() throws Throwable { trace.hasSpansSatisfyingExactly( parentSpan -> parentSpan.hasName("parent").hasKind(INTERNAL), span -> - span.hasName("WithSpanTester.testWithClientSpan") + span.hasName(unproxiedTesterSimpleClassName + ".testWithClientSpan") .hasKind(CLIENT) .hasParent(trace.getSpan(0)) .hasAttributesSatisfyingExactly( - equalTo(CODE_NAMESPACE, WithSpanTester.class.getName()), + equalTo(CODE_NAMESPACE, unproxiedTesterClassName), equalTo(CODE_FUNCTION, "testWithClientSpan")))); } @Test - @DisplayName("") - void withSpanAttributes() throws Throwable { + void withSpanAttributes() { // when testing.runWithSpan( "parent", () -> withSpanTester.withSpanAttributes("foo", "bar", "baz", null, "fizz")); @@ -208,11 +192,11 @@ void withSpanAttributes() throws Throwable { trace.hasSpansSatisfyingExactly( parentSpan -> parentSpan.hasName("parent").hasKind(INTERNAL), span -> - span.hasName("WithSpanTester.withSpanAttributes") + span.hasName(unproxiedTesterSimpleClassName + ".withSpanAttributes") .hasKind(INTERNAL) .hasParent(trace.getSpan(0)) .hasAttributesSatisfyingExactly( - equalTo(CODE_NAMESPACE, WithSpanTester.class.getName()), + equalTo(CODE_NAMESPACE, unproxiedTesterClassName), equalTo(CODE_FUNCTION, "withSpanAttributes"), equalTo(stringKey("discoveredName"), "foo"), equalTo(stringKey("implicitName"), "bar"), @@ -225,7 +209,7 @@ class WithCompletionStage { @Test @DisplayName("should end Span on complete") - void onComplete() throws Throwable { + void onComplete() { CompletableFuture future = new CompletableFuture<>(); // when @@ -250,17 +234,17 @@ void onComplete() throws Throwable { trace.hasSpansSatisfyingExactly( parentSpan -> parentSpan.hasName("parent").hasKind(INTERNAL), span -> - span.hasName("WithSpanTester.testAsyncCompletionStage") + span.hasName(unproxiedTesterSimpleClassName + ".testAsyncCompletionStage") .hasKind(INTERNAL) .hasParent(trace.getSpan(0)) .hasAttributesSatisfyingExactly( - equalTo(CODE_NAMESPACE, WithSpanTester.class.getName()), + equalTo(CODE_NAMESPACE, unproxiedTesterClassName), equalTo(CODE_FUNCTION, "testAsyncCompletionStage")))); } @Test @DisplayName("should end Span on completeException AND should record the exception") - void onCompleteExceptionally() throws Throwable { + void onCompleteExceptionally() { CompletableFuture future = new CompletableFuture<>(); // when @@ -285,18 +269,18 @@ void onCompleteExceptionally() throws Throwable { trace.hasSpansSatisfyingExactly( parentSpan -> parentSpan.hasName("parent").hasKind(INTERNAL), span -> - span.hasName("WithSpanTester.testAsyncCompletionStage") + span.hasName(unproxiedTesterSimpleClassName + ".testAsyncCompletionStage") .hasKind(INTERNAL) .hasStatus(StatusData.error()) .hasParent(trace.getSpan(0)) .hasAttributesSatisfyingExactly( - equalTo(CODE_NAMESPACE, WithSpanTester.class.getName()), + equalTo(CODE_NAMESPACE, unproxiedTesterClassName), equalTo(CODE_FUNCTION, "testAsyncCompletionStage")))); } @Test @DisplayName("should end Span on incompatible return value") - void onIncompatibleReturnValue() throws Throwable { + void onIncompatibleReturnValue() { // when testing.runWithSpan("parent", () -> withSpanTester.testAsyncCompletionStage(null)); @@ -308,11 +292,11 @@ void onIncompatibleReturnValue() throws Throwable { trace.hasSpansSatisfyingExactly( parentSpan -> parentSpan.hasName("parent").hasKind(INTERNAL), span -> - span.hasName("WithSpanTester.testAsyncCompletionStage") + span.hasName(unproxiedTesterSimpleClassName + ".testAsyncCompletionStage") .hasKind(INTERNAL) .hasParent(trace.getSpan(0)) .hasAttributesSatisfyingExactly( - equalTo(CODE_NAMESPACE, WithSpanTester.class.getName()), + equalTo(CODE_NAMESPACE, unproxiedTesterClassName), equalTo(CODE_FUNCTION, "testAsyncCompletionStage")))); } } @@ -323,7 +307,7 @@ class WithCompletableFuture { @Test @DisplayName("should end Span on complete") - void onComplete() throws Throwable { + void onComplete() { CompletableFuture future = new CompletableFuture<>(); // when @@ -348,17 +332,18 @@ void onComplete() throws Throwable { trace.hasSpansSatisfyingExactly( parentSpan -> parentSpan.hasName("parent").hasKind(INTERNAL), span -> - span.hasName("WithSpanTester.testAsyncCompletableFuture") + span.hasName( + unproxiedTesterSimpleClassName + ".testAsyncCompletableFuture") .hasKind(INTERNAL) .hasParent(trace.getSpan(0)) .hasAttributesSatisfyingExactly( - equalTo(CODE_NAMESPACE, WithSpanTester.class.getName()), + equalTo(CODE_NAMESPACE, unproxiedTesterClassName), equalTo(CODE_FUNCTION, "testAsyncCompletableFuture")))); } @Test @DisplayName("should end Span on completeException AND should record the exception") - void onCompleteExceptionally() throws Throwable { + void onCompleteExceptionally() { CompletableFuture future = new CompletableFuture<>(); // when @@ -383,18 +368,19 @@ void onCompleteExceptionally() throws Throwable { trace.hasSpansSatisfyingExactly( parentSpan -> parentSpan.hasName("parent").hasKind(INTERNAL), span -> - span.hasName("WithSpanTester.testAsyncCompletableFuture") + span.hasName( + unproxiedTesterSimpleClassName + ".testAsyncCompletableFuture") .hasKind(INTERNAL) .hasStatus(StatusData.error()) .hasParent(trace.getSpan(0)) .hasAttributesSatisfyingExactly( - equalTo(CODE_NAMESPACE, WithSpanTester.class.getName()), + equalTo(CODE_NAMESPACE, unproxiedTesterClassName), equalTo(CODE_FUNCTION, "testAsyncCompletableFuture")))); } @Test @DisplayName("should end the Span when already complete") - void onCompletedFuture() throws Throwable { + void onCompletedFuture() { CompletableFuture future = CompletableFuture.completedFuture("Done"); // when @@ -408,17 +394,18 @@ void onCompletedFuture() throws Throwable { trace.hasSpansSatisfyingExactly( parentSpan -> parentSpan.hasName("parent").hasKind(INTERNAL), span -> - span.hasName("WithSpanTester.testAsyncCompletableFuture") + span.hasName( + unproxiedTesterSimpleClassName + ".testAsyncCompletableFuture") .hasKind(INTERNAL) .hasParent(trace.getSpan(0)) .hasAttributesSatisfyingExactly( - equalTo(CODE_NAMESPACE, WithSpanTester.class.getName()), + equalTo(CODE_NAMESPACE, unproxiedTesterClassName), equalTo(CODE_FUNCTION, "testAsyncCompletableFuture")))); } @Test @DisplayName("should end the Span when already failed") - void onFailedFuture() throws Throwable { + void onFailedFuture() { CompletableFuture future = new CompletableFuture<>(); future.completeExceptionally(new Exception("Test @WithSpan With completeExceptionally")); @@ -433,18 +420,19 @@ void onFailedFuture() throws Throwable { trace.hasSpansSatisfyingExactly( parentSpan -> parentSpan.hasName("parent").hasKind(INTERNAL), span -> - span.hasName("WithSpanTester.testAsyncCompletableFuture") + span.hasName( + unproxiedTesterSimpleClassName + ".testAsyncCompletableFuture") .hasKind(INTERNAL) .hasStatus(StatusData.error()) .hasParent(trace.getSpan(0)) .hasAttributesSatisfyingExactly( - equalTo(CODE_NAMESPACE, WithSpanTester.class.getName()), + equalTo(CODE_NAMESPACE, unproxiedTesterClassName), equalTo(CODE_FUNCTION, "testAsyncCompletableFuture")))); } @Test @DisplayName("should end Span on incompatible return value") - void onIncompatibleReturnValue() throws Throwable { + void onIncompatibleReturnValue() { // when testing.runWithSpan("parent", () -> withSpanTester.testAsyncCompletableFuture(null)); @@ -456,11 +444,12 @@ void onIncompatibleReturnValue() throws Throwable { trace.hasSpansSatisfyingExactly( parentSpan -> parentSpan.hasName("parent").hasKind(INTERNAL), span -> - span.hasName("WithSpanTester.testAsyncCompletableFuture") + span.hasName( + unproxiedTesterSimpleClassName + ".testAsyncCompletableFuture") .hasKind(INTERNAL) .hasParent(trace.getSpan(0)) .hasAttributesSatisfyingExactly( - equalTo(CODE_NAMESPACE, WithSpanTester.class.getName()), + equalTo(CODE_NAMESPACE, unproxiedTesterClassName), equalTo(CODE_FUNCTION, "testAsyncCompletableFuture")))); } } diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/aspects/InstrumentationWithSpanAspectTest.java b/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/aspects/InstrumentationWithSpanAspectTest.java new file mode 100644 index 000000000000..c7f119f78ef3 --- /dev/null +++ b/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/aspects/InstrumentationWithSpanAspectTest.java @@ -0,0 +1,71 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.spring.autoconfigure.aspects; + +import static io.opentelemetry.api.trace.SpanKind.CLIENT; + +import io.opentelemetry.instrumentation.annotations.SpanAttribute; +import io.opentelemetry.instrumentation.annotations.WithSpan; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; + +class InstrumentationWithSpanAspectTest extends AbstractWithSpanAspectTest { + + @Override + WithSpanTester newWithSpanTester() { + return new InstrumentationWithSpanTester(); + } + + static class InstrumentationWithSpanTester implements WithSpanTester { + @Override + @WithSpan + public String testWithSpan() { + return "Span with name testWithSpan was created"; + } + + @Override + @WithSpan("greatestSpanEver") + public String testWithSpanWithValue() { + return "Span with name greatestSpanEver was created"; + } + + @Override + @WithSpan + public String testWithSpanWithException() throws Exception { + throw new Exception("Test @WithSpan With Exception"); + } + + @Override + @WithSpan(kind = CLIENT) + public String testWithClientSpan() { + return "Span with name testWithClientSpan and SpanKind.CLIENT was created"; + } + + @Override + @WithSpan + public CompletionStage testAsyncCompletionStage(CompletionStage stage) { + return stage; + } + + @Override + @WithSpan + public CompletableFuture testAsyncCompletableFuture(CompletableFuture stage) { + return stage; + } + + @Override + @WithSpan + public String withSpanAttributes( + @SpanAttribute String discoveredName, + @SpanAttribute String implicitName, + @SpanAttribute("explicitName") String parameter, + @SpanAttribute("nullAttribute") String nullAttribute, + String notTraced) { + + return "hello!"; + } + } +} diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/aspects/SdkExtensionWithSpanAspectTest.java b/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/aspects/SdkExtensionWithSpanAspectTest.java new file mode 100644 index 000000000000..5a5f9443b55d --- /dev/null +++ b/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/aspects/SdkExtensionWithSpanAspectTest.java @@ -0,0 +1,71 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.spring.autoconfigure.aspects; + +import static io.opentelemetry.api.trace.SpanKind.CLIENT; + +import io.opentelemetry.extension.annotations.SpanAttribute; +import io.opentelemetry.extension.annotations.WithSpan; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; + +class SdkExtensionWithSpanAspectTest extends AbstractWithSpanAspectTest { + + @Override + WithSpanTester newWithSpanTester() { + return new SdkExtensionWithSpanTester(); + } + + static class SdkExtensionWithSpanTester implements WithSpanTester { + @Override + @WithSpan + public String testWithSpan() { + return "Span with name testWithSpan was created"; + } + + @Override + @WithSpan("greatestSpanEver") + public String testWithSpanWithValue() { + return "Span with name greatestSpanEver was created"; + } + + @Override + @WithSpan + public String testWithSpanWithException() throws Exception { + throw new Exception("Test @WithSpan With Exception"); + } + + @Override + @WithSpan(kind = CLIENT) + public String testWithClientSpan() { + return "Span with name testWithClientSpan and SpanKind.CLIENT was created"; + } + + @Override + @WithSpan + public CompletionStage testAsyncCompletionStage(CompletionStage stage) { + return stage; + } + + @Override + @WithSpan + public CompletableFuture testAsyncCompletableFuture(CompletableFuture stage) { + return stage; + } + + @Override + @WithSpan + public String withSpanAttributes( + @SpanAttribute String discoveredName, + @SpanAttribute String implicitName, + @SpanAttribute("explicitName") String parameter, + @SpanAttribute("nullAttribute") String nullAttribute, + String notTraced) { + + return "hello!"; + } + } +} diff --git a/instrumentation/spring/starters/spring-starter/build.gradle.kts b/instrumentation/spring/starters/spring-starter/build.gradle.kts index c21740dd58f6..0f77714382dc 100644 --- a/instrumentation/spring/starters/spring-starter/build.gradle.kts +++ b/instrumentation/spring/starters/spring-starter/build.gradle.kts @@ -11,9 +11,9 @@ dependencies { api("org.springframework.boot:spring-boot-starter:${versions["org.springframework.boot"]}") api("org.springframework.boot:spring-boot-starter-aop:${versions["org.springframework.boot"]}") api(project(":instrumentation:spring:spring-boot-autoconfigure")) - api("io.opentelemetry:opentelemetry-extension-annotations") api("io.opentelemetry:opentelemetry-sdk-extension-autoconfigure-spi") api("io.opentelemetry:opentelemetry-api") api("io.opentelemetry:opentelemetry-exporter-logging") api("io.opentelemetry:opentelemetry-sdk") + api(project(":instrumentation-annotations")) }