remove support for deprecated @WithSpan annotation from spring starter (#10530)

This commit is contained in:
Gregor Zeitlinger 2024-02-13 17:27:08 +01:00 committed by GitHub
parent 4d70bb3bbd
commit d73304b078
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 423 additions and 639 deletions

View File

@ -30,11 +30,4 @@ public class InstrumentationAnnotationsAutoConfiguration {
InstrumentationWithSpanAspect otelInstrumentationWithSpanAspect(OpenTelemetry openTelemetry) {
return new InstrumentationWithSpanAspect(openTelemetry, parameterNameDiscoverer);
}
@Bean
@SuppressWarnings("deprecation") // instrumenting deprecated class for backwards compatibility
@ConditionalOnClass(io.opentelemetry.extension.annotations.WithSpan.class)
SdkExtensionWithSpanAspect otelSdkExtensionWithSpanAspect(OpenTelemetry openTelemetry) {
return new SdkExtensionWithSpanAspect(openTelemetry, parameterNameDiscoverer);
}
}

View File

@ -69,25 +69,4 @@ final class JoinPointRequest {
return new JoinPointRequest(joinPoint, method, spanName, spanKind);
}
}
static final class SdkExtensionAnnotationFactory implements Factory {
@Override
@SuppressWarnings("deprecation") // instrumenting deprecated class for backwards compatibility
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);
}
}
}

View File

@ -1,32 +0,0 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.instrumentation.spring.autoconfigure.instrumentation.annotations;
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);
}
}

View File

