Fix in spring autoconfigure
ADD
instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/aspects/InstrumentationWithSpanAspect.java
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.instrumentation.spring.autoconfigure.aspects;
+
+import io.opentelemetry.api.OpenTelemetry;
+import org.aspectj.lang.ProceedingJoinPoint;
+import org.aspectj.lang.annotation.Around;
+import org.aspectj.lang.annotation.Aspect;
+import org.springframework.core.ParameterNameDiscoverer;
+
+@Aspect
+class InstrumentationWithSpanAspect extends WithSpanAspect {
+
+ InstrumentationWithSpanAspect(
+ OpenTelemetry openTelemetry, ParameterNameDiscoverer parameterNameDiscoverer) {
+ super(
+ openTelemetry,
+ parameterNameDiscoverer,
+ new JoinPointRequest.InstrumentationAnnotationFactory(),
+ new WithSpanAspectParameterAttributeNamesExtractor
+ .InstrumentationAnnotationAttributeNameSupplier());
+ }
+
+ @Override
+ @Around("@annotation(io.opentelemetry.instrumentation.annotations.WithSpan)")
+ public Object traceMethod(ProceedingJoinPoint pjp) throws Throwable {
+ return super.traceMethod(pjp);
+ }
+}

MODIFY
instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/aspects/JoinPointRequest.java
instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/aspects/JoinPointRequest.java
@@ -19,35 +19,13 @@ final class JoinPointRequest {
private final String spanName;
private final SpanKind spanKind;
- JoinPointRequest(JoinPoint joinPoint) {
- this.joinPoint = joinPoint;
- MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
- this.method = methodSignature.getMethod();
-
- // 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();
- }
-
+ private JoinPointRequest(JoinPoint joinPoint, Method method, String spanName, SpanKind spanKind) {
if (spanName.isEmpty()) {
spanName = SpanNames.fromMethod(method);
}
+ this.joinPoint = joinPoint;
+ this.method = method;
this.spanName = spanName;
this.spanKind = spanKind;
}
@@ -67,4 +45,48 @@ final class JoinPointRequest {
Object[] args() {
return joinPoint.getArgs();
}
+
+ interface Factory {
+
+ JoinPointRequest create(JoinPoint joinPoint);
+ }
+
+ static final class InstrumentationAnnotationFactory implements Factory {
+
+ @Override
+ public JoinPointRequest create(JoinPoint joinPoint) {
+ MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
+ Method method = methodSignature.getMethod();
+
+ // 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
+ WithSpan annotation = method.getDeclaredAnnotation(WithSpan.class);
+ String spanName = annotation != null ? annotation.value() : "";
+ SpanKind spanKind = annotation != null ? annotation.kind() : SpanKind.INTERNAL;
+
+ return new JoinPointRequest(joinPoint, method, spanName, spanKind);
+ }
+ }
+
+ static final class SdkExtensionAnnotationFactory implements Factory {
+
+ @Override
+ public JoinPointRequest create(JoinPoint joinPoint) {
+ MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
+ Method method = methodSignature.getMethod();
+
+ // 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
+ io.opentelemetry.extension.annotations.WithSpan annotation =
+ method.getDeclaredAnnotation(io.opentelemetry.extension.annotations.WithSpan.class);
+ String spanName = annotation != null ? annotation.value() : "";
+ SpanKind spanKind = annotation != null ? annotation.kind() : SpanKind.INTERNAL;
+
+ return new JoinPointRequest(joinPoint, method, spanName, spanKind);
+ }
+ }
}

ADD
instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/aspects/SdkExtensionWithSpanAspect.java
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.instrumentation.spring.autoconfigure.aspects;
+
+import io.opentelemetry.api.OpenTelemetry;
+import org.aspectj.lang.ProceedingJoinPoint;
+import org.aspectj.lang.annotation.Around;
+import org.aspectj.lang.annotation.Aspect;
+import org.springframework.core.ParameterNameDiscoverer;
+
+@Aspect
+class SdkExtensionWithSpanAspect extends WithSpanAspect {
+
+ SdkExtensionWithSpanAspect(
+ OpenTelemetry openTelemetry, ParameterNameDiscoverer parameterNameDiscoverer) {
+ super(
+ openTelemetry,
+ parameterNameDiscoverer,
+ new JoinPointRequest.SdkExtensionAnnotationFactory(),
+ new WithSpanAspectParameterAttributeNamesExtractor
+ .SdkExtensionAnnotationAttributeNameSupplier());
+ }
+
+ @Override
+ @Around("@annotation(io.opentelemetry.extension.annotations.WithSpan)")
+ public Object traceMethod(ProceedingJoinPoint pjp) throws Throwable {
+ return super.traceMethod(pjp);
+ }
+}

MODIFY
instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/aspects/TraceAspectAutoConfiguration.java
instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/aspects/TraceAspectAutoConfiguration.java
package io.opentelemetry.instrumentation.spring.autoconfigure.aspects;
import io.opentelemetry.api.OpenTelemetry;
-import io.opentelemetry.extension.annotations.WithSpan;
+import io.opentelemetry.instrumentation.annotations.WithSpan;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
@@ -21,8 +21,7 @@ import org.springframework.core.ParameterNameDiscoverer;
@Configuration
@EnableConfigurationProperties(TraceAspectProperties.class)
@ConditionalOnProperty(prefix = "otel.springboot.aspects", name = "enabled", matchIfMissing = true)
-// TODO (trask) need to handle both WithSpan annotations here
-@ConditionalOnClass({Aspect.class, WithSpan.class})
+@ConditionalOnClass(Aspect.class)
public class TraceAspectAutoConfiguration {
@Bean
@@ -32,8 +31,16 @@ public class TraceAspectAutoConfiguration {
}
@Bean
- public WithSpanAspect withSpanAspect(
+ @ConditionalOnClass(WithSpan.class)
+ public WithSpanAspect instrumentationWithSpanAspect(
OpenTelemetry openTelemetry, ParameterNameDiscoverer parameterNameDiscoverer) {
- return new WithSpanAspect(openTelemetry, parameterNameDiscoverer);
+ return new InstrumentationWithSpanAspect(openTelemetry, parameterNameDiscoverer);
+ }
+
+ @Bean
+ @ConditionalOnClass(io.opentelemetry.extension.annotations.WithSpan.class)
+ public WithSpanAspect sdkExtensionWithSpanAspect(
+ OpenTelemetry openTelemetry, ParameterNameDiscoverer parameterNameDiscoverer) {
+ return new SdkExtensionWithSpanAspect(openTelemetry, parameterNameDiscoverer);
}
}

MODIFY
instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/aspects/WithSpanAspect.java
instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/aspects/WithSpanAspect.java
@@ -16,8 +16,6 @@ import io.opentelemetry.instrumentation.api.annotation.support.async.AsyncOperat
import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter;
import io.opentelemetry.instrumentation.api.instrumenter.code.CodeAttributesExtractor;
import org.aspectj.lang.ProceedingJoinPoint;
-import org.aspectj.lang.annotation.Around;
-import org.aspectj.lang.annotation.Aspect;
import org.springframework.core.ParameterNameDiscoverer;
/**
@@ -30,17 +28,22 @@ import org.springframework.core.ParameterNameDiscoverer;
* <p>Note: This Aspect uses spring-aop to proxy beans. Therefore, the {@link WithSpan} annotation
* can not be applied to constructors.
*/
-@Aspect
-public class WithSpanAspect {
+abstract class WithSpanAspect {
private static final String INSTRUMENTATION_NAME = "io.opentelemetry.spring-boot-autoconfigure";
private final Instrumenter<JoinPointRequest, Object> instrumenter;
+ private final JoinPointRequest.Factory requestFactory;
- public WithSpanAspect(
- OpenTelemetry openTelemetry, ParameterNameDiscoverer parameterNameDiscoverer) {
+ WithSpanAspect(
+ OpenTelemetry openTelemetry,
+ ParameterNameDiscoverer parameterNameDiscoverer,
+ JoinPointRequest.Factory requestFactory,
+ WithSpanAspectParameterAttributeNamesExtractor.SpanAttributeNameSupplier
+ spanAttributeNameSupplier) {
ParameterAttributeNamesExtractor parameterAttributeNamesExtractor =
- new WithSpanAspectParameterAttributeNamesExtractor(parameterNameDiscoverer);
+ new WithSpanAspectParameterAttributeNamesExtractor(
+ parameterNameDiscoverer, spanAttributeNameSupplier);
instrumenter =
Instrumenter.builder(openTelemetry, INSTRUMENTATION_NAME, JoinPointRequest::spanName)
@@ -52,14 +55,11 @@ public class WithSpanAspect {
parameterAttributeNamesExtractor,
JoinPointRequest::args))
.buildInstrumenter(JoinPointRequest::spanKind);
+ this.requestFactory = requestFactory;
}
- @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);
+ JoinPointRequest request = requestFactory.create(pjp);
Context parentContext = Context.current();
if (!instrumenter.shouldStart(parentContext, request)) {
return pjp.proceed();

MODIFY
instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/aspects/WithSpanAspectParameterAttributeNamesExtractor.java
instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/aspects/WithSpanAspectParameterAttributeNamesExtractor.java
@@ -13,11 +13,15 @@ import javax.annotation.Nullable;
import org.springframework.core.ParameterNameDiscoverer;
class WithSpanAspectParameterAttributeNamesExtractor implements ParameterAttributeNamesExtractor {
+
private final ParameterNameDiscoverer parameterNameDiscoverer;
+ private final SpanAttributeNameSupplier spanAttributeNameSupplier;
public WithSpanAspectParameterAttributeNamesExtractor(
- ParameterNameDiscoverer parameterNameDiscoverer) {
+ ParameterNameDiscoverer parameterNameDiscoverer,
+ SpanAttributeNameSupplier spanAttributeNameSupplier) {
this.parameterNameDiscoverer = parameterNameDiscoverer;
+ this.spanAttributeNameSupplier = spanAttributeNameSupplier;
}
@Override
@@ -33,24 +37,13 @@ class WithSpanAspectParameterAttributeNamesExtractor implements ParameterAttribu
}
@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 (oldAnnotation == null && annotation == null) {
+ private String attributeName(Parameter parameter, String[] parameterNames, int index) {
+ String annotationValue = spanAttributeNameSupplier.spanAttributeName(parameter);
+ if (annotationValue == null) {
return null;
}
- 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 (!annotationValue.isEmpty()) {
+ return annotationValue;
}
if (parameterNames != null && index < parameterNames.length) {
String parameterName = parameterNames[index];
@@ -63,4 +56,34 @@ class WithSpanAspectParameterAttributeNamesExtractor implements ParameterAttribu
}
return null;
}
+
+ interface SpanAttributeNameSupplier {
+
+ @Nullable
+ String spanAttributeName(Parameter parameter);
+ }
+
+ static final class InstrumentationAnnotationAttributeNameSupplier
+ implements SpanAttributeNameSupplier {
+
+ @Nullable
+ @Override
+ public String spanAttributeName(Parameter parameter) {
+ SpanAttribute annotation = parameter.getDeclaredAnnotation(SpanAttribute.class);
+ return annotation == null ? null : annotation.value();
+ }
+ }
+
+ static final class SdkExtensionAnnotationAttributeNameSupplier
+ implements SpanAttributeNameSupplier {
+
+ @Nullable
+ @Override
+ public String spanAttributeName(Parameter parameter) {
+ io.opentelemetry.extension.annotations.SpanAttribute annotation =
+ parameter.getDeclaredAnnotation(
+ io.opentelemetry.extension.annotations.SpanAttribute.class);
+ return annotation == null ? null : annotation.value();
+ }
+ }
}

MODIFY
instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/aspects/AbstractWithSpanAspectTest.java
instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/aspects/AbstractWithSpanAspectTest.java
@@ -14,6 +14,7 @@ import static io.opentelemetry.semconv.trace.attributes.SemanticAttributes.CODE_
import static io.opentelemetry.semconv.trace.attributes.SemanticAttributes.CODE_NAMESPACE;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import io.opentelemetry.api.OpenTelemetry;
import io.opentelemetry.instrumentation.testing.junit.LibraryInstrumentationExtension;
import io.opentelemetry.sdk.trace.data.SpanData;
import io.opentelemetry.sdk.trace.data.StatusData;
@@ -39,8 +40,6 @@ abstract class AbstractWithSpanAspectTest {
private String unproxiedTesterSimpleClassName;
private String unproxiedTesterClassName;
- abstract WithSpanTester newWithSpanTester();
-
public interface WithSpanTester {
String testWithSpan();
@@ -62,6 +61,11 @@ abstract class AbstractWithSpanAspectTest {
String notTraced);
}
+ abstract WithSpanTester newWithSpanTester();
+
+ abstract WithSpanAspect newWithSpanAspect(
+ OpenTelemetry openTelemetry, ParameterNameDiscoverer parameterNameDiscoverer);
+
@BeforeEach
void setup() {
WithSpanTester unproxiedTester = newWithSpanTester();

MODIFY
instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/aspects/InstrumentationWithSpanAspectTest.java
instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/aspects/InstrumentationWithSpanAspectTest.java
@@ -7,10 +7,12 @@ package io.opentelemetry.instrumentation.spring.autoconfigure.aspects;
import static io.opentelemetry.api.trace.SpanKind.CLIENT;
+import io.opentelemetry.api.OpenTelemetry;
import io.opentelemetry.instrumentation.annotations.SpanAttribute;
import io.opentelemetry.instrumentation.annotations.WithSpan;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage;
+import org.springframework.core.ParameterNameDiscoverer;
class InstrumentationWithSpanAspectTest extends AbstractWithSpanAspectTest {
@@ -19,6 +21,12 @@ class InstrumentationWithSpanAspectTest extends AbstractWithSpanAspectTest {
return new InstrumentationWithSpanTester();
}
+ @Override
+ WithSpanAspect newWithSpanAspect(
+ OpenTelemetry openTelemetry, ParameterNameDiscoverer parameterNameDiscoverer) {
+ return new InstrumentationWithSpanAspect(openTelemetry, parameterNameDiscoverer);
+ }
+
static class InstrumentationWithSpanTester implements WithSpanTester {
@Override
@WithSpan

MODIFY
instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/aspects/SdkExtensionWithSpanAspectTest.java
instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/aspects/SdkExtensionWithSpanAspectTest.java
@@ -7,10 +7,12 @@ package io.opentelemetry.instrumentation.spring.autoconfigure.aspects;
import static io.opentelemetry.api.trace.SpanKind.CLIENT;
+import io.opentelemetry.api.OpenTelemetry;
import io.opentelemetry.extension.annotations.SpanAttribute;
import io.opentelemetry.extension.annotations.WithSpan;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage;
+import org.springframework.core.ParameterNameDiscoverer;
class SdkExtensionWithSpanAspectTest extends AbstractWithSpanAspectTest {
@@ -19,6 +21,12 @@ class SdkExtensionWithSpanAspectTest extends AbstractWithSpanAspectTest {
return new SdkExtensionWithSpanTester();
}
+ @Override
+ WithSpanAspect newWithSpanAspect(
+ OpenTelemetry openTelemetry, ParameterNameDiscoverer parameterNameDiscoverer) {
+ return new SdkExtensionWithSpanAspect(openTelemetry, parameterNameDiscoverer);
+ }
+
static class SdkExtensionWithSpanTester implements WithSpanTester {
@Override
@WithSpan

MODIFY
instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/aspects/TraceAspectAutoConfigurationTest.java
instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/aspects/TraceAspectAutoConfigurationTest.java
@@ -29,7 +29,9 @@ public class TraceAspectAutoConfigurationTest {
.withPropertyValues("otel.springboot.aspects.enabled=true")
.run(
context ->
- assertThat(context.getBean("withSpanAspect", WithSpanAspect.class)).isNotNull());
+ assertThat(context)
+ .hasBean("instrumentationWithSpanAspect")
+ .hasBean("sdkExtensionWithSpanAspect"));
}
@Test
@@ -37,13 +39,20 @@ public class TraceAspectAutoConfigurationTest {
void disabledProperty() {
this.contextRunner
.withPropertyValues("otel.springboot.aspects.enabled=false")
- .run(context -> assertThat(context.containsBean("withSpanAspect")).isFalse());
+ .run(
+ context ->
+ assertThat(context)
+ .doesNotHaveBean("instrumentationWithSpanAspect")
+ .doesNotHaveBean("sdkExtensionWithSpanAspect"));
}
@Test
@DisplayName("when aspects enabled property is MISSING should initialize WithSpanAspect bean")
void noProperty() {
this.contextRunner.run(
- context -> assertThat(context.getBean("withSpanAspect", WithSpanAspect.class)).isNotNull());
+ context ->
+ assertThat(context)
+ .hasBean("instrumentationWithSpanAspect")
+ .hasBean("sdkExtensionWithSpanAspect"));
}
}

Fix @WithSpan in spring autoconfigure (#6398)