From 27aba3b5f0cec7a88d7098e5a54f72a52d910051 Mon Sep 17 00:00:00 2001 From: kyy1996 Date: Sun, 13 Oct 2024 17:39:40 +0800 Subject: [PATCH] Add auto configuration for spring scheduling instrumentation using aop --- .../InternalThreadAttributesExtractor.java | 37 +++ ...SpringSchedulingInstrumentationAspect.java | 91 ++++++++ ...ulingInstrumentationAutoConfiguration.java | 34 +++ .../OpenTelemetryAnnotationsRuntimeHints.java | 4 + .../main/resources/META-INF/spring.factories | 3 +- ...ot.autoconfigure.AutoConfiguration.imports | 1 + .../SchedulingInstrumentationAspectTest.java | 210 ++++++++++++++++++ ...gInstrumentationAutoConfigurationTest.java | 46 ++++ 8 files changed, 425 insertions(+), 1 deletion(-) create mode 100644 instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/scheduling/InternalThreadAttributesExtractor.java create mode 100644 instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/scheduling/SpringSchedulingInstrumentationAspect.java create mode 100644 instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/scheduling/SpringSchedulingInstrumentationAutoConfiguration.java create mode 100644 instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/scheduling/SchedulingInstrumentationAspectTest.java create mode 100644 instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/scheduling/SchedulingInstrumentationAutoConfigurationTest.java diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/scheduling/InternalThreadAttributesExtractor.java b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/scheduling/InternalThreadAttributesExtractor.java new file mode 100644 index 000000000000..c98a7b31e443 --- /dev/null +++ b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/scheduling/InternalThreadAttributesExtractor.java @@ -0,0 +1,37 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.scheduling; + +import io.opentelemetry.api.common.AttributesBuilder; +import io.opentelemetry.context.Context; +import io.opentelemetry.instrumentation.api.instrumenter.AttributesExtractor; +import io.opentelemetry.semconv.incubating.ThreadIncubatingAttributes; +import javax.annotation.Nullable; +import org.springframework.lang.NonNull; + +/** Expose current thread to attributes. */ +class InternalThreadAttributesExtractor + implements AttributesExtractor { + @Override + public void onStart( + @NonNull AttributesBuilder attributes, + @NonNull Context parentContext, + @NonNull REQUEST classAndMethod) { + attributes + .put(ThreadIncubatingAttributes.THREAD_ID, Thread.currentThread().getId()) + .put(ThreadIncubatingAttributes.THREAD_NAME, Thread.currentThread().getName()); + } + + @Override + public void onEnd( + @NonNull AttributesBuilder attributes, + @NonNull Context context, + @NonNull REQUEST classAndMethod, + @Nullable RESPONSE o, + @Nullable Throwable error) { + // ignored + } +} diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/scheduling/SpringSchedulingInstrumentationAspect.java b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/scheduling/SpringSchedulingInstrumentationAspect.java new file mode 100644 index 000000000000..7b1e3745d87a --- /dev/null +++ b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/scheduling/SpringSchedulingInstrumentationAspect.java @@ -0,0 +1,91 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.scheduling; + +import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.api.common.AttributeKey; +import io.opentelemetry.context.Context; +import io.opentelemetry.context.Scope; +import io.opentelemetry.instrumentation.api.incubator.semconv.code.CodeAttributesExtractor; +import io.opentelemetry.instrumentation.api.incubator.semconv.code.CodeAttributesGetter; +import io.opentelemetry.instrumentation.api.incubator.semconv.code.CodeSpanNameExtractor; +import io.opentelemetry.instrumentation.api.incubator.semconv.util.ClassAndMethod; +import io.opentelemetry.instrumentation.api.instrumenter.AttributesExtractor; +import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; +import io.opentelemetry.instrumentation.api.instrumenter.InstrumenterBuilder; +import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.annotation.Around; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.annotation.Pointcut; +import org.aspectj.lang.reflect.MethodSignature; +import org.springframework.aop.framework.AopProxyUtils; + +/** + * Spring Scheduling instrumentation aop. + * + *

This aspect would intercept all methods annotated with {@link + * org.springframework.scheduling.annotation.Scheduled} and {@link + * org.springframework.scheduling.annotation.Schedules}. + * + *

Normally this would cover most of the Spring Scheduling use cases, but if you register jobs + * programmatically such as {@link + * org.springframework.scheduling.config.ScheduledTaskRegistrar#addCronTask}, this aspect would not + * cover them. You may use {@link io.opentelemetry.instrumentation.annotations.WithSpan} to trace + * these jobs manually. + */ +@Aspect +final class SpringSchedulingInstrumentationAspect { + public static final String INSTRUMENTATION_NAME = "io.opentelemetry.spring-scheduling-3.1"; + private final Instrumenter instrumenter; + + public SpringSchedulingInstrumentationAspect( + OpenTelemetry openTelemetry, ConfigProperties configProperties) { + CodeAttributesGetter codedAttributesGetter = + ClassAndMethod.codeAttributesGetter(); + InstrumenterBuilder builder = + Instrumenter.builder( + openTelemetry, + INSTRUMENTATION_NAME, + CodeSpanNameExtractor.create(codedAttributesGetter)) + .addAttributesExtractor(CodeAttributesExtractor.create(codedAttributesGetter)) + .addAttributesExtractor(new InternalThreadAttributesExtractor<>()); + if (configProperties.getBoolean( + "otel.instrumentation.spring-scheduling.experimental-span-attributes", false)) { + builder.addAttributesExtractor( + AttributesExtractor.constant(AttributeKey.stringKey("job.system"), "spring_scheduling")); + } + instrumenter = builder.buildInstrumenter(); + } + + @Pointcut( + "@annotation(org.springframework.scheduling.annotation.Scheduled)" + + "|| @annotation(org.springframework.scheduling.annotation.Schedules)") + public void pointcut() { + // ignored + } + + @Around("pointcut()") + public Object execution(ProceedingJoinPoint joinPoint) throws Throwable { + Context parent = Context.current(); + ClassAndMethod request = + ClassAndMethod.create( + AopProxyUtils.ultimateTargetClass(joinPoint.getTarget()), + ((MethodSignature) joinPoint.getSignature()).getMethod().getName()); + if (!instrumenter.shouldStart(parent, request)) { + return joinPoint.proceed(); + } + Context context = instrumenter.start(parent, request); + try (Scope ignored = context.makeCurrent()) { + Object object = joinPoint.proceed(); + instrumenter.end(context, request, object, null); + return object; + } catch (Throwable t) { + instrumenter.end(context, request, null, t); + throw t; + } + } +} diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/scheduling/SpringSchedulingInstrumentationAutoConfiguration.java b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/scheduling/SpringSchedulingInstrumentationAutoConfiguration.java new file mode 100644 index 000000000000..8135bead9c0b --- /dev/null +++ b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/scheduling/SpringSchedulingInstrumentationAutoConfiguration.java @@ -0,0 +1,34 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.scheduling; + +import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.instrumentation.spring.autoconfigure.internal.ConditionalOnEnabledInstrumentation; +import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; +import org.aspectj.lang.annotation.Aspect; +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.scheduling.annotation.Scheduled; + +/** + * Configures an aspect for tracing. + * + *

This class is internal and is hence not for public use. Its APIs are unstable and can change + * at any time. + */ +@ConditionalOnBean(OpenTelemetry.class) +@ConditionalOnEnabledInstrumentation(module = "spring-scheduling") +@ConditionalOnClass({Scheduled.class, Aspect.class}) +@Configuration +class SpringSchedulingInstrumentationAutoConfiguration { + @Bean + SpringSchedulingInstrumentationAspect springSchedulingInstrumentationAspect( + OpenTelemetry openTelemetry, ConfigProperties configProperties) { + return new SpringSchedulingInstrumentationAspect(openTelemetry, configProperties); + } +} diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/main/javaSpring3/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/annotations/OpenTelemetryAnnotationsRuntimeHints.java b/instrumentation/spring/spring-boot-autoconfigure/src/main/javaSpring3/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/annotations/OpenTelemetryAnnotationsRuntimeHints.java index 79a9fad3c069..7bb5e2aa24bf 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/main/javaSpring3/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/annotations/OpenTelemetryAnnotationsRuntimeHints.java +++ b/instrumentation/spring/spring-boot-autoconfigure/src/main/javaSpring3/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/annotations/OpenTelemetryAnnotationsRuntimeHints.java @@ -19,6 +19,10 @@ public void registerHints(RuntimeHints hints, ClassLoader classLoader) { .registerType( TypeReference.of( "io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.annotations.InstrumentationWithSpanAspect"), + hint -> hint.withMembers(MemberCategory.INVOKE_PUBLIC_METHODS)) + .registerType( + TypeReference.of( + "io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.scheduling.SpringSchedulingInstrumentationAspect"), hint -> hint.withMembers(MemberCategory.INVOKE_PUBLIC_METHODS)); } } diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories b/instrumentation/spring/spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories index b389d6a1e381..049b3b068f1d 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories +++ b/instrumentation/spring/spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories @@ -9,7 +9,8 @@ io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.m io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.r2dbc.R2dbcInstrumentationAutoConfiguration,\ io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.web.SpringWebInstrumentationAutoConfiguration,\ io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.webflux.SpringWebfluxInstrumentationAutoConfiguration,\ -io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.webmvc.SpringWebMvc5InstrumentationAutoConfiguration +io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.webmvc.SpringWebMvc5InstrumentationAutoConfiguration,\ +io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.scheduling.SpringSchedulingInstrumentationAutoConfiguration org.springframework.context.ApplicationListener=\ io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.logging.LogbackAppenderApplicationListener diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/instrumentation/spring/spring-boot-autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports index 227a59072e48..73b6f4a6f840 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports +++ b/instrumentation/spring/spring-boot-autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -10,3 +10,4 @@ io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.w io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.webflux.SpringWebfluxInstrumentationAutoConfiguration io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.web.RestClientInstrumentationAutoConfiguration io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.webmvc.SpringWebMvc6InstrumentationAutoConfiguration +io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.scheduling.SpringSchedulingInstrumentationAutoConfiguration diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/scheduling/SchedulingInstrumentationAspectTest.java b/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/scheduling/SchedulingInstrumentationAspectTest.java new file mode 100644 index 000000000000..1943d70a3443 --- /dev/null +++ b/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/scheduling/SchedulingInstrumentationAspectTest.java @@ -0,0 +1,210 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.scheduling; + +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.incubating.CodeIncubatingAttributes.CODE_FUNCTION; +import static io.opentelemetry.semconv.incubating.CodeIncubatingAttributes.CODE_NAMESPACE; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.api.trace.Span; +import io.opentelemetry.api.trace.Tracer; +import io.opentelemetry.context.Context; +import io.opentelemetry.context.Scope; +import io.opentelemetry.instrumentation.testing.junit.LibraryInstrumentationExtension; +import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; +import io.opentelemetry.sdk.autoconfigure.spi.internal.DefaultConfigProperties; +import io.opentelemetry.sdk.trace.data.SpanData; +import io.opentelemetry.sdk.trace.data.StatusData; +import java.util.Collections; +import java.util.List; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; +import org.springframework.aop.aspectj.annotation.AspectJProxyFactory; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.scheduling.annotation.Schedules; + +class SchedulingInstrumentationAspectTest { + + @RegisterExtension + static final LibraryInstrumentationExtension testing = LibraryInstrumentationExtension.create(); + + private InstrumentationSchedulingTester schedulingTester; + private String unproxiedTesterSimpleClassName; + private String unproxiedTesterClassName; + + SpringSchedulingInstrumentationAspect newAspect( + OpenTelemetry openTelemetry, ConfigProperties configProperties) { + return new SpringSchedulingInstrumentationAspect(openTelemetry, configProperties); + } + + @BeforeEach + void setup() { + InstrumentationSchedulingTester unproxiedTester = + new InstrumentationSchedulingTester(testing.getOpenTelemetry()); + unproxiedTesterSimpleClassName = unproxiedTester.getClass().getSimpleName(); + unproxiedTesterClassName = unproxiedTester.getClass().getName(); + + AspectJProxyFactory factory = new AspectJProxyFactory(); + factory.setTarget(unproxiedTester); + + SpringSchedulingInstrumentationAspect aspect = + newAspect( + testing.getOpenTelemetry(), + DefaultConfigProperties.createFromMap(Collections.emptyMap())); + factory.addAspect(aspect); + + schedulingTester = factory.getProxy(); + } + + @Test + @DisplayName("when method is annotated with @Scheduled should start a new span.") + void scheduled() { + // when + schedulingTester.testScheduled(); + + // then + List> traces = testing.waitForTraces(1); + assertThat(traces) + .hasTracesSatisfyingExactly( + trace -> + trace.hasSpansSatisfyingExactly( + span -> + span.hasName(unproxiedTesterSimpleClassName + ".testScheduled") + .hasKind(INTERNAL) + .hasAttributesSatisfying( + equalTo(CODE_NAMESPACE, unproxiedTesterClassName), + equalTo(CODE_FUNCTION, "testScheduled")))); + } + + @Test + @DisplayName("when method is annotated with multiple @Scheduled should start a new span.") + void multiScheduled() { + // when + schedulingTester.testMultiScheduled(); + + // then + List> traces = testing.waitForTraces(1); + assertThat(traces) + .hasTracesSatisfyingExactly( + trace -> + trace.hasSpansSatisfyingExactly( + span -> + span.hasName(unproxiedTesterSimpleClassName + ".testMultiScheduled") + .hasKind(INTERNAL) + .hasAttributesSatisfying( + equalTo(CODE_NAMESPACE, unproxiedTesterClassName), + equalTo(CODE_FUNCTION, "testMultiScheduled")))); + } + + @Test + @DisplayName("when method is annotated with @Schedules should start a new span.") + void schedules() { + // when + schedulingTester.testSchedules(); + + // then + List> traces = testing.waitForTraces(1); + assertThat(traces) + .hasTracesSatisfyingExactly( + trace -> + trace.hasSpansSatisfyingExactly( + span -> + span.hasName(unproxiedTesterSimpleClassName + ".testSchedules") + .hasKind(INTERNAL) + .hasAttributesSatisfying( + equalTo(CODE_NAMESPACE, unproxiedTesterClassName), + equalTo(CODE_FUNCTION, "testSchedules")))); + } + + @Test + @DisplayName( + "when method is annotated with @Scheduled and it starts nested span, spans should be nested.") + void nestedSpanInScheduled() { + // when + schedulingTester.testNestedSpan(); + + // then + List> traces = testing.waitForTraces(1); + assertThat(traces) + .hasTracesSatisfyingExactly( + trace -> + trace.hasSpansSatisfyingExactly( + span -> + span.hasName(unproxiedTesterSimpleClassName + ".testNestedSpan") + .hasKind(INTERNAL) + .hasAttributesSatisfying( + equalTo(CODE_NAMESPACE, unproxiedTesterClassName), + equalTo(CODE_FUNCTION, "testNestedSpan")), + nestedSpan -> + nestedSpan.hasParent(trace.getSpan(0)).hasKind(INTERNAL).hasName("test"))); + } + + @Test + @DisplayName( + "when method is annotated with @WithSpan AND an exception is thrown span should record the exception") + void withSpanError() { + assertThatThrownBy(() -> schedulingTester.testScheduledWithException()) + .isInstanceOf(Exception.class); + + List> traces = testing.waitForTraces(1); + assertThat(traces) + .hasTracesSatisfyingExactly( + trace -> + trace.hasSpansSatisfyingExactly( + span -> + span.hasName(unproxiedTesterSimpleClassName + ".testScheduledWithException") + .hasKind(INTERNAL) + .hasStatus(StatusData.error()) + .hasAttributesSatisfying( + equalTo(CODE_NAMESPACE, unproxiedTesterClassName), + equalTo(CODE_FUNCTION, "testScheduledWithException")))); + } + + static class InstrumentationSchedulingTester { + private final OpenTelemetry openTelemetry; + + InstrumentationSchedulingTester(OpenTelemetry openTelemetry) { + this.openTelemetry = openTelemetry; + } + + @Scheduled(fixedRate = 1L) + public void testScheduled() { + // no-op + } + + @Scheduled(fixedRate = 1L) + @Scheduled(fixedRate = 2L) + public void testMultiScheduled() { + // no-op + } + + @Schedules({@Scheduled(fixedRate = 1L), @Scheduled(fixedRate = 2L)}) + public void testSchedules() { + // no-op + } + + @Scheduled(fixedRate = 1L) + public void testNestedSpan() { + Context current = Context.current(); + Tracer tracer = openTelemetry.getTracer("test"); + try (Scope ignored = current.makeCurrent()) { + Span span = tracer.spanBuilder("test").startSpan(); + span.end(); + } + } + + @Scheduled(fixedRate = 1L) + public void testScheduledWithException() { + throw new IllegalStateException("something went wrong"); + } + } +} diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/scheduling/SchedulingInstrumentationAutoConfigurationTest.java b/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/scheduling/SchedulingInstrumentationAutoConfigurationTest.java new file mode 100644 index 000000000000..37d91b411d5a --- /dev/null +++ b/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/scheduling/SchedulingInstrumentationAutoConfigurationTest.java @@ -0,0 +1,46 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.scheduling; + +import static org.assertj.core.api.Assertions.assertThat; + +import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; +import io.opentelemetry.sdk.autoconfigure.spi.internal.DefaultConfigProperties; +import java.util.Collections; +import org.junit.jupiter.api.Test; +import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; + +class SchedulingInstrumentationAutoConfigurationTest { + private final ApplicationContextRunner runner = + new ApplicationContextRunner() + .withBean(OpenTelemetry.class, OpenTelemetry::noop) + .withBean( + ConfigProperties.class, + () -> DefaultConfigProperties.createFromMap(Collections.emptyMap())) + .withConfiguration( + AutoConfigurations.of(SpringSchedulingInstrumentationAutoConfiguration.class)); + + @Test + void instrumentationEnabled() { + runner + .withPropertyValues("otel.instrumentation.spring-scheduling.enabled=true") + .run( + context -> + assertThat(context.containsBean("springSchedulingInstrumentationAspect")).isTrue()); + } + + @Test + void instrumentationDisabled() { + runner + .withPropertyValues("otel.instrumentation.spring-scheduling.enabled=false") + .run( + context -> + assertThat(context.containsBean("springSchedulingInstrumentationAspect")) + .isFalse()); + } +}