@ -19,8 +19,7 @@ import org.aspectj.lang.ProceedingJoinPoint;
import org.springframework.core.ParameterNameDiscoverer;
/**
* Uses Spring-AOP to wrap methods marked by {@link WithSpan} (or the deprecated {@link
* io.opentelemetry.extension.annotations.WithSpan}) in a {@link Span}.
* Uses Spring-AOP to wrap methods marked by {@link WithSpan} in a {@link Span}.
*
* <p>Ensure methods annotated with {@link WithSpan} are implemented on beans managed by the Spring
* container.

View File

@ -73,18 +73,4 @@ class WithSpanAspectParameterAttributeNamesExtractor implements ParameterAttribu
return annotation == null ? null : annotation.value();
}
}
static final class SdkExtensionAnnotationAttributeNameSupplier
implements SpanAttributeNameSupplier {
@Nullable
@Override
@SuppressWarnings("deprecation") // instrumenting deprecated class for backwards compatibility
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();
}
}
}

View File

@ -1,456 +0,0 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.instrumentation.spring.autoconfigure.instrumentation.annotations;
import static io.opentelemetry.api.common.AttributeKey.stringKey;
import static io.opentelemetry.api.trace.SpanKind.CLIENT;
import static io.opentelemetry.api.trace.SpanKind.INTERNAL;
import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo;
import static io.opentelemetry.sdk.testing.assertj.TracesAssert.assertThat;
import static io.opentelemetry.semconv.SemanticAttributes.CODE_FUNCTION;
import static io.opentelemetry.semconv.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;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
import org.springframework.aop.aspectj.annotation.AspectJProxyFactory;
import org.springframework.core.ParameterNameDiscoverer;
/** Spring AOP Test for {@link WithSpanAspect}. */
abstract class AbstractWithSpanAspectTest {
@RegisterExtension
static final LibraryInstrumentationExtension testing = LibraryInstrumentationExtension.create();
private WithSpanTester withSpanTester;
private String unproxiedTesterSimpleClassName;
private String unproxiedTesterClassName;
public interface WithSpanTester {
String testWithSpan();
String testWithSpanWithValue();
String testWithSpanWithException() throws Exception;
String testWithClientSpan();
CompletionStage<String> testAsyncCompletionStage(CompletionStage<String> stage);
CompletableFuture<String> testAsyncCompletableFuture(CompletableFuture<String> stage);
String withSpanAttributes(
String discoveredName,
String implicitName,
String parameter,
String nullAttribute,
String notTraced);
}
abstract WithSpanTester newWithSpanTester();
abstract WithSpanAspect newWithSpanAspect(
OpenTelemetry openTelemetry, ParameterNameDiscoverer parameterNameDiscoverer);
@BeforeEach
void setup() {
WithSpanTester unproxiedTester = newWithSpanTester();
unproxiedTesterSimpleClassName = unproxiedTester.getClass().getSimpleName();
unproxiedTesterClassName = unproxiedTester.getClass().getName();
AspectJProxyFactory factory = new AspectJProxyFactory();
factory.setTarget(unproxiedTester);
ParameterNameDiscoverer parameterNameDiscoverer =
new ParameterNameDiscoverer() {
@Override
public String[] getParameterNames(Method method) {
return new String[] {"discoveredName", null, "parameter", "nullAttribute", "notTraced"};
}
@Override
public String[] getParameterNames(Constructor<?> constructor) {
return null;
}
};
WithSpanAspect aspect = newWithSpanAspect(testing.getOpenTelemetry(), parameterNameDiscoverer);
factory.addAspect(aspect);
withSpanTester = factory.getProxy();
}
@Test
@DisplayName("when method is annotated with @WithSpan should wrap method execution in a Span")
void withSpanWithDefaults() {
// when
testing.runWithSpan("parent", withSpanTester::testWithSpan);
// then
List<List<SpanData>> traces = testing.waitForTraces(1);
assertThat(traces)
.hasTracesSatisfyingExactly(
trace ->
trace.hasSpansSatisfyingExactly(
parentSpan -> parentSpan.hasName("parent").hasKind(INTERNAL),
span ->
span.hasName(unproxiedTesterSimpleClassName + ".testWithSpan")
.hasKind(INTERNAL)
.hasParent(trace.getSpan(0))
.hasAttributesSatisfyingExactly(
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() {
// when
testing.runWithSpan("parent", () -> withSpanTester.testWithSpanWithValue());
// then
List<List<SpanData>> traces = testing.waitForTraces(1);
assertThat(traces)
.hasTracesSatisfyingExactly(
trace ->
trace.hasSpansSatisfyingExactly(
parentSpan -> parentSpan.hasName("parent").hasKind(INTERNAL),
span ->
span.hasName("greatestSpanEver")
.hasKind(INTERNAL)
.hasParent(trace.getSpan(0))
.hasAttributesSatisfyingExactly(
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() {
assertThatThrownBy(() -> withSpanTester.testWithSpanWithException())
.isInstanceOf(Exception.class);
List<List<SpanData>> traces = testing.waitForTraces(1);
assertThat(traces)
.hasTracesSatisfyingExactly(
trace ->
trace.hasSpansSatisfyingExactly(
span ->
span.hasName(unproxiedTesterSimpleClassName + ".testWithSpanWithException")
.hasKind(INTERNAL)
.hasStatus(StatusData.error())
.hasAttributesSatisfyingExactly(
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() {
// when
testing.runWithSpan("parent", () -> withSpanTester.testWithClientSpan());
// then
List<List<SpanData>> traces = testing.waitForTraces(1);
assertThat(traces)
.hasTracesSatisfyingExactly(
trace ->
trace.hasSpansSatisfyingExactly(
parentSpan -> parentSpan.hasName("parent").hasKind(INTERNAL),
span ->
span.hasName(unproxiedTesterSimpleClassName + ".testWithClientSpan")
.hasKind(CLIENT)
.hasParent(trace.getSpan(0))
.hasAttributesSatisfyingExactly(
equalTo(CODE_NAMESPACE, unproxiedTesterClassName),
equalTo(CODE_FUNCTION, "testWithClientSpan"))));
}
@Test
void withSpanAttributes() {
// when
testing.runWithSpan(
"parent", () -> withSpanTester.withSpanAttributes("foo", "bar", "baz", null, "fizz"));
// then
List<List<SpanData>> traces = testing.waitForTraces(1);
assertThat(traces)
.hasTracesSatisfyingExactly(
trace ->
trace.hasSpansSatisfyingExactly(
parentSpan -> parentSpan.hasName("parent").hasKind(INTERNAL),
span ->
span.hasName(unproxiedTesterSimpleClassName + ".withSpanAttributes")
.hasKind(INTERNAL)
.hasParent(trace.getSpan(0))
.hasAttributesSatisfyingExactly(
equalTo(CODE_NAMESPACE, unproxiedTesterClassName),
equalTo(CODE_FUNCTION, "withSpanAttributes"),
equalTo(stringKey("discoveredName"), "foo"),
equalTo(stringKey("implicitName"), "bar"),
equalTo(stringKey("explicitName"), "baz"))));
}
@Nested
@DisplayName("with a method annotated with @WithSpan returns CompletionStage")
class WithCompletionStage {
@Test
@DisplayName("should end Span on complete")
void onComplete() {
CompletableFuture<String> future = new CompletableFuture<>();
// when
testing.runWithSpan("parent", () -> withSpanTester.testAsyncCompletionStage(future));
// then
assertThat(testing.waitForTraces(1))
.hasTracesSatisfyingExactly(
trace ->
trace.hasSpansSatisfyingExactly(
span -> span.hasName("parent").hasKind(INTERNAL)));
// when
future.complete("DONE");
// then
List<List<SpanData>> traces = testing.waitForTraces(1);
assertThat(traces)
.hasTracesSatisfyingExactly(
trace ->
trace.hasSpansSatisfyingExactly(
parentSpan -> parentSpan.hasName("parent").hasKind(INTERNAL),
span ->
span.hasName(unproxiedTesterSimpleClassName + ".testAsyncCompletionStage")
.hasKind(INTERNAL)
.hasParent(trace.getSpan(0))
.hasAttributesSatisfyingExactly(
equalTo(CODE_NAMESPACE, unproxiedTesterClassName),
equalTo(CODE_FUNCTION, "testAsyncCompletionStage"))));
}
@Test
@DisplayName("should end Span on completeException AND should record the exception")
void onCompleteExceptionally() {
CompletableFuture<String> future = new CompletableFuture<>();
// when
testing.runWithSpan("parent", () -> withSpanTester.testAsyncCompletionStage(future));
// then
assertThat(testing.waitForTraces(1))
.hasTracesSatisfyingExactly(
trace ->
trace.hasSpansSatisfyingExactly(
span -> span.hasName("parent").hasKind(INTERNAL)));
// when
future.completeExceptionally(new Exception("Test @WithSpan With completeExceptionally"));
// then
List<List<SpanData>> traces = testing.waitForTraces(1);
assertThat(traces)
.hasTracesSatisfyingExactly(
trace ->
trace.hasSpansSatisfyingExactly(
parentSpan -> parentSpan.hasName("parent").hasKind(INTERNAL),
span ->
span.hasName(unproxiedTesterSimpleClassName + ".testAsyncCompletionStage")
.hasKind(INTERNAL)
.hasStatus(StatusData.error())
.hasParent(trace.getSpan(0))
.hasAttributesSatisfyingExactly(
equalTo(CODE_NAMESPACE, unproxiedTesterClassName),
equalTo(CODE_FUNCTION, "testAsyncCompletionStage"))));
}
@Test
@DisplayName("should end Span on incompatible return value")
void onIncompatibleReturnValue() {
// when
testing.runWithSpan("parent", () -> withSpanTester.testAsyncCompletionStage(null));
// then
List<List<SpanData>> traces = testing.waitForTraces(1);
assertThat(traces)
.hasTracesSatisfyingExactly(
trace ->
trace.hasSpansSatisfyingExactly(
parentSpan -> parentSpan.hasName("parent").hasKind(INTERNAL),
span ->
span.hasName(unproxiedTesterSimpleClassName + ".testAsyncCompletionStage")
.hasKind(INTERNAL)
.hasParent(trace.getSpan(0))
.hasAttributesSatisfyingExactly(
equalTo(CODE_NAMESPACE, unproxiedTesterClassName),
equalTo(CODE_FUNCTION, "testAsyncCompletionStage"))));
}
}
@Nested
@DisplayName("with a method annotated with @WithSpan returns CompletableFuture")
class WithCompletableFuture {
@Test
@DisplayName("should end Span on complete")
void onComplete() {
CompletableFuture<String> future = new CompletableFuture<>();
// when
testing.runWithSpan("parent", () -> withSpanTester.testAsyncCompletableFuture(future));
// then
assertThat(testing.waitForTraces(1))
.hasTracesSatisfyingExactly(
trace ->
trace.hasSpansSatisfyingExactly(
span -> span.hasName("parent").hasKind(INTERNAL)));
// when
future.complete("DONE");
// then
List<List<SpanData>> traces = testing.waitForTraces(1);
assertThat(traces)
.hasTracesSatisfyingExactly(
trace ->
trace.hasSpansSatisfyingExactly(
parentSpan -> parentSpan.hasName("parent").hasKind(INTERNAL),
span ->
span.hasName(
unproxiedTesterSimpleClassName + ".testAsyncCompletableFuture")
.hasKind(INTERNAL)
.hasParent(trace.getSpan(0))
.hasAttributesSatisfyingExactly(
equalTo(CODE_NAMESPACE, unproxiedTesterClassName),
equalTo(CODE_FUNCTION, "testAsyncCompletableFuture"))));
}
@Test
@DisplayName("should end Span on completeException AND should record the exception")
void onCompleteExceptionally() {
CompletableFuture<String> future = new CompletableFuture<>();
// when
testing.runWithSpan("parent", () -> withSpanTester.testAsyncCompletableFuture(future));
// then
assertThat(testing.waitForTraces(1))
.hasTracesSatisfyingExactly(
trace ->
trace.hasSpansSatisfyingExactly(
span -> span.hasName("parent").hasKind(INTERNAL)));
// when
future.completeExceptionally(new Exception("Test @WithSpan With completeExceptionally"));
// then
List<List<SpanData>> traces = testing.waitForTraces(1);
assertThat(traces)
.hasTracesSatisfyingExactly(
trace ->
trace.hasSpansSatisfyingExactly(
parentSpan -> parentSpan.hasName("parent").hasKind(INTERNAL),
span ->
span.hasName(
unproxiedTesterSimpleClassName + ".testAsyncCompletableFuture")
.hasKind(INTERNAL)
.hasStatus(StatusData.error())
.hasParent(trace.getSpan(0))
.hasAttributesSatisfyingExactly(
equalTo(CODE_NAMESPACE, unproxiedTesterClassName),
equalTo(CODE_FUNCTION, "testAsyncCompletableFuture"))));
}
@Test
@DisplayName("should end the Span when already complete")
void onCompletedFuture() {
CompletableFuture<String> future = CompletableFuture.completedFuture("Done");
// when
testing.runWithSpan("parent", () -> withSpanTester.testAsyncCompletableFuture(future));
// then
List<List<SpanData>> traces = testing.waitForTraces(1);
assertThat(traces)
.hasTracesSatisfyingExactly(
trace ->
trace.hasSpansSatisfyingExactly(
parentSpan -> parentSpan.hasName("parent").hasKind(INTERNAL),
span ->
span.hasName(
unproxiedTesterSimpleClassName + ".testAsyncCompletableFuture")
.hasKind(INTERNAL)
.hasParent(trace.getSpan(0))
.hasAttributesSatisfyingExactly(
equalTo(CODE_NAMESPACE, unproxiedTesterClassName),
equalTo(CODE_FUNCTION, "testAsyncCompletableFuture"))));
}
@Test
@DisplayName("should end the Span when already failed")
void onFailedFuture() {
CompletableFuture<String> future = new CompletableFuture<>();
future.completeExceptionally(new Exception("Test @WithSpan With completeExceptionally"));
// when
testing.runWithSpan("parent", () -> withSpanTester.testAsyncCompletableFuture(future));
// then
List<List<SpanData>> traces = testing.waitForTraces(1);
assertThat(traces)
.hasTracesSatisfyingExactly(
trace ->
trace.hasSpansSatisfyingExactly(
parentSpan -> parentSpan.hasName("parent").hasKind(INTERNAL),
span ->
span.hasName(
unproxiedTesterSimpleClassName + ".testAsyncCompletableFuture")
.hasKind(INTERNAL)
.hasStatus(StatusData.error())
.hasParent(trace.getSpan(0))
.hasAttributesSatisfyingExactly(
equalTo(CODE_NAMESPACE, unproxiedTesterClassName),
equalTo(CODE_FUNCTION, "testAsyncCompletableFuture"))));
}
@Test
@DisplayName("should end Span on incompatible return value")
void onIncompatibleReturnValue() {
// when
testing.runWithSpan("parent", () -> withSpanTester.testAsyncCompletableFuture(null));
// then
List<List<SpanData>> traces = testing.waitForTraces(1);
assertThat(traces)
.hasTracesSatisfyingExactly(
trace ->
trace.hasSpansSatisfyingExactly(
parentSpan -> parentSpan.hasName("parent").hasKind(INTERNAL),
span ->
span.hasName(
unproxiedTesterSimpleClassName + ".testAsyncCompletableFuture")
.hasKind(INTERNAL)
.hasParent(trace.getSpan(0))
.hasAttributesSatisfyingExactly(
equalTo(CODE_NAMESPACE, unproxiedTesterClassName),
equalTo(CODE_FUNCTION, "testAsyncCompletableFuture"))));
}
}
}

View File

@ -24,30 +24,18 @@ class InstrumentationAnnotationsAutoConfigurationTest {
void instrumentationEnabled() {
contextRunner
.withPropertyValues("otel.instrumentation.annotations.enabled=true")
.run(
context ->
assertThat(context)
.hasBean("otelInstrumentationWithSpanAspect")
.hasBean("otelSdkExtensionWithSpanAspect"));
.run(context -> assertThat(context).hasBean("otelInstrumentationWithSpanAspect"));
}
@Test
void instrumentationDisabled() {
contextRunner
.withPropertyValues("otel.instrumentation.annotations.enabled=false")
.run(
context ->
assertThat(context)
.doesNotHaveBean("otelInstrumentationWithSpanAspect")
.doesNotHaveBean("otelSdkExtensionWithSpanAspect"));
.run(context -> assertThat(context).doesNotHaveBean("otelInstrumentationWithSpanAspect"));
}
@Test
void defaultConfiguration() {
contextRunner.run(
context ->
assertThat(context)
.hasBean("otelInstrumentationWithSpanAspect")
.hasBean("otelSdkExtensionWithSpanAspect"));
contextRunner.run(context -> assertThat(context).hasBean("otelInstrumentationWithSpanAspect"));
}
}

View File

@ -5,66 +5,220 @@
package io.opentelemetry.instrumentation.spring.autoconfigure.instrumentation.annotations;
import static io.opentelemetry.api.common.AttributeKey.stringKey;
import static io.opentelemetry.api.trace.SpanKind.CLIENT;
import static io.opentelemetry.api.trace.SpanKind.INTERNAL;
import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo;
import static io.opentelemetry.sdk.testing.assertj.TracesAssert.assertThat;
import static io.opentelemetry.semconv.SemanticAttributes.CODE_FUNCTION;
import static io.opentelemetry.semconv.SemanticAttributes.CODE_NAMESPACE;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import io.opentelemetry.api.OpenTelemetry;
import io.opentelemetry.instrumentation.annotations.SpanAttribute;
import io.opentelemetry.instrumentation.annotations.WithSpan;
import io.opentelemetry.instrumentation.testing.junit.LibraryInstrumentationExtension;
import io.opentelemetry.sdk.trace.data.SpanData;
import io.opentelemetry.sdk.trace.data.StatusData;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
import org.springframework.aop.aspectj.annotation.AspectJProxyFactory;
import org.springframework.core.ParameterNameDiscoverer;
class InstrumentationWithSpanAspectTest extends AbstractWithSpanAspectTest {
class InstrumentationWithSpanAspectTest {
@Override
WithSpanTester newWithSpanTester() {
return new InstrumentationWithSpanTester();
}
@RegisterExtension
static final LibraryInstrumentationExtension testing = LibraryInstrumentationExtension.create();
private InstrumentationWithSpanTester withSpanTester;
private String unproxiedTesterSimpleClassName;
private String unproxiedTesterClassName;
@Override
WithSpanAspect newWithSpanAspect(
OpenTelemetry openTelemetry, ParameterNameDiscoverer parameterNameDiscoverer) {
return new InstrumentationWithSpanAspect(openTelemetry, parameterNameDiscoverer);
}
static class InstrumentationWithSpanTester implements WithSpanTester {
@Override
@BeforeEach
void setup() {
InstrumentationWithSpanTester unproxiedTester = new InstrumentationWithSpanTester();
unproxiedTesterSimpleClassName = unproxiedTester.getClass().getSimpleName();
unproxiedTesterClassName = unproxiedTester.getClass().getName();
AspectJProxyFactory factory = new AspectJProxyFactory();
factory.setTarget(unproxiedTester);
ParameterNameDiscoverer parameterNameDiscoverer =
new ParameterNameDiscoverer() {
@Override
public String[] getParameterNames(Method method) {
return new String[] {"discoveredName", null, "parameter", "nullAttribute", "notTraced"};
}
@Override
public String[] getParameterNames(Constructor<?> constructor) {
return null;
}
};
WithSpanAspect aspect = newWithSpanAspect(testing.getOpenTelemetry(), parameterNameDiscoverer);
factory.addAspect(aspect);
withSpanTester = factory.getProxy();
}
@Test
@DisplayName("when method is annotated with @WithSpan should wrap method execution in a Span")
void withSpanWithDefaults() {
// when
testing.runWithSpan("parent", withSpanTester::testWithSpan);
// then
List<List<SpanData>> traces = testing.waitForTraces(1);
assertThat(traces)
.hasTracesSatisfyingExactly(
trace ->
trace.hasSpansSatisfyingExactly(
parentSpan -> parentSpan.hasName("parent").hasKind(INTERNAL),
span ->
span.hasName(unproxiedTesterSimpleClassName + ".testWithSpan")
.hasKind(INTERNAL)
.hasParent(trace.getSpan(0))
.hasAttributesSatisfyingExactly(
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() {
// when
testing.runWithSpan("parent", () -> withSpanTester.testWithSpanWithValue());
// then
List<List<SpanData>> traces = testing.waitForTraces(1);
assertThat(traces)
.hasTracesSatisfyingExactly(
trace ->
trace.hasSpansSatisfyingExactly(
parentSpan -> parentSpan.hasName("parent").hasKind(INTERNAL),
span ->
span.hasName("greatestSpanEver")
.hasKind(INTERNAL)
.hasParent(trace.getSpan(0))
.hasAttributesSatisfyingExactly(
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() {
assertThatThrownBy(() -> withSpanTester.testWithSpanWithException())
.isInstanceOf(Exception.class);
List<List<SpanData>> traces = testing.waitForTraces(1);
assertThat(traces)
.hasTracesSatisfyingExactly(
trace ->
trace.hasSpansSatisfyingExactly(
span ->
span.hasName(unproxiedTesterSimpleClassName + ".testWithSpanWithException")
.hasKind(INTERNAL)
.hasStatus(StatusData.error())
.hasAttributesSatisfyingExactly(
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() {
// when
testing.runWithSpan("parent", () -> withSpanTester.testWithClientSpan());
// then
List<List<SpanData>> traces = testing.waitForTraces(1);
assertThat(traces)
.hasTracesSatisfyingExactly(
trace ->
trace.hasSpansSatisfyingExactly(
parentSpan -> parentSpan.hasName("parent").hasKind(INTERNAL),
span ->
span.hasName(unproxiedTesterSimpleClassName + ".testWithClientSpan")
.hasKind(CLIENT)
.hasParent(trace.getSpan(0))
.hasAttributesSatisfyingExactly(
equalTo(CODE_NAMESPACE, unproxiedTesterClassName),
equalTo(CODE_FUNCTION, "testWithClientSpan"))));
}
@Test
void withSpanAttributes() {
// when
testing.runWithSpan(
"parent", () -> withSpanTester.withSpanAttributes("foo", "bar", "baz", null, "fizz"));
// then
List<List<SpanData>> traces = testing.waitForTraces(1);
assertThat(traces)
.hasTracesSatisfyingExactly(
trace ->
trace.hasSpansSatisfyingExactly(
parentSpan -> parentSpan.hasName("parent").hasKind(INTERNAL),
span ->
span.hasName(unproxiedTesterSimpleClassName + ".withSpanAttributes")
.hasKind(INTERNAL)
.hasParent(trace.getSpan(0))
.hasAttributesSatisfyingExactly(
equalTo(CODE_NAMESPACE, unproxiedTesterClassName),
equalTo(CODE_FUNCTION, "withSpanAttributes"),
equalTo(stringKey("discoveredName"), "foo"),
equalTo(stringKey("implicitName"), "bar"),
equalTo(stringKey("explicitName"), "baz"))));
}
static class InstrumentationWithSpanTester {
@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<String> testAsyncCompletionStage(CompletionStage<String> stage) {
return stage;
}
@Override
@WithSpan
public CompletableFuture<String> testAsyncCompletableFuture(CompletableFuture<String> stage) {
return stage;
}
@Override
@WithSpan
public String withSpanAttributes(
@SpanAttribute String discoveredName,
@ -76,4 +230,255 @@ class InstrumentationWithSpanAspectTest extends AbstractWithSpanAspectTest {
return "hello!";
}
}
@Nested
@DisplayName("with a method annotated with @WithSpan returns CompletionStage")
class WithCompletionStage {
@Test
@DisplayName("should end Span on complete")
void onComplete() {
CompletableFuture<String> future = new CompletableFuture<>();
// when
testing.runWithSpan("parent", () -> withSpanTester.testAsyncCompletionStage(future));
// then
assertThat(testing.waitForTraces(1))
.hasTracesSatisfyingExactly(
trace ->
trace
.hasSize(1)
.hasSpansSatisfyingExactly(span -> span.hasName("parent").hasKind(INTERNAL)));
// when
future.complete("DONE");
// then
List<List<SpanData>> traces = testing.waitForTraces(1);
assertThat(traces)
.hasTracesSatisfyingExactly(
trace ->
trace.hasSpansSatisfyingExactly(
parentSpan -> parentSpan.hasName("parent").hasKind(INTERNAL),
span ->
span.hasName(unproxiedTesterSimpleClassName + ".testAsyncCompletionStage")
.hasKind(INTERNAL)
.hasParent(trace.getSpan(0))
.hasAttributesSatisfyingExactly(
equalTo(CODE_NAMESPACE, unproxiedTesterClassName),
equalTo(CODE_FUNCTION, "testAsyncCompletionStage"))));
}
@Test
@DisplayName("should end Span on completeException AND should record the exception")
void onCompleteExceptionally() {
CompletableFuture<String> future = new CompletableFuture<>();
// when
testing.runWithSpan("parent", () -> withSpanTester.testAsyncCompletionStage(future));
// then
assertThat(testing.waitForTraces(1))
.hasTracesSatisfyingExactly(
trace ->
trace
.hasSize(1)
.hasSpansSatisfyingExactly(span -> span.hasName("parent").hasKind(INTERNAL)));
// when
future.completeExceptionally(new Exception("Test @WithSpan With completeExceptionally"));
// then
List<List<SpanData>> traces = testing.waitForTraces(1);
assertThat(traces)
.hasTracesSatisfyingExactly(
trace ->
trace.hasSpansSatisfyingExactly(
parentSpan -> parentSpan.hasName("parent").hasKind(INTERNAL),
span ->
span.hasName(unproxiedTesterSimpleClassName + ".testAsyncCompletionStage")
.hasKind(INTERNAL)
.hasStatus(StatusData.error())
.hasParent(trace.getSpan(0))
.hasAttributesSatisfyingExactly(
equalTo(CODE_NAMESPACE, unproxiedTesterClassName),
equalTo(CODE_FUNCTION, "testAsyncCompletionStage"))));
}
@Test
@DisplayName("should end Span on incompatible return value")
void onIncompatibleReturnValue() {
// when
testing.runWithSpan("parent", () -> withSpanTester.testAsyncCompletionStage(null));
// then
List<List<SpanData>> traces = testing.waitForTraces(1);
assertThat(traces)
.hasTracesSatisfyingExactly(
trace ->
trace.hasSpansSatisfyingExactly(
parentSpan -> parentSpan.hasName("parent").hasKind(INTERNAL),
span ->
span.hasName(unproxiedTesterSimpleClassName + ".testAsyncCompletionStage")
.hasKind(INTERNAL)
.hasParent(trace.getSpan(0))
.hasAttributesSatisfyingExactly(
equalTo(CODE_NAMESPACE, unproxiedTesterClassName),
equalTo(CODE_FUNCTION, "testAsyncCompletionStage"))));
}
}
@Nested
@DisplayName("with a method annotated with @WithSpan returns CompletableFuture")
class WithCompletableFuture {
@Test
@DisplayName("should end Span on complete")
void onComplete() {
CompletableFuture<String> future = new CompletableFuture<>();
// when
testing.runWithSpan("parent", () -> withSpanTester.testAsyncCompletableFuture(future));
// then
assertThat(testing.waitForTraces(1))
.hasTracesSatisfyingExactly(
trace ->
trace
.hasSize(1)
.hasSpansSatisfyingExactly(span -> span.hasName("parent").hasKind(INTERNAL)));
// when
future.complete("DONE");
// then
List<List<SpanData>> traces = testing.waitForTraces(1);
assertThat(traces)
.hasTracesSatisfyingExactly(
trace ->
trace.hasSpansSatisfyingExactly(
parentSpan -> parentSpan.hasName("parent").hasKind(INTERNAL),
span ->
span.hasName(
unproxiedTesterSimpleClassName + ".testAsyncCompletableFuture")
.hasKind(INTERNAL)
.hasParent(trace.getSpan(0))
.hasAttributesSatisfyingExactly(
equalTo(CODE_NAMESPACE, unproxiedTesterClassName),
equalTo(CODE_FUNCTION, "testAsyncCompletableFuture"))));
}
@Test
@DisplayName("should end Span on completeException AND should record the exception")
void onCompleteExceptionally() {
CompletableFuture<String> future = new CompletableFuture<>();
// when
testing.runWithSpan("parent", () -> withSpanTester.testAsyncCompletableFuture(future));
// then
assertThat(testing.waitForTraces(1))
.hasTracesSatisfyingExactly(
trace ->
trace
.hasSize(1)
.hasSpansSatisfyingExactly(span -> span.hasName("parent").hasKind(INTERNAL)));
// when
future.completeExceptionally(new Exception("Test @WithSpan With completeExceptionally"));
// then
List<List<SpanData>> traces = testing.waitForTraces(1);
assertThat(traces)
.hasTracesSatisfyingExactly(
trace ->
trace.hasSpansSatisfyingExactly(
parentSpan -> parentSpan.hasName("parent").hasKind(INTERNAL),
span ->
span.hasName(
unproxiedTesterSimpleClassName + ".testAsyncCompletableFuture")
.hasKind(INTERNAL)
.hasStatus(StatusData.error())
.hasParent(trace.getSpan(0))
.hasAttributesSatisfyingExactly(
equalTo(CODE_NAMESPACE, unproxiedTesterClassName),
equalTo(CODE_FUNCTION, "testAsyncCompletableFuture"))));
}
@Test
@DisplayName("should end the Span when already complete")
void onCompletedFuture() {
CompletableFuture<String> future = CompletableFuture.completedFuture("Done");
// when
testing.runWithSpan("parent", () -> withSpanTester.testAsyncCompletableFuture(future));
// then
List<List<SpanData>> traces = testing.waitForTraces(1);
assertThat(traces)
.hasTracesSatisfyingExactly(
trace ->
trace.hasSpansSatisfyingExactly(
parentSpan -> parentSpan.hasName("parent").hasKind(INTERNAL),
span ->
span.hasName(
unproxiedTesterSimpleClassName + ".testAsyncCompletableFuture")
.hasKind(INTERNAL)
.hasParent(trace.getSpan(0))
.hasAttributesSatisfyingExactly(
equalTo(CODE_NAMESPACE, unproxiedTesterClassName),
equalTo(CODE_FUNCTION, "testAsyncCompletableFuture"))));
}
@Test
@DisplayName("should end the Span when already failed")
void onFailedFuture() {
CompletableFuture<String> future = new CompletableFuture<>();
future.completeExceptionally(new Exception("Test @WithSpan With completeExceptionally"));
// when
testing.runWithSpan("parent", () -> withSpanTester.testAsyncCompletableFuture(future));
// then
List<List<SpanData>> traces = testing.waitForTraces(1);
assertThat(traces)
.hasTracesSatisfyingExactly(
trace ->
trace.hasSpansSatisfyingExactly(
parentSpan -> parentSpan.hasName("parent").hasKind(INTERNAL),
span ->
span.hasName(
unproxiedTesterSimpleClassName + ".testAsyncCompletableFuture")
.hasKind(INTERNAL)
.hasStatus(StatusData.error())
.hasParent(trace.getSpan(0))
.hasAttributesSatisfyingExactly(
equalTo(CODE_NAMESPACE, unproxiedTesterClassName),
equalTo(CODE_FUNCTION, "testAsyncCompletableFuture"))));
}
@Test
@DisplayName("should end Span on incompatible return value")
void onIncompatibleReturnValue() {
// when
testing.runWithSpan("parent", () -> withSpanTester.testAsyncCompletableFuture(null));
// then
List<List<SpanData>> traces = testing.waitForTraces(1);
assertThat(traces)
.hasTracesSatisfyingExactly(
trace ->
trace.hasSpansSatisfyingExactly(
parentSpan -> parentSpan.hasName("parent").hasKind(INTERNAL),
span ->
span.hasName(
unproxiedTesterSimpleClassName + ".testAsyncCompletableFuture")
.hasKind(INTERNAL)
.hasParent(trace.getSpan(0))
.hasAttributesSatisfyingExactly(
equalTo(CODE_NAMESPACE, unproxiedTesterClassName),
equalTo(CODE_FUNCTION, "testAsyncCompletableFuture"))));
}
}
}

View File

@ -1,78 +0,0 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.instrumentation.spring.autoconfigure.instrumentation.annotations;
import static io.opentelemetry.api.trace.SpanKind.CLIENT;
import io.opentelemetry.api.OpenTelemetry;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage;
import org.springframework.core.ParameterNameDiscoverer;
@SuppressWarnings("deprecation") // instrumenting deprecated class for backwards compatibility
class SdkExtensionWithSpanAspectTest extends AbstractWithSpanAspectTest {
@Override
WithSpanTester newWithSpanTester() {
return new SdkExtensionWithSpanTester();
}
@Override
WithSpanAspect newWithSpanAspect(
OpenTelemetry openTelemetry, ParameterNameDiscoverer parameterNameDiscoverer) {
return new SdkExtensionWithSpanAspect(openTelemetry, parameterNameDiscoverer);
}
static class SdkExtensionWithSpanTester implements WithSpanTester {
@Override
@io.opentelemetry.extension.annotations.WithSpan
public String testWithSpan() {
return "Span with name testWithSpan was created";
}
@Override
@io.opentelemetry.extension.annotations.WithSpan("greatestSpanEver")
public String testWithSpanWithValue() {
return "Span with name greatestSpanEver was created";
}
@Override
@io.opentelemetry.extension.annotations.WithSpan
public String testWithSpanWithException() throws Exception {
throw new Exception("Test @WithSpan With Exception");
}
@Override
@io.opentelemetry.extension.annotations.WithSpan(kind = CLIENT)
public String testWithClientSpan() {
return "Span with name testWithClientSpan and SpanKind.CLIENT was created";
}
@Override
@io.opentelemetry.extension.annotations.WithSpan
public CompletionStage<String> testAsyncCompletionStage(CompletionStage<String> stage) {
return stage;
}
@Override
@io.opentelemetry.extension.annotations.WithSpan
public CompletableFuture<String> testAsyncCompletableFuture(CompletableFuture<String> stage) {
return stage;
}
@Override
@io.opentelemetry.extension.annotations.WithSpan
public String withSpanAttributes(
@io.opentelemetry.extension.annotations.SpanAttribute String discoveredName,
@io.opentelemetry.extension.annotations.SpanAttribute String implicitName,
@io.opentelemetry.extension.annotations.SpanAttribute("explicitName") String parameter,
@io.opentelemetry.extension.annotations.SpanAttribute("nullAttribute") String nullAttribute,
String notTraced) {
return "hello!";
}
}
}