From 5b1236dc4ebfde40159cb26ef43d7a1100056e78 Mon Sep 17 00:00:00 2001 From: Jay DeLuca Date: Tue, 25 Nov 2025 16:16:49 -0500 Subject: [PATCH 01/40] autoconfigure and smoke --- .../build.gradle.kts | 52 +++ ...KafkaInstrumentationAutoConfiguration.java | 2 + ...mentationSpringBoot4AutoConfiguration.java | 70 ++++ ...ot.autoconfigure.AutoConfiguration.imports | 1 + ...Instrumentation7AutoConfigurationTest.java | 52 +++ settings.gradle.kts | 2 + .../spring-boot-4/build.gradle.kts | 41 +++ ...OtelSpringStarterSmokeTestApplication.java | 19 + .../src/main/resources/application.properties | 1 + .../AbstractOtelSpringStarterSmokeTest.java | 333 ++++++++++++++++++ .../smoketest/OtelSpringStarterSmokeTest.java | 64 ++++ 11 files changed, 637 insertions(+) create mode 100644 instrumentation/spring/spring-boot-autoconfigure/src/main/javaSpring4/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/kafka/KafkaInstrumentationSpringBoot4AutoConfiguration.java create mode 100644 instrumentation/spring/spring-boot-autoconfigure/src/testSpring4/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/webmvc/SpringWebMvcInstrumentation7AutoConfigurationTest.java create mode 100644 smoke-tests-otel-starter/spring-boot-4/build.gradle.kts create mode 100644 smoke-tests-otel-starter/spring-boot-4/src/main/java/io/opentelemetry/spring/smoketest/OtelSpringStarterSmokeTestApplication.java create mode 100644 smoke-tests-otel-starter/spring-boot-4/src/main/resources/application.properties create mode 100644 smoke-tests-otel-starter/spring-boot-4/src/test/java/io/opentelemetry/spring/smoketest/AbstractOtelSpringStarterSmokeTest.java create mode 100644 smoke-tests-otel-starter/spring-boot-4/src/test/java/io/opentelemetry/spring/smoketest/OtelSpringStarterSmokeTest.java diff --git a/instrumentation/spring/spring-boot-autoconfigure/build.gradle.kts b/instrumentation/spring/spring-boot-autoconfigure/build.gradle.kts index 11999b5c79d4..48fdedd04d93 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/build.gradle.kts +++ b/instrumentation/spring/spring-boot-autoconfigure/build.gradle.kts @@ -23,12 +23,20 @@ sourceSets { setSrcDirs(listOf("src/main/javaSpring3")) } } + create("javaSpring4") { + java { + setSrcDirs(listOf("src/main/javaSpring4")) + } + } } configurations { named("javaSpring3CompileOnly") { extendsFrom(configurations["compileOnly"]) } + named("javaSpring4CompileOnly") { + extendsFrom(configurations["compileOnly"]) + } } dependencies { @@ -110,6 +118,18 @@ dependencies { add("javaSpring3CompileOnly", project(":instrumentation:spring:spring-web:spring-web-3.1:library")) add("javaSpring3CompileOnly", project(":instrumentation:spring:spring-webmvc:spring-webmvc-6.0:library")) + // Spring Boot 4 + add("javaSpring4CompileOnly", files(sourceSets.main.get().output.classesDirs)) + add("javaSpring4CompileOnly", "org.springframework.boot:spring-boot-starter-web:4.0.0") + add("javaSpring4CompileOnly", "org.springframework.boot:spring-boot-starter-data-jdbc:4.0.0") + add("javaSpring4CompileOnly", "org.springframework.boot:spring-boot-starter-kafka:4.0.0") + add("javaSpring4CompileOnly", "io.opentelemetry:opentelemetry-sdk-extension-autoconfigure") + add("javaSpring4CompileOnly", "jakarta.servlet:jakarta.servlet-api:6.1.0") + add("javaSpring4CompileOnly", project(":instrumentation:spring:spring-web:spring-web-3.1:library")) + add("javaSpring4CompileOnly", project(":instrumentation:spring:spring-webmvc:spring-webmvc-6.0:library")) + add("javaSpring4CompileOnly", project(":instrumentation:kafka:kafka-clients:kafka-clients-2.6:library")) + add("javaSpring4CompileOnly", project(":instrumentation:spring:spring-kafka-2.7:library")) + // tests don't work with spring boot 4 yet latestDepTestLibrary("org.springframework.boot:spring-boot-starter-test:3.+") // documented limitation latestDepTestLibrary("org.springframework.boot:spring-boot-starter-actuator:3.+") // documented limitation @@ -186,6 +206,20 @@ testing { } } + val testSpring4 by registering(JvmTestSuite::class) { + dependencies { + implementation(project()) + implementation("org.springframework.boot:spring-boot-starter-web:4.0.0") + implementation("io.opentelemetry:opentelemetry-sdk-extension-autoconfigure") + implementation(project(":instrumentation:spring:spring-web:spring-web-3.1:library")) + implementation(project(":instrumentation:spring:spring-webmvc:spring-webmvc-6.0:library")) + implementation("jakarta.servlet:jakarta.servlet-api:6.1.0") + implementation("org.springframework.boot:spring-boot-starter-test:4.0.0") { + exclude("org.junit.vintage", "junit-vintage-engine") + } + } + } + val testDeclarativeConfig by registering(JvmTestSuite::class) { dependencies { implementation(project()) @@ -234,12 +268,30 @@ tasks { isEnabled = testSpring3 } + named("compileJavaSpring4Java") { + sourceCompatibility = "17" + targetCompatibility = "17" + options.release.set(17) + } + + named("compileTestSpring4Java") { + sourceCompatibility = "17" + targetCompatibility = "17" + options.release.set(17) + } + + named("testSpring4") { + isEnabled = testSpring3 // same condition as Spring 3 (requires Java 17+) + } + named("jar") { from(sourceSets["javaSpring3"].output) + from(sourceSets["javaSpring4"].output) } named("sourcesJar") { from(sourceSets["javaSpring3"].java) + from(sourceSets["javaSpring4"].java) } val testStableSemconv by registering(Test::class) { diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/kafka/KafkaInstrumentationAutoConfiguration.java b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/kafka/KafkaInstrumentationAutoConfiguration.java index 4df611685733..6b313d613f6f 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/kafka/KafkaInstrumentationAutoConfiguration.java +++ b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/kafka/KafkaInstrumentationAutoConfiguration.java @@ -29,6 +29,8 @@ ConcurrentKafkaListenerContainerFactory.class, DefaultKafkaProducerFactoryCustomizer.class }) +@org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass( + "org.springframework.boot.kafka.autoconfigure.DefaultKafkaProducerFactoryCustomizer") @Configuration public class KafkaInstrumentationAutoConfiguration { diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/main/javaSpring4/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/kafka/KafkaInstrumentationSpringBoot4AutoConfiguration.java b/instrumentation/spring/spring-boot-autoconfigure/src/main/javaSpring4/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/kafka/KafkaInstrumentationSpringBoot4AutoConfiguration.java new file mode 100644 index 000000000000..bfceff9394ef --- /dev/null +++ b/instrumentation/spring/spring-boot-autoconfigure/src/main/javaSpring4/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/kafka/KafkaInstrumentationSpringBoot4AutoConfiguration.java @@ -0,0 +1,70 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.kafka; + +import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.instrumentation.api.incubator.config.internal.InstrumentationConfig; +import io.opentelemetry.instrumentation.kafkaclients.v2_6.KafkaTelemetry; +import io.opentelemetry.instrumentation.spring.autoconfigure.internal.ConditionalOnEnabledInstrumentation; +import io.opentelemetry.instrumentation.spring.kafka.v2_7.SpringKafkaTelemetry; +import org.springframework.beans.factory.ObjectProvider; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.kafka.autoconfigure.DefaultKafkaProducerFactoryCustomizer; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.kafka.config.ConcurrentKafkaListenerContainerFactory; +import org.springframework.kafka.core.KafkaTemplate; + +/** + * Spring Boot 4+ specific Kafka instrumentation auto-configuration. This class is internal and is + * hence not for public use. Its APIs are unstable and can change at any time. + */ +@ConditionalOnEnabledInstrumentation(module = "kafka") +@ConditionalOnClass({ + KafkaTemplate.class, + ConcurrentKafkaListenerContainerFactory.class, + DefaultKafkaProducerFactoryCustomizer.class +}) +@ConditionalOnMissingBean(name = "otelKafkaProducerFactoryCustomizer") +@Configuration +public class KafkaInstrumentationSpringBoot4AutoConfiguration { + + @Bean + DefaultKafkaProducerFactoryCustomizer otelKafkaProducerFactoryCustomizer( + OpenTelemetry openTelemetry) { + KafkaTelemetry kafkaTelemetry = KafkaTelemetry.create(openTelemetry); + return producerFactory -> producerFactory.addPostProcessor(kafkaTelemetry::wrap); + } + + @Bean + static SpringKafkaTelemetry getTelemetry( + ObjectProvider openTelemetryProvider, + ObjectProvider configProvider) { + return SpringKafkaTelemetry.builder(openTelemetryProvider.getObject()) + .setCaptureExperimentalSpanAttributes( + configProvider + .getObject() + .getBoolean("otel.instrumentation.kafka.experimental-span-attributes", false)) + .build(); + } + + // static to avoid "is not eligible for getting processed by all BeanPostProcessors" warning + @Bean + @ConditionalOnProperty( + name = "otel.instrumentation.kafka.autoconfigure-interceptor", + havingValue = "true", + matchIfMissing = true) + @ConditionalOnMissingBean + static ConcurrentKafkaListenerContainerFactoryPostProcessor + otelKafkaListenerContainerFactoryBeanPostProcessor( + ObjectProvider openTelemetryProvider, + ObjectProvider configProvider) { + return new ConcurrentKafkaListenerContainerFactoryPostProcessor( + () -> getTelemetry(openTelemetryProvider, configProvider)); + } +} 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 e8c5e8a83d7a..086b024852b5 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 @@ -1,6 +1,7 @@ io.opentelemetry.instrumentation.spring.autoconfigure.OpenTelemetryAutoConfiguration io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.annotations.InstrumentationAnnotationsAutoConfiguration io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.kafka.KafkaInstrumentationAutoConfiguration +io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.kafka.KafkaInstrumentationSpringBoot4AutoConfiguration io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.mongo.MongoClientInstrumentationAutoConfiguration io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.logging.OpenTelemetryAppenderAutoConfiguration io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.jdbc.JdbcInstrumentationAutoConfiguration diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/testSpring4/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/webmvc/SpringWebMvcInstrumentation7AutoConfigurationTest.java b/instrumentation/spring/spring-boot-autoconfigure/src/testSpring4/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/webmvc/SpringWebMvcInstrumentation7AutoConfigurationTest.java new file mode 100644 index 000000000000..ecb0bd6c722e --- /dev/null +++ b/instrumentation/spring/spring-boot-autoconfigure/src/testSpring4/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/webmvc/SpringWebMvcInstrumentation7AutoConfigurationTest.java @@ -0,0 +1,52 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.webmvc; + +import static org.assertj.core.api.Assertions.assertThat; + +import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.instrumentation.api.incubator.config.internal.InstrumentationConfig; +import io.opentelemetry.instrumentation.spring.autoconfigure.internal.properties.ConfigPropertiesBridge; +import io.opentelemetry.sdk.autoconfigure.spi.internal.DefaultConfigProperties; +import jakarta.servlet.Filter; +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 SpringWebMvcInstrumentation7AutoConfigurationTest { + + private final ApplicationContextRunner contextRunner = + new ApplicationContextRunner() + .withBean(OpenTelemetry.class, OpenTelemetry::noop) + .withBean( + InstrumentationConfig.class, + () -> + new ConfigPropertiesBridge( + DefaultConfigProperties.createFromMap(Collections.emptyMap()))) + .withConfiguration( + AutoConfigurations.of(SpringWebMvc7InstrumentationAutoConfiguration.class)); + + @Test + void instrumentationEnabled() { + this.contextRunner + .withPropertyValues("otel.instrumentation.spring-webmvc.enabled=true") + .run(context -> assertThat(context.getBean("otelWebMvcFilter", Filter.class)).isNotNull()); + } + + @Test + void instrumentationDisabled() { + this.contextRunner + .withPropertyValues("otel.instrumentation.spring-webmvc.enabled=false") + .run(context -> assertThat(context.containsBean("otelWebMvcFilter")).isFalse()); + } + + @Test + void defaultConfiguration() { + this.contextRunner.run( + context -> assertThat(context.getBean("otelWebMvcFilter", Filter.class)).isNotNull()); + } +} diff --git a/settings.gradle.kts b/settings.gradle.kts index 125371b711ea..9e520a25bd4e 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -44,6 +44,7 @@ dependencyResolutionManagement { // spring boot 3.0 is not compatible with graalvm native image addSpringBootCatalog("springBoot31", "3.1.0", "3.+") addSpringBootCatalog("springBoot32", "3.2.0", "3.+") + addSpringBootCatalog("springBoot40", "4.0.0", "4.+") } } @@ -161,6 +162,7 @@ include(":smoke-tests-otel-starter:spring-smoke-testing") include(":smoke-tests-otel-starter:spring-boot-2") include(":smoke-tests-otel-starter:spring-boot-3") include(":smoke-tests-otel-starter:spring-boot-3.2") +include(":smoke-tests-otel-starter:spring-boot-4") include(":smoke-tests-otel-starter:spring-boot-common") include(":smoke-tests-otel-starter:spring-boot-reactive-2") include(":smoke-tests-otel-starter:spring-boot-reactive-3") diff --git a/smoke-tests-otel-starter/spring-boot-4/build.gradle.kts b/smoke-tests-otel-starter/spring-boot-4/build.gradle.kts new file mode 100644 index 000000000000..f91f9c931fdb --- /dev/null +++ b/smoke-tests-otel-starter/spring-boot-4/build.gradle.kts @@ -0,0 +1,41 @@ +plugins { + id("otel.java-conventions") + alias(springBoot40.plugins.versions) +} + +description = "smoke-tests-otel-starter-spring-boot-4" + +otelJava { + minJavaVersionSupported.set(JavaVersion.VERSION_17) +} + +dependencies { + implementation(platform(org.springframework.boot.gradle.plugin.SpringBootPlugin.BOM_COORDINATES)) + + implementation("org.springframework.boot:spring-boot-starter-web") + implementation("org.springframework.boot:spring-boot-starter-data-jdbc") + implementation("org.springframework.boot:spring-boot-starter-kafka") + runtimeOnly("com.h2database:h2") + + implementation(project(":smoke-tests-otel-starter:spring-boot-common")) + testImplementation("org.springframework.boot:spring-boot-starter-test") + testImplementation("org.springframework.boot:spring-boot-resttestclient") + testImplementation("org.awaitility:awaitility") + testImplementation(project(":instrumentation:spring:starters:spring-boot-starter")) + testImplementation(project(":smoke-tests-otel-starter:spring-smoke-testing")) +} + +springBoot { + mainClass = "io.opentelemetry.spring.smoketest.OtelSpringStarterSmokeTestApplication" +} + +// Disable -Werror for Spring Framework 7.0 compatibility +tasks.withType().configureEach { + options.compilerArgs.removeAll(listOf("-Werror")) +} + +tasks { + test { + usesService(gradle.sharedServices.registrations["testcontainersBuildService"].service) + } +} diff --git a/smoke-tests-otel-starter/spring-boot-4/src/main/java/io/opentelemetry/spring/smoketest/OtelSpringStarterSmokeTestApplication.java b/smoke-tests-otel-starter/spring-boot-4/src/main/java/io/opentelemetry/spring/smoketest/OtelSpringStarterSmokeTestApplication.java new file mode 100644 index 000000000000..0a43366e77b7 --- /dev/null +++ b/smoke-tests-otel-starter/spring-boot-4/src/main/java/io/opentelemetry/spring/smoketest/OtelSpringStarterSmokeTestApplication.java @@ -0,0 +1,19 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.spring.smoketest; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class OtelSpringStarterSmokeTestApplication { + + public OtelSpringStarterSmokeTestApplication() {} + + public static void main(String[] args) { + SpringApplication.run(OtelSpringStarterSmokeTestApplication.class); + } +} diff --git a/smoke-tests-otel-starter/spring-boot-4/src/main/resources/application.properties b/smoke-tests-otel-starter/spring-boot-4/src/main/resources/application.properties new file mode 100644 index 000000000000..01ad129d02b1 --- /dev/null +++ b/smoke-tests-otel-starter/spring-boot-4/src/main/resources/application.properties @@ -0,0 +1 @@ +spring.application.name=otel-spring-starter-smoke-test diff --git a/smoke-tests-otel-starter/spring-boot-4/src/test/java/io/opentelemetry/spring/smoketest/AbstractOtelSpringStarterSmokeTest.java b/smoke-tests-otel-starter/spring-boot-4/src/test/java/io/opentelemetry/spring/smoketest/AbstractOtelSpringStarterSmokeTest.java new file mode 100644 index 000000000000..a32051e26f98 --- /dev/null +++ b/smoke-tests-otel-starter/spring-boot-4/src/test/java/io/opentelemetry/spring/smoketest/AbstractOtelSpringStarterSmokeTest.java @@ -0,0 +1,333 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.spring.smoketest; + +import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo; +import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.satisfies; +import static org.assertj.core.api.Assertions.assertThat; + +import io.opentelemetry.api.common.AttributeKey; +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.trace.SpanKind; +import io.opentelemetry.instrumentation.api.internal.SemconvStability; +import io.opentelemetry.instrumentation.spring.autoconfigure.internal.properties.OtelResourceProperties; +import io.opentelemetry.instrumentation.spring.autoconfigure.internal.properties.OtelSpringProperties; +import io.opentelemetry.instrumentation.spring.autoconfigure.internal.properties.OtlpExporterProperties; +import io.opentelemetry.instrumentation.spring.autoconfigure.internal.properties.SpringConfigProperties; +import io.opentelemetry.sdk.autoconfigure.spi.AutoConfigurationCustomizerProvider; +import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; +import io.opentelemetry.sdk.autoconfigure.spi.internal.DefaultConfigProperties; +import io.opentelemetry.sdk.logs.data.LogRecordData; +import io.opentelemetry.sdk.resources.Resource; +import io.opentelemetry.semconv.ClientAttributes; +import io.opentelemetry.semconv.CodeAttributes; +import io.opentelemetry.semconv.HttpAttributes; +import io.opentelemetry.semconv.ServerAttributes; +import io.opentelemetry.semconv.UrlAttributes; +import io.opentelemetry.semconv.incubating.CodeIncubatingAttributes; +import io.opentelemetry.semconv.incubating.DbIncubatingAttributes; +import io.opentelemetry.semconv.incubating.ServiceIncubatingAttributes; +import io.opentelemetry.semconv.incubating.ThreadIncubatingAttributes; +import java.net.URI; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import org.assertj.core.api.AbstractIterableAssert; +import org.assertj.core.api.MapAssert; +import org.junit.jupiter.api.MethodOrderer; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestMethodOrder; +import org.junit.jupiter.api.condition.OS; +import org.springframework.beans.factory.ObjectProvider; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.context.event.ApplicationReadyEvent; +import org.springframework.boot.restclient.RestTemplateBuilder; +import org.springframework.boot.resttestclient.TestRestTemplate; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.event.EventListener; +import org.springframework.core.annotation.Order; +import org.springframework.core.env.Environment; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.http.RequestEntity; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.web.client.RestTemplate; + +/** + * This test class enforces the order of the tests to make sure that {@link #shouldSendTelemetry()}, + * which asserts the telemetry data from the application startup, is executed first. + */ +@SuppressWarnings("deprecation") // using deprecated semconv +@TestMethodOrder(MethodOrderer.OrderAnnotation.class) +abstract class AbstractOtelSpringStarterSmokeTest extends AbstractSpringStarterSmokeTest { + + @Autowired private TestRestTemplate testRestTemplate; + + @Autowired private Environment environment; + @Autowired private OtelSpringProperties otelSpringProperties; + @Autowired private OtelResourceProperties otelResourceProperties; + @Autowired private OtlpExporterProperties otlpExporterProperties; + @Autowired private RestTemplateBuilder restTemplateBuilder; + @Autowired private JdbcTemplate jdbcTemplate; + + // can't use @LocalServerPort annotation since it moved packages between Spring Boot 2 and 3 + @Value("${local.server.port}") + private int port; + + @Configuration(proxyBeanMethods = false) + static class TestConfiguration { + @Autowired private ObjectProvider jdbcTemplate; + + @EventListener(ApplicationReadyEvent.class) + public void loadData() { + jdbcTemplate + .getObject() + .execute( + "create table customer (id bigint not null, name varchar not null, primary key (id))"); + } + + @Bean + @Order(1) + AutoConfigurationCustomizerProvider hiddenPropagatorCustomizer() { + return customizer -> + customizer.addResourceCustomizer( + (resource, config) -> + resource.merge( + Resource.create( + Attributes.of( + AttributeKey.booleanKey("keyFromResourceCustomizer"), false)))); + } + + @Bean + @Order(2) + AutoConfigurationCustomizerProvider propagatorCustomizer() { + return customizer -> + customizer.addResourceCustomizer( + (resource, config) -> + resource.merge( + Resource.create( + Attributes.of( + AttributeKey.booleanKey("keyFromResourceCustomizer"), true)))); + } + + @Bean + AutoConfigurationCustomizerProvider customizerUsingPropertyDefinedInaSpringFile() { + return customizer -> + customizer.addResourceCustomizer( + (resource, config) -> { + String valueForKeyDeclaredZsEnvVariable = config.getString("APPLICATION_PROP"); + assertThat(valueForKeyDeclaredZsEnvVariable).isNotEmpty(); + + String valueForKeyWithDash = config.getString("application.prop-with-dash"); + assertThat(valueForKeyWithDash).isNotEmpty(); + + return resource; + }); + } + } + + @Test + void propertyConversion() { + ConfigProperties configProperties = + SpringConfigProperties.create( + environment, + otlpExporterProperties, + otelResourceProperties, + otelSpringProperties, + DefaultConfigProperties.createFromMap( + Collections.singletonMap("otel.exporter.otlp.headers", "a=1,b=2"))); + assertThat(configProperties.getMap("otel.exporter.otlp.headers")) + .containsEntry("a", "1") + .containsEntry("b", "2") + .containsEntry("c", "3"); + assertThat(configProperties.getList("otel.propagators")).containsExactly("b3"); + } + + @Test + @org.junit.jupiter.api.Order(1) + @SuppressWarnings("deprecation") // testing deprecated code semconv + void shouldSendTelemetry() { + HttpHeaders headers = new HttpHeaders(); + headers.add("key", "value"); + + testRestTemplate.exchange( + new RequestEntity<>( + null, headers, HttpMethod.GET, URI.create(OtelSpringStarterSmokeTestController.PING)), + String.class); + + // Span + testing.waitAndAssertTraces( + traceAssert -> + traceAssert.hasSpansSatisfyingExactly( + spanDataAssert -> + spanDataAssert + .hasKind(SpanKind.CLIENT) + .hasAttribute( + DbIncubatingAttributes.DB_STATEMENT, + "create table customer (id bigint not null, name varchar not null, primary key (id))")), + traceAssert -> + traceAssert.hasSpansSatisfyingExactly( + clientSpan -> + clientSpan + .hasKind(SpanKind.CLIENT) + .hasAttributesSatisfying( + satisfies( + UrlAttributes.URL_FULL, + stringAssert -> stringAssert.endsWith("/ping")), + equalTo(ServerAttributes.SERVER_ADDRESS, "localhost"), + satisfies(ServerAttributes.SERVER_PORT, val -> val.isNotZero())), + serverSpan -> + HttpSpanDataAssert.create(serverSpan) + .assertServerGetRequest("/ping") + .hasResourceSatisfying( + r -> + r.hasAttribute( + AttributeKey.booleanKey("keyFromResourceCustomizer"), true) + .hasAttribute( + AttributeKey.stringKey("attributeFromYaml"), "true") + .hasAttribute( + satisfies( + ServiceIncubatingAttributes.SERVICE_INSTANCE_ID, + val -> val.isNotBlank()))) + .hasAttributesSatisfying( + equalTo(HttpAttributes.HTTP_REQUEST_METHOD, "GET"), + equalTo(HttpAttributes.HTTP_RESPONSE_STATUS_CODE, 200L), + equalTo(HttpAttributes.HTTP_ROUTE, "/ping"), + equalTo(ServerAttributes.SERVER_ADDRESS, "localhost"), + satisfies( + ClientAttributes.CLIENT_ADDRESS, + s -> s.isIn("127.0.0.1", "0:0:0:0:0:0:0:1")), + equalTo( + AttributeKey.stringArrayKey("http.request.header.key"), + Collections.singletonList("value")), + satisfies(ServerAttributes.SERVER_PORT, val -> val.isNotZero()), + satisfies(ThreadIncubatingAttributes.THREAD_ID, val -> val.isNotZero()), + satisfies( + ThreadIncubatingAttributes.THREAD_NAME, val -> val.isNotBlank())), + val -> withSpanAssert(val))); + + // Metric + testing.waitAndAssertMetrics( + OtelSpringStarterSmokeTestController.METER_SCOPE_NAME, + OtelSpringStarterSmokeTestController.TEST_HISTOGRAM, + AbstractIterableAssert::isNotEmpty); + + // JMX based metrics - test one per JMX bean + List jmxMetrics = + new ArrayList<>(Arrays.asList("jvm.thread.count", "jvm.memory.used", "jvm.memory.init")); + + double javaVersion = Double.parseDouble(System.getProperty("java.specification.version")); + // See https://github.com/open-telemetry/opentelemetry-java-instrumentation/issues/13503 + // Also not available on Windows (getSystemLoadAverage returns -1) + if (javaVersion < 23 && !OS.WINDOWS.isCurrentOs()) { + jmxMetrics.add("jvm.system.cpu.load_1m"); + } + + boolean nativeImage = System.getProperty("org.graalvm.nativeimage.imagecode") != null; + if (!nativeImage) { + // GraalVM native image does not support buffer pools - have to investigate why + jmxMetrics.add("jvm.buffer.memory.used"); + } + jmxMetrics.forEach( + metricName -> + testing.waitAndAssertMetrics( + "io.opentelemetry.runtime-telemetry-java8", + metricName, + AbstractIterableAssert::isNotEmpty)); + + assertAdditionalMetrics(); + + // Log + List exportedLogRecords = testing.getExportedLogRecords(); + assertThat(exportedLogRecords).as("No log record exported.").isNotEmpty(); + if (!nativeImage) { + // log records differ in native image mode due to different startup timing + LogRecordData firstLog = exportedLogRecords.get(0); + assertThat(firstLog.getBodyValue().asString()) + .as("Should instrument logs") + .startsWith("Starting ") + .contains(this.getClass().getSimpleName()); + + MapAssert, Object> attributesAssert = + assertThat(firstLog.getAttributes().asMap()).as("Should capture code attributes"); + + if (SemconvStability.emitStableDatabaseSemconv()) { + attributesAssert.containsEntry( + CodeAttributes.CODE_FUNCTION_NAME, + "org.springframework.boot.StartupInfoLogger.logStarting"); + } + if (SemconvStability.isEmitOldCodeSemconv()) { + attributesAssert + .containsEntry( + CodeIncubatingAttributes.CODE_NAMESPACE, + "org.springframework.boot.StartupInfoLogger") + .containsEntry(CodeIncubatingAttributes.CODE_FUNCTION, "logStarting"); + } + } + } + + protected void assertAdditionalMetrics() {} + + @Test + void databaseQuery() { + testing.clearAllExportedData(); + + testing.runWithSpan( + "server", + () -> { + jdbcTemplate.query( + "select name from customer where id = 1", (rs, rowNum) -> rs.getString("name")); + }); + + // 1 is not replaced by ?, otel.instrumentation.common.db-statement-sanitizer.enabled=false + testing.waitAndAssertTraces( + traceAssert -> + traceAssert.hasSpansSatisfyingExactly( + span -> span.hasName("server"), + span -> + span.hasKind(SpanKind.CLIENT) + .hasAttribute( + DbIncubatingAttributes.DB_STATEMENT, + "select name from customer where id = 1"))); + } + + @Test + void restTemplate() { + testing.clearAllExportedData(); + + RestTemplate restTemplate = restTemplateBuilder.rootUri("http://localhost:" + port).build(); + restTemplate.getForObject(OtelSpringStarterSmokeTestController.PING, String.class); + testing.waitAndAssertTraces( + traceAssert -> + traceAssert.hasSpansSatisfyingExactly( + span -> HttpSpanDataAssert.create(span).assertClientGetRequest("/ping"), + span -> + span.hasKind(SpanKind.SERVER).hasAttribute(HttpAttributes.HTTP_ROUTE, "/ping"), + span -> withSpanAssert(span))); + } + + @Test + void shouldRedactSomeUrlParameters() { + testing.clearAllExportedData(); + + RestTemplate restTemplate = restTemplateBuilder.rootUri("http://localhost:" + port).build(); + restTemplate.getForObject( + "/test?X-Goog-Signature=39Up9jzHkxhuIhFE9594DJxe7w6cIRCg0V6ICGS0", String.class); + + testing.waitAndAssertTraces( + traceAssert -> + traceAssert.hasSpansSatisfyingExactly( + span -> + HttpSpanDataAssert.create(span) + .assertClientGetRequest("/test?X-Goog-Signature=REDACTED"), + span -> + span.hasKind(SpanKind.SERVER) + .hasAttribute(HttpAttributes.HTTP_ROUTE, "/test"))); + } +} diff --git a/smoke-tests-otel-starter/spring-boot-4/src/test/java/io/opentelemetry/spring/smoketest/OtelSpringStarterSmokeTest.java b/smoke-tests-otel-starter/spring-boot-4/src/test/java/io/opentelemetry/spring/smoketest/OtelSpringStarterSmokeTest.java new file mode 100644 index 000000000000..ccd04afe1cc4 --- /dev/null +++ b/smoke-tests-otel-starter/spring-boot-4/src/test/java/io/opentelemetry/spring/smoketest/OtelSpringStarterSmokeTest.java @@ -0,0 +1,64 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.spring.smoketest; + +import java.util.List; +import org.assertj.core.api.AbstractIterableAssert; +import org.springframework.boot.resttestclient.autoconfigure.AutoConfigureTestRestTemplate; +import org.springframework.boot.test.context.SpringBootTest; + +@SpringBootTest( + classes = { + OtelSpringStarterSmokeTestApplication.class, + AbstractOtelSpringStarterSmokeTest.TestConfiguration.class, + SpringSmokeOtelConfiguration.class + }, + webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, + properties = { + // The headers are simply set here to make sure that headers can be parsed + "otel.exporter.otlp.headers.c=3", + "otel.instrumentation.runtime-telemetry.emit-experimental-telemetry=true", + "otel.instrumentation.runtime-telemetry-java17.enable-all=true", + "otel.instrumentation.common.thread_details.enabled=true", + }) +@AutoConfigureTestRestTemplate +class OtelSpringStarterSmokeTest extends AbstractOtelSpringStarterSmokeTest { + + @Override + protected void assertAdditionalMetrics() { + if (!isFlightRecorderAvailable()) { + return; + } + + // JFR based metrics + for (String metric : + List.of( + "jvm.cpu.limit", + "jvm.buffer.count", + "jvm.class.count", + "jvm.cpu.context_switch", + "jvm.cpu.longlock", + "jvm.system.cpu.utilization", + "jvm.gc.duration", + "jvm.memory.init", + "jvm.memory.used", + "jvm.memory.allocation", + "jvm.network.io", + "jvm.thread.count")) { + testing.waitAndAssertMetrics( + "io.opentelemetry.runtime-telemetry-java17", metric, AbstractIterableAssert::isNotEmpty); + } + } + + private static boolean isFlightRecorderAvailable() { + try { + return (boolean) + Class.forName("jdk.jfr.FlightRecorder").getMethod("isAvailable").invoke(null); + } catch (ReflectiveOperationException exception) { + return false; + } + } +} From 38d6dc47bad0ade5c95562f0a923ef5cfde7e2ac Mon Sep 17 00:00:00 2001 From: Jay DeLuca Date: Tue, 25 Nov 2025 18:01:15 -0500 Subject: [PATCH 02/40] progress --- .../build.gradle.kts | 2 + .../JdbcInstrumentationAutoConfiguration.java | 3 ++ ...KafkaInstrumentationAutoConfiguration.java | 3 +- ...mentationSpringBoot4AutoConfiguration.java | 42 +++++++++++++++ ...mentationSpringBoot4AutoConfiguration.java | 7 ++- .../MicrometerBridgeAutoConfiguration.java | 39 ++++++++++++++ ...ot.autoconfigure.AutoConfiguration.imports | 1 + ...Instrumentation7AutoConfigurationTest.java | 52 ------------------- 8 files changed, 94 insertions(+), 55 deletions(-) create mode 100644 instrumentation/spring/spring-boot-autoconfigure/src/main/javaSpring4/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/jdbc/JdbcInstrumentationSpringBoot4AutoConfiguration.java create mode 100644 instrumentation/spring/spring-boot-autoconfigure/src/main/javaSpring4/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/micrometer/MicrometerBridgeAutoConfiguration.java delete mode 100644 instrumentation/spring/spring-boot-autoconfigure/src/testSpring4/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/webmvc/SpringWebMvcInstrumentation7AutoConfigurationTest.java diff --git a/instrumentation/spring/spring-boot-autoconfigure/build.gradle.kts b/instrumentation/spring/spring-boot-autoconfigure/build.gradle.kts index 48fdedd04d93..e319eb66b109 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/build.gradle.kts +++ b/instrumentation/spring/spring-boot-autoconfigure/build.gradle.kts @@ -123,11 +123,13 @@ dependencies { add("javaSpring4CompileOnly", "org.springframework.boot:spring-boot-starter-web:4.0.0") add("javaSpring4CompileOnly", "org.springframework.boot:spring-boot-starter-data-jdbc:4.0.0") add("javaSpring4CompileOnly", "org.springframework.boot:spring-boot-starter-kafka:4.0.0") + add("javaSpring4CompileOnly", "org.springframework.boot:spring-boot-starter-micrometer:4.0.0") add("javaSpring4CompileOnly", "io.opentelemetry:opentelemetry-sdk-extension-autoconfigure") add("javaSpring4CompileOnly", "jakarta.servlet:jakarta.servlet-api:6.1.0") add("javaSpring4CompileOnly", project(":instrumentation:spring:spring-web:spring-web-3.1:library")) add("javaSpring4CompileOnly", project(":instrumentation:spring:spring-webmvc:spring-webmvc-6.0:library")) add("javaSpring4CompileOnly", project(":instrumentation:kafka:kafka-clients:kafka-clients-2.6:library")) + add("javaSpring4CompileOnly", project(":instrumentation:micrometer:micrometer-1.5:library")) add("javaSpring4CompileOnly", project(":instrumentation:spring:spring-kafka-2.7:library")) // tests don't work with spring boot 4 yet diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/jdbc/JdbcInstrumentationAutoConfiguration.java b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/jdbc/JdbcInstrumentationAutoConfiguration.java index 1732c3be178d..614c75a0650e 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/jdbc/JdbcInstrumentationAutoConfiguration.java +++ b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/jdbc/JdbcInstrumentationAutoConfiguration.java @@ -12,6 +12,7 @@ import org.springframework.beans.factory.ObjectProvider; import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass; import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -23,6 +24,8 @@ @ConditionalOnEnabledInstrumentation(module = "jdbc") @AutoConfiguration(after = DataSourceAutoConfiguration.class) @ConditionalOnBean({DataSource.class}) +@ConditionalOnMissingClass( + "org.springframework.boot.jdbc.autoconfigure.DataSourceAutoConfiguration") @Configuration(proxyBeanMethods = false) public class JdbcInstrumentationAutoConfiguration { diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/kafka/KafkaInstrumentationAutoConfiguration.java b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/kafka/KafkaInstrumentationAutoConfiguration.java index 6b313d613f6f..a3bf169cf98f 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/kafka/KafkaInstrumentationAutoConfiguration.java +++ b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/kafka/KafkaInstrumentationAutoConfiguration.java @@ -12,6 +12,7 @@ import io.opentelemetry.instrumentation.spring.kafka.v2_7.SpringKafkaTelemetry; import org.springframework.beans.factory.ObjectProvider; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.autoconfigure.kafka.DefaultKafkaProducerFactoryCustomizer; import org.springframework.context.annotation.Bean; @@ -29,7 +30,7 @@ ConcurrentKafkaListenerContainerFactory.class, DefaultKafkaProducerFactoryCustomizer.class }) -@org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass( +@ConditionalOnMissingClass( "org.springframework.boot.kafka.autoconfigure.DefaultKafkaProducerFactoryCustomizer") @Configuration public class KafkaInstrumentationAutoConfiguration { diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/main/javaSpring4/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/jdbc/JdbcInstrumentationSpringBoot4AutoConfiguration.java b/instrumentation/spring/spring-boot-autoconfigure/src/main/javaSpring4/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/jdbc/JdbcInstrumentationSpringBoot4AutoConfiguration.java new file mode 100644 index 000000000000..424bf0e60e1a --- /dev/null +++ b/instrumentation/spring/spring-boot-autoconfigure/src/main/javaSpring4/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/jdbc/JdbcInstrumentationSpringBoot4AutoConfiguration.java @@ -0,0 +1,42 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.jdbc; + +import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.instrumentation.api.incubator.config.internal.InstrumentationConfig; +import io.opentelemetry.instrumentation.spring.autoconfigure.internal.ConditionalOnEnabledInstrumentation; +import javax.sql.DataSource; +import org.springframework.beans.factory.ObjectProvider; +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass; +import org.springframework.boot.jdbc.autoconfigure.DataSourceAutoConfiguration; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * This class is internal and is hence not for public use. Its APIs are unstable and can change at + * any time. + */ +@ConditionalOnEnabledInstrumentation(module = "jdbc") +@AutoConfiguration(after = DataSourceAutoConfiguration.class) +@ConditionalOnBean({DataSource.class}) +@ConditionalOnMissingClass( + "org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration") +@Configuration(proxyBeanMethods = false) +public class JdbcInstrumentationSpringBoot4AutoConfiguration { + + // For error prone + public JdbcInstrumentationSpringBoot4AutoConfiguration() {} + + @Bean + // static to avoid "is not eligible for getting processed by all BeanPostProcessors" warning + static DataSourcePostProcessor dataSourcePostProcessor( + ObjectProvider openTelemetryProvider, + ObjectProvider configProvider) { + return new DataSourcePostProcessor(openTelemetryProvider, configProvider); + } +} diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/main/javaSpring4/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/kafka/KafkaInstrumentationSpringBoot4AutoConfiguration.java b/instrumentation/spring/spring-boot-autoconfigure/src/main/javaSpring4/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/kafka/KafkaInstrumentationSpringBoot4AutoConfiguration.java index bfceff9394ef..cb1328f01e06 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/main/javaSpring4/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/kafka/KafkaInstrumentationSpringBoot4AutoConfiguration.java +++ b/instrumentation/spring/spring-boot-autoconfigure/src/main/javaSpring4/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/kafka/KafkaInstrumentationSpringBoot4AutoConfiguration.java @@ -13,6 +13,7 @@ import org.springframework.beans.factory.ObjectProvider; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.kafka.autoconfigure.DefaultKafkaProducerFactoryCustomizer; import org.springframework.context.annotation.Bean; @@ -21,8 +22,8 @@ import org.springframework.kafka.core.KafkaTemplate; /** - * Spring Boot 4+ specific Kafka instrumentation auto-configuration. This class is internal and is - * hence not for public use. Its APIs are unstable and can change at any time. + * This class is internal and is hence not for public use. Its APIs are unstable and can change at + * any time. */ @ConditionalOnEnabledInstrumentation(module = "kafka") @ConditionalOnClass({ @@ -31,6 +32,8 @@ DefaultKafkaProducerFactoryCustomizer.class }) @ConditionalOnMissingBean(name = "otelKafkaProducerFactoryCustomizer") +@ConditionalOnMissingClass( + "org.springframework.boot.autoconfigure.kafka.DefaultKafkaProducerFactoryCustomizer") @Configuration public class KafkaInstrumentationSpringBoot4AutoConfiguration { diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/main/javaSpring4/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/micrometer/MicrometerBridgeAutoConfiguration.java b/instrumentation/spring/spring-boot-autoconfigure/src/main/javaSpring4/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/micrometer/MicrometerBridgeAutoConfiguration.java new file mode 100644 index 000000000000..89e5c21032e7 --- /dev/null +++ b/instrumentation/spring/spring-boot-autoconfigure/src/main/javaSpring4/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/micrometer/MicrometerBridgeAutoConfiguration.java @@ -0,0 +1,39 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.micrometer; + +import io.micrometer.core.instrument.Clock; +import io.micrometer.core.instrument.MeterRegistry; +import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.instrumentation.micrometer.v1_5.OpenTelemetryMeterRegistry; +import io.opentelemetry.instrumentation.spring.autoconfigure.OpenTelemetryAutoConfiguration; +import io.opentelemetry.instrumentation.spring.autoconfigure.internal.ConditionalOnEnabledInstrumentation; +import org.springframework.boot.actuate.autoconfigure.metrics.CompositeMeterRegistryAutoConfiguration; +import org.springframework.boot.actuate.autoconfigure.metrics.MetricsAutoConfiguration; +import org.springframework.boot.autoconfigure.AutoConfigureAfter; +import org.springframework.boot.autoconfigure.AutoConfigureBefore; +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; + +/** + * This class is internal and is hence not for public use. Its APIs are unstable and can change at + * any time. + */ +@ConditionalOnEnabledInstrumentation(module = "micrometer", enabledByDefault = false) +@AutoConfigureAfter({MetricsAutoConfiguration.class, OpenTelemetryAutoConfiguration.class}) +@AutoConfigureBefore(CompositeMeterRegistryAutoConfiguration.class) +@ConditionalOnBean(Clock.class) +@ConditionalOnClass(MeterRegistry.class) +@Configuration +public class MicrometerBridgeAutoConfiguration { + + @Bean + MeterRegistry otelMeterRegistry(OpenTelemetry openTelemetry, Clock micrometerClock) { + return OpenTelemetryMeterRegistry.builder(openTelemetry).setClock(micrometerClock).build(); + } +} 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 086b024852b5..d916ffe801d3 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 @@ -5,6 +5,7 @@ io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.k io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.mongo.MongoClientInstrumentationAutoConfiguration io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.logging.OpenTelemetryAppenderAutoConfiguration io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.jdbc.JdbcInstrumentationAutoConfiguration +io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.jdbc.JdbcInstrumentationSpringBoot4AutoConfiguration io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.micrometer.MicrometerBridgeAutoConfiguration io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.r2dbc.R2dbcInstrumentationAutoConfiguration io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.web.SpringWebInstrumentationAutoConfiguration diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/testSpring4/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/webmvc/SpringWebMvcInstrumentation7AutoConfigurationTest.java b/instrumentation/spring/spring-boot-autoconfigure/src/testSpring4/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/webmvc/SpringWebMvcInstrumentation7AutoConfigurationTest.java deleted file mode 100644 index ecb0bd6c722e..000000000000 --- a/instrumentation/spring/spring-boot-autoconfigure/src/testSpring4/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/webmvc/SpringWebMvcInstrumentation7AutoConfigurationTest.java +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.webmvc; - -import static org.assertj.core.api.Assertions.assertThat; - -import io.opentelemetry.api.OpenTelemetry; -import io.opentelemetry.instrumentation.api.incubator.config.internal.InstrumentationConfig; -import io.opentelemetry.instrumentation.spring.autoconfigure.internal.properties.ConfigPropertiesBridge; -import io.opentelemetry.sdk.autoconfigure.spi.internal.DefaultConfigProperties; -import jakarta.servlet.Filter; -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 SpringWebMvcInstrumentation7AutoConfigurationTest { - - private final ApplicationContextRunner contextRunner = - new ApplicationContextRunner() - .withBean(OpenTelemetry.class, OpenTelemetry::noop) - .withBean( - InstrumentationConfig.class, - () -> - new ConfigPropertiesBridge( - DefaultConfigProperties.createFromMap(Collections.emptyMap()))) - .withConfiguration( - AutoConfigurations.of(SpringWebMvc7InstrumentationAutoConfiguration.class)); - - @Test - void instrumentationEnabled() { - this.contextRunner - .withPropertyValues("otel.instrumentation.spring-webmvc.enabled=true") - .run(context -> assertThat(context.getBean("otelWebMvcFilter", Filter.class)).isNotNull()); - } - - @Test - void instrumentationDisabled() { - this.contextRunner - .withPropertyValues("otel.instrumentation.spring-webmvc.enabled=false") - .run(context -> assertThat(context.containsBean("otelWebMvcFilter")).isFalse()); - } - - @Test - void defaultConfiguration() { - this.contextRunner.run( - context -> assertThat(context.getBean("otelWebMvcFilter", Filter.class)).isNotNull()); - } -} From ba32a8db12c051fbd27061b1ae5fd9d797301bb3 Mon Sep 17 00:00:00 2001 From: Jay DeLuca Date: Tue, 25 Nov 2025 20:29:17 -0500 Subject: [PATCH 03/40] kafka --- .../build.gradle.kts | 27 +++---- ...ngWebInstrumentationAutoConfiguration.java | 2 + ...mentationSpringBoot4AutoConfiguration.java | 3 - .../MicrometerBridgeAutoConfiguration.java | 39 --------- ...mentationSpringBoot4AutoConfiguration.java | 49 ++++++++++++ ...ot.autoconfigure.AutoConfiguration.imports | 1 + .../spring-boot-4/build.gradle.kts | 7 +- ...bstractJvmKafkaSpringStarterSmokeTest.java | 79 +++++++++++++++++++ .../KafkaSpringStarterSmokeTest.java | 11 +++ .../smoketest/OtelSpringStarterSmokeTest.java | 1 + 10 files changed, 158 insertions(+), 61 deletions(-) delete mode 100644 instrumentation/spring/spring-boot-autoconfigure/src/main/javaSpring4/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/micrometer/MicrometerBridgeAutoConfiguration.java create mode 100644 instrumentation/spring/spring-boot-autoconfigure/src/main/javaSpring4/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/web/SpringWebInstrumentationSpringBoot4AutoConfiguration.java create mode 100644 smoke-tests-otel-starter/spring-boot-4/src/test/java/io/opentelemetry/spring/smoketest/AbstractJvmKafkaSpringStarterSmokeTest.java create mode 100644 smoke-tests-otel-starter/spring-boot-4/src/test/java/io/opentelemetry/spring/smoketest/KafkaSpringStarterSmokeTest.java diff --git a/instrumentation/spring/spring-boot-autoconfigure/build.gradle.kts b/instrumentation/spring/spring-boot-autoconfigure/build.gradle.kts index e319eb66b109..264f6936819e 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/build.gradle.kts +++ b/instrumentation/spring/spring-boot-autoconfigure/build.gradle.kts @@ -120,27 +120,18 @@ dependencies { // Spring Boot 4 add("javaSpring4CompileOnly", files(sourceSets.main.get().output.classesDirs)) - add("javaSpring4CompileOnly", "org.springframework.boot:spring-boot-starter-web:4.0.0") - add("javaSpring4CompileOnly", "org.springframework.boot:spring-boot-starter-data-jdbc:4.0.0") +// add("javaSpring4CompileOnly", "org.springframework.boot:spring-boot-starter-web:4.0.0") add("javaSpring4CompileOnly", "org.springframework.boot:spring-boot-starter-kafka:4.0.0") - add("javaSpring4CompileOnly", "org.springframework.boot:spring-boot-starter-micrometer:4.0.0") - add("javaSpring4CompileOnly", "io.opentelemetry:opentelemetry-sdk-extension-autoconfigure") - add("javaSpring4CompileOnly", "jakarta.servlet:jakarta.servlet-api:6.1.0") - add("javaSpring4CompileOnly", project(":instrumentation:spring:spring-web:spring-web-3.1:library")) - add("javaSpring4CompileOnly", project(":instrumentation:spring:spring-webmvc:spring-webmvc-6.0:library")) +// add("javaSpring4CompileOnly", "org.springframework.boot:spring-boot-autoconfigure:4.0.0") +// add("javaSpring4CompileOnly", "org.springframework.boot:spring-boot-jdbc:4.0.0") + add("javaSpring4CompileOnly", "org.springframework.boot:spring-boot-starter-jdbc:4.0.0") + add("javaSpring4CompileOnly", "org.springframework.boot:spring-boot-restclient:4.0.0") +// add("javaSpring4CompileOnly", "io.opentelemetry:opentelemetry-sdk-extension-autoconfigure") +// add("javaSpring4CompileOnly", project(":instrumentation:spring:spring-web:spring-web-3.1:library")) +// add("javaSpring4CompileOnly", project(":instrumentation:spring:spring-webmvc:spring-webmvc-6.0:library")) add("javaSpring4CompileOnly", project(":instrumentation:kafka:kafka-clients:kafka-clients-2.6:library")) - add("javaSpring4CompileOnly", project(":instrumentation:micrometer:micrometer-1.5:library")) add("javaSpring4CompileOnly", project(":instrumentation:spring:spring-kafka-2.7:library")) - - // tests don't work with spring boot 4 yet - latestDepTestLibrary("org.springframework.boot:spring-boot-starter-test:3.+") // documented limitation - latestDepTestLibrary("org.springframework.boot:spring-boot-starter-actuator:3.+") // documented limitation - latestDepTestLibrary("org.springframework.boot:spring-boot-starter-aop:3.+") // documented limitation - latestDepTestLibrary("org.springframework.boot:spring-boot-starter-web:3.+") // documented limitation - latestDepTestLibrary("org.springframework.boot:spring-boot-starter-webflux:3.+") // documented limitation - latestDepTestLibrary("org.springframework.boot:spring-boot-starter-data-mongodb:3.+") // documented limitation - latestDepTestLibrary("org.springframework.boot:spring-boot-starter-data-r2dbc:3.+") // documented limitation - latestDepTestLibrary("org.springframework.boot:spring-boot-starter-data-jdbc:3.+") // documented limitation +// add("javaSpring4CompileOnly", project(":instrumentation:micrometer:micrometer-1.5:library")) } val latestDepTest = findProperty("testLatestDeps") as Boolean diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/web/SpringWebInstrumentationAutoConfiguration.java b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/web/SpringWebInstrumentationAutoConfiguration.java index 97d5bd9b52d5..765ed4f5f474 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/web/SpringWebInstrumentationAutoConfiguration.java +++ b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/web/SpringWebInstrumentationAutoConfiguration.java @@ -10,6 +10,7 @@ import io.opentelemetry.instrumentation.spring.autoconfigure.internal.ConditionalOnEnabledInstrumentation; import org.springframework.beans.factory.ObjectProvider; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass; import org.springframework.boot.web.client.RestTemplateCustomizer; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -25,6 +26,7 @@ */ @ConditionalOnEnabledInstrumentation(module = "spring-web") @ConditionalOnClass({RestTemplate.class, RestTemplateCustomizer.class}) +@ConditionalOnMissingClass("org.springframework.boot.restclient.RestTemplateCustomizer") @Configuration public class SpringWebInstrumentationAutoConfiguration { diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/main/javaSpring4/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/jdbc/JdbcInstrumentationSpringBoot4AutoConfiguration.java b/instrumentation/spring/spring-boot-autoconfigure/src/main/javaSpring4/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/jdbc/JdbcInstrumentationSpringBoot4AutoConfiguration.java index 424bf0e60e1a..58d26eae25e7 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/main/javaSpring4/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/jdbc/JdbcInstrumentationSpringBoot4AutoConfiguration.java +++ b/instrumentation/spring/spring-boot-autoconfigure/src/main/javaSpring4/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/jdbc/JdbcInstrumentationSpringBoot4AutoConfiguration.java @@ -12,7 +12,6 @@ import org.springframework.beans.factory.ObjectProvider; import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; -import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass; import org.springframework.boot.jdbc.autoconfigure.DataSourceAutoConfiguration; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -24,8 +23,6 @@ @ConditionalOnEnabledInstrumentation(module = "jdbc") @AutoConfiguration(after = DataSourceAutoConfiguration.class) @ConditionalOnBean({DataSource.class}) -@ConditionalOnMissingClass( - "org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration") @Configuration(proxyBeanMethods = false) public class JdbcInstrumentationSpringBoot4AutoConfiguration { diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/main/javaSpring4/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/micrometer/MicrometerBridgeAutoConfiguration.java b/instrumentation/spring/spring-boot-autoconfigure/src/main/javaSpring4/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/micrometer/MicrometerBridgeAutoConfiguration.java deleted file mode 100644 index 89e5c21032e7..000000000000 --- a/instrumentation/spring/spring-boot-autoconfigure/src/main/javaSpring4/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/micrometer/MicrometerBridgeAutoConfiguration.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.micrometer; - -import io.micrometer.core.instrument.Clock; -import io.micrometer.core.instrument.MeterRegistry; -import io.opentelemetry.api.OpenTelemetry; -import io.opentelemetry.instrumentation.micrometer.v1_5.OpenTelemetryMeterRegistry; -import io.opentelemetry.instrumentation.spring.autoconfigure.OpenTelemetryAutoConfiguration; -import io.opentelemetry.instrumentation.spring.autoconfigure.internal.ConditionalOnEnabledInstrumentation; -import org.springframework.boot.actuate.autoconfigure.metrics.CompositeMeterRegistryAutoConfiguration; -import org.springframework.boot.actuate.autoconfigure.metrics.MetricsAutoConfiguration; -import org.springframework.boot.autoconfigure.AutoConfigureAfter; -import org.springframework.boot.autoconfigure.AutoConfigureBefore; -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; - -/** - * This class is internal and is hence not for public use. Its APIs are unstable and can change at - * any time. - */ -@ConditionalOnEnabledInstrumentation(module = "micrometer", enabledByDefault = false) -@AutoConfigureAfter({MetricsAutoConfiguration.class, OpenTelemetryAutoConfiguration.class}) -@AutoConfigureBefore(CompositeMeterRegistryAutoConfiguration.class) -@ConditionalOnBean(Clock.class) -@ConditionalOnClass(MeterRegistry.class) -@Configuration -public class MicrometerBridgeAutoConfiguration { - - @Bean - MeterRegistry otelMeterRegistry(OpenTelemetry openTelemetry, Clock micrometerClock) { - return OpenTelemetryMeterRegistry.builder(openTelemetry).setClock(micrometerClock).build(); - } -} diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/main/javaSpring4/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/web/SpringWebInstrumentationSpringBoot4AutoConfiguration.java b/instrumentation/spring/spring-boot-autoconfigure/src/main/javaSpring4/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/web/SpringWebInstrumentationSpringBoot4AutoConfiguration.java new file mode 100644 index 000000000000..04bd21413678 --- /dev/null +++ b/instrumentation/spring/spring-boot-autoconfigure/src/main/javaSpring4/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/web/SpringWebInstrumentationSpringBoot4AutoConfiguration.java @@ -0,0 +1,49 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.web; + +import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.instrumentation.api.incubator.config.internal.InstrumentationConfig; +import io.opentelemetry.instrumentation.spring.autoconfigure.internal.ConditionalOnEnabledInstrumentation; +import org.springframework.beans.factory.ObjectProvider; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.restclient.RestTemplateCustomizer; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.client.RestTemplate; + +/** + * Configures {@link RestTemplate} for tracing. + * + *

Adds OpenTelemetry instrumentation to RestTemplate beans after initialization. + * + *

This class is internal and is hence not for public use. Its APIs are unstable and can change + * at any time. + */ +@ConditionalOnEnabledInstrumentation(module = "spring-web") +@ConditionalOnClass({RestTemplate.class, RestTemplateCustomizer.class}) +@Configuration +public class SpringWebInstrumentationSpringBoot4AutoConfiguration { + + public SpringWebInstrumentationSpringBoot4AutoConfiguration() {} + + // static to avoid "is not eligible for getting processed by all BeanPostProcessors" warning + @Bean + static RestTemplateBeanPostProcessor otelRestTemplateBeanPostProcessor( + ObjectProvider openTelemetryProvider, + ObjectProvider configProvider) { + return new RestTemplateBeanPostProcessor(openTelemetryProvider, configProvider); + } + + @Bean + RestTemplateCustomizer otelRestTemplateCustomizer( + ObjectProvider openTelemetryProvider, + ObjectProvider configProvider) { + return restTemplate -> + RestTemplateInstrumentation.addIfNotPresent( + restTemplate, openTelemetryProvider.getObject(), configProvider.getObject()); + } +} 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 d916ffe801d3..7f6ff904c014 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 @@ -9,6 +9,7 @@ io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.j io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.micrometer.MicrometerBridgeAutoConfiguration 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.web.SpringWebInstrumentationSpringBoot4AutoConfiguration 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 diff --git a/smoke-tests-otel-starter/spring-boot-4/build.gradle.kts b/smoke-tests-otel-starter/spring-boot-4/build.gradle.kts index f91f9c931fdb..5d42556714fc 100644 --- a/smoke-tests-otel-starter/spring-boot-4/build.gradle.kts +++ b/smoke-tests-otel-starter/spring-boot-4/build.gradle.kts @@ -15,14 +15,19 @@ dependencies { implementation("org.springframework.boot:spring-boot-starter-web") implementation("org.springframework.boot:spring-boot-starter-data-jdbc") implementation("org.springframework.boot:spring-boot-starter-kafka") + testImplementation("org.springframework:spring-aop") + testImplementation("org.aspectj:aspectjweaver") runtimeOnly("com.h2database:h2") implementation(project(":smoke-tests-otel-starter:spring-boot-common")) + testImplementation("org.springframework.boot:spring-boot-starter-test") testImplementation("org.springframework.boot:spring-boot-resttestclient") - testImplementation("org.awaitility:awaitility") testImplementation(project(":instrumentation:spring:starters:spring-boot-starter")) testImplementation(project(":smoke-tests-otel-starter:spring-smoke-testing")) + testImplementation("org.springframework.boot:spring-boot-starter-kafka") + testImplementation("org.testcontainers:testcontainers-junit-jupiter") + testImplementation("org.testcontainers:testcontainers-kafka") } springBoot { diff --git a/smoke-tests-otel-starter/spring-boot-4/src/test/java/io/opentelemetry/spring/smoketest/AbstractJvmKafkaSpringStarterSmokeTest.java b/smoke-tests-otel-starter/spring-boot-4/src/test/java/io/opentelemetry/spring/smoketest/AbstractJvmKafkaSpringStarterSmokeTest.java new file mode 100644 index 000000000000..8d967308d774 --- /dev/null +++ b/smoke-tests-otel-starter/spring-boot-4/src/test/java/io/opentelemetry/spring/smoketest/AbstractJvmKafkaSpringStarterSmokeTest.java @@ -0,0 +1,79 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.spring.smoketest; + +import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.instrumentation.spring.autoconfigure.OpenTelemetryAutoConfiguration; +import io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.kafka.KafkaInstrumentationAutoConfiguration; +import io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.thread.ThreadDetailsAutoConfiguration; +import java.time.Duration; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.kafka.autoconfigure.KafkaAutoConfiguration; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; +import org.springframework.kafka.core.KafkaTemplate; +import org.testcontainers.containers.wait.strategy.Wait; +import org.testcontainers.kafka.KafkaContainer; +import org.testcontainers.utility.DockerImageName; + +/** Spring has a test container integration, but that doesn't work for Spring Boot 2 */ +public class AbstractJvmKafkaSpringStarterSmokeTest extends AbstractKafkaSpringStarterSmokeTest { + static KafkaContainer kafka; + + private ApplicationContextRunner contextRunner; + + @BeforeAll + static void setUpKafka() { + kafka = + new KafkaContainer(DockerImageName.parse("apache/kafka:3.8.0")) + .withEnv("KAFKA_HEAP_OPTS", "-Xmx256m") + .waitingFor(Wait.forLogMessage(".*started \\(kafka.server.Kafka.*Server\\).*", 1)) + .withStartupTimeout(Duration.ofMinutes(1)); + kafka.start(); + } + + @AfterAll + static void tearDownKafka() { + kafka.stop(); + } + + @BeforeEach + void setUpContext() { + contextRunner = + new ApplicationContextRunner() + .withAllowBeanDefinitionOverriding(true) + .withConfiguration( + AutoConfigurations.of( + OpenTelemetryAutoConfiguration.class, + ThreadDetailsAutoConfiguration.class, + SpringSmokeOtelConfiguration.class, + KafkaAutoConfiguration.class, + KafkaInstrumentationAutoConfiguration.class, + KafkaConfig.class)) + .withPropertyValues( + "otel.instrumentation.kafka.experimental-span-attributes=true", + "spring.kafka.bootstrap-servers=" + kafka.getBootstrapServers(), + "spring.kafka.consumer.auto-offset-reset=earliest", + "spring.kafka.consumer.linger-ms=10", + "spring.kafka.listener.idle-between-polls=1000", + "spring.kafka.producer.transaction-id-prefix=test-"); + } + + @SuppressWarnings("unchecked") // we lose parameter types for the KafkaTemplate + @Override + @Test + void shouldInstrumentProducerAndConsumer() { + contextRunner.run( + applicationContext -> { + testing = new SpringSmokeTestRunner(applicationContext.getBean(OpenTelemetry.class)); + kafkaTemplate = applicationContext.getBean(KafkaTemplate.class); + super.shouldInstrumentProducerAndConsumer(); + }); + } +} diff --git a/smoke-tests-otel-starter/spring-boot-4/src/test/java/io/opentelemetry/spring/smoketest/KafkaSpringStarterSmokeTest.java b/smoke-tests-otel-starter/spring-boot-4/src/test/java/io/opentelemetry/spring/smoketest/KafkaSpringStarterSmokeTest.java new file mode 100644 index 000000000000..5c8ad0f748e0 --- /dev/null +++ b/smoke-tests-otel-starter/spring-boot-4/src/test/java/io/opentelemetry/spring/smoketest/KafkaSpringStarterSmokeTest.java @@ -0,0 +1,11 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.spring.smoketest; + +import org.junit.jupiter.api.condition.DisabledInNativeImage; + +@DisabledInNativeImage // See GraalVmNativeKafkaSpringStarterSmokeTest for the GraalVM native test +class KafkaSpringStarterSmokeTest extends AbstractJvmKafkaSpringStarterSmokeTest {} diff --git a/smoke-tests-otel-starter/spring-boot-4/src/test/java/io/opentelemetry/spring/smoketest/OtelSpringStarterSmokeTest.java b/smoke-tests-otel-starter/spring-boot-4/src/test/java/io/opentelemetry/spring/smoketest/OtelSpringStarterSmokeTest.java index ccd04afe1cc4..0fd06e97e27f 100644 --- a/smoke-tests-otel-starter/spring-boot-4/src/test/java/io/opentelemetry/spring/smoketest/OtelSpringStarterSmokeTest.java +++ b/smoke-tests-otel-starter/spring-boot-4/src/test/java/io/opentelemetry/spring/smoketest/OtelSpringStarterSmokeTest.java @@ -23,6 +23,7 @@ "otel.instrumentation.runtime-telemetry.emit-experimental-telemetry=true", "otel.instrumentation.runtime-telemetry-java17.enable-all=true", "otel.instrumentation.common.thread_details.enabled=true", + "logging.level.org.springframework.boot.autoconfigure=DEBUG", }) @AutoConfigureTestRestTemplate class OtelSpringStarterSmokeTest extends AbstractOtelSpringStarterSmokeTest { From 6c13ecb8e8a7dbd64fe926a0530d1f4801c5f942 Mon Sep 17 00:00:00 2001 From: Jay DeLuca Date: Wed, 26 Nov 2025 06:44:52 -0500 Subject: [PATCH 04/40] kafka working --- .../build.gradle.kts | 16 ++- ...mentationSpringBoot4AutoConfiguration.java | 3 + ...tainerFactorySpringBoot4PostProcessor.java | 61 ++++++++++ ...mentationSpringBoot4AutoConfiguration.java | 4 +- ...cInstrumentationAutoConfigurationTest.java | 73 ++++++++++++ ...bInstrumentationAutoConfigurationTest.java | 104 ++++++++++++++++++ ...bstractJvmKafkaSpringStarterSmokeTest.java | 4 +- 7 files changed, 255 insertions(+), 10 deletions(-) create mode 100644 instrumentation/spring/spring-boot-autoconfigure/src/main/javaSpring4/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/kafka/ConcurrentKafkaListenerContainerFactorySpringBoot4PostProcessor.java create mode 100644 instrumentation/spring/spring-boot-autoconfigure/src/testSpring4/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/jdbc/JdbcInstrumentationAutoConfigurationTest.java create mode 100644 instrumentation/spring/spring-boot-autoconfigure/src/testSpring4/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/web/SpringWebInstrumentationAutoConfigurationTest.java diff --git a/instrumentation/spring/spring-boot-autoconfigure/build.gradle.kts b/instrumentation/spring/spring-boot-autoconfigure/build.gradle.kts index 264f6936819e..2b8dca42cd1f 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/build.gradle.kts +++ b/instrumentation/spring/spring-boot-autoconfigure/build.gradle.kts @@ -122,8 +122,8 @@ dependencies { add("javaSpring4CompileOnly", files(sourceSets.main.get().output.classesDirs)) // add("javaSpring4CompileOnly", "org.springframework.boot:spring-boot-starter-web:4.0.0") add("javaSpring4CompileOnly", "org.springframework.boot:spring-boot-starter-kafka:4.0.0") -// add("javaSpring4CompileOnly", "org.springframework.boot:spring-boot-autoconfigure:4.0.0") -// add("javaSpring4CompileOnly", "org.springframework.boot:spring-boot-jdbc:4.0.0") + add("javaSpring4CompileOnly", "org.springframework.boot:spring-boot-autoconfigure:4.0.0") + add("javaSpring4CompileOnly", "org.springframework.boot:spring-boot-jdbc:4.0.0") add("javaSpring4CompileOnly", "org.springframework.boot:spring-boot-starter-jdbc:4.0.0") add("javaSpring4CompileOnly", "org.springframework.boot:spring-boot-restclient:4.0.0") // add("javaSpring4CompileOnly", "io.opentelemetry:opentelemetry-sdk-extension-autoconfigure") @@ -202,14 +202,18 @@ testing { val testSpring4 by registering(JvmTestSuite::class) { dependencies { implementation(project()) - implementation("org.springframework.boot:spring-boot-starter-web:4.0.0") +// implementation("org.springframework.boot:spring-boot-starter-web:4.0.0") + implementation("org.springframework.boot:spring-boot-starter-jdbc:4.0.0") implementation("io.opentelemetry:opentelemetry-sdk-extension-autoconfigure") - implementation(project(":instrumentation:spring:spring-web:spring-web-3.1:library")) - implementation(project(":instrumentation:spring:spring-webmvc:spring-webmvc-6.0:library")) - implementation("jakarta.servlet:jakarta.servlet-api:6.1.0") + implementation("org.springframework.boot:spring-boot-restclient:4.0.0") + implementation("org.springframework.boot:spring-boot-starter-kafka:4.0.0") +// implementation(project(":instrumentation:spring:spring-web:spring-web-3.1:library")) +// implementation(project(":instrumentation:spring:spring-webmvc:spring-webmvc-6.0:library")) +// implementation("jakarta.servlet:jakarta.servlet-api:6.1.0") implementation("org.springframework.boot:spring-boot-starter-test:4.0.0") { exclude("org.junit.vintage", "junit-vintage-engine") } + runtimeOnly("com.h2database:h2:1.4.197") } } diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/main/javaSpring4/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/jdbc/JdbcInstrumentationSpringBoot4AutoConfiguration.java b/instrumentation/spring/spring-boot-autoconfigure/src/main/javaSpring4/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/jdbc/JdbcInstrumentationSpringBoot4AutoConfiguration.java index 58d26eae25e7..e8bc73a2fde7 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/main/javaSpring4/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/jdbc/JdbcInstrumentationSpringBoot4AutoConfiguration.java +++ b/instrumentation/spring/spring-boot-autoconfigure/src/main/javaSpring4/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/jdbc/JdbcInstrumentationSpringBoot4AutoConfiguration.java @@ -12,6 +12,7 @@ import org.springframework.beans.factory.ObjectProvider; import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass; import org.springframework.boot.jdbc.autoconfigure.DataSourceAutoConfiguration; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -24,6 +25,8 @@ @AutoConfiguration(after = DataSourceAutoConfiguration.class) @ConditionalOnBean({DataSource.class}) @Configuration(proxyBeanMethods = false) +@ConditionalOnMissingClass( + "org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration") public class JdbcInstrumentationSpringBoot4AutoConfiguration { // For error prone diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/main/javaSpring4/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/kafka/ConcurrentKafkaListenerContainerFactorySpringBoot4PostProcessor.java b/instrumentation/spring/spring-boot-autoconfigure/src/main/javaSpring4/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/kafka/ConcurrentKafkaListenerContainerFactorySpringBoot4PostProcessor.java new file mode 100644 index 000000000000..e971f4b27b85 --- /dev/null +++ b/instrumentation/spring/spring-boot-autoconfigure/src/main/javaSpring4/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/kafka/ConcurrentKafkaListenerContainerFactorySpringBoot4PostProcessor.java @@ -0,0 +1,61 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.kafka; + +import io.opentelemetry.instrumentation.spring.kafka.v2_7.SpringKafkaTelemetry; +import java.lang.reflect.Field; +import java.util.function.Supplier; +import javax.annotation.Nullable; +import org.springframework.beans.factory.config.BeanPostProcessor; +import org.springframework.kafka.config.AbstractKafkaListenerContainerFactory; +import org.springframework.kafka.config.ConcurrentKafkaListenerContainerFactory; +import org.springframework.kafka.listener.BatchInterceptor; +import org.springframework.kafka.listener.RecordInterceptor; + +class ConcurrentKafkaListenerContainerFactorySpringBoot4PostProcessor implements BeanPostProcessor { + + private final Supplier springKafkaTelemetry; + + ConcurrentKafkaListenerContainerFactorySpringBoot4PostProcessor( + Supplier springKafkaTelemetry) { + this.springKafkaTelemetry = springKafkaTelemetry; + } + + @SuppressWarnings("unchecked") // we check the bean type before casting + @Override + public Object postProcessAfterInitialization(Object bean, String beanName) { + if (!(bean instanceof ConcurrentKafkaListenerContainerFactory)) { + return bean; + } + + ConcurrentKafkaListenerContainerFactory listenerContainerFactory = + (ConcurrentKafkaListenerContainerFactory) bean; + SpringKafkaTelemetry springKafkaTelemetry = this.springKafkaTelemetry.get(); + + // use reflection to read existing values to avoid overwriting user configured interceptors + BatchInterceptor batchInterceptor = + readField(listenerContainerFactory, "batchInterceptor", BatchInterceptor.class); + RecordInterceptor recordInterceptor = + readField(listenerContainerFactory, "recordInterceptor", RecordInterceptor.class); + listenerContainerFactory.setBatchInterceptor( + springKafkaTelemetry.createBatchInterceptor(batchInterceptor)); + listenerContainerFactory.setRecordInterceptor( + springKafkaTelemetry.createRecordInterceptor(recordInterceptor)); + + return listenerContainerFactory; + } + + @Nullable + private static T readField(Object container, String filedName, Class fieldType) { + try { + Field field = AbstractKafkaListenerContainerFactory.class.getDeclaredField(filedName); + field.setAccessible(true); + return fieldType.cast(field.get(container)); + } catch (Exception exception) { + return null; + } + } +} diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/main/javaSpring4/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/kafka/KafkaInstrumentationSpringBoot4AutoConfiguration.java b/instrumentation/spring/spring-boot-autoconfigure/src/main/javaSpring4/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/kafka/KafkaInstrumentationSpringBoot4AutoConfiguration.java index cb1328f01e06..9c5242144669 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/main/javaSpring4/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/kafka/KafkaInstrumentationSpringBoot4AutoConfiguration.java +++ b/instrumentation/spring/spring-boot-autoconfigure/src/main/javaSpring4/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/kafka/KafkaInstrumentationSpringBoot4AutoConfiguration.java @@ -63,11 +63,11 @@ static SpringKafkaTelemetry getTelemetry( havingValue = "true", matchIfMissing = true) @ConditionalOnMissingBean - static ConcurrentKafkaListenerContainerFactoryPostProcessor + static ConcurrentKafkaListenerContainerFactorySpringBoot4PostProcessor otelKafkaListenerContainerFactoryBeanPostProcessor( ObjectProvider openTelemetryProvider, ObjectProvider configProvider) { - return new ConcurrentKafkaListenerContainerFactoryPostProcessor( + return new ConcurrentKafkaListenerContainerFactorySpringBoot4PostProcessor( () -> getTelemetry(openTelemetryProvider, configProvider)); } } diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/testSpring4/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/jdbc/JdbcInstrumentationAutoConfigurationTest.java b/instrumentation/spring/spring-boot-autoconfigure/src/testSpring4/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/jdbc/JdbcInstrumentationAutoConfigurationTest.java new file mode 100644 index 000000000000..98a7d547d70a --- /dev/null +++ b/instrumentation/spring/spring-boot-autoconfigure/src/testSpring4/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/jdbc/JdbcInstrumentationAutoConfigurationTest.java @@ -0,0 +1,73 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.jdbc; + +import static io.opentelemetry.instrumentation.testing.junit.db.SemconvStabilityUtil.maybeStable; +import static io.opentelemetry.semconv.incubating.DbIncubatingAttributes.DB_STATEMENT; +import static org.assertj.core.api.Assertions.assertThat; + +import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.instrumentation.api.incubator.config.internal.InstrumentationConfig; +import io.opentelemetry.instrumentation.spring.autoconfigure.internal.properties.ConfigPropertiesBridge; +import io.opentelemetry.instrumentation.testing.junit.LibraryInstrumentationExtension; +import io.opentelemetry.sdk.autoconfigure.spi.internal.DefaultConfigProperties; +import java.sql.Connection; +import java.sql.Statement; +import java.util.Collections; +import javax.sql.DataSource; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; +import org.springframework.aop.framework.Advised; +import org.springframework.aop.support.AopUtils; +import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.jdbc.autoconfigure.DataSourceAutoConfiguration; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; + +class JdbcInstrumentationAutoConfigurationTest { + + @RegisterExtension + static final LibraryInstrumentationExtension testing = LibraryInstrumentationExtension.create(); + + private final ApplicationContextRunner runner = + new ApplicationContextRunner() + .withBean( + InstrumentationConfig.class, + () -> + new ConfigPropertiesBridge( + DefaultConfigProperties.createFromMap(Collections.emptyMap()))) + .withConfiguration( + AutoConfigurations.of( + JdbcInstrumentationSpringBoot4AutoConfiguration.class, + DataSourceAutoConfiguration.class)) + .withBean("openTelemetry", OpenTelemetry.class, testing::getOpenTelemetry); + + @SuppressWarnings("deprecation") // using deprecated semconv + @Test + void statementSanitizerEnabledByDefault() { + runner.run( + context -> { + DataSource dataSource = context.getBean(DataSource.class); + + assertThat(AopUtils.isAopProxy(dataSource)).isTrue(); + assertThat(dataSource.getClass().getSimpleName()).isNotEqualTo("HikariDataSource"); + // unwrap the instrumented data source to get the original data source + Object original = ((Advised) dataSource).getTargetSource().getTarget(); + assertThat(AopUtils.isAopProxy(original)).isFalse(); + assertThat(original.getClass().getSimpleName()).isEqualTo("HikariDataSource"); + + try (Connection connection = dataSource.getConnection()) { + try (Statement statement = connection.createStatement()) { + statement.execute("SELECT 1"); + } + } + + testing.waitAndAssertTraces( + trace -> + trace.hasSpansSatisfyingExactly( + span -> span.hasAttribute(maybeStable(DB_STATEMENT), "SELECT ?"))); + }); + } +} diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/testSpring4/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/web/SpringWebInstrumentationAutoConfigurationTest.java b/instrumentation/spring/spring-boot-autoconfigure/src/testSpring4/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/web/SpringWebInstrumentationAutoConfigurationTest.java new file mode 100644 index 000000000000..c19b8dec242a --- /dev/null +++ b/instrumentation/spring/spring-boot-autoconfigure/src/testSpring4/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/web/SpringWebInstrumentationAutoConfigurationTest.java @@ -0,0 +1,104 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.web; + +import static org.assertj.core.api.Assertions.assertThat; + +import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.instrumentation.api.incubator.config.internal.InstrumentationConfig; +import io.opentelemetry.instrumentation.spring.autoconfigure.internal.properties.ConfigPropertiesBridge; +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; +import org.springframework.web.client.RestTemplate; + +class SpringWebInstrumentationAutoConfigurationTest { + + private final ApplicationContextRunner contextRunner = + new ApplicationContextRunner() + .withBean(OpenTelemetry.class, OpenTelemetry::noop) + .withBean( + InstrumentationConfig.class, + () -> + new ConfigPropertiesBridge( + DefaultConfigProperties.createFromMap(Collections.emptyMap()))) + .withBean(RestTemplate.class, RestTemplate::new) + .withConfiguration( + AutoConfigurations.of(SpringWebInstrumentationSpringBoot4AutoConfiguration.class)); + + /** + * Tests that users create {@link RestTemplate} bean is instrumented. + * + *

{@code
+   * @Bean public RestTemplate restTemplate() {
+   *     return new RestTemplate();
+   * }
+   * }
+ */ + @Test + void instrumentationEnabled() { + contextRunner + .withPropertyValues("otel.instrumentation.spring-web.enabled=true") + .withPropertyValues("otel.instrumentation.common.default-enabled=false") + .run( + context -> { + assertThat( + context.getBean( + "otelRestTemplateBeanPostProcessor", RestTemplateBeanPostProcessor.class)) + .isNotNull(); + + assertThat( + context.getBean(RestTemplate.class).getInterceptors().stream() + .filter( + rti -> + rti.getClass() + .getName() + .startsWith("io.opentelemetry.instrumentation")) + .count()) + .isEqualTo(1); + }); + } + + @Test + void instrumentationDisabled() { + contextRunner + .withPropertyValues("otel.instrumentation.spring-web.enabled=false") + .run( + context -> + assertThat(context.containsBean("otelRestTemplateBeanPostProcessor")).isFalse()); + } + + @Test + void instrumentationDisabledButAllEnabled() { + contextRunner + .withPropertyValues("otel.instrumentation.spring-web.enabled=false") + .withPropertyValues("otel.instrumentation.common.default-enabled=true") + .run( + context -> + assertThat(context.containsBean("otelRestTemplateBeanPostProcessor")).isFalse()); + } + + @Test + void allInstrumentationDisabled() { + contextRunner + .withPropertyValues("otel.instrumentation.common.default-enabled=false") + .run( + context -> + assertThat(context.containsBean("otelRestTemplateBeanPostProcessor")).isFalse()); + } + + @Test + void defaultConfiguration() { + contextRunner.run( + context -> + assertThat( + context.getBean( + "otelRestTemplateBeanPostProcessor", RestTemplateBeanPostProcessor.class)) + .isNotNull()); + } +} diff --git a/smoke-tests-otel-starter/spring-boot-4/src/test/java/io/opentelemetry/spring/smoketest/AbstractJvmKafkaSpringStarterSmokeTest.java b/smoke-tests-otel-starter/spring-boot-4/src/test/java/io/opentelemetry/spring/smoketest/AbstractJvmKafkaSpringStarterSmokeTest.java index 8d967308d774..cbfbebc385bc 100644 --- a/smoke-tests-otel-starter/spring-boot-4/src/test/java/io/opentelemetry/spring/smoketest/AbstractJvmKafkaSpringStarterSmokeTest.java +++ b/smoke-tests-otel-starter/spring-boot-4/src/test/java/io/opentelemetry/spring/smoketest/AbstractJvmKafkaSpringStarterSmokeTest.java @@ -7,7 +7,7 @@ import io.opentelemetry.api.OpenTelemetry; import io.opentelemetry.instrumentation.spring.autoconfigure.OpenTelemetryAutoConfiguration; -import io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.kafka.KafkaInstrumentationAutoConfiguration; +import io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.kafka.KafkaInstrumentationSpringBoot4AutoConfiguration; import io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.thread.ThreadDetailsAutoConfiguration; import java.time.Duration; import org.junit.jupiter.api.AfterAll; @@ -54,7 +54,7 @@ void setUpContext() { ThreadDetailsAutoConfiguration.class, SpringSmokeOtelConfiguration.class, KafkaAutoConfiguration.class, - KafkaInstrumentationAutoConfiguration.class, + KafkaInstrumentationSpringBoot4AutoConfiguration.class, KafkaConfig.class)) .withPropertyValues( "otel.instrumentation.kafka.experimental-span-attributes=true", From 330cd8937f5ab093e02e91fb7d236b786dcd811f Mon Sep 17 00:00:00 2001 From: Jay DeLuca Date: Wed, 26 Nov 2025 08:09:26 -0500 Subject: [PATCH 05/40] most things working --- .../build.gradle.kts | 13 +-- ...lientInstrumentationAutoConfiguration.java | 3 + ...tainerFactorySpringBoot4PostProcessor.java | 61 -------------- ...mentationSpringBoot4AutoConfiguration.java | 4 +- ...mentationSpringBoot4AutoConfiguration.java | 43 ++++++++++ .../main/resources/META-INF/spring.factories | 1 + ...ot.autoconfigure.AutoConfiguration.imports | 2 +- .../spring-boot-4/build.gradle.kts | 11 ++- ...bstractJvmKafkaSpringStarterSmokeTest.java | 79 ------------------- ...alVmNativeKafkaSpringStarterSmokeTest.java | 32 ++++++++ ...VmNativeMongodbSpringStarterSmokeTest.java | 28 +++++++ .../KafkaSpringStarterSmokeTest.java | 71 ++++++++++++++++- .../MongoSpringStarterSmokeTest.java | 65 +++++++++++++++ .../OtelSpringStarterDisabledSmokeTest.java | 52 ++++++++++++ ...elSpringStarterUserDataSourceBeanTest.java | 14 ++++ ...gbackInstrumentationDisabledSmokeTest.java | 38 +++++++++ 16 files changed, 359 insertions(+), 158 deletions(-) delete mode 100644 instrumentation/spring/spring-boot-autoconfigure/src/main/javaSpring4/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/kafka/ConcurrentKafkaListenerContainerFactorySpringBoot4PostProcessor.java create mode 100644 instrumentation/spring/spring-boot-autoconfigure/src/main/javaSpring4/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/mongo/MongoClientInstrumentationSpringBoot4AutoConfiguration.java delete mode 100644 smoke-tests-otel-starter/spring-boot-4/src/test/java/io/opentelemetry/spring/smoketest/AbstractJvmKafkaSpringStarterSmokeTest.java create mode 100644 smoke-tests-otel-starter/spring-boot-4/src/test/java/io/opentelemetry/spring/smoketest/GraalVmNativeKafkaSpringStarterSmokeTest.java create mode 100644 smoke-tests-otel-starter/spring-boot-4/src/test/java/io/opentelemetry/spring/smoketest/GraalVmNativeMongodbSpringStarterSmokeTest.java create mode 100644 smoke-tests-otel-starter/spring-boot-4/src/test/java/io/opentelemetry/spring/smoketest/MongoSpringStarterSmokeTest.java create mode 100644 smoke-tests-otel-starter/spring-boot-4/src/test/java/io/opentelemetry/spring/smoketest/OtelSpringStarterDisabledSmokeTest.java create mode 100644 smoke-tests-otel-starter/spring-boot-4/src/test/java/io/opentelemetry/spring/smoketest/OtelSpringStarterUserDataSourceBeanTest.java create mode 100644 smoke-tests-otel-starter/spring-boot-4/src/test/java/io/opentelemetry/spring/smoketest/OtelSpringStarterWithLogbackInstrumentationDisabledSmokeTest.java diff --git a/instrumentation/spring/spring-boot-autoconfigure/build.gradle.kts b/instrumentation/spring/spring-boot-autoconfigure/build.gradle.kts index 2b8dca42cd1f..730672bb460c 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/build.gradle.kts +++ b/instrumentation/spring/spring-boot-autoconfigure/build.gradle.kts @@ -120,18 +120,15 @@ dependencies { // Spring Boot 4 add("javaSpring4CompileOnly", files(sourceSets.main.get().output.classesDirs)) -// add("javaSpring4CompileOnly", "org.springframework.boot:spring-boot-starter-web:4.0.0") add("javaSpring4CompileOnly", "org.springframework.boot:spring-boot-starter-kafka:4.0.0") add("javaSpring4CompileOnly", "org.springframework.boot:spring-boot-autoconfigure:4.0.0") add("javaSpring4CompileOnly", "org.springframework.boot:spring-boot-jdbc:4.0.0") add("javaSpring4CompileOnly", "org.springframework.boot:spring-boot-starter-jdbc:4.0.0") add("javaSpring4CompileOnly", "org.springframework.boot:spring-boot-restclient:4.0.0") -// add("javaSpring4CompileOnly", "io.opentelemetry:opentelemetry-sdk-extension-autoconfigure") -// add("javaSpring4CompileOnly", project(":instrumentation:spring:spring-web:spring-web-3.1:library")) -// add("javaSpring4CompileOnly", project(":instrumentation:spring:spring-webmvc:spring-webmvc-6.0:library")) + add("javaSpring4CompileOnly", "org.springframework.boot:spring-boot-starter-data-mongodb:4.0.0") add("javaSpring4CompileOnly", project(":instrumentation:kafka:kafka-clients:kafka-clients-2.6:library")) add("javaSpring4CompileOnly", project(":instrumentation:spring:spring-kafka-2.7:library")) -// add("javaSpring4CompileOnly", project(":instrumentation:micrometer:micrometer-1.5:library")) + add("javaSpring4CompileOnly", project(":instrumentation:mongo:mongo-3.1:library")) } val latestDepTest = findProperty("testLatestDeps") as Boolean @@ -202,14 +199,10 @@ testing { val testSpring4 by registering(JvmTestSuite::class) { dependencies { implementation(project()) -// implementation("org.springframework.boot:spring-boot-starter-web:4.0.0") - implementation("org.springframework.boot:spring-boot-starter-jdbc:4.0.0") implementation("io.opentelemetry:opentelemetry-sdk-extension-autoconfigure") + implementation("org.springframework.boot:spring-boot-starter-jdbc:4.0.0") implementation("org.springframework.boot:spring-boot-restclient:4.0.0") implementation("org.springframework.boot:spring-boot-starter-kafka:4.0.0") -// implementation(project(":instrumentation:spring:spring-web:spring-web-3.1:library")) -// implementation(project(":instrumentation:spring:spring-webmvc:spring-webmvc-6.0:library")) -// implementation("jakarta.servlet:jakarta.servlet-api:6.1.0") implementation("org.springframework.boot:spring-boot-starter-test:4.0.0") { exclude("org.junit.vintage", "junit-vintage-engine") } diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/mongo/MongoClientInstrumentationAutoConfiguration.java b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/mongo/MongoClientInstrumentationAutoConfiguration.java index fcc3bc46a315..5cd07a39e2f2 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/mongo/MongoClientInstrumentationAutoConfiguration.java +++ b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/mongo/MongoClientInstrumentationAutoConfiguration.java @@ -12,6 +12,7 @@ import io.opentelemetry.instrumentation.spring.autoconfigure.internal.ConditionalOnEnabledInstrumentation; import io.opentelemetry.instrumentation.spring.autoconfigure.internal.properties.InstrumentationConfigUtil; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass; import org.springframework.boot.autoconfigure.mongo.MongoClientSettingsBuilderCustomizer; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -22,6 +23,8 @@ */ @ConditionalOnClass({MongoClientSettings.class, MongoClientSettingsBuilderCustomizer.class}) @ConditionalOnEnabledInstrumentation(module = "mongo") +@ConditionalOnMissingClass( + "org.springframework.boot.mongodb.autoconfigure.MongoClientSettingsBuilderCustomizer") @Configuration public class MongoClientInstrumentationAutoConfiguration { diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/main/javaSpring4/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/kafka/ConcurrentKafkaListenerContainerFactorySpringBoot4PostProcessor.java b/instrumentation/spring/spring-boot-autoconfigure/src/main/javaSpring4/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/kafka/ConcurrentKafkaListenerContainerFactorySpringBoot4PostProcessor.java deleted file mode 100644 index e971f4b27b85..000000000000 --- a/instrumentation/spring/spring-boot-autoconfigure/src/main/javaSpring4/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/kafka/ConcurrentKafkaListenerContainerFactorySpringBoot4PostProcessor.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.kafka; - -import io.opentelemetry.instrumentation.spring.kafka.v2_7.SpringKafkaTelemetry; -import java.lang.reflect.Field; -import java.util.function.Supplier; -import javax.annotation.Nullable; -import org.springframework.beans.factory.config.BeanPostProcessor; -import org.springframework.kafka.config.AbstractKafkaListenerContainerFactory; -import org.springframework.kafka.config.ConcurrentKafkaListenerContainerFactory; -import org.springframework.kafka.listener.BatchInterceptor; -import org.springframework.kafka.listener.RecordInterceptor; - -class ConcurrentKafkaListenerContainerFactorySpringBoot4PostProcessor implements BeanPostProcessor { - - private final Supplier springKafkaTelemetry; - - ConcurrentKafkaListenerContainerFactorySpringBoot4PostProcessor( - Supplier springKafkaTelemetry) { - this.springKafkaTelemetry = springKafkaTelemetry; - } - - @SuppressWarnings("unchecked") // we check the bean type before casting - @Override - public Object postProcessAfterInitialization(Object bean, String beanName) { - if (!(bean instanceof ConcurrentKafkaListenerContainerFactory)) { - return bean; - } - - ConcurrentKafkaListenerContainerFactory listenerContainerFactory = - (ConcurrentKafkaListenerContainerFactory) bean; - SpringKafkaTelemetry springKafkaTelemetry = this.springKafkaTelemetry.get(); - - // use reflection to read existing values to avoid overwriting user configured interceptors - BatchInterceptor batchInterceptor = - readField(listenerContainerFactory, "batchInterceptor", BatchInterceptor.class); - RecordInterceptor recordInterceptor = - readField(listenerContainerFactory, "recordInterceptor", RecordInterceptor.class); - listenerContainerFactory.setBatchInterceptor( - springKafkaTelemetry.createBatchInterceptor(batchInterceptor)); - listenerContainerFactory.setRecordInterceptor( - springKafkaTelemetry.createRecordInterceptor(recordInterceptor)); - - return listenerContainerFactory; - } - - @Nullable - private static T readField(Object container, String filedName, Class fieldType) { - try { - Field field = AbstractKafkaListenerContainerFactory.class.getDeclaredField(filedName); - field.setAccessible(true); - return fieldType.cast(field.get(container)); - } catch (Exception exception) { - return null; - } - } -} diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/main/javaSpring4/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/kafka/KafkaInstrumentationSpringBoot4AutoConfiguration.java b/instrumentation/spring/spring-boot-autoconfigure/src/main/javaSpring4/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/kafka/KafkaInstrumentationSpringBoot4AutoConfiguration.java index 9c5242144669..cb1328f01e06 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/main/javaSpring4/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/kafka/KafkaInstrumentationSpringBoot4AutoConfiguration.java +++ b/instrumentation/spring/spring-boot-autoconfigure/src/main/javaSpring4/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/kafka/KafkaInstrumentationSpringBoot4AutoConfiguration.java @@ -63,11 +63,11 @@ static SpringKafkaTelemetry getTelemetry( havingValue = "true", matchIfMissing = true) @ConditionalOnMissingBean - static ConcurrentKafkaListenerContainerFactorySpringBoot4PostProcessor + static ConcurrentKafkaListenerContainerFactoryPostProcessor otelKafkaListenerContainerFactoryBeanPostProcessor( ObjectProvider openTelemetryProvider, ObjectProvider configProvider) { - return new ConcurrentKafkaListenerContainerFactorySpringBoot4PostProcessor( + return new ConcurrentKafkaListenerContainerFactoryPostProcessor( () -> getTelemetry(openTelemetryProvider, configProvider)); } } diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/main/javaSpring4/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/mongo/MongoClientInstrumentationSpringBoot4AutoConfiguration.java b/instrumentation/spring/spring-boot-autoconfigure/src/main/javaSpring4/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/mongo/MongoClientInstrumentationSpringBoot4AutoConfiguration.java new file mode 100644 index 000000000000..0f8b02cdf3ca --- /dev/null +++ b/instrumentation/spring/spring-boot-autoconfigure/src/main/javaSpring4/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/mongo/MongoClientInstrumentationSpringBoot4AutoConfiguration.java @@ -0,0 +1,43 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.mongo; + +import com.mongodb.MongoClientSettings; +import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.instrumentation.api.incubator.config.internal.InstrumentationConfig; +import io.opentelemetry.instrumentation.mongo.v3_1.MongoTelemetry; +import io.opentelemetry.instrumentation.spring.autoconfigure.internal.ConditionalOnEnabledInstrumentation; +import io.opentelemetry.instrumentation.spring.autoconfigure.internal.properties.InstrumentationConfigUtil; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass; +import org.springframework.boot.mongodb.autoconfigure.MongoClientSettingsBuilderCustomizer; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * This class is internal and is hence not for public use. Its APIs are unstable and can change at + * any time. + */ +@ConditionalOnClass({MongoClientSettings.class, MongoClientSettingsBuilderCustomizer.class}) +@ConditionalOnEnabledInstrumentation(module = "mongo") +@ConditionalOnMissingClass( + "org.springframework.boot.autoconfigure.mongo.MongoClientSettingsBuilderCustomizer") +@Configuration +public class MongoClientInstrumentationSpringBoot4AutoConfiguration { + + @Bean + MongoClientSettingsBuilderCustomizer customizer( + OpenTelemetry openTelemetry, InstrumentationConfig config) { + return builder -> + builder.addCommandListener( + MongoTelemetry.builder(openTelemetry) + .setStatementSanitizationEnabled( + InstrumentationConfigUtil.isStatementSanitizationEnabled( + config, "otel.instrumentation.mongo.statement-sanitizer.enabled")) + .build() + .newCommandListener()); + } +} 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 aa3f1300ec31..19fb234190e5 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 @@ -3,6 +3,7 @@ io.opentelemetry.instrumentation.spring.autoconfigure.OpenTelemetryAutoConfigura io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.annotations.InstrumentationAnnotationsAutoConfiguration,\ io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.kafka.KafkaInstrumentationAutoConfiguration,\ io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.mongo.MongoClientInstrumentationAutoConfiguration,\ +io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.mongo.MongoClientInstrumentationSpringBoot4AutoConfiguration,\ io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.logging.OpenTelemetryAppenderAutoConfiguration,\ io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.jdbc.JdbcInstrumentationAutoConfiguration,\ io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.micrometer.MicrometerBridgeAutoConfiguration,\ 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 7f6ff904c014..5977a97ab10d 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 @@ -2,7 +2,7 @@ io.opentelemetry.instrumentation.spring.autoconfigure.OpenTelemetryAutoConfigura io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.annotations.InstrumentationAnnotationsAutoConfiguration io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.kafka.KafkaInstrumentationAutoConfiguration io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.kafka.KafkaInstrumentationSpringBoot4AutoConfiguration -io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.mongo.MongoClientInstrumentationAutoConfiguration +io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.mongo.MongoClientInstrumentationSpringBoot4AutoConfiguration io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.logging.OpenTelemetryAppenderAutoConfiguration io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.jdbc.JdbcInstrumentationAutoConfiguration io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.jdbc.JdbcInstrumentationSpringBoot4AutoConfiguration diff --git a/smoke-tests-otel-starter/spring-boot-4/build.gradle.kts b/smoke-tests-otel-starter/spring-boot-4/build.gradle.kts index 5d42556714fc..ea57653ff7f2 100644 --- a/smoke-tests-otel-starter/spring-boot-4/build.gradle.kts +++ b/smoke-tests-otel-starter/spring-boot-4/build.gradle.kts @@ -10,24 +10,27 @@ otelJava { } dependencies { - implementation(platform(org.springframework.boot.gradle.plugin.SpringBootPlugin.BOM_COORDINATES)) - implementation("org.springframework.boot:spring-boot-starter-web") implementation("org.springframework.boot:spring-boot-starter-data-jdbc") implementation("org.springframework.boot:spring-boot-starter-kafka") - testImplementation("org.springframework:spring-aop") - testImplementation("org.aspectj:aspectjweaver") + implementation("org.springframework.boot:spring-boot-starter-data-mongodb") + implementation("org.springframework:spring-aop") + implementation("org.aspectj:aspectjweaver") + implementation("org.apache.commons:commons-dbcp2") runtimeOnly("com.h2database:h2") + implementation(platform(org.springframework.boot.gradle.plugin.SpringBootPlugin.BOM_COORDINATES)) implementation(project(":smoke-tests-otel-starter:spring-boot-common")) testImplementation("org.springframework.boot:spring-boot-starter-test") testImplementation("org.springframework.boot:spring-boot-resttestclient") + testImplementation("org.springframework.boot:spring-boot-starter-data-mongodb") testImplementation(project(":instrumentation:spring:starters:spring-boot-starter")) testImplementation(project(":smoke-tests-otel-starter:spring-smoke-testing")) testImplementation("org.springframework.boot:spring-boot-starter-kafka") testImplementation("org.testcontainers:testcontainers-junit-jupiter") testImplementation("org.testcontainers:testcontainers-kafka") + testImplementation("org.testcontainers:testcontainers-mongodb") } springBoot { diff --git a/smoke-tests-otel-starter/spring-boot-4/src/test/java/io/opentelemetry/spring/smoketest/AbstractJvmKafkaSpringStarterSmokeTest.java b/smoke-tests-otel-starter/spring-boot-4/src/test/java/io/opentelemetry/spring/smoketest/AbstractJvmKafkaSpringStarterSmokeTest.java deleted file mode 100644 index cbfbebc385bc..000000000000 --- a/smoke-tests-otel-starter/spring-boot-4/src/test/java/io/opentelemetry/spring/smoketest/AbstractJvmKafkaSpringStarterSmokeTest.java +++ /dev/null @@ -1,79 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.spring.smoketest; - -import io.opentelemetry.api.OpenTelemetry; -import io.opentelemetry.instrumentation.spring.autoconfigure.OpenTelemetryAutoConfiguration; -import io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.kafka.KafkaInstrumentationSpringBoot4AutoConfiguration; -import io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.thread.ThreadDetailsAutoConfiguration; -import java.time.Duration; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.springframework.boot.autoconfigure.AutoConfigurations; -import org.springframework.boot.kafka.autoconfigure.KafkaAutoConfiguration; -import org.springframework.boot.test.context.runner.ApplicationContextRunner; -import org.springframework.kafka.core.KafkaTemplate; -import org.testcontainers.containers.wait.strategy.Wait; -import org.testcontainers.kafka.KafkaContainer; -import org.testcontainers.utility.DockerImageName; - -/** Spring has a test container integration, but that doesn't work for Spring Boot 2 */ -public class AbstractJvmKafkaSpringStarterSmokeTest extends AbstractKafkaSpringStarterSmokeTest { - static KafkaContainer kafka; - - private ApplicationContextRunner contextRunner; - - @BeforeAll - static void setUpKafka() { - kafka = - new KafkaContainer(DockerImageName.parse("apache/kafka:3.8.0")) - .withEnv("KAFKA_HEAP_OPTS", "-Xmx256m") - .waitingFor(Wait.forLogMessage(".*started \\(kafka.server.Kafka.*Server\\).*", 1)) - .withStartupTimeout(Duration.ofMinutes(1)); - kafka.start(); - } - - @AfterAll - static void tearDownKafka() { - kafka.stop(); - } - - @BeforeEach - void setUpContext() { - contextRunner = - new ApplicationContextRunner() - .withAllowBeanDefinitionOverriding(true) - .withConfiguration( - AutoConfigurations.of( - OpenTelemetryAutoConfiguration.class, - ThreadDetailsAutoConfiguration.class, - SpringSmokeOtelConfiguration.class, - KafkaAutoConfiguration.class, - KafkaInstrumentationSpringBoot4AutoConfiguration.class, - KafkaConfig.class)) - .withPropertyValues( - "otel.instrumentation.kafka.experimental-span-attributes=true", - "spring.kafka.bootstrap-servers=" + kafka.getBootstrapServers(), - "spring.kafka.consumer.auto-offset-reset=earliest", - "spring.kafka.consumer.linger-ms=10", - "spring.kafka.listener.idle-between-polls=1000", - "spring.kafka.producer.transaction-id-prefix=test-"); - } - - @SuppressWarnings("unchecked") // we lose parameter types for the KafkaTemplate - @Override - @Test - void shouldInstrumentProducerAndConsumer() { - contextRunner.run( - applicationContext -> { - testing = new SpringSmokeTestRunner(applicationContext.getBean(OpenTelemetry.class)); - kafkaTemplate = applicationContext.getBean(KafkaTemplate.class); - super.shouldInstrumentProducerAndConsumer(); - }); - } -} diff --git a/smoke-tests-otel-starter/spring-boot-4/src/test/java/io/opentelemetry/spring/smoketest/GraalVmNativeKafkaSpringStarterSmokeTest.java b/smoke-tests-otel-starter/spring-boot-4/src/test/java/io/opentelemetry/spring/smoketest/GraalVmNativeKafkaSpringStarterSmokeTest.java new file mode 100644 index 000000000000..aeb23c97f371 --- /dev/null +++ b/smoke-tests-otel-starter/spring-boot-4/src/test/java/io/opentelemetry/spring/smoketest/GraalVmNativeKafkaSpringStarterSmokeTest.java @@ -0,0 +1,32 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.spring.smoketest; + +import org.junit.jupiter.api.condition.EnabledInNativeImage; +import org.springframework.boot.test.context.SpringBootTest; + +/** + * GraalVM native image doesn't support Testcontainers in our case, so the docker container is + * started manually before running the tests. + * + *

In other cases, it does work, e.g. here, + * it's not yet clear why it doesn't work in our case. + * + *

In CI, this is done in reusable-native-tests.yml. If you want to run the tests locally, you + * need to start the container manually: see .github/workflows/reusable-native-tests.yml for the + * command. + */ +@SpringBootTest( + classes = { + OtelSpringStarterSmokeTestApplication.class, + SpringSmokeOtelConfiguration.class, + AbstractKafkaSpringStarterSmokeTest.KafkaConfig.class + }, + webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +@EnabledInNativeImage // see AbstractJvmKafkaSpringStarterSmokeTest for the JVM test +@RequiresDockerCompose +class GraalVmNativeKafkaSpringStarterSmokeTest extends AbstractKafkaSpringStarterSmokeTest {} diff --git a/smoke-tests-otel-starter/spring-boot-4/src/test/java/io/opentelemetry/spring/smoketest/GraalVmNativeMongodbSpringStarterSmokeTest.java b/smoke-tests-otel-starter/spring-boot-4/src/test/java/io/opentelemetry/spring/smoketest/GraalVmNativeMongodbSpringStarterSmokeTest.java new file mode 100644 index 000000000000..f800d75ef794 --- /dev/null +++ b/smoke-tests-otel-starter/spring-boot-4/src/test/java/io/opentelemetry/spring/smoketest/GraalVmNativeMongodbSpringStarterSmokeTest.java @@ -0,0 +1,28 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.spring.smoketest; + +import org.junit.jupiter.api.condition.EnabledInNativeImage; +import org.springframework.boot.test.context.SpringBootTest; + +/** + * GraalVM native image doesn't support Testcontainers in our case, so the docker container is + * started manually before running the tests. + * + *

In other cases, it does work, e.g. here, + * it's not yet clear why it doesn't work in our case. + * + *

In CI, this is done in reusable-native-tests.yml. If you want to run the tests locally, you + * need to start the container manually: see .github/workflows/reusable-native-tests.yml for the + * command. + */ +@SpringBootTest( + classes = {OtelSpringStarterSmokeTestApplication.class, SpringSmokeOtelConfiguration.class}, + webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +@EnabledInNativeImage // see AbstractJvmMongodbSpringStarterSmokeTest for the JVM test +@RequiresDockerCompose +class GraalVmNativeMongodbSpringStarterSmokeTest extends AbstractMongodbSpringStarterSmokeTest {} diff --git a/smoke-tests-otel-starter/spring-boot-4/src/test/java/io/opentelemetry/spring/smoketest/KafkaSpringStarterSmokeTest.java b/smoke-tests-otel-starter/spring-boot-4/src/test/java/io/opentelemetry/spring/smoketest/KafkaSpringStarterSmokeTest.java index 5c8ad0f748e0..40ddb3a290aa 100644 --- a/smoke-tests-otel-starter/spring-boot-4/src/test/java/io/opentelemetry/spring/smoketest/KafkaSpringStarterSmokeTest.java +++ b/smoke-tests-otel-starter/spring-boot-4/src/test/java/io/opentelemetry/spring/smoketest/KafkaSpringStarterSmokeTest.java @@ -5,7 +5,76 @@ package io.opentelemetry.spring.smoketest; +import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.instrumentation.spring.autoconfigure.OpenTelemetryAutoConfiguration; +import io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.kafka.KafkaInstrumentationSpringBoot4AutoConfiguration; +import io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.thread.ThreadDetailsAutoConfiguration; +import java.time.Duration; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; import org.junit.jupiter.api.condition.DisabledInNativeImage; +import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.kafka.autoconfigure.KafkaAutoConfiguration; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; +import org.springframework.kafka.core.KafkaTemplate; +import org.testcontainers.containers.wait.strategy.Wait; +import org.testcontainers.kafka.KafkaContainer; +import org.testcontainers.utility.DockerImageName; @DisabledInNativeImage // See GraalVmNativeKafkaSpringStarterSmokeTest for the GraalVM native test -class KafkaSpringStarterSmokeTest extends AbstractJvmKafkaSpringStarterSmokeTest {} +class KafkaSpringStarterSmokeTest extends AbstractKafkaSpringStarterSmokeTest { + static KafkaContainer kafka; + + private ApplicationContextRunner contextRunner; + + @BeforeAll + static void setUpKafka() { + kafka = + new KafkaContainer(DockerImageName.parse("apache/kafka:3.8.0")) + .withEnv("KAFKA_HEAP_OPTS", "-Xmx256m") + .waitingFor(Wait.forLogMessage(".*started \\(kafka.server.Kafka.*Server\\).*", 1)) + .withStartupTimeout(Duration.ofMinutes(1)); + kafka.start(); + } + + @AfterAll + static void tearDownKafka() { + kafka.stop(); + } + + @BeforeEach + void setUpContext() { + contextRunner = + new ApplicationContextRunner() + .withAllowBeanDefinitionOverriding(true) + .withConfiguration( + AutoConfigurations.of( + OpenTelemetryAutoConfiguration.class, + ThreadDetailsAutoConfiguration.class, + SpringSmokeOtelConfiguration.class, + KafkaAutoConfiguration.class, + KafkaInstrumentationSpringBoot4AutoConfiguration.class, + KafkaConfig.class)) + .withPropertyValues( + "otel.instrumentation.kafka.experimental-span-attributes=true", + "spring.kafka.bootstrap-servers=" + kafka.getBootstrapServers(), + "spring.kafka.consumer.auto-offset-reset=earliest", + "spring.kafka.consumer.linger-ms=10", + "spring.kafka.listener.idle-between-polls=1000", + "spring.kafka.producer.transaction-id-prefix=test-"); + } + + @SuppressWarnings("unchecked") // we lose parameter types for the KafkaTemplate + @Override + @Test + void shouldInstrumentProducerAndConsumer() { + contextRunner.run( + applicationContext -> { + testing = new SpringSmokeTestRunner(applicationContext.getBean(OpenTelemetry.class)); + kafkaTemplate = applicationContext.getBean(KafkaTemplate.class); + super.shouldInstrumentProducerAndConsumer(); + }); + } +} diff --git a/smoke-tests-otel-starter/spring-boot-4/src/test/java/io/opentelemetry/spring/smoketest/MongoSpringStarterSmokeTest.java b/smoke-tests-otel-starter/spring-boot-4/src/test/java/io/opentelemetry/spring/smoketest/MongoSpringStarterSmokeTest.java new file mode 100644 index 000000000000..f1df8dd3d5fb --- /dev/null +++ b/smoke-tests-otel-starter/spring-boot-4/src/test/java/io/opentelemetry/spring/smoketest/MongoSpringStarterSmokeTest.java @@ -0,0 +1,65 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.spring.smoketest; + +import com.mongodb.client.MongoClient; +import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.instrumentation.spring.autoconfigure.OpenTelemetryAutoConfiguration; +import io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.mongo.MongoClientInstrumentationSpringBoot4AutoConfiguration; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.DisabledInNativeImage; +import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.mongodb.autoconfigure.MongoAutoConfiguration; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.mongodb.MongoDBContainer; + +@DisabledInNativeImage // See GraalVmNativeMongodbSpringStarterSmokeTest for the GraalVM native test +class MongoSpringStarterSmokeTest extends AbstractMongodbSpringStarterSmokeTest { + + @Container static MongoDBContainer container; + + private ApplicationContextRunner contextRunner; + + @BeforeAll + static void setUpContainer() { + container = new MongoDBContainer("mongo:8.2.2"); + container.start(); + } + + @AfterAll + static void tearDownContainer() { + container.stop(); + } + + @BeforeEach + void setUpContext() { + contextRunner = + new ApplicationContextRunner() + .withAllowBeanDefinitionOverriding(true) + .withConfiguration( + AutoConfigurations.of( + OpenTelemetryAutoConfiguration.class, + SpringSmokeOtelConfiguration.class, + MongoAutoConfiguration.class, + MongoClientInstrumentationSpringBoot4AutoConfiguration.class)) + .withPropertyValues("spring.mongodb.uri=" + container.getReplicaSetUrl()); + } + + @Override + @Test + void mongodb() { + contextRunner.run( + applicationContext -> { + testing = new SpringSmokeTestRunner(applicationContext.getBean(OpenTelemetry.class)); + mongoClient = applicationContext.getBean(MongoClient.class); + super.mongodb(); + }); + } +} diff --git a/smoke-tests-otel-starter/spring-boot-4/src/test/java/io/opentelemetry/spring/smoketest/OtelSpringStarterDisabledSmokeTest.java b/smoke-tests-otel-starter/spring-boot-4/src/test/java/io/opentelemetry/spring/smoketest/OtelSpringStarterDisabledSmokeTest.java new file mode 100644 index 000000000000..77dfc0f83ac4 --- /dev/null +++ b/smoke-tests-otel-starter/spring-boot-4/src/test/java/io/opentelemetry/spring/smoketest/OtelSpringStarterDisabledSmokeTest.java @@ -0,0 +1,52 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.spring.smoketest; + +import static org.assertj.core.api.Assertions.assertThat; + +import io.opentelemetry.sdk.logs.data.LogRecordData; +import io.opentelemetry.sdk.metrics.data.MetricData; +import io.opentelemetry.sdk.trace.data.SpanData; +import java.util.List; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.DisabledInNativeImage; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.resttestclient.TestRestTemplate; +import org.springframework.boot.resttestclient.autoconfigure.AutoConfigureTestRestTemplate; +import org.springframework.boot.test.context.SpringBootTest; + +@SpringBootTest( + classes = { + OtelSpringStarterSmokeTestApplication.class, + AbstractOtelSpringStarterSmokeTest.TestConfiguration.class, + SpringSmokeOtelConfiguration.class + }, + webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, + properties = {"otel.sdk.disabled=true"}) +@AutoConfigureTestRestTemplate +@DisabledInNativeImage // Without this the native tests in the OtelSpringStarterSmokeTest class will +// fail with org.h2.jdbc.JdbcSQLSyntaxErrorException: Table "CUSTOMER" already exists +class OtelSpringStarterDisabledSmokeTest extends AbstractSpringStarterSmokeTest { + + @Autowired private TestRestTemplate testRestTemplate; + + @Test + void shouldNotSendTelemetry() throws InterruptedException { + testRestTemplate.getForObject(OtelSpringStarterSmokeTestController.PING, String.class); + + // See SpringSmokeOtelConfiguration + Thread.sleep(200); + + List exportedSpans = testing.getExportedSpans(); + assertThat(exportedSpans).isEmpty(); + + List exportedMetrics = testing.getExportedMetrics(); + assertThat(exportedMetrics).isEmpty(); + + List exportedLogRecords = testing.getExportedLogRecords(); + assertThat(exportedLogRecords).isEmpty(); + } +} diff --git a/smoke-tests-otel-starter/spring-boot-4/src/test/java/io/opentelemetry/spring/smoketest/OtelSpringStarterUserDataSourceBeanTest.java b/smoke-tests-otel-starter/spring-boot-4/src/test/java/io/opentelemetry/spring/smoketest/OtelSpringStarterUserDataSourceBeanTest.java new file mode 100644 index 000000000000..023fcf68c89c --- /dev/null +++ b/smoke-tests-otel-starter/spring-boot-4/src/test/java/io/opentelemetry/spring/smoketest/OtelSpringStarterUserDataSourceBeanTest.java @@ -0,0 +1,14 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.spring.smoketest; + +import org.junit.jupiter.api.condition.DisabledInNativeImage; +import org.springframework.test.context.ActiveProfiles; + +@DisabledInNativeImage // Spring native does not support the profile setting at runtime: +// https://docs.spring.io/spring-boot/docs/current/reference/html/howto.html#howto.aot.conditions +@ActiveProfiles(value = "user-has-defined-datasource-bean") +class OtelSpringStarterUserDataSourceBeanTest extends OtelSpringStarterSmokeTest {} diff --git a/smoke-tests-otel-starter/spring-boot-4/src/test/java/io/opentelemetry/spring/smoketest/OtelSpringStarterWithLogbackInstrumentationDisabledSmokeTest.java b/smoke-tests-otel-starter/spring-boot-4/src/test/java/io/opentelemetry/spring/smoketest/OtelSpringStarterWithLogbackInstrumentationDisabledSmokeTest.java new file mode 100644 index 000000000000..62a7625a4b46 --- /dev/null +++ b/smoke-tests-otel-starter/spring-boot-4/src/test/java/io/opentelemetry/spring/smoketest/OtelSpringStarterWithLogbackInstrumentationDisabledSmokeTest.java @@ -0,0 +1,38 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.spring.smoketest; + +import static org.assertj.core.api.Assertions.assertThat; + +import io.opentelemetry.sdk.logs.data.LogRecordData; +import java.util.List; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.DisabledInNativeImage; +import org.springframework.boot.test.context.SpringBootTest; + +@SpringBootTest( + classes = { + OtelSpringStarterSmokeTestApplication.class, + AbstractOtelSpringStarterSmokeTest.TestConfiguration.class, + SpringSmokeOtelConfiguration.class + }, + webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, + properties = {"otel.instrumentation.logback-appender.enabled=false"}) +@DisabledInNativeImage // Without this the native tests in the OtelSpringStarterSmokeTest class will +// fail with org.h2.jdbc.JdbcSQLSyntaxErrorException: Table "CUSTOMER" already exists +class OtelSpringStarterWithLogbackInstrumentationDisabledSmokeTest + extends AbstractSpringStarterSmokeTest { + + @Test + void shouldNotSendLogRecordTelemetry() throws InterruptedException { + + // See SpringSmokeOtelConfiguration + Thread.sleep(200); + + List exportedLogRecords = testing.getExportedLogRecords(); + assertThat(exportedLogRecords).isEmpty(); + } +} From f1006583b5edafab03759fad113ef19e2434fc37 Mon Sep 17 00:00:00 2001 From: Jay DeLuca Date: Wed, 26 Nov 2025 09:14:37 -0500 Subject: [PATCH 06/40] fix more tests, exclude tests from latestdeps --- .../build.gradle.kts | 29 ++++++++ .../MicrometerBridgeAutoConfiguration.java | 39 +++++++++++ ...MicrometerBridgeAutoConfigurationTest.java | 59 ++++++++++++++++ ...cInstrumentationAutoConfigurationTest.java | 68 +++++++++++++++++++ 4 files changed, 195 insertions(+) create mode 100644 instrumentation/spring/spring-boot-autoconfigure/src/main/javaSpring4/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/micrometer/MicrometerBridgeAutoConfiguration.java create mode 100644 instrumentation/spring/spring-boot-autoconfigure/src/testSpring4/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/micrometer/MicrometerBridgeAutoConfigurationTest.java create mode 100644 instrumentation/spring/spring-boot-autoconfigure/src/testSpring4/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/r2dbc/R2DbcInstrumentationAutoConfigurationTest.java diff --git a/instrumentation/spring/spring-boot-autoconfigure/build.gradle.kts b/instrumentation/spring/spring-boot-autoconfigure/build.gradle.kts index 730672bb460c..bfb543fa641f 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/build.gradle.kts +++ b/instrumentation/spring/spring-boot-autoconfigure/build.gradle.kts @@ -108,6 +108,8 @@ dependencies { testImplementation("io.opentelemetry:opentelemetry-exporter-zipkin") testImplementation(project(":instrumentation-annotations")) + latestDepTestLibrary("org.springframework.boot:spring-boot-starter-micrometer-metrics:latest.release") + // needed for the Spring Boot 3 support implementation(project(":instrumentation:spring:spring-webmvc:spring-webmvc-6.0:library")) @@ -126,6 +128,7 @@ dependencies { add("javaSpring4CompileOnly", "org.springframework.boot:spring-boot-starter-jdbc:4.0.0") add("javaSpring4CompileOnly", "org.springframework.boot:spring-boot-restclient:4.0.0") add("javaSpring4CompileOnly", "org.springframework.boot:spring-boot-starter-data-mongodb:4.0.0") + add("javaSpring4CompileOnly", "org.springframework.boot:spring-boot-starter-micrometer-metrics:4.0.0") add("javaSpring4CompileOnly", project(":instrumentation:kafka:kafka-clients:kafka-clients-2.6:library")) add("javaSpring4CompileOnly", project(":instrumentation:spring:spring-kafka-2.7:library")) add("javaSpring4CompileOnly", project(":instrumentation:mongo:mongo-3.1:library")) @@ -203,10 +206,16 @@ testing { implementation("org.springframework.boot:spring-boot-starter-jdbc:4.0.0") implementation("org.springframework.boot:spring-boot-restclient:4.0.0") implementation("org.springframework.boot:spring-boot-starter-kafka:4.0.0") + implementation("org.springframework.boot:spring-boot-starter-actuator:4.0.0") + implementation("org.springframework.boot:spring-boot-starter-data-r2dbc:4.0.0") + implementation("io.opentelemetry:opentelemetry-sdk") + implementation("io.opentelemetry:opentelemetry-sdk-testing") + implementation(project(":instrumentation-api")) implementation("org.springframework.boot:spring-boot-starter-test:4.0.0") { exclude("org.junit.vintage", "junit-vintage-engine") } runtimeOnly("com.h2database:h2:1.4.197") + runtimeOnly("io.r2dbc:r2dbc-h2:1.0.0.RELEASE") } } @@ -232,6 +241,15 @@ configurations.configureEach { tasks { compileTestJava { options.compilerArgs.add("-parameters") + + // Exclude Spring Boot specific tests from compilation when testLatestDeps is true + // These tests are covered by testSpring4 suite + if (latestDepTest) { + exclude("**/micrometer/MicrometerBridgeAutoConfigurationTest.java") + exclude("**/r2dbc/R2DbcInstrumentationAutoConfigurationTest.java") + exclude("**/jdbc/JdbcInstrumentationAutoConfigurationTest.java") + exclude("**/web/SpringWebInstrumentationAutoConfigurationTest.java") + } } withType().configureEach { @@ -242,6 +260,17 @@ tasks { jvmArgs("-XX:+IgnoreUnrecognizedVMOptions") } + test { + // Exclude Spring Boot specific tests when testLatestDeps is true + // These tests are covered by testSpring4 suite + if (latestDepTest) { + exclude("**/micrometer/MicrometerBridgeAutoConfigurationTest.class") + exclude("**/r2dbc/R2DbcInstrumentationAutoConfigurationTest.class") + exclude("**/jdbc/JdbcInstrumentationAutoConfigurationTest.class") + exclude("**/web/SpringWebInstrumentationAutoConfigurationTest.class") + } + } + named("compileJavaSpring3Java") { sourceCompatibility = "17" targetCompatibility = "17" diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/main/javaSpring4/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/micrometer/MicrometerBridgeAutoConfiguration.java b/instrumentation/spring/spring-boot-autoconfigure/src/main/javaSpring4/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/micrometer/MicrometerBridgeAutoConfiguration.java new file mode 100644 index 000000000000..c9aa28318954 --- /dev/null +++ b/instrumentation/spring/spring-boot-autoconfigure/src/main/javaSpring4/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/micrometer/MicrometerBridgeAutoConfiguration.java @@ -0,0 +1,39 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.micrometer; + +import io.micrometer.core.instrument.Clock; +import io.micrometer.core.instrument.MeterRegistry; +import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.instrumentation.micrometer.v1_5.OpenTelemetryMeterRegistry; +import io.opentelemetry.instrumentation.spring.autoconfigure.OpenTelemetryAutoConfiguration; +import io.opentelemetry.instrumentation.spring.autoconfigure.internal.ConditionalOnEnabledInstrumentation; +import org.springframework.boot.actuate.autoconfigure.metrics.CompositeMeterRegistryAutoConfiguration; +import org.springframework.boot.actuate.autoconfigure.metrics.MetricsAutoConfiguration; +import org.springframework.boot.autoconfigure.AutoConfigureAfter; +import org.springframework.boot.autoconfigure.AutoConfigureBefore; +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; + +/** + * This class is internal and is hence not for public use. Its APIs are unstable and can change at + * any time. + */ +@ConditionalOnEnabledInstrumentation(module = "micrometer", enabledByDefault = false) +@AutoConfigureAfter({MetricsAutoConfiguration.class, OpenTelemetryAutoConfiguration.class}) +@AutoConfigureBefore(CompositeMeterRegistryAutoConfiguration.class) +@ConditionalOnBean(Clock.class) +@ConditionalOnClass(MeterRegistry.class) +@Configuration +public class MicrometerBridgeAutoSpringBoot4Configuration { + + @Bean + MeterRegistry otelMeterRegistry(OpenTelemetry openTelemetry, Clock micrometerClock) { + return OpenTelemetryMeterRegistry.builder(openTelemetry).setClock(micrometerClock).build(); + } +} diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/testSpring4/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/micrometer/MicrometerBridgeAutoConfigurationTest.java b/instrumentation/spring/spring-boot-autoconfigure/src/testSpring4/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/micrometer/MicrometerBridgeAutoConfigurationTest.java new file mode 100644 index 000000000000..23489645ed47 --- /dev/null +++ b/instrumentation/spring/spring-boot-autoconfigure/src/testSpring4/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/micrometer/MicrometerBridgeAutoConfigurationTest.java @@ -0,0 +1,59 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.micrometer; + +import static org.assertj.core.api.Assertions.assertThat; + +import io.micrometer.core.instrument.MeterRegistry; +import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.instrumentation.micrometer.v1_5.OpenTelemetryMeterRegistry; +import org.junit.jupiter.api.Test; +import org.springframework.boot.actuate.autoconfigure.metrics.MetricsAutoConfiguration; +import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; + +class MicrometerBridgeAutoConfigurationTest { + + private final ApplicationContextRunner runner = + new ApplicationContextRunner() + .withBean(OpenTelemetry.class, OpenTelemetry::noop) + .withConfiguration( + AutoConfigurations.of(MicrometerBridgeAutoSpringBoot4Configuration.class)); + + @Test + void metricsEnabled() { + runner + .withConfiguration(AutoConfigurations.of(MetricsAutoConfiguration.class)) + .withPropertyValues("otel.instrumentation.micrometer.enabled = true") + .run( + context -> + assertThat(context.getBean("otelMeterRegistry", MeterRegistry.class)) + .isNotNull() + .isInstanceOf(OpenTelemetryMeterRegistry.class)); + } + + @Test + void metricsDisabledByDefault() { + runner + .withConfiguration(AutoConfigurations.of(MetricsAutoConfiguration.class)) + .run(context -> assertThat(context.containsBean("otelMeterRegistry")).isFalse()); + } + + @Test + void metricsDisabled() { + runner + .withConfiguration(AutoConfigurations.of(MetricsAutoConfiguration.class)) + .withPropertyValues("otel.instrumentation.micrometer.enabled = false") + .run(context -> assertThat(context.containsBean("otelMeterRegistry")).isFalse()); + } + + @Test + void noActuatorAutoConfiguration() { + runner + .withPropertyValues("otel.instrumentation.micrometer.enabled = true") + .run(context -> assertThat(context.containsBean("otelMeterRegistry")).isFalse()); + } +} diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/testSpring4/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/r2dbc/R2DbcInstrumentationAutoConfigurationTest.java b/instrumentation/spring/spring-boot-autoconfigure/src/testSpring4/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/r2dbc/R2DbcInstrumentationAutoConfigurationTest.java new file mode 100644 index 000000000000..bed063c28d1f --- /dev/null +++ b/instrumentation/spring/spring-boot-autoconfigure/src/testSpring4/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/r2dbc/R2DbcInstrumentationAutoConfigurationTest.java @@ -0,0 +1,68 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.r2dbc; + +import static io.opentelemetry.instrumentation.testing.junit.db.SemconvStabilityUtil.maybeStable; +import static io.opentelemetry.semconv.incubating.DbIncubatingAttributes.DB_STATEMENT; + +import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.instrumentation.api.incubator.config.internal.InstrumentationConfig; +import io.opentelemetry.instrumentation.spring.autoconfigure.internal.properties.ConfigPropertiesBridge; +import io.opentelemetry.instrumentation.testing.junit.LibraryInstrumentationExtension; +import io.opentelemetry.sdk.autoconfigure.spi.internal.DefaultConfigProperties; +import java.util.Collections; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; +import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.autoconfigure.r2dbc.R2dbcAutoConfiguration; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; +import org.springframework.r2dbc.core.DatabaseClient; + +class R2DbcInstrumentationAutoConfigurationTest { + + @RegisterExtension + static final LibraryInstrumentationExtension testing = LibraryInstrumentationExtension.create(); + + private final ApplicationContextRunner runner = + new ApplicationContextRunner() + .withBean( + InstrumentationConfig.class, + () -> + new ConfigPropertiesBridge( + DefaultConfigProperties.createFromMap(Collections.emptyMap()))) + .withConfiguration( + AutoConfigurations.of( + R2dbcInstrumentationAutoConfiguration.class, R2dbcAutoConfiguration.class)) + .withBean("openTelemetry", OpenTelemetry.class, testing::getOpenTelemetry); + + @SuppressWarnings("deprecation") // using deprecated semconv + @Test + void statementSanitizerEnabledByDefault() { + runner.run( + context -> { + DatabaseClient client = context.getBean(DatabaseClient.class); + client + .sql( + "CREATE TABLE IF NOT EXISTS player(id INT NOT NULL AUTO_INCREMENT, name VARCHAR(255), age INT, PRIMARY KEY (id))") + .fetch() + .all() + .blockLast(); + client.sql("SELECT * FROM player WHERE id = 1").fetch().all().blockLast(); + testing.waitAndAssertTraces( + trace -> + trace.hasSpansSatisfyingExactly( + span -> + span.hasAttribute( + maybeStable(DB_STATEMENT), + "CREATE TABLE IF NOT EXISTS player(id INT NOT NULL AUTO_INCREMENT, name VARCHAR(?), age INT, PRIMARY KEY (id))")), + trace -> + trace.hasSpansSatisfyingExactly( + span -> + span.hasAttribute( + maybeStable(DB_STATEMENT), "SELECT * FROM player WHERE id = ?"))); + }); + } +} From 9c6f987c96f1112af0084d379b4f53c3218b3047 Mon Sep 17 00:00:00 2001 From: Jay DeLuca Date: Wed, 26 Nov 2025 09:40:01 -0500 Subject: [PATCH 07/40] micrometer fix --- .../spring/spring-boot-autoconfigure/build.gradle.kts | 3 +++ ...va => MicrometerBridgeSpringBoot4AutoConfiguration.java} | 6 +++--- ...ngframework.boot.autoconfigure.AutoConfiguration.imports | 1 + .../micrometer/MicrometerBridgeAutoConfigurationTest.java | 4 ++-- .../r2dbc/R2DbcInstrumentationAutoConfigurationTest.java | 2 +- 5 files changed, 10 insertions(+), 6 deletions(-) rename instrumentation/spring/spring-boot-autoconfigure/src/main/javaSpring4/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/micrometer/{MicrometerBridgeAutoConfiguration.java => MicrometerBridgeSpringBoot4AutoConfiguration.java} (86%) diff --git a/instrumentation/spring/spring-boot-autoconfigure/build.gradle.kts b/instrumentation/spring/spring-boot-autoconfigure/build.gradle.kts index bfb543fa641f..8c8a760ba4ad 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/build.gradle.kts +++ b/instrumentation/spring/spring-boot-autoconfigure/build.gradle.kts @@ -132,6 +132,7 @@ dependencies { add("javaSpring4CompileOnly", project(":instrumentation:kafka:kafka-clients:kafka-clients-2.6:library")) add("javaSpring4CompileOnly", project(":instrumentation:spring:spring-kafka-2.7:library")) add("javaSpring4CompileOnly", project(":instrumentation:mongo:mongo-3.1:library")) + add("javaSpring4CompileOnly", project(":instrumentation:micrometer:micrometer-1.5:library")) } val latestDepTest = findProperty("testLatestDeps") as Boolean @@ -208,9 +209,11 @@ testing { implementation("org.springframework.boot:spring-boot-starter-kafka:4.0.0") implementation("org.springframework.boot:spring-boot-starter-actuator:4.0.0") implementation("org.springframework.boot:spring-boot-starter-data-r2dbc:4.0.0") + implementation("org.springframework.boot:spring-boot-starter-micrometer-metrics:4.0.0") implementation("io.opentelemetry:opentelemetry-sdk") implementation("io.opentelemetry:opentelemetry-sdk-testing") implementation(project(":instrumentation-api")) + implementation(project(":instrumentation:micrometer:micrometer-1.5:library")) implementation("org.springframework.boot:spring-boot-starter-test:4.0.0") { exclude("org.junit.vintage", "junit-vintage-engine") } diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/main/javaSpring4/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/micrometer/MicrometerBridgeAutoConfiguration.java b/instrumentation/spring/spring-boot-autoconfigure/src/main/javaSpring4/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/micrometer/MicrometerBridgeSpringBoot4AutoConfiguration.java similarity index 86% rename from instrumentation/spring/spring-boot-autoconfigure/src/main/javaSpring4/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/micrometer/MicrometerBridgeAutoConfiguration.java rename to instrumentation/spring/spring-boot-autoconfigure/src/main/javaSpring4/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/micrometer/MicrometerBridgeSpringBoot4AutoConfiguration.java index c9aa28318954..89e6dd8e5771 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/main/javaSpring4/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/micrometer/MicrometerBridgeAutoConfiguration.java +++ b/instrumentation/spring/spring-boot-autoconfigure/src/main/javaSpring4/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/micrometer/MicrometerBridgeSpringBoot4AutoConfiguration.java @@ -11,12 +11,12 @@ import io.opentelemetry.instrumentation.micrometer.v1_5.OpenTelemetryMeterRegistry; import io.opentelemetry.instrumentation.spring.autoconfigure.OpenTelemetryAutoConfiguration; import io.opentelemetry.instrumentation.spring.autoconfigure.internal.ConditionalOnEnabledInstrumentation; -import org.springframework.boot.actuate.autoconfigure.metrics.CompositeMeterRegistryAutoConfiguration; -import org.springframework.boot.actuate.autoconfigure.metrics.MetricsAutoConfiguration; import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.AutoConfigureBefore; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.micrometer.metrics.autoconfigure.CompositeMeterRegistryAutoConfiguration; +import org.springframework.boot.micrometer.metrics.autoconfigure.MetricsAutoConfiguration; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -30,7 +30,7 @@ @ConditionalOnBean(Clock.class) @ConditionalOnClass(MeterRegistry.class) @Configuration -public class MicrometerBridgeAutoSpringBoot4Configuration { +public class MicrometerBridgeSpringBoot4AutoConfiguration { @Bean MeterRegistry otelMeterRegistry(OpenTelemetry openTelemetry, Clock micrometerClock) { 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 5977a97ab10d..8601674c2d9a 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 @@ -7,6 +7,7 @@ io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.l io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.jdbc.JdbcInstrumentationAutoConfiguration io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.jdbc.JdbcInstrumentationSpringBoot4AutoConfiguration io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.micrometer.MicrometerBridgeAutoConfiguration +io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.micrometer.MicrometerBridgeSpringBoot4AutoConfiguration 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.web.SpringWebInstrumentationSpringBoot4AutoConfiguration diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/testSpring4/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/micrometer/MicrometerBridgeAutoConfigurationTest.java b/instrumentation/spring/spring-boot-autoconfigure/src/testSpring4/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/micrometer/MicrometerBridgeAutoConfigurationTest.java index 23489645ed47..0a15e8f66ff9 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/testSpring4/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/micrometer/MicrometerBridgeAutoConfigurationTest.java +++ b/instrumentation/spring/spring-boot-autoconfigure/src/testSpring4/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/micrometer/MicrometerBridgeAutoConfigurationTest.java @@ -11,8 +11,8 @@ import io.opentelemetry.api.OpenTelemetry; import io.opentelemetry.instrumentation.micrometer.v1_5.OpenTelemetryMeterRegistry; import org.junit.jupiter.api.Test; -import org.springframework.boot.actuate.autoconfigure.metrics.MetricsAutoConfiguration; import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.micrometer.metrics.autoconfigure.MetricsAutoConfiguration; import org.springframework.boot.test.context.runner.ApplicationContextRunner; class MicrometerBridgeAutoConfigurationTest { @@ -21,7 +21,7 @@ class MicrometerBridgeAutoConfigurationTest { new ApplicationContextRunner() .withBean(OpenTelemetry.class, OpenTelemetry::noop) .withConfiguration( - AutoConfigurations.of(MicrometerBridgeAutoSpringBoot4Configuration.class)); + AutoConfigurations.of(MicrometerBridgeSpringBoot4AutoConfiguration.class)); @Test void metricsEnabled() { diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/testSpring4/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/r2dbc/R2DbcInstrumentationAutoConfigurationTest.java b/instrumentation/spring/spring-boot-autoconfigure/src/testSpring4/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/r2dbc/R2DbcInstrumentationAutoConfigurationTest.java index bed063c28d1f..683bad231a0e 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/testSpring4/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/r2dbc/R2DbcInstrumentationAutoConfigurationTest.java +++ b/instrumentation/spring/spring-boot-autoconfigure/src/testSpring4/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/r2dbc/R2DbcInstrumentationAutoConfigurationTest.java @@ -17,7 +17,7 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; import org.springframework.boot.autoconfigure.AutoConfigurations; -import org.springframework.boot.autoconfigure.r2dbc.R2dbcAutoConfiguration; +import org.springframework.boot.r2dbc.autoconfigure.R2dbcAutoConfiguration; import org.springframework.boot.test.context.runner.ApplicationContextRunner; import org.springframework.r2dbc.core.DatabaseClient; From 79629d28249bc86b125c71daaf5110451f296158 Mon Sep 17 00:00:00 2001 From: Jay DeLuca Date: Wed, 26 Nov 2025 09:54:42 -0500 Subject: [PATCH 08/40] add kafka test --- ...aInstrumentationAutoConfigurationTest.java | 81 +++++++++++++++++++ 1 file changed, 81 insertions(+) create mode 100644 instrumentation/spring/spring-boot-autoconfigure/src/testSpring4/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/kafka/KafkaInstrumentationAutoConfigurationTest.java diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/testSpring4/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/kafka/KafkaInstrumentationAutoConfigurationTest.java b/instrumentation/spring/spring-boot-autoconfigure/src/testSpring4/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/kafka/KafkaInstrumentationAutoConfigurationTest.java new file mode 100644 index 000000000000..3899b56bde31 --- /dev/null +++ b/instrumentation/spring/spring-boot-autoconfigure/src/testSpring4/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/kafka/KafkaInstrumentationAutoConfigurationTest.java @@ -0,0 +1,81 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.kafka; + +import static org.assertj.core.api.Assertions.assertThat; + +import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.instrumentation.api.incubator.config.internal.InstrumentationConfig; +import io.opentelemetry.instrumentation.spring.autoconfigure.internal.properties.ConfigPropertiesBridge; +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.kafka.autoconfigure.DefaultKafkaProducerFactoryCustomizer; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; +import org.springframework.kafka.core.DefaultKafkaProducerFactory; + +class KafkaInstrumentationAutoConfigurationTest { + + private final ApplicationContextRunner runner = + new ApplicationContextRunner() + .withBean( + InstrumentationConfig.class, + () -> + new ConfigPropertiesBridge( + DefaultConfigProperties.createFromMap(Collections.emptyMap()))) + .withConfiguration( + AutoConfigurations.of(KafkaInstrumentationSpringBoot4AutoConfiguration.class)) + .withBean("openTelemetry", OpenTelemetry.class, OpenTelemetry::noop); + + @Test + void defaultConfiguration() { + runner.run( + context -> { + assertThat(context.containsBean("otelKafkaProducerFactoryCustomizer")).isTrue(); + assertThat(context.containsBean("otelKafkaListenerContainerFactoryBeanPostProcessor")) + .isTrue(); + + DefaultKafkaProducerFactoryCustomizer customizer = + context.getBean( + "otelKafkaProducerFactoryCustomizer", + DefaultKafkaProducerFactoryCustomizer.class); + assertThat(customizer).isNotNull(); + + // Verify the customizer works by applying it to a producer factory + DefaultKafkaProducerFactory factory = + new DefaultKafkaProducerFactory<>(Collections.emptyMap()); + customizer.customize(factory); + + // Check that interceptors were added (the customizer adds a post processor) + assertThat(factory.getPostProcessors()).isNotEmpty(); + }); + } + + @Test + void instrumentationDisabled() { + runner + .withPropertyValues("otel.instrumentation.kafka.enabled=false") + .run( + context -> { + assertThat(context.containsBean("otelKafkaProducerFactoryCustomizer")).isFalse(); + assertThat(context.containsBean("otelKafkaListenerContainerFactoryBeanPostProcessor")) + .isFalse(); + }); + } + + @Test + void listenerInterceptorCanBeDisabled() { + runner + .withPropertyValues("otel.instrumentation.kafka.autoconfigure-interceptor=false") + .run( + context -> { + assertThat(context.containsBean("otelKafkaProducerFactoryCustomizer")).isTrue(); + assertThat(context.containsBean("otelKafkaListenerContainerFactoryBeanPostProcessor")) + .isFalse(); + }); + } +} From 30d0e2c60a87a9308f8cb665df3fa7f32e28943e Mon Sep 17 00:00:00 2001 From: Jay DeLuca Date: Wed, 26 Nov 2025 10:53:16 -0500 Subject: [PATCH 09/40] native and another micrometer issue --- ...terBridgeSpringBoot4AutoConfiguration.java | 2 +- .../spring-boot-4/build.gradle.kts | 42 +++++++++++++++++++ 2 files changed, 43 insertions(+), 1 deletion(-) diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/main/javaSpring4/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/micrometer/MicrometerBridgeSpringBoot4AutoConfiguration.java b/instrumentation/spring/spring-boot-autoconfigure/src/main/javaSpring4/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/micrometer/MicrometerBridgeSpringBoot4AutoConfiguration.java index 89e6dd8e5771..f3b381d01fbe 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/main/javaSpring4/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/micrometer/MicrometerBridgeSpringBoot4AutoConfiguration.java +++ b/instrumentation/spring/spring-boot-autoconfigure/src/main/javaSpring4/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/micrometer/MicrometerBridgeSpringBoot4AutoConfiguration.java @@ -28,7 +28,7 @@ @AutoConfigureAfter({MetricsAutoConfiguration.class, OpenTelemetryAutoConfiguration.class}) @AutoConfigureBefore(CompositeMeterRegistryAutoConfiguration.class) @ConditionalOnBean(Clock.class) -@ConditionalOnClass(MeterRegistry.class) +@ConditionalOnClass({MeterRegistry.class, MetricsAutoConfiguration.class}) @Configuration public class MicrometerBridgeSpringBoot4AutoConfiguration { diff --git a/smoke-tests-otel-starter/spring-boot-4/build.gradle.kts b/smoke-tests-otel-starter/spring-boot-4/build.gradle.kts index ea57653ff7f2..c9c64cac2d42 100644 --- a/smoke-tests-otel-starter/spring-boot-4/build.gradle.kts +++ b/smoke-tests-otel-starter/spring-boot-4/build.gradle.kts @@ -1,6 +1,7 @@ plugins { id("otel.java-conventions") alias(springBoot40.plugins.versions) + id("org.graalvm.buildtools.native") } description = "smoke-tests-otel-starter-spring-boot-4" @@ -43,7 +44,48 @@ tasks.withType().configureEach { } tasks { + compileAotJava { + with(options) { + compilerArgs.add("-Xlint:-deprecation,-unchecked,none") + // To disable warnings/failure coming from the Java compiler during the Spring AOT processing + // -deprecation,-unchecked and none are required (none is not enough) + } + } + compileAotTestJava { + with(options) { + compilerArgs.add("-Xlint:-deprecation,-unchecked,none") + // To disable warnings/failure coming from the Java compiler during the Spring AOT processing + // -deprecation,-unchecked and none are required (none is not enough) + } + } + checkstyleAot { + isEnabled = false + } + checkstyleAotTest { + isEnabled = false + } + bootJar { + enabled = false + } test { usesService(gradle.sharedServices.registrations["testcontainersBuildService"].service) } } + +graalvmNative { + // See https://github.com/graalvm/native-build-tools/issues/572 + metadataRepository { + enabled.set(false) + } + + tasks.test { + useJUnitPlatform() + setForkEvery(1) + } +} + +// Disable collectReachabilityMetadata task to avoid configuration isolation issues +// See https://github.com/gradle/gradle/issues/17559 +tasks.named("collectReachabilityMetadata").configure { + enabled = false +} From be14d94362144f5aeb3adfd31af706034b72c432 Mon Sep 17 00:00:00 2001 From: Jay DeLuca Date: Wed, 26 Nov 2025 16:47:46 -0500 Subject: [PATCH 10/40] fix codeql --- .github/workflows/codeql.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index efa2ce923c19..f1ffa729feaa 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -80,6 +80,7 @@ jobs: ./gradlew assemble -x javadoc -x :smoke-tests-otel-starter:spring-boot-3:collectReachabilityMetadata -x :smoke-tests-otel-starter:spring-boot-3.2:collectReachabilityMetadata + -x :smoke-tests-otel-starter:spring-boot-4:collectReachabilityMetadata -x :smoke-tests-otel-starter:spring-boot-reactive-3:collectReachabilityMetadata --no-build-cache --no-daemon From 6757a63ef146331a1ae1f23a5f9db85fdec9bf98 Mon Sep 17 00:00:00 2001 From: Jay DeLuca Date: Wed, 26 Nov 2025 19:37:52 -0500 Subject: [PATCH 11/40] fix test on java 25 --- .../spring/smoketest/OtelSpringStarterSmokeTest.java | 1 - 1 file changed, 1 deletion(-) diff --git a/smoke-tests-otel-starter/spring-boot-4/src/test/java/io/opentelemetry/spring/smoketest/OtelSpringStarterSmokeTest.java b/smoke-tests-otel-starter/spring-boot-4/src/test/java/io/opentelemetry/spring/smoketest/OtelSpringStarterSmokeTest.java index 0fd06e97e27f..513e6f6544ec 100644 --- a/smoke-tests-otel-starter/spring-boot-4/src/test/java/io/opentelemetry/spring/smoketest/OtelSpringStarterSmokeTest.java +++ b/smoke-tests-otel-starter/spring-boot-4/src/test/java/io/opentelemetry/spring/smoketest/OtelSpringStarterSmokeTest.java @@ -41,7 +41,6 @@ protected void assertAdditionalMetrics() { "jvm.buffer.count", "jvm.class.count", "jvm.cpu.context_switch", - "jvm.cpu.longlock", "jvm.system.cpu.utilization", "jvm.gc.duration", "jvm.memory.init", From 6562745ee0b87c18b6f4f039f8f27f80a37b8084 Mon Sep 17 00:00:00 2001 From: Jay DeLuca Date: Thu, 27 Nov 2025 06:32:07 -0500 Subject: [PATCH 12/40] ignore transient kafka startup errors --- .../spring/smoketest/AbstractSpringStarterSmokeTest.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/smoke-tests-otel-starter/spring-smoke-testing/src/main/java/io/opentelemetry/spring/smoketest/AbstractSpringStarterSmokeTest.java b/smoke-tests-otel-starter/spring-smoke-testing/src/main/java/io/opentelemetry/spring/smoketest/AbstractSpringStarterSmokeTest.java index 3b93d9dad437..02b327fff2cf 100644 --- a/smoke-tests-otel-starter/spring-smoke-testing/src/main/java/io/opentelemetry/spring/smoketest/AbstractSpringStarterSmokeTest.java +++ b/smoke-tests-otel-starter/spring-smoke-testing/src/main/java/io/opentelemetry/spring/smoketest/AbstractSpringStarterSmokeTest.java @@ -30,7 +30,10 @@ public abstract class AbstractSpringStarterSmokeTest { "The DescribeTopicPartitions API is not supported, using Metadata API to describe topics", // triggered by // https://github.com/spring-projects/spring-data-mongodb/blob/9a40b7e701871affb88c691b8ac8c044155e421b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MongoConverters.java#L473 - "Registering converter from interface java.util.List to interface org.springframework.data.domain.Vector as reading converter although it doesn't convert from a store-supported type; You might want to check your annotation setup at the converter implementation"); + "Registering converter from interface java.util.List to interface org.springframework.data.domain.Vector as reading converter although it doesn't convert from a store-supported type; You might want to check your annotation setup at the converter implementation", + "Node may not be available.", + "Could not configure topics", + "(id: -1 rack: null isFenced: false) disconnected"); @Autowired protected OpenTelemetry openTelemetry; From a37c8a5b67f87cb82f1c559e3e7ccc9b92789b0d Mon Sep 17 00:00:00 2001 From: Jay DeLuca Date: Thu, 27 Nov 2025 06:55:11 -0500 Subject: [PATCH 13/40] register both mongo configs --- .../smoketest/AbstractJvmMongodbSpringStarterSmokeTest.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/smoke-tests-otel-starter/spring-boot-common/src/main/java/io/opentelemetry/spring/smoketest/AbstractJvmMongodbSpringStarterSmokeTest.java b/smoke-tests-otel-starter/spring-boot-common/src/main/java/io/opentelemetry/spring/smoketest/AbstractJvmMongodbSpringStarterSmokeTest.java index 60d0cafa8b67..54cbca80ac56 100644 --- a/smoke-tests-otel-starter/spring-boot-common/src/main/java/io/opentelemetry/spring/smoketest/AbstractJvmMongodbSpringStarterSmokeTest.java +++ b/smoke-tests-otel-starter/spring-boot-common/src/main/java/io/opentelemetry/spring/smoketest/AbstractJvmMongodbSpringStarterSmokeTest.java @@ -9,6 +9,7 @@ import io.opentelemetry.api.OpenTelemetry; import io.opentelemetry.instrumentation.spring.autoconfigure.OpenTelemetryAutoConfiguration; import io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.mongo.MongoClientInstrumentationAutoConfiguration; +import io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.mongo.MongoClientInstrumentationSpringBoot4AutoConfiguration; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; @@ -48,6 +49,7 @@ void setUpContext() { OpenTelemetryAutoConfiguration.class, SpringSmokeOtelConfiguration.class, MongoAutoConfiguration.class, + MongoClientInstrumentationSpringBoot4AutoConfiguration.class, MongoClientInstrumentationAutoConfiguration.class)) .withPropertyValues("spring.data.mongodb.uri=" + container.getReplicaSetUrl()); } From a406382ee17807205ed628a20ba22d68c9bc18e0 Mon Sep 17 00:00:00 2001 From: Jay DeLuca Date: Thu, 27 Nov 2025 06:57:38 -0500 Subject: [PATCH 14/40] more smoke test changes --- .../spring/smoketest/KafkaSpringStarterSmokeTest.java | 2 ++ .../smoketest/AbstractJvmMongodbSpringStarterSmokeTest.java | 6 +++++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/smoke-tests-otel-starter/spring-boot-4/src/test/java/io/opentelemetry/spring/smoketest/KafkaSpringStarterSmokeTest.java b/smoke-tests-otel-starter/spring-boot-4/src/test/java/io/opentelemetry/spring/smoketest/KafkaSpringStarterSmokeTest.java index 40ddb3a290aa..0e9386d58c23 100644 --- a/smoke-tests-otel-starter/spring-boot-4/src/test/java/io/opentelemetry/spring/smoketest/KafkaSpringStarterSmokeTest.java +++ b/smoke-tests-otel-starter/spring-boot-4/src/test/java/io/opentelemetry/spring/smoketest/KafkaSpringStarterSmokeTest.java @@ -7,6 +7,7 @@ import io.opentelemetry.api.OpenTelemetry; import io.opentelemetry.instrumentation.spring.autoconfigure.OpenTelemetryAutoConfiguration; +import io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.kafka.KafkaInstrumentationAutoConfiguration; import io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.kafka.KafkaInstrumentationSpringBoot4AutoConfiguration; import io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.thread.ThreadDetailsAutoConfiguration; import java.time.Duration; @@ -55,6 +56,7 @@ void setUpContext() { ThreadDetailsAutoConfiguration.class, SpringSmokeOtelConfiguration.class, KafkaAutoConfiguration.class, + KafkaInstrumentationAutoConfiguration.class, KafkaInstrumentationSpringBoot4AutoConfiguration.class, KafkaConfig.class)) .withPropertyValues( diff --git a/smoke-tests-otel-starter/spring-boot-common/src/main/java/io/opentelemetry/spring/smoketest/AbstractJvmMongodbSpringStarterSmokeTest.java b/smoke-tests-otel-starter/spring-boot-common/src/main/java/io/opentelemetry/spring/smoketest/AbstractJvmMongodbSpringStarterSmokeTest.java index 54cbca80ac56..f5a3a04c54bd 100644 --- a/smoke-tests-otel-starter/spring-boot-common/src/main/java/io/opentelemetry/spring/smoketest/AbstractJvmMongodbSpringStarterSmokeTest.java +++ b/smoke-tests-otel-starter/spring-boot-common/src/main/java/io/opentelemetry/spring/smoketest/AbstractJvmMongodbSpringStarterSmokeTest.java @@ -51,7 +51,11 @@ void setUpContext() { MongoAutoConfiguration.class, MongoClientInstrumentationSpringBoot4AutoConfiguration.class, MongoClientInstrumentationAutoConfiguration.class)) - .withPropertyValues("spring.data.mongodb.uri=" + container.getReplicaSetUrl()); + .withPropertyValues( + // Spring 2 and Spring 3 use different property names for the MongoDB URI + "spring.data.mongodb.uri=" + container.getReplicaSetUrl(), + // Spring Boot 4 + "spring.mongodb.uri=" + container.getReplicaSetUrl()); } @Override From 3113936c15560ce4c4062185d86d502b162d63a2 Mon Sep 17 00:00:00 2001 From: Jay DeLuca Date: Thu, 27 Nov 2025 09:37:13 -0500 Subject: [PATCH 15/40] test fix --- .../smoketest/GraalVmNativeMongodbSpringStarterSmokeTest.java | 2 +- .../smoketest/AbstractJvmMongodbSpringStarterSmokeTest.java | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/smoke-tests-otel-starter/spring-boot-4/src/test/java/io/opentelemetry/spring/smoketest/GraalVmNativeMongodbSpringStarterSmokeTest.java b/smoke-tests-otel-starter/spring-boot-4/src/test/java/io/opentelemetry/spring/smoketest/GraalVmNativeMongodbSpringStarterSmokeTest.java index f800d75ef794..9391bec94f58 100644 --- a/smoke-tests-otel-starter/spring-boot-4/src/test/java/io/opentelemetry/spring/smoketest/GraalVmNativeMongodbSpringStarterSmokeTest.java +++ b/smoke-tests-otel-starter/spring-boot-4/src/test/java/io/opentelemetry/spring/smoketest/GraalVmNativeMongodbSpringStarterSmokeTest.java @@ -23,6 +23,6 @@ @SpringBootTest( classes = {OtelSpringStarterSmokeTestApplication.class, SpringSmokeOtelConfiguration.class}, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) -@EnabledInNativeImage // see AbstractJvmMongodbSpringStarterSmokeTest for the JVM test +@EnabledInNativeImage @RequiresDockerCompose class GraalVmNativeMongodbSpringStarterSmokeTest extends AbstractMongodbSpringStarterSmokeTest {} diff --git a/smoke-tests-otel-starter/spring-boot-common/src/main/java/io/opentelemetry/spring/smoketest/AbstractJvmMongodbSpringStarterSmokeTest.java b/smoke-tests-otel-starter/spring-boot-common/src/main/java/io/opentelemetry/spring/smoketest/AbstractJvmMongodbSpringStarterSmokeTest.java index f5a3a04c54bd..d327e0847287 100644 --- a/smoke-tests-otel-starter/spring-boot-common/src/main/java/io/opentelemetry/spring/smoketest/AbstractJvmMongodbSpringStarterSmokeTest.java +++ b/smoke-tests-otel-starter/spring-boot-common/src/main/java/io/opentelemetry/spring/smoketest/AbstractJvmMongodbSpringStarterSmokeTest.java @@ -9,7 +9,6 @@ import io.opentelemetry.api.OpenTelemetry; import io.opentelemetry.instrumentation.spring.autoconfigure.OpenTelemetryAutoConfiguration; import io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.mongo.MongoClientInstrumentationAutoConfiguration; -import io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.mongo.MongoClientInstrumentationSpringBoot4AutoConfiguration; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; @@ -49,7 +48,6 @@ void setUpContext() { OpenTelemetryAutoConfiguration.class, SpringSmokeOtelConfiguration.class, MongoAutoConfiguration.class, - MongoClientInstrumentationSpringBoot4AutoConfiguration.class, MongoClientInstrumentationAutoConfiguration.class)) .withPropertyValues( // Spring 2 and Spring 3 use different property names for the MongoDB URI From f86e06e98b68d3dc3e95babbf90d52c58c1fde61 Mon Sep 17 00:00:00 2001 From: Jay DeLuca Date: Thu, 27 Nov 2025 10:40:17 -0500 Subject: [PATCH 16/40] missing mongo --- ....springframework.boot.autoconfigure.AutoConfiguration.imports | 1 + 1 file changed, 1 insertion(+) 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 8601674c2d9a..c2326c5a1dee 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 @@ -2,6 +2,7 @@ io.opentelemetry.instrumentation.spring.autoconfigure.OpenTelemetryAutoConfigura io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.annotations.InstrumentationAnnotationsAutoConfiguration io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.kafka.KafkaInstrumentationAutoConfiguration io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.kafka.KafkaInstrumentationSpringBoot4AutoConfiguration +io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.mongo.MongoClientInstrumentationAutoConfiguration io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.mongo.MongoClientInstrumentationSpringBoot4AutoConfiguration io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.logging.OpenTelemetryAppenderAutoConfiguration io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.jdbc.JdbcInstrumentationAutoConfiguration From bc3519dd02290559b8f1fe486d47a36419297df2 Mon Sep 17 00:00:00 2001 From: Jay DeLuca Date: Thu, 27 Nov 2025 13:12:21 -0500 Subject: [PATCH 17/40] avoid using common to fix dependency issues --- .../spring-boot-4/build.gradle.kts | 4 +- .../AbstractKafkaSpringStarterSmokeTest.java | 132 ++++++++++++++++++ ...AbstractMongodbSpringStarterSmokeTest.java | 43 ++++++ .../AbstractOtelSpringStarterSmokeTest.java | 2 + .../OtelSpringStarterSmokeTestController.java | 42 ++++++ .../spring/smoketest/SpringComponent.java | 18 +++ 6 files changed, 238 insertions(+), 3 deletions(-) create mode 100644 smoke-tests-otel-starter/spring-boot-4/src/test/java/io/opentelemetry/spring/smoketest/AbstractKafkaSpringStarterSmokeTest.java create mode 100644 smoke-tests-otel-starter/spring-boot-4/src/test/java/io/opentelemetry/spring/smoketest/AbstractMongodbSpringStarterSmokeTest.java create mode 100644 smoke-tests-otel-starter/spring-boot-4/src/test/java/io/opentelemetry/spring/smoketest/OtelSpringStarterSmokeTestController.java create mode 100644 smoke-tests-otel-starter/spring-boot-4/src/test/java/io/opentelemetry/spring/smoketest/SpringComponent.java diff --git a/smoke-tests-otel-starter/spring-boot-4/build.gradle.kts b/smoke-tests-otel-starter/spring-boot-4/build.gradle.kts index c9c64cac2d42..13d81bea7a2c 100644 --- a/smoke-tests-otel-starter/spring-boot-4/build.gradle.kts +++ b/smoke-tests-otel-starter/spring-boot-4/build.gradle.kts @@ -21,10 +21,8 @@ dependencies { runtimeOnly("com.h2database:h2") implementation(platform(org.springframework.boot.gradle.plugin.SpringBootPlugin.BOM_COORDINATES)) - implementation(project(":smoke-tests-otel-starter:spring-boot-common")) - - testImplementation("org.springframework.boot:spring-boot-starter-test") testImplementation("org.springframework.boot:spring-boot-resttestclient") + testImplementation("org.springframework.boot:spring-boot-starter-test") testImplementation("org.springframework.boot:spring-boot-starter-data-mongodb") testImplementation(project(":instrumentation:spring:starters:spring-boot-starter")) testImplementation(project(":smoke-tests-otel-starter:spring-smoke-testing")) diff --git a/smoke-tests-otel-starter/spring-boot-4/src/test/java/io/opentelemetry/spring/smoketest/AbstractKafkaSpringStarterSmokeTest.java b/smoke-tests-otel-starter/spring-boot-4/src/test/java/io/opentelemetry/spring/smoketest/AbstractKafkaSpringStarterSmokeTest.java new file mode 100644 index 000000000000..f92feb482367 --- /dev/null +++ b/smoke-tests-otel-starter/spring-boot-4/src/test/java/io/opentelemetry/spring/smoketest/AbstractKafkaSpringStarterSmokeTest.java @@ -0,0 +1,132 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.spring.smoketest; + +import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo; +import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.satisfies; + +import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.api.common.AttributeKey; +import io.opentelemetry.api.trace.SpanKind; +import io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes; +import org.apache.kafka.clients.admin.NewTopic; +import org.apache.kafka.clients.consumer.ConsumerRecord; +import org.assertj.core.api.AbstractLongAssert; +import org.assertj.core.api.AbstractStringAssert; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.kafka.annotation.KafkaListener; +import org.springframework.kafka.config.TopicBuilder; +import org.springframework.kafka.core.KafkaTemplate; + +abstract class AbstractKafkaSpringStarterSmokeTest extends AbstractSpringStarterSmokeTest { + + @Autowired protected KafkaTemplate kafkaTemplate; + + private static final AttributeKey MESSAGING_CLIENT_ID = + AttributeKey.stringKey("messaging.client_id"); + + @SuppressWarnings("deprecation") // using deprecated semconv + @Test + void shouldInstrumentProducerAndConsumer() { + testing.runWithSpan( + "producer", + () -> { + kafkaTemplate.executeInTransaction( + ops -> { + // return type is incompatible between Spring Boot 2 and 3 + try { + ops.getClass() + .getDeclaredMethod("send", String.class, Object.class, Object.class) + .invoke(ops, "testTopic", "10", "testSpan"); + } catch (Exception e) { + throw new IllegalStateException(e); + } + return 0; + }); + }); + + testing.waitAndAssertTraces( + trace -> + trace.hasSpansSatisfyingExactly( + span -> span.hasName("producer"), + span -> + span.hasName("testTopic publish") + .hasKind(SpanKind.PRODUCER) + .hasParent(trace.getSpan(0)) + .hasAttributesSatisfyingExactly( + equalTo(MessagingIncubatingAttributes.MESSAGING_SYSTEM, "kafka"), + equalTo( + MessagingIncubatingAttributes.MESSAGING_DESTINATION_NAME, + "testTopic"), + equalTo(MessagingIncubatingAttributes.MESSAGING_OPERATION, "publish"), + satisfies( + MESSAGING_CLIENT_ID, + stringAssert -> stringAssert.startsWith("producer")), + satisfies( + MessagingIncubatingAttributes.MESSAGING_DESTINATION_PARTITION_ID, + AbstractStringAssert::isNotEmpty), + satisfies( + MessagingIncubatingAttributes.MESSAGING_KAFKA_MESSAGE_OFFSET, + AbstractLongAssert::isNotNegative), + equalTo( + MessagingIncubatingAttributes.MESSAGING_KAFKA_MESSAGE_KEY, "10")), + span -> + span.hasName("testTopic process") + .hasKind(SpanKind.CONSUMER) + .hasParent(trace.getSpan(1)) + .hasAttributesSatisfying( + equalTo(MessagingIncubatingAttributes.MESSAGING_SYSTEM, "kafka"), + equalTo( + MessagingIncubatingAttributes.MESSAGING_DESTINATION_NAME, + "testTopic"), + equalTo(MessagingIncubatingAttributes.MESSAGING_OPERATION, "process"), + satisfies( + MessagingIncubatingAttributes.MESSAGING_MESSAGE_BODY_SIZE, + AbstractLongAssert::isNotNegative), + satisfies( + MessagingIncubatingAttributes.MESSAGING_DESTINATION_PARTITION_ID, + AbstractStringAssert::isNotEmpty), + satisfies( + MessagingIncubatingAttributes.MESSAGING_KAFKA_MESSAGE_OFFSET, + AbstractLongAssert::isNotNegative), + equalTo( + MessagingIncubatingAttributes.MESSAGING_KAFKA_MESSAGE_KEY, "10"), + equalTo( + MessagingIncubatingAttributes.MESSAGING_KAFKA_CONSUMER_GROUP, + "testListener"), + satisfies( + AttributeKey.longKey("kafka.record.queue_time_ms"), + AbstractLongAssert::isNotNegative), + satisfies( + MESSAGING_CLIENT_ID, + stringAssert -> stringAssert.startsWith("consumer"))), + span -> span.hasName("consumer").hasParent(trace.getSpan(2)))); + } + + @Configuration + public static class KafkaConfig { + + @Autowired OpenTelemetry openTelemetry; + + @Bean + public NewTopic testTopic() { + return TopicBuilder.name("testTopic").partitions(1).replicas(1).build(); + } + + @KafkaListener(id = "testListener", topics = "testTopic") + public void listener(ConsumerRecord record) { + openTelemetry + .getTracer("consumer", "1.0") + .spanBuilder("consumer") + .setSpanKind(SpanKind.CONSUMER) + .startSpan() + .end(); + } + } +} diff --git a/smoke-tests-otel-starter/spring-boot-4/src/test/java/io/opentelemetry/spring/smoketest/AbstractMongodbSpringStarterSmokeTest.java b/smoke-tests-otel-starter/spring-boot-4/src/test/java/io/opentelemetry/spring/smoketest/AbstractMongodbSpringStarterSmokeTest.java new file mode 100644 index 000000000000..b66987c65a48 --- /dev/null +++ b/smoke-tests-otel-starter/spring-boot-4/src/test/java/io/opentelemetry/spring/smoketest/AbstractMongodbSpringStarterSmokeTest.java @@ -0,0 +1,43 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.spring.smoketest; + +import static io.opentelemetry.instrumentation.api.internal.SemconvStability.stableDbSystemName; +import static io.opentelemetry.instrumentation.testing.junit.db.SemconvStabilityUtil.maybeStable; + +import com.mongodb.client.MongoClient; +import io.opentelemetry.api.trace.SpanKind; +import io.opentelemetry.semconv.incubating.DbIncubatingAttributes; +import java.util.ArrayList; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; + +abstract class AbstractMongodbSpringStarterSmokeTest extends AbstractSpringStarterSmokeTest { + + @Autowired protected MongoClient mongoClient; + + @SuppressWarnings("deprecation") // uses deprecated semconv + @Test + void mongodb() { + testing.runWithSpan( + "server", + () -> { + mongoClient.listDatabaseNames().into(new ArrayList<>()); + }); + + testing.waitAndAssertTraces( + trace -> + trace.hasSpansSatisfyingExactly( + span -> span.hasName("server"), + span -> + span.hasKind(SpanKind.CLIENT) + .hasName("listDatabases admin") + .hasAttribute( + maybeStable(DbIncubatingAttributes.DB_SYSTEM), + stableDbSystemName( + DbIncubatingAttributes.DbSystemIncubatingValues.MONGODB)))); + } +} diff --git a/smoke-tests-otel-starter/spring-boot-4/src/test/java/io/opentelemetry/spring/smoketest/AbstractOtelSpringStarterSmokeTest.java b/smoke-tests-otel-starter/spring-boot-4/src/test/java/io/opentelemetry/spring/smoketest/AbstractOtelSpringStarterSmokeTest.java index a32051e26f98..624e7be7b9fd 100644 --- a/smoke-tests-otel-starter/spring-boot-4/src/test/java/io/opentelemetry/spring/smoketest/AbstractOtelSpringStarterSmokeTest.java +++ b/smoke-tests-otel-starter/spring-boot-4/src/test/java/io/opentelemetry/spring/smoketest/AbstractOtelSpringStarterSmokeTest.java @@ -48,6 +48,7 @@ import org.springframework.boot.context.event.ApplicationReadyEvent; import org.springframework.boot.restclient.RestTemplateBuilder; import org.springframework.boot.resttestclient.TestRestTemplate; +import org.springframework.boot.resttestclient.autoconfigure.AutoConfigureTestRestTemplate; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.event.EventListener; @@ -65,6 +66,7 @@ */ @SuppressWarnings("deprecation") // using deprecated semconv @TestMethodOrder(MethodOrderer.OrderAnnotation.class) +@AutoConfigureTestRestTemplate abstract class AbstractOtelSpringStarterSmokeTest extends AbstractSpringStarterSmokeTest { @Autowired private TestRestTemplate testRestTemplate; diff --git a/smoke-tests-otel-starter/spring-boot-4/src/test/java/io/opentelemetry/spring/smoketest/OtelSpringStarterSmokeTestController.java b/smoke-tests-otel-starter/spring-boot-4/src/test/java/io/opentelemetry/spring/smoketest/OtelSpringStarterSmokeTestController.java new file mode 100644 index 000000000000..68c6fd0d199f --- /dev/null +++ b/smoke-tests-otel-starter/spring-boot-4/src/test/java/io/opentelemetry/spring/smoketest/OtelSpringStarterSmokeTestController.java @@ -0,0 +1,42 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.spring.smoketest; + +import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.api.metrics.LongHistogram; +import io.opentelemetry.api.metrics.Meter; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +public class OtelSpringStarterSmokeTestController { + + public static final String PING = "/ping"; + public static final String TEST = "/test"; + public static final String TEST_HISTOGRAM = "histogram-test-otel-spring-starter"; + public static final String METER_SCOPE_NAME = "scope"; + private final LongHistogram histogram; + private final SpringComponent component; + + public OtelSpringStarterSmokeTestController( + OpenTelemetry openTelemetry, SpringComponent springComponent) { + Meter meter = openTelemetry.getMeter(METER_SCOPE_NAME); + histogram = meter.histogramBuilder(TEST_HISTOGRAM).ofLongs().build(); + this.component = springComponent; + } + + @GetMapping(PING) + public String ping() { + histogram.record(10); + component.withSpanMethod("from-controller"); + return "pong"; + } + + @GetMapping(TEST) + public String testUrlToRedact() { + return "ok"; + } +} diff --git a/smoke-tests-otel-starter/spring-boot-4/src/test/java/io/opentelemetry/spring/smoketest/SpringComponent.java b/smoke-tests-otel-starter/spring-boot-4/src/test/java/io/opentelemetry/spring/smoketest/SpringComponent.java new file mode 100644 index 000000000000..072b39393b01 --- /dev/null +++ b/smoke-tests-otel-starter/spring-boot-4/src/test/java/io/opentelemetry/spring/smoketest/SpringComponent.java @@ -0,0 +1,18 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.spring.smoketest; + +import io.opentelemetry.instrumentation.annotations.SpanAttribute; +import io.opentelemetry.instrumentation.annotations.WithSpan; +import org.springframework.stereotype.Component; + +@Component +public class SpringComponent { + + @SuppressWarnings("MethodCanBeStatic") + @WithSpan + public void withSpanMethod(@SpanAttribute String paramName) {} +} From fcf6bce3eb81eb514250c739fa629dc0c35fb2f4 Mon Sep 17 00:00:00 2001 From: Jay DeLuca Date: Thu, 27 Nov 2025 13:49:07 -0500 Subject: [PATCH 18/40] add propagators and app file --- .../spring-boot-4/build.gradle.kts | 1 + .../src/main/resources/application.yaml | 39 +++++++++++++++++++ 2 files changed, 40 insertions(+) create mode 100644 smoke-tests-otel-starter/spring-boot-4/src/main/resources/application.yaml diff --git a/smoke-tests-otel-starter/spring-boot-4/build.gradle.kts b/smoke-tests-otel-starter/spring-boot-4/build.gradle.kts index 13d81bea7a2c..c438045af506 100644 --- a/smoke-tests-otel-starter/spring-boot-4/build.gradle.kts +++ b/smoke-tests-otel-starter/spring-boot-4/build.gradle.kts @@ -20,6 +20,7 @@ dependencies { implementation("org.apache.commons:commons-dbcp2") runtimeOnly("com.h2database:h2") implementation(platform(org.springframework.boot.gradle.plugin.SpringBootPlugin.BOM_COORDINATES)) + implementation("io.opentelemetry:opentelemetry-extension-trace-propagators") testImplementation("org.springframework.boot:spring-boot-resttestclient") testImplementation("org.springframework.boot:spring-boot-starter-test") diff --git a/smoke-tests-otel-starter/spring-boot-4/src/main/resources/application.yaml b/smoke-tests-otel-starter/spring-boot-4/src/main/resources/application.yaml new file mode 100644 index 000000000000..79ecd43e6291 --- /dev/null +++ b/smoke-tests-otel-starter/spring-boot-4/src/main/resources/application.yaml @@ -0,0 +1,39 @@ +otel: + instrumentation: + common: + db-statement-sanitizer: + enabled: false + kafka: + experimental-span-attributes: true + logback-appender: + experimental: + capture-code-attributes: true + micrometer: + enabled: true + http: + client: + emit-experimental-telemetry: true + server: + emit-experimental-telemetry: true + capture-request-headers: [key] + propagators: + - b3 + resource: + attributes: + attributeFromYaml: true # boolean will be automatically converted to string by spring + +application: + prop: propValue + prop-with-dash: provWithDashValue + +spring: + application: + name: otel-spring-starter-smoke-test + kafka: + bootstrap-servers: localhost:9094 + consumer: + auto-offset-reset: earliest + listener: + idle-between-polls: 1000 + producer: + transaction-id-prefix: test- \ No newline at end of file From cbc8b1e390139d9e8296ca24df924c23d6882831 Mon Sep 17 00:00:00 2001 From: Jay DeLuca Date: Fri, 28 Nov 2025 06:22:53 -0500 Subject: [PATCH 19/40] only run native tests on java 25+ --- .../spring-boot-4/build.gradle.kts | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/smoke-tests-otel-starter/spring-boot-4/build.gradle.kts b/smoke-tests-otel-starter/spring-boot-4/build.gradle.kts index c438045af506..ad5315d30271 100644 --- a/smoke-tests-otel-starter/spring-boot-4/build.gradle.kts +++ b/smoke-tests-otel-starter/spring-boot-4/build.gradle.kts @@ -22,6 +22,7 @@ dependencies { implementation(platform(org.springframework.boot.gradle.plugin.SpringBootPlugin.BOM_COORDINATES)) implementation("io.opentelemetry:opentelemetry-extension-trace-propagators") + testImplementation("org.springframework:spring-test:7.0.1") testImplementation("org.springframework.boot:spring-boot-resttestclient") testImplementation("org.springframework.boot:spring-boot-starter-test") testImplementation("org.springframework.boot:spring-boot-starter-data-mongodb") @@ -69,6 +70,16 @@ tasks { test { usesService(gradle.sharedServices.registrations["testcontainersBuildService"].service) } + + // Spring Boot 4 requires GraalVM native-image with Java 25+ support + // Disable native test tasks if running on Java < 25 + val javaVersionSupportsNative = JavaVersion.current().isCompatibleWith(JavaVersion.VERSION_25) + named("nativeTest").configure { + enabled = javaVersionSupportsNative + } + named("nativeTestCompile").configure { + enabled = javaVersionSupportsNative + } } graalvmNative { From 1a83d20b3da8a91a04a0dcbf5ffebd236a1f3b8f Mon Sep 17 00:00:00 2001 From: Jay DeLuca Date: Fri, 28 Nov 2025 14:25:14 -0500 Subject: [PATCH 20/40] cleanup --- .../JdbcInstrumentationAutoConfiguration.java | 2 +- ...KafkaInstrumentationAutoConfiguration.java | 3 +- ...lientInstrumentationAutoConfiguration.java | 4 +- ...ngWebInstrumentationAutoConfiguration.java | 3 +- ...mentationSpringBoot4AutoConfiguration.java | 2 +- ...mentationSpringBoot4AutoConfiguration.java | 4 +- ...mentationSpringBoot4AutoConfiguration.java | 4 +- .../src/main/resources/application.properties | 1 - .../AbstractKafkaSpringStarterSmokeTest.java | 22 ++-- ...AbstractMongodbSpringStarterSmokeTest.java | 6 +- .../AbstractOtelSpringStarterSmokeTest.java | 100 ++++++++---------- .../KafkaSpringStarterSmokeTest.java | 2 - 12 files changed, 70 insertions(+), 83 deletions(-) delete mode 100644 smoke-tests-otel-starter/spring-boot-4/src/main/resources/application.properties diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/jdbc/JdbcInstrumentationAutoConfiguration.java b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/jdbc/JdbcInstrumentationAutoConfiguration.java index 614c75a0650e..fce4ca1714bb 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/jdbc/JdbcInstrumentationAutoConfiguration.java +++ b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/jdbc/JdbcInstrumentationAutoConfiguration.java @@ -25,7 +25,7 @@ @AutoConfiguration(after = DataSourceAutoConfiguration.class) @ConditionalOnBean({DataSource.class}) @ConditionalOnMissingClass( - "org.springframework.boot.jdbc.autoconfigure.DataSourceAutoConfiguration") + "org.springframework.boot.jdbc.autoconfigure.DataSourceAutoConfiguration") // Spring Boot 4+ @Configuration(proxyBeanMethods = false) public class JdbcInstrumentationAutoConfiguration { diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/kafka/KafkaInstrumentationAutoConfiguration.java b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/kafka/KafkaInstrumentationAutoConfiguration.java index a3bf169cf98f..764177f367ab 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/kafka/KafkaInstrumentationAutoConfiguration.java +++ b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/kafka/KafkaInstrumentationAutoConfiguration.java @@ -31,7 +31,8 @@ DefaultKafkaProducerFactoryCustomizer.class }) @ConditionalOnMissingClass( - "org.springframework.boot.kafka.autoconfigure.DefaultKafkaProducerFactoryCustomizer") + "org.springframework.boot.kafka.autoconfigure.DefaultKafkaProducerFactoryCustomizer") // Spring +// Boot 4+ @Configuration public class KafkaInstrumentationAutoConfiguration { diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/mongo/MongoClientInstrumentationAutoConfiguration.java b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/mongo/MongoClientInstrumentationAutoConfiguration.java index 5cd07a39e2f2..a1d9f437cfdd 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/mongo/MongoClientInstrumentationAutoConfiguration.java +++ b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/mongo/MongoClientInstrumentationAutoConfiguration.java @@ -24,7 +24,9 @@ @ConditionalOnClass({MongoClientSettings.class, MongoClientSettingsBuilderCustomizer.class}) @ConditionalOnEnabledInstrumentation(module = "mongo") @ConditionalOnMissingClass( - "org.springframework.boot.mongodb.autoconfigure.MongoClientSettingsBuilderCustomizer") + "org.springframework.boot.mongodb.autoconfigure.MongoClientSettingsBuilderCustomizer") // Spring +// Boot +// 4+ @Configuration public class MongoClientInstrumentationAutoConfiguration { diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/web/SpringWebInstrumentationAutoConfiguration.java b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/web/SpringWebInstrumentationAutoConfiguration.java index 765ed4f5f474..0c9a40f856f7 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/web/SpringWebInstrumentationAutoConfiguration.java +++ b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/web/SpringWebInstrumentationAutoConfiguration.java @@ -26,7 +26,8 @@ */ @ConditionalOnEnabledInstrumentation(module = "spring-web") @ConditionalOnClass({RestTemplate.class, RestTemplateCustomizer.class}) -@ConditionalOnMissingClass("org.springframework.boot.restclient.RestTemplateCustomizer") +@ConditionalOnMissingClass( + "org.springframework.boot.restclient.RestTemplateCustomizer") // Spring Boot 4+ @Configuration public class SpringWebInstrumentationAutoConfiguration { diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/main/javaSpring4/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/jdbc/JdbcInstrumentationSpringBoot4AutoConfiguration.java b/instrumentation/spring/spring-boot-autoconfigure/src/main/javaSpring4/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/jdbc/JdbcInstrumentationSpringBoot4AutoConfiguration.java index e8bc73a2fde7..93b7d11f1ab4 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/main/javaSpring4/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/jdbc/JdbcInstrumentationSpringBoot4AutoConfiguration.java +++ b/instrumentation/spring/spring-boot-autoconfigure/src/main/javaSpring4/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/jdbc/JdbcInstrumentationSpringBoot4AutoConfiguration.java @@ -26,7 +26,7 @@ @ConditionalOnBean({DataSource.class}) @Configuration(proxyBeanMethods = false) @ConditionalOnMissingClass( - "org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration") + "org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration") // Spring Boot 2 & 3 public class JdbcInstrumentationSpringBoot4AutoConfiguration { // For error prone diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/main/javaSpring4/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/kafka/KafkaInstrumentationSpringBoot4AutoConfiguration.java b/instrumentation/spring/spring-boot-autoconfigure/src/main/javaSpring4/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/kafka/KafkaInstrumentationSpringBoot4AutoConfiguration.java index cb1328f01e06..f3545ce68bbc 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/main/javaSpring4/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/kafka/KafkaInstrumentationSpringBoot4AutoConfiguration.java +++ b/instrumentation/spring/spring-boot-autoconfigure/src/main/javaSpring4/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/kafka/KafkaInstrumentationSpringBoot4AutoConfiguration.java @@ -33,7 +33,9 @@ }) @ConditionalOnMissingBean(name = "otelKafkaProducerFactoryCustomizer") @ConditionalOnMissingClass( - "org.springframework.boot.autoconfigure.kafka.DefaultKafkaProducerFactoryCustomizer") + "org.springframework.boot.autoconfigure.kafka.DefaultKafkaProducerFactoryCustomizer") // Spring +// Boot 2 +// & 3 @Configuration public class KafkaInstrumentationSpringBoot4AutoConfiguration { diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/main/javaSpring4/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/mongo/MongoClientInstrumentationSpringBoot4AutoConfiguration.java b/instrumentation/spring/spring-boot-autoconfigure/src/main/javaSpring4/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/mongo/MongoClientInstrumentationSpringBoot4AutoConfiguration.java index 0f8b02cdf3ca..8b3c61e24b62 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/main/javaSpring4/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/mongo/MongoClientInstrumentationSpringBoot4AutoConfiguration.java +++ b/instrumentation/spring/spring-boot-autoconfigure/src/main/javaSpring4/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/mongo/MongoClientInstrumentationSpringBoot4AutoConfiguration.java @@ -24,7 +24,9 @@ @ConditionalOnClass({MongoClientSettings.class, MongoClientSettingsBuilderCustomizer.class}) @ConditionalOnEnabledInstrumentation(module = "mongo") @ConditionalOnMissingClass( - "org.springframework.boot.autoconfigure.mongo.MongoClientSettingsBuilderCustomizer") + "org.springframework.boot.autoconfigure.mongo.MongoClientSettingsBuilderCustomizer") // Spring +// Boot 2 & +// 3 @Configuration public class MongoClientInstrumentationSpringBoot4AutoConfiguration { diff --git a/smoke-tests-otel-starter/spring-boot-4/src/main/resources/application.properties b/smoke-tests-otel-starter/spring-boot-4/src/main/resources/application.properties deleted file mode 100644 index 01ad129d02b1..000000000000 --- a/smoke-tests-otel-starter/spring-boot-4/src/main/resources/application.properties +++ /dev/null @@ -1 +0,0 @@ -spring.application.name=otel-spring-starter-smoke-test diff --git a/smoke-tests-otel-starter/spring-boot-4/src/test/java/io/opentelemetry/spring/smoketest/AbstractKafkaSpringStarterSmokeTest.java b/smoke-tests-otel-starter/spring-boot-4/src/test/java/io/opentelemetry/spring/smoketest/AbstractKafkaSpringStarterSmokeTest.java index f92feb482367..f73225e40a1c 100644 --- a/smoke-tests-otel-starter/spring-boot-4/src/test/java/io/opentelemetry/spring/smoketest/AbstractKafkaSpringStarterSmokeTest.java +++ b/smoke-tests-otel-starter/spring-boot-4/src/test/java/io/opentelemetry/spring/smoketest/AbstractKafkaSpringStarterSmokeTest.java @@ -26,7 +26,7 @@ abstract class AbstractKafkaSpringStarterSmokeTest extends AbstractSpringStarterSmokeTest { - @Autowired protected KafkaTemplate kafkaTemplate; + protected KafkaTemplate kafkaTemplate; private static final AttributeKey MESSAGING_CLIENT_ID = AttributeKey.stringKey("messaging.client_id"); @@ -36,20 +36,12 @@ abstract class AbstractKafkaSpringStarterSmokeTest extends AbstractSpringStarter void shouldInstrumentProducerAndConsumer() { testing.runWithSpan( "producer", - () -> { - kafkaTemplate.executeInTransaction( - ops -> { - // return type is incompatible between Spring Boot 2 and 3 - try { - ops.getClass() - .getDeclaredMethod("send", String.class, Object.class, Object.class) - .invoke(ops, "testTopic", "10", "testSpan"); - } catch (Exception e) { - throw new IllegalStateException(e); - } - return 0; - }); - }); + () -> + kafkaTemplate.executeInTransaction( + ops -> { + ops.send("testTopic", "10", "testSpan"); + return 0; + })); testing.waitAndAssertTraces( trace -> diff --git a/smoke-tests-otel-starter/spring-boot-4/src/test/java/io/opentelemetry/spring/smoketest/AbstractMongodbSpringStarterSmokeTest.java b/smoke-tests-otel-starter/spring-boot-4/src/test/java/io/opentelemetry/spring/smoketest/AbstractMongodbSpringStarterSmokeTest.java index b66987c65a48..6bda62b9b3e3 100644 --- a/smoke-tests-otel-starter/spring-boot-4/src/test/java/io/opentelemetry/spring/smoketest/AbstractMongodbSpringStarterSmokeTest.java +++ b/smoke-tests-otel-starter/spring-boot-4/src/test/java/io/opentelemetry/spring/smoketest/AbstractMongodbSpringStarterSmokeTest.java @@ -22,11 +22,7 @@ abstract class AbstractMongodbSpringStarterSmokeTest extends AbstractSpringStart @SuppressWarnings("deprecation") // uses deprecated semconv @Test void mongodb() { - testing.runWithSpan( - "server", - () -> { - mongoClient.listDatabaseNames().into(new ArrayList<>()); - }); + testing.runWithSpan("server", () -> mongoClient.listDatabaseNames().into(new ArrayList<>())); testing.waitAndAssertTraces( trace -> diff --git a/smoke-tests-otel-starter/spring-boot-4/src/test/java/io/opentelemetry/spring/smoketest/AbstractOtelSpringStarterSmokeTest.java b/smoke-tests-otel-starter/spring-boot-4/src/test/java/io/opentelemetry/spring/smoketest/AbstractOtelSpringStarterSmokeTest.java index 624e7be7b9fd..a52146e51dd7 100644 --- a/smoke-tests-otel-starter/spring-boot-4/src/test/java/io/opentelemetry/spring/smoketest/AbstractOtelSpringStarterSmokeTest.java +++ b/smoke-tests-otel-starter/spring-boot-4/src/test/java/io/opentelemetry/spring/smoketest/AbstractOtelSpringStarterSmokeTest.java @@ -5,8 +5,22 @@ package io.opentelemetry.spring.smoketest; +import static io.opentelemetry.api.common.AttributeKey.booleanKey; +import static io.opentelemetry.api.common.AttributeKey.stringArrayKey; +import static io.opentelemetry.api.common.AttributeKey.stringKey; import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo; import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.satisfies; +import static io.opentelemetry.semconv.ClientAttributes.CLIENT_ADDRESS; +import static io.opentelemetry.semconv.CodeAttributes.CODE_FUNCTION_NAME; +import static io.opentelemetry.semconv.HttpAttributes.HTTP_REQUEST_METHOD; +import static io.opentelemetry.semconv.HttpAttributes.HTTP_RESPONSE_STATUS_CODE; +import static io.opentelemetry.semconv.HttpAttributes.HTTP_ROUTE; +import static io.opentelemetry.semconv.ServerAttributes.SERVER_ADDRESS; +import static io.opentelemetry.semconv.ServerAttributes.SERVER_PORT; +import static io.opentelemetry.semconv.incubating.CodeIncubatingAttributes.CODE_FUNCTION; +import static io.opentelemetry.semconv.incubating.CodeIncubatingAttributes.CODE_NAMESPACE; +import static java.util.Arrays.asList; +import static java.util.Collections.singletonList; import static org.assertj.core.api.Assertions.assertThat; import io.opentelemetry.api.common.AttributeKey; @@ -22,21 +36,17 @@ import io.opentelemetry.sdk.autoconfigure.spi.internal.DefaultConfigProperties; import io.opentelemetry.sdk.logs.data.LogRecordData; import io.opentelemetry.sdk.resources.Resource; -import io.opentelemetry.semconv.ClientAttributes; -import io.opentelemetry.semconv.CodeAttributes; -import io.opentelemetry.semconv.HttpAttributes; -import io.opentelemetry.semconv.ServerAttributes; import io.opentelemetry.semconv.UrlAttributes; -import io.opentelemetry.semconv.incubating.CodeIncubatingAttributes; import io.opentelemetry.semconv.incubating.DbIncubatingAttributes; import io.opentelemetry.semconv.incubating.ServiceIncubatingAttributes; import io.opentelemetry.semconv.incubating.ThreadIncubatingAttributes; import java.net.URI; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collections; import java.util.List; +import org.assertj.core.api.AbstractCharSequenceAssert; import org.assertj.core.api.AbstractIterableAssert; +import org.assertj.core.api.AbstractLongAssert; import org.assertj.core.api.MapAssert; import org.junit.jupiter.api.MethodOrderer; import org.junit.jupiter.api.Test; @@ -44,11 +54,11 @@ import org.junit.jupiter.api.condition.OS; import org.springframework.beans.factory.ObjectProvider; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.context.event.ApplicationReadyEvent; import org.springframework.boot.restclient.RestTemplateBuilder; import org.springframework.boot.resttestclient.TestRestTemplate; import org.springframework.boot.resttestclient.autoconfigure.AutoConfigureTestRestTemplate; +import org.springframework.boot.test.web.server.LocalServerPort; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.event.EventListener; @@ -78,9 +88,7 @@ abstract class AbstractOtelSpringStarterSmokeTest extends AbstractSpringStarterS @Autowired private RestTemplateBuilder restTemplateBuilder; @Autowired private JdbcTemplate jdbcTemplate; - // can't use @LocalServerPort annotation since it moved packages between Spring Boot 2 and 3 - @Value("${local.server.port}") - private int port; + @LocalServerPort private int port; @Configuration(proxyBeanMethods = false) static class TestConfiguration { @@ -102,8 +110,7 @@ AutoConfigurationCustomizerProvider hiddenPropagatorCustomizer() { (resource, config) -> resource.merge( Resource.create( - Attributes.of( - AttributeKey.booleanKey("keyFromResourceCustomizer"), false)))); + Attributes.of(booleanKey("keyFromResourceCustomizer"), false)))); } @Bean @@ -114,8 +121,7 @@ AutoConfigurationCustomizerProvider propagatorCustomizer() { (resource, config) -> resource.merge( Resource.create( - Attributes.of( - AttributeKey.booleanKey("keyFromResourceCustomizer"), true)))); + Attributes.of(booleanKey("keyFromResourceCustomizer"), true)))); } @Bean @@ -163,7 +169,6 @@ void shouldSendTelemetry() { null, headers, HttpMethod.GET, URI.create(OtelSpringStarterSmokeTestController.PING)), String.class); - // Span testing.waitAndAssertTraces( traceAssert -> traceAssert.hasSpansSatisfyingExactly( @@ -182,39 +187,36 @@ void shouldSendTelemetry() { satisfies( UrlAttributes.URL_FULL, stringAssert -> stringAssert.endsWith("/ping")), - equalTo(ServerAttributes.SERVER_ADDRESS, "localhost"), - satisfies(ServerAttributes.SERVER_PORT, val -> val.isNotZero())), + equalTo(SERVER_ADDRESS, "localhost"), + satisfies(SERVER_PORT, val -> val.isNotZero())), serverSpan -> HttpSpanDataAssert.create(serverSpan) .assertServerGetRequest("/ping") .hasResourceSatisfying( r -> - r.hasAttribute( - AttributeKey.booleanKey("keyFromResourceCustomizer"), true) - .hasAttribute( - AttributeKey.stringKey("attributeFromYaml"), "true") + r.hasAttribute(booleanKey("keyFromResourceCustomizer"), true) + .hasAttribute(stringKey("attributeFromYaml"), "true") .hasAttribute( satisfies( ServiceIncubatingAttributes.SERVICE_INSTANCE_ID, - val -> val.isNotBlank()))) + AbstractCharSequenceAssert::isNotBlank))) .hasAttributesSatisfying( - equalTo(HttpAttributes.HTTP_REQUEST_METHOD, "GET"), - equalTo(HttpAttributes.HTTP_RESPONSE_STATUS_CODE, 200L), - equalTo(HttpAttributes.HTTP_ROUTE, "/ping"), - equalTo(ServerAttributes.SERVER_ADDRESS, "localhost"), - satisfies( - ClientAttributes.CLIENT_ADDRESS, - s -> s.isIn("127.0.0.1", "0:0:0:0:0:0:0:1")), + equalTo(HTTP_REQUEST_METHOD, "GET"), + equalTo(HTTP_RESPONSE_STATUS_CODE, 200L), + equalTo(HTTP_ROUTE, "/ping"), + equalTo(SERVER_ADDRESS, "localhost"), + satisfies(CLIENT_ADDRESS, s -> s.isIn("127.0.0.1", "0:0:0:0:0:0:0:1")), equalTo( - AttributeKey.stringArrayKey("http.request.header.key"), - Collections.singletonList("value")), - satisfies(ServerAttributes.SERVER_PORT, val -> val.isNotZero()), - satisfies(ThreadIncubatingAttributes.THREAD_ID, val -> val.isNotZero()), + stringArrayKey("http.request.header.key"), singletonList("value")), + satisfies(SERVER_PORT, AbstractLongAssert::isNotZero), + satisfies( + ThreadIncubatingAttributes.THREAD_ID, + AbstractLongAssert::isNotZero), satisfies( - ThreadIncubatingAttributes.THREAD_NAME, val -> val.isNotBlank())), - val -> withSpanAssert(val))); + ThreadIncubatingAttributes.THREAD_NAME, + AbstractCharSequenceAssert::isNotBlank)), + AbstractSpringStarterSmokeTest::withSpanAssert)); - // Metric testing.waitAndAssertMetrics( OtelSpringStarterSmokeTestController.METER_SCOPE_NAME, OtelSpringStarterSmokeTestController.TEST_HISTOGRAM, @@ -222,7 +224,7 @@ void shouldSendTelemetry() { // JMX based metrics - test one per JMX bean List jmxMetrics = - new ArrayList<>(Arrays.asList("jvm.thread.count", "jvm.memory.used", "jvm.memory.init")); + new ArrayList<>(asList("jvm.thread.count", "jvm.memory.used", "jvm.memory.init")); double javaVersion = Double.parseDouble(System.getProperty("java.specification.version")); // See https://github.com/open-telemetry/opentelemetry-java-instrumentation/issues/13503 @@ -245,7 +247,6 @@ void shouldSendTelemetry() { assertAdditionalMetrics(); - // Log List exportedLogRecords = testing.getExportedLogRecords(); assertThat(exportedLogRecords).as("No log record exported.").isNotEmpty(); if (!nativeImage) { @@ -261,15 +262,12 @@ void shouldSendTelemetry() { if (SemconvStability.emitStableDatabaseSemconv()) { attributesAssert.containsEntry( - CodeAttributes.CODE_FUNCTION_NAME, - "org.springframework.boot.StartupInfoLogger.logStarting"); + CODE_FUNCTION_NAME, "org.springframework.boot.StartupInfoLogger.logStarting"); } if (SemconvStability.isEmitOldCodeSemconv()) { attributesAssert - .containsEntry( - CodeIncubatingAttributes.CODE_NAMESPACE, - "org.springframework.boot.StartupInfoLogger") - .containsEntry(CodeIncubatingAttributes.CODE_FUNCTION, "logStarting"); + .containsEntry(CODE_NAMESPACE, "org.springframework.boot.StartupInfoLogger") + .containsEntry(CODE_FUNCTION, "logStarting"); } } } @@ -282,10 +280,9 @@ void databaseQuery() { testing.runWithSpan( "server", - () -> { - jdbcTemplate.query( - "select name from customer where id = 1", (rs, rowNum) -> rs.getString("name")); - }); + () -> + jdbcTemplate.query( + "select name from customer where id = 1", (rs, rowNum) -> rs.getString("name"))); // 1 is not replaced by ?, otel.instrumentation.common.db-statement-sanitizer.enabled=false testing.waitAndAssertTraces( @@ -309,8 +306,7 @@ void restTemplate() { traceAssert -> traceAssert.hasSpansSatisfyingExactly( span -> HttpSpanDataAssert.create(span).assertClientGetRequest("/ping"), - span -> - span.hasKind(SpanKind.SERVER).hasAttribute(HttpAttributes.HTTP_ROUTE, "/ping"), + span -> span.hasKind(SpanKind.SERVER).hasAttribute(HTTP_ROUTE, "/ping"), span -> withSpanAssert(span))); } @@ -328,8 +324,6 @@ void shouldRedactSomeUrlParameters() { span -> HttpSpanDataAssert.create(span) .assertClientGetRequest("/test?X-Goog-Signature=REDACTED"), - span -> - span.hasKind(SpanKind.SERVER) - .hasAttribute(HttpAttributes.HTTP_ROUTE, "/test"))); + span -> span.hasKind(SpanKind.SERVER).hasAttribute(HTTP_ROUTE, "/test"))); } } diff --git a/smoke-tests-otel-starter/spring-boot-4/src/test/java/io/opentelemetry/spring/smoketest/KafkaSpringStarterSmokeTest.java b/smoke-tests-otel-starter/spring-boot-4/src/test/java/io/opentelemetry/spring/smoketest/KafkaSpringStarterSmokeTest.java index 0e9386d58c23..40ddb3a290aa 100644 --- a/smoke-tests-otel-starter/spring-boot-4/src/test/java/io/opentelemetry/spring/smoketest/KafkaSpringStarterSmokeTest.java +++ b/smoke-tests-otel-starter/spring-boot-4/src/test/java/io/opentelemetry/spring/smoketest/KafkaSpringStarterSmokeTest.java @@ -7,7 +7,6 @@ import io.opentelemetry.api.OpenTelemetry; import io.opentelemetry.instrumentation.spring.autoconfigure.OpenTelemetryAutoConfiguration; -import io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.kafka.KafkaInstrumentationAutoConfiguration; import io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.kafka.KafkaInstrumentationSpringBoot4AutoConfiguration; import io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.thread.ThreadDetailsAutoConfiguration; import java.time.Duration; @@ -56,7 +55,6 @@ void setUpContext() { ThreadDetailsAutoConfiguration.class, SpringSmokeOtelConfiguration.class, KafkaAutoConfiguration.class, - KafkaInstrumentationAutoConfiguration.class, KafkaInstrumentationSpringBoot4AutoConfiguration.class, KafkaConfig.class)) .withPropertyValues( From c2d52304fa018e507814ecee5162d79bc78c7f49 Mon Sep 17 00:00:00 2001 From: Jay DeLuca Date: Fri, 28 Nov 2025 14:33:48 -0500 Subject: [PATCH 21/40] cleanup --- .../kafka/KafkaInstrumentationAutoConfigurationTest.java | 8 +++----- .../AbstractJvmMongodbSpringStarterSmokeTest.java | 6 +----- 2 files changed, 4 insertions(+), 10 deletions(-) diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/testSpring4/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/kafka/KafkaInstrumentationAutoConfigurationTest.java b/instrumentation/spring/spring-boot-autoconfigure/src/testSpring4/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/kafka/KafkaInstrumentationAutoConfigurationTest.java index 3899b56bde31..2b60a084110f 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/testSpring4/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/kafka/KafkaInstrumentationAutoConfigurationTest.java +++ b/instrumentation/spring/spring-boot-autoconfigure/src/testSpring4/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/kafka/KafkaInstrumentationAutoConfigurationTest.java @@ -5,13 +5,13 @@ package io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.kafka; +import static java.util.Collections.emptyMap; import static org.assertj.core.api.Assertions.assertThat; import io.opentelemetry.api.OpenTelemetry; import io.opentelemetry.instrumentation.api.incubator.config.internal.InstrumentationConfig; import io.opentelemetry.instrumentation.spring.autoconfigure.internal.properties.ConfigPropertiesBridge; 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.kafka.autoconfigure.DefaultKafkaProducerFactoryCustomizer; @@ -24,9 +24,7 @@ class KafkaInstrumentationAutoConfigurationTest { new ApplicationContextRunner() .withBean( InstrumentationConfig.class, - () -> - new ConfigPropertiesBridge( - DefaultConfigProperties.createFromMap(Collections.emptyMap()))) + () -> new ConfigPropertiesBridge(DefaultConfigProperties.createFromMap(emptyMap()))) .withConfiguration( AutoConfigurations.of(KafkaInstrumentationSpringBoot4AutoConfiguration.class)) .withBean("openTelemetry", OpenTelemetry.class, OpenTelemetry::noop); @@ -47,7 +45,7 @@ void defaultConfiguration() { // Verify the customizer works by applying it to a producer factory DefaultKafkaProducerFactory factory = - new DefaultKafkaProducerFactory<>(Collections.emptyMap()); + new DefaultKafkaProducerFactory<>(emptyMap()); customizer.customize(factory); // Check that interceptors were added (the customizer adds a post processor) diff --git a/smoke-tests-otel-starter/spring-boot-common/src/main/java/io/opentelemetry/spring/smoketest/AbstractJvmMongodbSpringStarterSmokeTest.java b/smoke-tests-otel-starter/spring-boot-common/src/main/java/io/opentelemetry/spring/smoketest/AbstractJvmMongodbSpringStarterSmokeTest.java index d327e0847287..60d0cafa8b67 100644 --- a/smoke-tests-otel-starter/spring-boot-common/src/main/java/io/opentelemetry/spring/smoketest/AbstractJvmMongodbSpringStarterSmokeTest.java +++ b/smoke-tests-otel-starter/spring-boot-common/src/main/java/io/opentelemetry/spring/smoketest/AbstractJvmMongodbSpringStarterSmokeTest.java @@ -49,11 +49,7 @@ void setUpContext() { SpringSmokeOtelConfiguration.class, MongoAutoConfiguration.class, MongoClientInstrumentationAutoConfiguration.class)) - .withPropertyValues( - // Spring 2 and Spring 3 use different property names for the MongoDB URI - "spring.data.mongodb.uri=" + container.getReplicaSetUrl(), - // Spring Boot 4 - "spring.mongodb.uri=" + container.getReplicaSetUrl()); + .withPropertyValues("spring.data.mongodb.uri=" + container.getReplicaSetUrl()); } @Override From d17fed1182906bd71009b6834a5ef458f9763ee7 Mon Sep 17 00:00:00 2001 From: Jay DeLuca Date: Fri, 28 Nov 2025 14:39:50 -0500 Subject: [PATCH 22/40] fix comment line breaks --- .../KafkaInstrumentationSpringBoot4AutoConfiguration.java | 5 ++--- ...ngoClientInstrumentationSpringBoot4AutoConfiguration.java | 5 ++--- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/main/javaSpring4/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/kafka/KafkaInstrumentationSpringBoot4AutoConfiguration.java b/instrumentation/spring/spring-boot-autoconfigure/src/main/javaSpring4/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/kafka/KafkaInstrumentationSpringBoot4AutoConfiguration.java index f3545ce68bbc..9a63b9b6035e 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/main/javaSpring4/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/kafka/KafkaInstrumentationSpringBoot4AutoConfiguration.java +++ b/instrumentation/spring/spring-boot-autoconfigure/src/main/javaSpring4/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/kafka/KafkaInstrumentationSpringBoot4AutoConfiguration.java @@ -33,9 +33,8 @@ }) @ConditionalOnMissingBean(name = "otelKafkaProducerFactoryCustomizer") @ConditionalOnMissingClass( - "org.springframework.boot.autoconfigure.kafka.DefaultKafkaProducerFactoryCustomizer") // Spring -// Boot 2 -// & 3 + // Spring Boot 2 & 3 + "org.springframework.boot.autoconfigure.kafka.DefaultKafkaProducerFactoryCustomizer") @Configuration public class KafkaInstrumentationSpringBoot4AutoConfiguration { diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/main/javaSpring4/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/mongo/MongoClientInstrumentationSpringBoot4AutoConfiguration.java b/instrumentation/spring/spring-boot-autoconfigure/src/main/javaSpring4/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/mongo/MongoClientInstrumentationSpringBoot4AutoConfiguration.java index 8b3c61e24b62..e7c780dd92b2 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/main/javaSpring4/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/mongo/MongoClientInstrumentationSpringBoot4AutoConfiguration.java +++ b/instrumentation/spring/spring-boot-autoconfigure/src/main/javaSpring4/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/mongo/MongoClientInstrumentationSpringBoot4AutoConfiguration.java @@ -24,9 +24,8 @@ @ConditionalOnClass({MongoClientSettings.class, MongoClientSettingsBuilderCustomizer.class}) @ConditionalOnEnabledInstrumentation(module = "mongo") @ConditionalOnMissingClass( - "org.springframework.boot.autoconfigure.mongo.MongoClientSettingsBuilderCustomizer") // Spring -// Boot 2 & -// 3 + // Spring Boot 2 & 3 + "org.springframework.boot.autoconfigure.mongo.MongoClientSettingsBuilderCustomizer") @Configuration public class MongoClientInstrumentationSpringBoot4AutoConfiguration { From 36c991a434e2d31d64e5152992a5aa9bc4e63e49 Mon Sep 17 00:00:00 2001 From: Jay DeLuca Date: Sat, 29 Nov 2025 08:02:17 -0500 Subject: [PATCH 23/40] switch to using conditionalOnClass --- .../jdbc/JdbcInstrumentationAutoConfiguration.java | 5 ++--- .../kafka/KafkaInstrumentationAutoConfiguration.java | 4 ---- .../mongo/MongoClientInstrumentationAutoConfiguration.java | 5 ----- .../web/SpringWebInstrumentationAutoConfiguration.java | 3 --- .../JdbcInstrumentationSpringBoot4AutoConfiguration.java | 5 ++--- .../KafkaInstrumentationSpringBoot4AutoConfiguration.java | 4 ---- ...ngoClientInstrumentationSpringBoot4AutoConfiguration.java | 4 ---- 7 files changed, 4 insertions(+), 26 deletions(-) diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/jdbc/JdbcInstrumentationAutoConfiguration.java b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/jdbc/JdbcInstrumentationAutoConfiguration.java index fce4ca1714bb..79d2adb30644 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/jdbc/JdbcInstrumentationAutoConfiguration.java +++ b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/jdbc/JdbcInstrumentationAutoConfiguration.java @@ -12,7 +12,7 @@ import org.springframework.beans.factory.ObjectProvider; import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; -import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -24,8 +24,7 @@ @ConditionalOnEnabledInstrumentation(module = "jdbc") @AutoConfiguration(after = DataSourceAutoConfiguration.class) @ConditionalOnBean({DataSource.class}) -@ConditionalOnMissingClass( - "org.springframework.boot.jdbc.autoconfigure.DataSourceAutoConfiguration") // Spring Boot 4+ +@ConditionalOnClass(DataSourceAutoConfiguration.class) @Configuration(proxyBeanMethods = false) public class JdbcInstrumentationAutoConfiguration { diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/kafka/KafkaInstrumentationAutoConfiguration.java b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/kafka/KafkaInstrumentationAutoConfiguration.java index 764177f367ab..4df611685733 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/kafka/KafkaInstrumentationAutoConfiguration.java +++ b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/kafka/KafkaInstrumentationAutoConfiguration.java @@ -12,7 +12,6 @@ import io.opentelemetry.instrumentation.spring.kafka.v2_7.SpringKafkaTelemetry; import org.springframework.beans.factory.ObjectProvider; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; -import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.autoconfigure.kafka.DefaultKafkaProducerFactoryCustomizer; import org.springframework.context.annotation.Bean; @@ -30,9 +29,6 @@ ConcurrentKafkaListenerContainerFactory.class, DefaultKafkaProducerFactoryCustomizer.class }) -@ConditionalOnMissingClass( - "org.springframework.boot.kafka.autoconfigure.DefaultKafkaProducerFactoryCustomizer") // Spring -// Boot 4+ @Configuration public class KafkaInstrumentationAutoConfiguration { diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/mongo/MongoClientInstrumentationAutoConfiguration.java b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/mongo/MongoClientInstrumentationAutoConfiguration.java index a1d9f437cfdd..fcc3bc46a315 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/mongo/MongoClientInstrumentationAutoConfiguration.java +++ b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/mongo/MongoClientInstrumentationAutoConfiguration.java @@ -12,7 +12,6 @@ import io.opentelemetry.instrumentation.spring.autoconfigure.internal.ConditionalOnEnabledInstrumentation; import io.opentelemetry.instrumentation.spring.autoconfigure.internal.properties.InstrumentationConfigUtil; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; -import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass; import org.springframework.boot.autoconfigure.mongo.MongoClientSettingsBuilderCustomizer; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -23,10 +22,6 @@ */ @ConditionalOnClass({MongoClientSettings.class, MongoClientSettingsBuilderCustomizer.class}) @ConditionalOnEnabledInstrumentation(module = "mongo") -@ConditionalOnMissingClass( - "org.springframework.boot.mongodb.autoconfigure.MongoClientSettingsBuilderCustomizer") // Spring -// Boot -// 4+ @Configuration public class MongoClientInstrumentationAutoConfiguration { diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/web/SpringWebInstrumentationAutoConfiguration.java b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/web/SpringWebInstrumentationAutoConfiguration.java index 0c9a40f856f7..97d5bd9b52d5 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/web/SpringWebInstrumentationAutoConfiguration.java +++ b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/web/SpringWebInstrumentationAutoConfiguration.java @@ -10,7 +10,6 @@ import io.opentelemetry.instrumentation.spring.autoconfigure.internal.ConditionalOnEnabledInstrumentation; import org.springframework.beans.factory.ObjectProvider; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; -import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass; import org.springframework.boot.web.client.RestTemplateCustomizer; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -26,8 +25,6 @@ */ @ConditionalOnEnabledInstrumentation(module = "spring-web") @ConditionalOnClass({RestTemplate.class, RestTemplateCustomizer.class}) -@ConditionalOnMissingClass( - "org.springframework.boot.restclient.RestTemplateCustomizer") // Spring Boot 4+ @Configuration public class SpringWebInstrumentationAutoConfiguration { diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/main/javaSpring4/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/jdbc/JdbcInstrumentationSpringBoot4AutoConfiguration.java b/instrumentation/spring/spring-boot-autoconfigure/src/main/javaSpring4/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/jdbc/JdbcInstrumentationSpringBoot4AutoConfiguration.java index 93b7d11f1ab4..ab8ad9cdf99f 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/main/javaSpring4/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/jdbc/JdbcInstrumentationSpringBoot4AutoConfiguration.java +++ b/instrumentation/spring/spring-boot-autoconfigure/src/main/javaSpring4/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/jdbc/JdbcInstrumentationSpringBoot4AutoConfiguration.java @@ -12,7 +12,7 @@ import org.springframework.beans.factory.ObjectProvider; import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; -import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.jdbc.autoconfigure.DataSourceAutoConfiguration; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -25,8 +25,7 @@ @AutoConfiguration(after = DataSourceAutoConfiguration.class) @ConditionalOnBean({DataSource.class}) @Configuration(proxyBeanMethods = false) -@ConditionalOnMissingClass( - "org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration") // Spring Boot 2 & 3 +@ConditionalOnClass(DataSourceAutoConfiguration.class) public class JdbcInstrumentationSpringBoot4AutoConfiguration { // For error prone diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/main/javaSpring4/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/kafka/KafkaInstrumentationSpringBoot4AutoConfiguration.java b/instrumentation/spring/spring-boot-autoconfigure/src/main/javaSpring4/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/kafka/KafkaInstrumentationSpringBoot4AutoConfiguration.java index 9a63b9b6035e..34980aa6daf3 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/main/javaSpring4/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/kafka/KafkaInstrumentationSpringBoot4AutoConfiguration.java +++ b/instrumentation/spring/spring-boot-autoconfigure/src/main/javaSpring4/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/kafka/KafkaInstrumentationSpringBoot4AutoConfiguration.java @@ -13,7 +13,6 @@ import org.springframework.beans.factory.ObjectProvider; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; -import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.kafka.autoconfigure.DefaultKafkaProducerFactoryCustomizer; import org.springframework.context.annotation.Bean; @@ -32,9 +31,6 @@ DefaultKafkaProducerFactoryCustomizer.class }) @ConditionalOnMissingBean(name = "otelKafkaProducerFactoryCustomizer") -@ConditionalOnMissingClass( - // Spring Boot 2 & 3 - "org.springframework.boot.autoconfigure.kafka.DefaultKafkaProducerFactoryCustomizer") @Configuration public class KafkaInstrumentationSpringBoot4AutoConfiguration { diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/main/javaSpring4/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/mongo/MongoClientInstrumentationSpringBoot4AutoConfiguration.java b/instrumentation/spring/spring-boot-autoconfigure/src/main/javaSpring4/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/mongo/MongoClientInstrumentationSpringBoot4AutoConfiguration.java index e7c780dd92b2..09dcf88b3480 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/main/javaSpring4/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/mongo/MongoClientInstrumentationSpringBoot4AutoConfiguration.java +++ b/instrumentation/spring/spring-boot-autoconfigure/src/main/javaSpring4/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/mongo/MongoClientInstrumentationSpringBoot4AutoConfiguration.java @@ -12,7 +12,6 @@ import io.opentelemetry.instrumentation.spring.autoconfigure.internal.ConditionalOnEnabledInstrumentation; import io.opentelemetry.instrumentation.spring.autoconfigure.internal.properties.InstrumentationConfigUtil; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; -import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass; import org.springframework.boot.mongodb.autoconfigure.MongoClientSettingsBuilderCustomizer; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -23,9 +22,6 @@ */ @ConditionalOnClass({MongoClientSettings.class, MongoClientSettingsBuilderCustomizer.class}) @ConditionalOnEnabledInstrumentation(module = "mongo") -@ConditionalOnMissingClass( - // Spring Boot 2 & 3 - "org.springframework.boot.autoconfigure.mongo.MongoClientSettingsBuilderCustomizer") @Configuration public class MongoClientInstrumentationSpringBoot4AutoConfiguration { From 10fef8c0a4d9384628a21ba77f9f66d1a3f6168b Mon Sep 17 00:00:00 2001 From: Jay DeLuca Date: Sat, 29 Nov 2025 09:18:44 -0500 Subject: [PATCH 24/40] abstract incompatible class --- .../smoketest/OtelSpringStarterSmokeTest.java | 28 ++ .../smoketest/OtelSpringStarterSmokeTest.java | 28 ++ .../spring-boot-4/build.gradle.kts | 2 + .../src/main/resources/application.yaml | 39 --- .../AbstractKafkaSpringStarterSmokeTest.java | 124 ------- ...AbstractMongodbSpringStarterSmokeTest.java | 39 --- .../AbstractOtelSpringStarterSmokeTest.java | 329 ------------------ .../smoketest/OtelSpringStarterSmokeTest.java | 28 ++ .../OtelSpringStarterSmokeTestController.java | 42 --- .../spring/smoketest/SpringComponent.java | 18 - .../AbstractOtelSpringStarterSmokeTest.java | 35 +- 11 files changed, 96 insertions(+), 616 deletions(-) delete mode 100644 smoke-tests-otel-starter/spring-boot-4/src/main/resources/application.yaml delete mode 100644 smoke-tests-otel-starter/spring-boot-4/src/test/java/io/opentelemetry/spring/smoketest/AbstractKafkaSpringStarterSmokeTest.java delete mode 100644 smoke-tests-otel-starter/spring-boot-4/src/test/java/io/opentelemetry/spring/smoketest/AbstractMongodbSpringStarterSmokeTest.java delete mode 100644 smoke-tests-otel-starter/spring-boot-4/src/test/java/io/opentelemetry/spring/smoketest/AbstractOtelSpringStarterSmokeTest.java delete mode 100644 smoke-tests-otel-starter/spring-boot-4/src/test/java/io/opentelemetry/spring/smoketest/OtelSpringStarterSmokeTestController.java delete mode 100644 smoke-tests-otel-starter/spring-boot-4/src/test/java/io/opentelemetry/spring/smoketest/SpringComponent.java diff --git a/smoke-tests-otel-starter/spring-boot-2/src/test/java/io/opentelemetry/spring/smoketest/OtelSpringStarterSmokeTest.java b/smoke-tests-otel-starter/spring-boot-2/src/test/java/io/opentelemetry/spring/smoketest/OtelSpringStarterSmokeTest.java index 95d86d5d4dc2..40ecda1e13f5 100644 --- a/smoke-tests-otel-starter/spring-boot-2/src/test/java/io/opentelemetry/spring/smoketest/OtelSpringStarterSmokeTest.java +++ b/smoke-tests-otel-starter/spring-boot-2/src/test/java/io/opentelemetry/spring/smoketest/OtelSpringStarterSmokeTest.java @@ -5,8 +5,16 @@ package io.opentelemetry.spring.smoketest; +import java.net.URI; import org.assertj.core.api.AbstractIterableAssert; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.web.client.TestRestTemplate; +import org.springframework.boot.web.client.RestTemplateBuilder; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.http.RequestEntity; +import org.springframework.web.client.RestTemplate; @SpringBootTest( classes = { @@ -23,6 +31,26 @@ }) class OtelSpringStarterSmokeTest extends AbstractOtelSpringStarterSmokeTest { + @Autowired protected TestRestTemplate testRestTemplate; + @Autowired private RestTemplateBuilder restTemplateBuilder; + + @Override + void makeClientCall() { + HttpHeaders headers = new HttpHeaders(); + headers.add("key", "value"); + + testRestTemplate.exchange( + new RequestEntity<>( + null, headers, HttpMethod.GET, URI.create(OtelSpringStarterSmokeTestController.PING)), + String.class); + } + + @Override + void restClientCall(String path) { + RestTemplate restTemplate = restTemplateBuilder.rootUri("http://localhost:" + port).build(); + restTemplate.getForObject(path, String.class); + } + @Override protected void assertAdditionalMetrics() { testing.waitAndAssertMetrics( diff --git a/smoke-tests-otel-starter/spring-boot-3/src/test/java/io/opentelemetry/spring/smoketest/OtelSpringStarterSmokeTest.java b/smoke-tests-otel-starter/spring-boot-3/src/test/java/io/opentelemetry/spring/smoketest/OtelSpringStarterSmokeTest.java index 538e68d3ffc3..2b376ac1d953 100644 --- a/smoke-tests-otel-starter/spring-boot-3/src/test/java/io/opentelemetry/spring/smoketest/OtelSpringStarterSmokeTest.java +++ b/smoke-tests-otel-starter/spring-boot-3/src/test/java/io/opentelemetry/spring/smoketest/OtelSpringStarterSmokeTest.java @@ -5,9 +5,17 @@ package io.opentelemetry.spring.smoketest; +import java.net.URI; import java.util.List; import org.assertj.core.api.AbstractIterableAssert; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.web.client.TestRestTemplate; +import org.springframework.boot.web.client.RestTemplateBuilder; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.http.RequestEntity; +import org.springframework.web.client.RestTemplate; @SpringBootTest( classes = { @@ -25,6 +33,26 @@ }) class OtelSpringStarterSmokeTest extends AbstractOtelSpringStarterSmokeTest { + @Autowired protected TestRestTemplate testRestTemplate; + @Autowired private RestTemplateBuilder restTemplateBuilder; + + @Override + void makeClientCall() { + HttpHeaders headers = new HttpHeaders(); + headers.add("key", "value"); + + testRestTemplate.exchange( + new RequestEntity<>( + null, headers, HttpMethod.GET, URI.create(OtelSpringStarterSmokeTestController.PING)), + String.class); + } + + @Override + void restClientCall(String path) { + RestTemplate restTemplate = restTemplateBuilder.rootUri("http://localhost:" + port).build(); + restTemplate.getForObject(path, String.class); + } + @Override protected void assertAdditionalMetrics() { if (!isFlightRecorderAvailable()) { diff --git a/smoke-tests-otel-starter/spring-boot-4/build.gradle.kts b/smoke-tests-otel-starter/spring-boot-4/build.gradle.kts index ad5315d30271..ae52b19cd1da 100644 --- a/smoke-tests-otel-starter/spring-boot-4/build.gradle.kts +++ b/smoke-tests-otel-starter/spring-boot-4/build.gradle.kts @@ -22,6 +22,8 @@ dependencies { implementation(platform(org.springframework.boot.gradle.plugin.SpringBootPlugin.BOM_COORDINATES)) implementation("io.opentelemetry:opentelemetry-extension-trace-propagators") + testImplementation(project(":smoke-tests-otel-starter:spring-boot-common")) + testImplementation("org.springframework:spring-test:7.0.1") testImplementation("org.springframework.boot:spring-boot-resttestclient") testImplementation("org.springframework.boot:spring-boot-starter-test") diff --git a/smoke-tests-otel-starter/spring-boot-4/src/main/resources/application.yaml b/smoke-tests-otel-starter/spring-boot-4/src/main/resources/application.yaml deleted file mode 100644 index 79ecd43e6291..000000000000 --- a/smoke-tests-otel-starter/spring-boot-4/src/main/resources/application.yaml +++ /dev/null @@ -1,39 +0,0 @@ -otel: - instrumentation: - common: - db-statement-sanitizer: - enabled: false - kafka: - experimental-span-attributes: true - logback-appender: - experimental: - capture-code-attributes: true - micrometer: - enabled: true - http: - client: - emit-experimental-telemetry: true - server: - emit-experimental-telemetry: true - capture-request-headers: [key] - propagators: - - b3 - resource: - attributes: - attributeFromYaml: true # boolean will be automatically converted to string by spring - -application: - prop: propValue - prop-with-dash: provWithDashValue - -spring: - application: - name: otel-spring-starter-smoke-test - kafka: - bootstrap-servers: localhost:9094 - consumer: - auto-offset-reset: earliest - listener: - idle-between-polls: 1000 - producer: - transaction-id-prefix: test- \ No newline at end of file diff --git a/smoke-tests-otel-starter/spring-boot-4/src/test/java/io/opentelemetry/spring/smoketest/AbstractKafkaSpringStarterSmokeTest.java b/smoke-tests-otel-starter/spring-boot-4/src/test/java/io/opentelemetry/spring/smoketest/AbstractKafkaSpringStarterSmokeTest.java deleted file mode 100644 index f73225e40a1c..000000000000 --- a/smoke-tests-otel-starter/spring-boot-4/src/test/java/io/opentelemetry/spring/smoketest/AbstractKafkaSpringStarterSmokeTest.java +++ /dev/null @@ -1,124 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.spring.smoketest; - -import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo; -import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.satisfies; - -import io.opentelemetry.api.OpenTelemetry; -import io.opentelemetry.api.common.AttributeKey; -import io.opentelemetry.api.trace.SpanKind; -import io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes; -import org.apache.kafka.clients.admin.NewTopic; -import org.apache.kafka.clients.consumer.ConsumerRecord; -import org.assertj.core.api.AbstractLongAssert; -import org.assertj.core.api.AbstractStringAssert; -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.kafka.annotation.KafkaListener; -import org.springframework.kafka.config.TopicBuilder; -import org.springframework.kafka.core.KafkaTemplate; - -abstract class AbstractKafkaSpringStarterSmokeTest extends AbstractSpringStarterSmokeTest { - - protected KafkaTemplate kafkaTemplate; - - private static final AttributeKey MESSAGING_CLIENT_ID = - AttributeKey.stringKey("messaging.client_id"); - - @SuppressWarnings("deprecation") // using deprecated semconv - @Test - void shouldInstrumentProducerAndConsumer() { - testing.runWithSpan( - "producer", - () -> - kafkaTemplate.executeInTransaction( - ops -> { - ops.send("testTopic", "10", "testSpan"); - return 0; - })); - - testing.waitAndAssertTraces( - trace -> - trace.hasSpansSatisfyingExactly( - span -> span.hasName("producer"), - span -> - span.hasName("testTopic publish") - .hasKind(SpanKind.PRODUCER) - .hasParent(trace.getSpan(0)) - .hasAttributesSatisfyingExactly( - equalTo(MessagingIncubatingAttributes.MESSAGING_SYSTEM, "kafka"), - equalTo( - MessagingIncubatingAttributes.MESSAGING_DESTINATION_NAME, - "testTopic"), - equalTo(MessagingIncubatingAttributes.MESSAGING_OPERATION, "publish"), - satisfies( - MESSAGING_CLIENT_ID, - stringAssert -> stringAssert.startsWith("producer")), - satisfies( - MessagingIncubatingAttributes.MESSAGING_DESTINATION_PARTITION_ID, - AbstractStringAssert::isNotEmpty), - satisfies( - MessagingIncubatingAttributes.MESSAGING_KAFKA_MESSAGE_OFFSET, - AbstractLongAssert::isNotNegative), - equalTo( - MessagingIncubatingAttributes.MESSAGING_KAFKA_MESSAGE_KEY, "10")), - span -> - span.hasName("testTopic process") - .hasKind(SpanKind.CONSUMER) - .hasParent(trace.getSpan(1)) - .hasAttributesSatisfying( - equalTo(MessagingIncubatingAttributes.MESSAGING_SYSTEM, "kafka"), - equalTo( - MessagingIncubatingAttributes.MESSAGING_DESTINATION_NAME, - "testTopic"), - equalTo(MessagingIncubatingAttributes.MESSAGING_OPERATION, "process"), - satisfies( - MessagingIncubatingAttributes.MESSAGING_MESSAGE_BODY_SIZE, - AbstractLongAssert::isNotNegative), - satisfies( - MessagingIncubatingAttributes.MESSAGING_DESTINATION_PARTITION_ID, - AbstractStringAssert::isNotEmpty), - satisfies( - MessagingIncubatingAttributes.MESSAGING_KAFKA_MESSAGE_OFFSET, - AbstractLongAssert::isNotNegative), - equalTo( - MessagingIncubatingAttributes.MESSAGING_KAFKA_MESSAGE_KEY, "10"), - equalTo( - MessagingIncubatingAttributes.MESSAGING_KAFKA_CONSUMER_GROUP, - "testListener"), - satisfies( - AttributeKey.longKey("kafka.record.queue_time_ms"), - AbstractLongAssert::isNotNegative), - satisfies( - MESSAGING_CLIENT_ID, - stringAssert -> stringAssert.startsWith("consumer"))), - span -> span.hasName("consumer").hasParent(trace.getSpan(2)))); - } - - @Configuration - public static class KafkaConfig { - - @Autowired OpenTelemetry openTelemetry; - - @Bean - public NewTopic testTopic() { - return TopicBuilder.name("testTopic").partitions(1).replicas(1).build(); - } - - @KafkaListener(id = "testListener", topics = "testTopic") - public void listener(ConsumerRecord record) { - openTelemetry - .getTracer("consumer", "1.0") - .spanBuilder("consumer") - .setSpanKind(SpanKind.CONSUMER) - .startSpan() - .end(); - } - } -} diff --git a/smoke-tests-otel-starter/spring-boot-4/src/test/java/io/opentelemetry/spring/smoketest/AbstractMongodbSpringStarterSmokeTest.java b/smoke-tests-otel-starter/spring-boot-4/src/test/java/io/opentelemetry/spring/smoketest/AbstractMongodbSpringStarterSmokeTest.java deleted file mode 100644 index 6bda62b9b3e3..000000000000 --- a/smoke-tests-otel-starter/spring-boot-4/src/test/java/io/opentelemetry/spring/smoketest/AbstractMongodbSpringStarterSmokeTest.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.spring.smoketest; - -import static io.opentelemetry.instrumentation.api.internal.SemconvStability.stableDbSystemName; -import static io.opentelemetry.instrumentation.testing.junit.db.SemconvStabilityUtil.maybeStable; - -import com.mongodb.client.MongoClient; -import io.opentelemetry.api.trace.SpanKind; -import io.opentelemetry.semconv.incubating.DbIncubatingAttributes; -import java.util.ArrayList; -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; - -abstract class AbstractMongodbSpringStarterSmokeTest extends AbstractSpringStarterSmokeTest { - - @Autowired protected MongoClient mongoClient; - - @SuppressWarnings("deprecation") // uses deprecated semconv - @Test - void mongodb() { - testing.runWithSpan("server", () -> mongoClient.listDatabaseNames().into(new ArrayList<>())); - - testing.waitAndAssertTraces( - trace -> - trace.hasSpansSatisfyingExactly( - span -> span.hasName("server"), - span -> - span.hasKind(SpanKind.CLIENT) - .hasName("listDatabases admin") - .hasAttribute( - maybeStable(DbIncubatingAttributes.DB_SYSTEM), - stableDbSystemName( - DbIncubatingAttributes.DbSystemIncubatingValues.MONGODB)))); - } -} diff --git a/smoke-tests-otel-starter/spring-boot-4/src/test/java/io/opentelemetry/spring/smoketest/AbstractOtelSpringStarterSmokeTest.java b/smoke-tests-otel-starter/spring-boot-4/src/test/java/io/opentelemetry/spring/smoketest/AbstractOtelSpringStarterSmokeTest.java deleted file mode 100644 index a52146e51dd7..000000000000 --- a/smoke-tests-otel-starter/spring-boot-4/src/test/java/io/opentelemetry/spring/smoketest/AbstractOtelSpringStarterSmokeTest.java +++ /dev/null @@ -1,329 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.spring.smoketest; - -import static io.opentelemetry.api.common.AttributeKey.booleanKey; -import static io.opentelemetry.api.common.AttributeKey.stringArrayKey; -import static io.opentelemetry.api.common.AttributeKey.stringKey; -import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo; -import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.satisfies; -import static io.opentelemetry.semconv.ClientAttributes.CLIENT_ADDRESS; -import static io.opentelemetry.semconv.CodeAttributes.CODE_FUNCTION_NAME; -import static io.opentelemetry.semconv.HttpAttributes.HTTP_REQUEST_METHOD; -import static io.opentelemetry.semconv.HttpAttributes.HTTP_RESPONSE_STATUS_CODE; -import static io.opentelemetry.semconv.HttpAttributes.HTTP_ROUTE; -import static io.opentelemetry.semconv.ServerAttributes.SERVER_ADDRESS; -import static io.opentelemetry.semconv.ServerAttributes.SERVER_PORT; -import static io.opentelemetry.semconv.incubating.CodeIncubatingAttributes.CODE_FUNCTION; -import static io.opentelemetry.semconv.incubating.CodeIncubatingAttributes.CODE_NAMESPACE; -import static java.util.Arrays.asList; -import static java.util.Collections.singletonList; -import static org.assertj.core.api.Assertions.assertThat; - -import io.opentelemetry.api.common.AttributeKey; -import io.opentelemetry.api.common.Attributes; -import io.opentelemetry.api.trace.SpanKind; -import io.opentelemetry.instrumentation.api.internal.SemconvStability; -import io.opentelemetry.instrumentation.spring.autoconfigure.internal.properties.OtelResourceProperties; -import io.opentelemetry.instrumentation.spring.autoconfigure.internal.properties.OtelSpringProperties; -import io.opentelemetry.instrumentation.spring.autoconfigure.internal.properties.OtlpExporterProperties; -import io.opentelemetry.instrumentation.spring.autoconfigure.internal.properties.SpringConfigProperties; -import io.opentelemetry.sdk.autoconfigure.spi.AutoConfigurationCustomizerProvider; -import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; -import io.opentelemetry.sdk.autoconfigure.spi.internal.DefaultConfigProperties; -import io.opentelemetry.sdk.logs.data.LogRecordData; -import io.opentelemetry.sdk.resources.Resource; -import io.opentelemetry.semconv.UrlAttributes; -import io.opentelemetry.semconv.incubating.DbIncubatingAttributes; -import io.opentelemetry.semconv.incubating.ServiceIncubatingAttributes; -import io.opentelemetry.semconv.incubating.ThreadIncubatingAttributes; -import java.net.URI; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import org.assertj.core.api.AbstractCharSequenceAssert; -import org.assertj.core.api.AbstractIterableAssert; -import org.assertj.core.api.AbstractLongAssert; -import org.assertj.core.api.MapAssert; -import org.junit.jupiter.api.MethodOrderer; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.TestMethodOrder; -import org.junit.jupiter.api.condition.OS; -import org.springframework.beans.factory.ObjectProvider; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.context.event.ApplicationReadyEvent; -import org.springframework.boot.restclient.RestTemplateBuilder; -import org.springframework.boot.resttestclient.TestRestTemplate; -import org.springframework.boot.resttestclient.autoconfigure.AutoConfigureTestRestTemplate; -import org.springframework.boot.test.web.server.LocalServerPort; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.event.EventListener; -import org.springframework.core.annotation.Order; -import org.springframework.core.env.Environment; -import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpMethod; -import org.springframework.http.RequestEntity; -import org.springframework.jdbc.core.JdbcTemplate; -import org.springframework.web.client.RestTemplate; - -/** - * This test class enforces the order of the tests to make sure that {@link #shouldSendTelemetry()}, - * which asserts the telemetry data from the application startup, is executed first. - */ -@SuppressWarnings("deprecation") // using deprecated semconv -@TestMethodOrder(MethodOrderer.OrderAnnotation.class) -@AutoConfigureTestRestTemplate -abstract class AbstractOtelSpringStarterSmokeTest extends AbstractSpringStarterSmokeTest { - - @Autowired private TestRestTemplate testRestTemplate; - - @Autowired private Environment environment; - @Autowired private OtelSpringProperties otelSpringProperties; - @Autowired private OtelResourceProperties otelResourceProperties; - @Autowired private OtlpExporterProperties otlpExporterProperties; - @Autowired private RestTemplateBuilder restTemplateBuilder; - @Autowired private JdbcTemplate jdbcTemplate; - - @LocalServerPort private int port; - - @Configuration(proxyBeanMethods = false) - static class TestConfiguration { - @Autowired private ObjectProvider jdbcTemplate; - - @EventListener(ApplicationReadyEvent.class) - public void loadData() { - jdbcTemplate - .getObject() - .execute( - "create table customer (id bigint not null, name varchar not null, primary key (id))"); - } - - @Bean - @Order(1) - AutoConfigurationCustomizerProvider hiddenPropagatorCustomizer() { - return customizer -> - customizer.addResourceCustomizer( - (resource, config) -> - resource.merge( - Resource.create( - Attributes.of(booleanKey("keyFromResourceCustomizer"), false)))); - } - - @Bean - @Order(2) - AutoConfigurationCustomizerProvider propagatorCustomizer() { - return customizer -> - customizer.addResourceCustomizer( - (resource, config) -> - resource.merge( - Resource.create( - Attributes.of(booleanKey("keyFromResourceCustomizer"), true)))); - } - - @Bean - AutoConfigurationCustomizerProvider customizerUsingPropertyDefinedInaSpringFile() { - return customizer -> - customizer.addResourceCustomizer( - (resource, config) -> { - String valueForKeyDeclaredZsEnvVariable = config.getString("APPLICATION_PROP"); - assertThat(valueForKeyDeclaredZsEnvVariable).isNotEmpty(); - - String valueForKeyWithDash = config.getString("application.prop-with-dash"); - assertThat(valueForKeyWithDash).isNotEmpty(); - - return resource; - }); - } - } - - @Test - void propertyConversion() { - ConfigProperties configProperties = - SpringConfigProperties.create( - environment, - otlpExporterProperties, - otelResourceProperties, - otelSpringProperties, - DefaultConfigProperties.createFromMap( - Collections.singletonMap("otel.exporter.otlp.headers", "a=1,b=2"))); - assertThat(configProperties.getMap("otel.exporter.otlp.headers")) - .containsEntry("a", "1") - .containsEntry("b", "2") - .containsEntry("c", "3"); - assertThat(configProperties.getList("otel.propagators")).containsExactly("b3"); - } - - @Test - @org.junit.jupiter.api.Order(1) - @SuppressWarnings("deprecation") // testing deprecated code semconv - void shouldSendTelemetry() { - HttpHeaders headers = new HttpHeaders(); - headers.add("key", "value"); - - testRestTemplate.exchange( - new RequestEntity<>( - null, headers, HttpMethod.GET, URI.create(OtelSpringStarterSmokeTestController.PING)), - String.class); - - testing.waitAndAssertTraces( - traceAssert -> - traceAssert.hasSpansSatisfyingExactly( - spanDataAssert -> - spanDataAssert - .hasKind(SpanKind.CLIENT) - .hasAttribute( - DbIncubatingAttributes.DB_STATEMENT, - "create table customer (id bigint not null, name varchar not null, primary key (id))")), - traceAssert -> - traceAssert.hasSpansSatisfyingExactly( - clientSpan -> - clientSpan - .hasKind(SpanKind.CLIENT) - .hasAttributesSatisfying( - satisfies( - UrlAttributes.URL_FULL, - stringAssert -> stringAssert.endsWith("/ping")), - equalTo(SERVER_ADDRESS, "localhost"), - satisfies(SERVER_PORT, val -> val.isNotZero())), - serverSpan -> - HttpSpanDataAssert.create(serverSpan) - .assertServerGetRequest("/ping") - .hasResourceSatisfying( - r -> - r.hasAttribute(booleanKey("keyFromResourceCustomizer"), true) - .hasAttribute(stringKey("attributeFromYaml"), "true") - .hasAttribute( - satisfies( - ServiceIncubatingAttributes.SERVICE_INSTANCE_ID, - AbstractCharSequenceAssert::isNotBlank))) - .hasAttributesSatisfying( - equalTo(HTTP_REQUEST_METHOD, "GET"), - equalTo(HTTP_RESPONSE_STATUS_CODE, 200L), - equalTo(HTTP_ROUTE, "/ping"), - equalTo(SERVER_ADDRESS, "localhost"), - satisfies(CLIENT_ADDRESS, s -> s.isIn("127.0.0.1", "0:0:0:0:0:0:0:1")), - equalTo( - stringArrayKey("http.request.header.key"), singletonList("value")), - satisfies(SERVER_PORT, AbstractLongAssert::isNotZero), - satisfies( - ThreadIncubatingAttributes.THREAD_ID, - AbstractLongAssert::isNotZero), - satisfies( - ThreadIncubatingAttributes.THREAD_NAME, - AbstractCharSequenceAssert::isNotBlank)), - AbstractSpringStarterSmokeTest::withSpanAssert)); - - testing.waitAndAssertMetrics( - OtelSpringStarterSmokeTestController.METER_SCOPE_NAME, - OtelSpringStarterSmokeTestController.TEST_HISTOGRAM, - AbstractIterableAssert::isNotEmpty); - - // JMX based metrics - test one per JMX bean - List jmxMetrics = - new ArrayList<>(asList("jvm.thread.count", "jvm.memory.used", "jvm.memory.init")); - - double javaVersion = Double.parseDouble(System.getProperty("java.specification.version")); - // See https://github.com/open-telemetry/opentelemetry-java-instrumentation/issues/13503 - // Also not available on Windows (getSystemLoadAverage returns -1) - if (javaVersion < 23 && !OS.WINDOWS.isCurrentOs()) { - jmxMetrics.add("jvm.system.cpu.load_1m"); - } - - boolean nativeImage = System.getProperty("org.graalvm.nativeimage.imagecode") != null; - if (!nativeImage) { - // GraalVM native image does not support buffer pools - have to investigate why - jmxMetrics.add("jvm.buffer.memory.used"); - } - jmxMetrics.forEach( - metricName -> - testing.waitAndAssertMetrics( - "io.opentelemetry.runtime-telemetry-java8", - metricName, - AbstractIterableAssert::isNotEmpty)); - - assertAdditionalMetrics(); - - List exportedLogRecords = testing.getExportedLogRecords(); - assertThat(exportedLogRecords).as("No log record exported.").isNotEmpty(); - if (!nativeImage) { - // log records differ in native image mode due to different startup timing - LogRecordData firstLog = exportedLogRecords.get(0); - assertThat(firstLog.getBodyValue().asString()) - .as("Should instrument logs") - .startsWith("Starting ") - .contains(this.getClass().getSimpleName()); - - MapAssert, Object> attributesAssert = - assertThat(firstLog.getAttributes().asMap()).as("Should capture code attributes"); - - if (SemconvStability.emitStableDatabaseSemconv()) { - attributesAssert.containsEntry( - CODE_FUNCTION_NAME, "org.springframework.boot.StartupInfoLogger.logStarting"); - } - if (SemconvStability.isEmitOldCodeSemconv()) { - attributesAssert - .containsEntry(CODE_NAMESPACE, "org.springframework.boot.StartupInfoLogger") - .containsEntry(CODE_FUNCTION, "logStarting"); - } - } - } - - protected void assertAdditionalMetrics() {} - - @Test - void databaseQuery() { - testing.clearAllExportedData(); - - testing.runWithSpan( - "server", - () -> - jdbcTemplate.query( - "select name from customer where id = 1", (rs, rowNum) -> rs.getString("name"))); - - // 1 is not replaced by ?, otel.instrumentation.common.db-statement-sanitizer.enabled=false - testing.waitAndAssertTraces( - traceAssert -> - traceAssert.hasSpansSatisfyingExactly( - span -> span.hasName("server"), - span -> - span.hasKind(SpanKind.CLIENT) - .hasAttribute( - DbIncubatingAttributes.DB_STATEMENT, - "select name from customer where id = 1"))); - } - - @Test - void restTemplate() { - testing.clearAllExportedData(); - - RestTemplate restTemplate = restTemplateBuilder.rootUri("http://localhost:" + port).build(); - restTemplate.getForObject(OtelSpringStarterSmokeTestController.PING, String.class); - testing.waitAndAssertTraces( - traceAssert -> - traceAssert.hasSpansSatisfyingExactly( - span -> HttpSpanDataAssert.create(span).assertClientGetRequest("/ping"), - span -> span.hasKind(SpanKind.SERVER).hasAttribute(HTTP_ROUTE, "/ping"), - span -> withSpanAssert(span))); - } - - @Test - void shouldRedactSomeUrlParameters() { - testing.clearAllExportedData(); - - RestTemplate restTemplate = restTemplateBuilder.rootUri("http://localhost:" + port).build(); - restTemplate.getForObject( - "/test?X-Goog-Signature=39Up9jzHkxhuIhFE9594DJxe7w6cIRCg0V6ICGS0", String.class); - - testing.waitAndAssertTraces( - traceAssert -> - traceAssert.hasSpansSatisfyingExactly( - span -> - HttpSpanDataAssert.create(span) - .assertClientGetRequest("/test?X-Goog-Signature=REDACTED"), - span -> span.hasKind(SpanKind.SERVER).hasAttribute(HTTP_ROUTE, "/test"))); - } -} diff --git a/smoke-tests-otel-starter/spring-boot-4/src/test/java/io/opentelemetry/spring/smoketest/OtelSpringStarterSmokeTest.java b/smoke-tests-otel-starter/spring-boot-4/src/test/java/io/opentelemetry/spring/smoketest/OtelSpringStarterSmokeTest.java index 513e6f6544ec..53ac20caf237 100644 --- a/smoke-tests-otel-starter/spring-boot-4/src/test/java/io/opentelemetry/spring/smoketest/OtelSpringStarterSmokeTest.java +++ b/smoke-tests-otel-starter/spring-boot-4/src/test/java/io/opentelemetry/spring/smoketest/OtelSpringStarterSmokeTest.java @@ -5,10 +5,18 @@ package io.opentelemetry.spring.smoketest; +import java.net.URI; import java.util.List; import org.assertj.core.api.AbstractIterableAssert; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.restclient.RestTemplateBuilder; +import org.springframework.boot.resttestclient.TestRestTemplate; import org.springframework.boot.resttestclient.autoconfigure.AutoConfigureTestRestTemplate; import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.http.RequestEntity; +import org.springframework.web.client.RestTemplate; @SpringBootTest( classes = { @@ -28,6 +36,26 @@ @AutoConfigureTestRestTemplate class OtelSpringStarterSmokeTest extends AbstractOtelSpringStarterSmokeTest { + @Autowired protected TestRestTemplate testRestTemplate; + @Autowired private RestTemplateBuilder restTemplateBuilder; + + @Override + void makeClientCall() { + HttpHeaders headers = new HttpHeaders(); + headers.add("key", "value"); + + testRestTemplate.exchange( + new RequestEntity<>( + null, headers, HttpMethod.GET, URI.create(OtelSpringStarterSmokeTestController.PING)), + String.class); + } + + @Override + void restClientCall(String path) { + RestTemplate restTemplate = restTemplateBuilder.rootUri("http://localhost:" + port).build(); + restTemplate.getForObject(path, String.class); + } + @Override protected void assertAdditionalMetrics() { if (!isFlightRecorderAvailable()) { diff --git a/smoke-tests-otel-starter/spring-boot-4/src/test/java/io/opentelemetry/spring/smoketest/OtelSpringStarterSmokeTestController.java b/smoke-tests-otel-starter/spring-boot-4/src/test/java/io/opentelemetry/spring/smoketest/OtelSpringStarterSmokeTestController.java deleted file mode 100644 index 68c6fd0d199f..000000000000 --- a/smoke-tests-otel-starter/spring-boot-4/src/test/java/io/opentelemetry/spring/smoketest/OtelSpringStarterSmokeTestController.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.spring.smoketest; - -import io.opentelemetry.api.OpenTelemetry; -import io.opentelemetry.api.metrics.LongHistogram; -import io.opentelemetry.api.metrics.Meter; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RestController; - -@RestController -public class OtelSpringStarterSmokeTestController { - - public static final String PING = "/ping"; - public static final String TEST = "/test"; - public static final String TEST_HISTOGRAM = "histogram-test-otel-spring-starter"; - public static final String METER_SCOPE_NAME = "scope"; - private final LongHistogram histogram; - private final SpringComponent component; - - public OtelSpringStarterSmokeTestController( - OpenTelemetry openTelemetry, SpringComponent springComponent) { - Meter meter = openTelemetry.getMeter(METER_SCOPE_NAME); - histogram = meter.histogramBuilder(TEST_HISTOGRAM).ofLongs().build(); - this.component = springComponent; - } - - @GetMapping(PING) - public String ping() { - histogram.record(10); - component.withSpanMethod("from-controller"); - return "pong"; - } - - @GetMapping(TEST) - public String testUrlToRedact() { - return "ok"; - } -} diff --git a/smoke-tests-otel-starter/spring-boot-4/src/test/java/io/opentelemetry/spring/smoketest/SpringComponent.java b/smoke-tests-otel-starter/spring-boot-4/src/test/java/io/opentelemetry/spring/smoketest/SpringComponent.java deleted file mode 100644 index 072b39393b01..000000000000 --- a/smoke-tests-otel-starter/spring-boot-4/src/test/java/io/opentelemetry/spring/smoketest/SpringComponent.java +++ /dev/null @@ -1,18 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.spring.smoketest; - -import io.opentelemetry.instrumentation.annotations.SpanAttribute; -import io.opentelemetry.instrumentation.annotations.WithSpan; -import org.springframework.stereotype.Component; - -@Component -public class SpringComponent { - - @SuppressWarnings("MethodCanBeStatic") - @WithSpan - public void withSpanMethod(@SpanAttribute String paramName) {} -} diff --git a/smoke-tests-otel-starter/spring-boot-common/src/main/java/io/opentelemetry/spring/smoketest/AbstractOtelSpringStarterSmokeTest.java b/smoke-tests-otel-starter/spring-boot-common/src/main/java/io/opentelemetry/spring/smoketest/AbstractOtelSpringStarterSmokeTest.java index a3d7bfc41e1d..080a6c9e91cf 100644 --- a/smoke-tests-otel-starter/spring-boot-common/src/main/java/io/opentelemetry/spring/smoketest/AbstractOtelSpringStarterSmokeTest.java +++ b/smoke-tests-otel-starter/spring-boot-common/src/main/java/io/opentelemetry/spring/smoketest/AbstractOtelSpringStarterSmokeTest.java @@ -31,7 +31,6 @@ import io.opentelemetry.semconv.incubating.DbIncubatingAttributes; import io.opentelemetry.semconv.incubating.ServiceIncubatingAttributes; import io.opentelemetry.semconv.incubating.ThreadIncubatingAttributes; -import java.net.URI; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -46,18 +45,12 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.context.event.ApplicationReadyEvent; -import org.springframework.boot.test.web.client.TestRestTemplate; -import org.springframework.boot.web.client.RestTemplateBuilder; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.event.EventListener; import org.springframework.core.annotation.Order; import org.springframework.core.env.Environment; -import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpMethod; -import org.springframework.http.RequestEntity; import org.springframework.jdbc.core.JdbcTemplate; -import org.springframework.web.client.RestTemplate; /** * This test class enforces the order of the tests to make sure that {@link #shouldSendTelemetry()}, @@ -65,20 +58,21 @@ */ @SuppressWarnings("deprecation") // using deprecated semconv @TestMethodOrder(MethodOrderer.OrderAnnotation.class) -class AbstractOtelSpringStarterSmokeTest extends AbstractSpringStarterSmokeTest { - - @Autowired private TestRestTemplate testRestTemplate; +abstract class AbstractOtelSpringStarterSmokeTest extends AbstractSpringStarterSmokeTest { @Autowired private Environment environment; @Autowired private OtelSpringProperties otelSpringProperties; @Autowired private OtelResourceProperties otelResourceProperties; @Autowired private OtlpExporterProperties otlpExporterProperties; - @Autowired private RestTemplateBuilder restTemplateBuilder; @Autowired private JdbcTemplate jdbcTemplate; + abstract void makeClientCall(); + + abstract void restClientCall(String path); + // can't use @LocalServerPort annotation since it moved packages between Spring Boot 2 and 3 @Value("${local.server.port}") - private int port; + protected int port; @Configuration(proxyBeanMethods = false) static class TestConfiguration { @@ -153,15 +147,8 @@ void propertyConversion() { @org.junit.jupiter.api.Order(1) @SuppressWarnings("deprecation") // testing deprecated code semconv void shouldSendTelemetry() { - HttpHeaders headers = new HttpHeaders(); - headers.add("key", "value"); + makeClientCall(); - testRestTemplate.exchange( - new RequestEntity<>( - null, headers, HttpMethod.GET, URI.create(OtelSpringStarterSmokeTestController.PING)), - String.class); - - // Span testing.waitAndAssertTraces( traceAssert -> traceAssert.hasSpansSatisfyingExactly( @@ -301,8 +288,8 @@ void databaseQuery() { void restTemplate() { testing.clearAllExportedData(); - RestTemplate restTemplate = restTemplateBuilder.rootUri("http://localhost:" + port).build(); - restTemplate.getForObject(OtelSpringStarterSmokeTestController.PING, String.class); + restClientCall(OtelSpringStarterSmokeTestController.PING); + testing.waitAndAssertTraces( traceAssert -> traceAssert.hasSpansSatisfyingExactly( @@ -316,9 +303,7 @@ void restTemplate() { void shouldRedactSomeUrlParameters() { testing.clearAllExportedData(); - RestTemplate restTemplate = restTemplateBuilder.rootUri("http://localhost:" + port).build(); - restTemplate.getForObject( - "/test?X-Goog-Signature=39Up9jzHkxhuIhFE9594DJxe7w6cIRCg0V6ICGS0", String.class); + restClientCall("/test?X-Goog-Signature=39Up9jzHkxhuIhFE9594DJxe7w6cIRCg0V6ICGS0"); testing.waitAndAssertTraces( traceAssert -> From 103cecbd8a9499ac785cceed1a1bcaf9f4fcc645 Mon Sep 17 00:00:00 2001 From: Jay DeLuca Date: Mon, 1 Dec 2025 20:19:34 -0500 Subject: [PATCH 25/40] start refactoring common test module --- .../build.gradle.kts | 3 + .../JdbcInstrumentationAutoConfiguration.java | 2 +- ...KafkaInstrumentationAutoConfiguration.java | 26 ++++---- .../MicrometerBridgeAutoConfiguration.java | 2 +- ...lientInstrumentationAutoConfiguration.java | 5 +- ...mentationSpringBoot4AutoConfiguration.java | 39 +---------- ...cInstrumentationAutoConfigurationTest.java | 65 +++++-------------- ...MicrometerBridgeAutoConfigurationTest.java | 41 +++--------- ...cInstrumentationAutoConfigurationTest.java | 46 +++---------- ...aInstrumentationAutoConfigurationTest.java | 36 +++------- ...MicrometerBridgeAutoConfigurationTest.java | 41 +++--------- .../testing/build.gradle.kts | 19 ++++++ ...cInstrumentationAutoConfigurationTest.java | 64 ++++++++++++++++++ ...aInstrumentationAutoConfigurationTest.java | 39 +++++++++++ ...MicrometerBridgeAutoConfigurationTest.java | 56 ++++++++++++++++ settings.gradle.kts | 4 +- 16 files changed, 260 insertions(+), 228 deletions(-) create mode 100644 instrumentation/spring/spring-boot-autoconfigure/testing/build.gradle.kts create mode 100644 instrumentation/spring/spring-boot-autoconfigure/testing/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/AbstractJdbcInstrumentationAutoConfigurationTest.java create mode 100644 instrumentation/spring/spring-boot-autoconfigure/testing/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/AbstractKafkaInstrumentationAutoConfigurationTest.java create mode 100644 instrumentation/spring/spring-boot-autoconfigure/testing/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/AbstractMicrometerBridgeAutoConfigurationTest.java diff --git a/instrumentation/spring/spring-boot-autoconfigure/build.gradle.kts b/instrumentation/spring/spring-boot-autoconfigure/build.gradle.kts index 8c8a760ba4ad..5ca4c737f92b 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/build.gradle.kts +++ b/instrumentation/spring/spring-boot-autoconfigure/build.gradle.kts @@ -107,6 +107,7 @@ dependencies { testImplementation("io.opentelemetry:opentelemetry-exporter-otlp") testImplementation("io.opentelemetry:opentelemetry-exporter-zipkin") testImplementation(project(":instrumentation-annotations")) + testImplementation(project(":instrumentation:spring:spring-boot-autoconfigure:testing")) latestDepTestLibrary("org.springframework.boot:spring-boot-starter-micrometer-metrics:latest.release") @@ -133,6 +134,7 @@ dependencies { add("javaSpring4CompileOnly", project(":instrumentation:spring:spring-kafka-2.7:library")) add("javaSpring4CompileOnly", project(":instrumentation:mongo:mongo-3.1:library")) add("javaSpring4CompileOnly", project(":instrumentation:micrometer:micrometer-1.5:library")) + add("javaSpring4CompileOnly", project(":instrumentation:spring:spring-boot-autoconfigure:testing")) } val latestDepTest = findProperty("testLatestDeps") as Boolean @@ -214,6 +216,7 @@ testing { implementation("io.opentelemetry:opentelemetry-sdk-testing") implementation(project(":instrumentation-api")) implementation(project(":instrumentation:micrometer:micrometer-1.5:library")) + implementation(project(":instrumentation:spring:spring-boot-autoconfigure:testing")) implementation("org.springframework.boot:spring-boot-starter-test:4.0.0") { exclude("org.junit.vintage", "junit-vintage-engine") } diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/jdbc/JdbcInstrumentationAutoConfiguration.java b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/jdbc/JdbcInstrumentationAutoConfiguration.java index 79d2adb30644..70ecf92407e6 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/jdbc/JdbcInstrumentationAutoConfiguration.java +++ b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/jdbc/JdbcInstrumentationAutoConfiguration.java @@ -24,7 +24,7 @@ @ConditionalOnEnabledInstrumentation(module = "jdbc") @AutoConfiguration(after = DataSourceAutoConfiguration.class) @ConditionalOnBean({DataSource.class}) -@ConditionalOnClass(DataSourceAutoConfiguration.class) +@ConditionalOnClass(DataSourceAutoConfiguration.class) // module changed in Spring Boot 4 @Configuration(proxyBeanMethods = false) public class JdbcInstrumentationAutoConfiguration { diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/kafka/KafkaInstrumentationAutoConfiguration.java b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/kafka/KafkaInstrumentationAutoConfiguration.java index 4df611685733..8e1e0bc6194d 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/kafka/KafkaInstrumentationAutoConfiguration.java +++ b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/kafka/KafkaInstrumentationAutoConfiguration.java @@ -24,20 +24,12 @@ * any time. */ @ConditionalOnEnabledInstrumentation(module = "kafka") -@ConditionalOnClass({ - KafkaTemplate.class, - ConcurrentKafkaListenerContainerFactory.class, - DefaultKafkaProducerFactoryCustomizer.class -}) +@ConditionalOnClass({KafkaTemplate.class, ConcurrentKafkaListenerContainerFactory.class}) @Configuration public class KafkaInstrumentationAutoConfiguration { - @Bean - DefaultKafkaProducerFactoryCustomizer otelKafkaProducerFactoryCustomizer( - OpenTelemetry openTelemetry) { - KafkaTelemetry kafkaTelemetry = KafkaTelemetry.create(openTelemetry); - return producerFactory -> producerFactory.addPostProcessor(kafkaTelemetry::wrap); - } + // For error prone + public KafkaInstrumentationAutoConfiguration() {} @Bean static SpringKafkaTelemetry getTelemetry( @@ -64,4 +56,16 @@ static SpringKafkaTelemetry getTelemetry( return new ConcurrentKafkaListenerContainerFactoryPostProcessor( () -> getTelemetry(openTelemetryProvider, configProvider)); } + + @ConditionalOnClass(DefaultKafkaProducerFactoryCustomizer.class) + @Configuration + static class ProducerFactoryCustomizerConfiguration { + + @Bean + DefaultKafkaProducerFactoryCustomizer otelKafkaProducerFactoryCustomizer( + OpenTelemetry openTelemetry) { + KafkaTelemetry kafkaTelemetry = KafkaTelemetry.create(openTelemetry); + return producerFactory -> producerFactory.addPostProcessor(kafkaTelemetry::wrap); + } + } } diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/micrometer/MicrometerBridgeAutoConfiguration.java b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/micrometer/MicrometerBridgeAutoConfiguration.java index 89e5c21032e7..2a483ae3d605 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/micrometer/MicrometerBridgeAutoConfiguration.java +++ b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/micrometer/MicrometerBridgeAutoConfiguration.java @@ -28,7 +28,7 @@ @AutoConfigureAfter({MetricsAutoConfiguration.class, OpenTelemetryAutoConfiguration.class}) @AutoConfigureBefore(CompositeMeterRegistryAutoConfiguration.class) @ConditionalOnBean(Clock.class) -@ConditionalOnClass(MeterRegistry.class) +@ConditionalOnClass(MeterRegistry.class) // module changed in Spring Boot 4 @Configuration public class MicrometerBridgeAutoConfiguration { diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/mongo/MongoClientInstrumentationAutoConfiguration.java b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/mongo/MongoClientInstrumentationAutoConfiguration.java index fcc3bc46a315..af47b2371991 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/mongo/MongoClientInstrumentationAutoConfiguration.java +++ b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/mongo/MongoClientInstrumentationAutoConfiguration.java @@ -20,7 +20,10 @@ * This class is internal and is hence not for public use. Its APIs are unstable and can change at * any time. */ -@ConditionalOnClass({MongoClientSettings.class, MongoClientSettingsBuilderCustomizer.class}) +@ConditionalOnClass({ + MongoClientSettings.class, + MongoClientSettingsBuilderCustomizer.class +}) // module changed in Spring Boot 4 @ConditionalOnEnabledInstrumentation(module = "mongo") @Configuration public class MongoClientInstrumentationAutoConfiguration { diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/main/javaSpring4/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/kafka/KafkaInstrumentationSpringBoot4AutoConfiguration.java b/instrumentation/spring/spring-boot-autoconfigure/src/main/javaSpring4/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/kafka/KafkaInstrumentationSpringBoot4AutoConfiguration.java index 34980aa6daf3..f334adc4fb8e 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/main/javaSpring4/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/kafka/KafkaInstrumentationSpringBoot4AutoConfiguration.java +++ b/instrumentation/spring/spring-boot-autoconfigure/src/main/javaSpring4/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/kafka/KafkaInstrumentationSpringBoot4AutoConfiguration.java @@ -6,30 +6,20 @@ package io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.kafka; import io.opentelemetry.api.OpenTelemetry; -import io.opentelemetry.instrumentation.api.incubator.config.internal.InstrumentationConfig; import io.opentelemetry.instrumentation.kafkaclients.v2_6.KafkaTelemetry; import io.opentelemetry.instrumentation.spring.autoconfigure.internal.ConditionalOnEnabledInstrumentation; -import io.opentelemetry.instrumentation.spring.kafka.v2_7.SpringKafkaTelemetry; -import org.springframework.beans.factory.ObjectProvider; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.kafka.autoconfigure.DefaultKafkaProducerFactoryCustomizer; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.kafka.config.ConcurrentKafkaListenerContainerFactory; -import org.springframework.kafka.core.KafkaTemplate; /** * This class is internal and is hence not for public use. Its APIs are unstable and can change at * any time. */ @ConditionalOnEnabledInstrumentation(module = "kafka") -@ConditionalOnClass({ - KafkaTemplate.class, - ConcurrentKafkaListenerContainerFactory.class, - DefaultKafkaProducerFactoryCustomizer.class -}) +@ConditionalOnClass(DefaultKafkaProducerFactoryCustomizer.class) @ConditionalOnMissingBean(name = "otelKafkaProducerFactoryCustomizer") @Configuration public class KafkaInstrumentationSpringBoot4AutoConfiguration { @@ -40,31 +30,4 @@ DefaultKafkaProducerFactoryCustomizer otelKafkaProducerFactoryCustomizer( KafkaTelemetry kafkaTelemetry = KafkaTelemetry.create(openTelemetry); return producerFactory -> producerFactory.addPostProcessor(kafkaTelemetry::wrap); } - - @Bean - static SpringKafkaTelemetry getTelemetry( - ObjectProvider openTelemetryProvider, - ObjectProvider configProvider) { - return SpringKafkaTelemetry.builder(openTelemetryProvider.getObject()) - .setCaptureExperimentalSpanAttributes( - configProvider - .getObject() - .getBoolean("otel.instrumentation.kafka.experimental-span-attributes", false)) - .build(); - } - - // static to avoid "is not eligible for getting processed by all BeanPostProcessors" warning - @Bean - @ConditionalOnProperty( - name = "otel.instrumentation.kafka.autoconfigure-interceptor", - havingValue = "true", - matchIfMissing = true) - @ConditionalOnMissingBean - static ConcurrentKafkaListenerContainerFactoryPostProcessor - otelKafkaListenerContainerFactoryBeanPostProcessor( - ObjectProvider openTelemetryProvider, - ObjectProvider configProvider) { - return new ConcurrentKafkaListenerContainerFactoryPostProcessor( - () -> getTelemetry(openTelemetryProvider, configProvider)); - } } diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/jdbc/JdbcInstrumentationAutoConfigurationTest.java b/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/jdbc/JdbcInstrumentationAutoConfigurationTest.java index a81051228f98..4dda63522dbe 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/jdbc/JdbcInstrumentationAutoConfigurationTest.java +++ b/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/jdbc/JdbcInstrumentationAutoConfigurationTest.java @@ -5,68 +5,39 @@ package io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.jdbc; -import static io.opentelemetry.instrumentation.testing.junit.db.SemconvStabilityUtil.maybeStable; -import static io.opentelemetry.semconv.incubating.DbIncubatingAttributes.DB_STATEMENT; -import static org.assertj.core.api.Assertions.assertThat; - import io.opentelemetry.api.OpenTelemetry; import io.opentelemetry.instrumentation.api.incubator.config.internal.InstrumentationConfig; +import io.opentelemetry.instrumentation.spring.autoconfigure.internal.AbstractJdbcInstrumentationAutoConfigurationTest; import io.opentelemetry.instrumentation.spring.autoconfigure.internal.properties.ConfigPropertiesBridge; +import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; import io.opentelemetry.instrumentation.testing.junit.LibraryInstrumentationExtension; import io.opentelemetry.sdk.autoconfigure.spi.internal.DefaultConfigProperties; -import java.sql.Connection; -import java.sql.Statement; import java.util.Collections; -import javax.sql.DataSource; -import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; -import org.springframework.aop.framework.Advised; -import org.springframework.aop.support.AopUtils; import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; import org.springframework.boot.test.context.runner.ApplicationContextRunner; -class JdbcInstrumentationAutoConfigurationTest { +class JdbcInstrumentationAutoConfigurationTest + extends AbstractJdbcInstrumentationAutoConfigurationTest { @RegisterExtension static final LibraryInstrumentationExtension testing = LibraryInstrumentationExtension.create(); - private final ApplicationContextRunner runner = - new ApplicationContextRunner() - .withBean( - InstrumentationConfig.class, - () -> - new ConfigPropertiesBridge( - DefaultConfigProperties.createFromMap(Collections.emptyMap()))) - .withConfiguration( - AutoConfigurations.of( - JdbcInstrumentationAutoConfiguration.class, DataSourceAutoConfiguration.class)) - .withBean("openTelemetry", OpenTelemetry.class, testing::getOpenTelemetry); - - @SuppressWarnings("deprecation") // using deprecated semconv - @Test - void statementSanitizerEnabledByDefault() { - runner.run( - context -> { - DataSource dataSource = context.getBean(DataSource.class); - - assertThat(AopUtils.isAopProxy(dataSource)).isTrue(); - assertThat(dataSource.getClass().getSimpleName()).isNotEqualTo("HikariDataSource"); - // unwrap the instrumented data source to get the original data source - Object original = ((Advised) dataSource).getTargetSource().getTarget(); - assertThat(AopUtils.isAopProxy(original)).isFalse(); - assertThat(original.getClass().getSimpleName()).isEqualTo("HikariDataSource"); - - try (Connection connection = dataSource.getConnection()) { - try (Statement statement = connection.createStatement()) { - statement.execute("SELECT 1"); - } - } + protected InstrumentationExtension testing() { + return testing; + } - testing.waitAndAssertTraces( - trace -> - trace.hasSpansSatisfyingExactly( - span -> span.hasAttribute(maybeStable(DB_STATEMENT), "SELECT ?"))); - }); + protected ApplicationContextRunner runner() { + return new ApplicationContextRunner() + .withBean( + InstrumentationConfig.class, + () -> + new ConfigPropertiesBridge( + DefaultConfigProperties.createFromMap(Collections.emptyMap()))) + .withConfiguration( + AutoConfigurations.of( + JdbcInstrumentationAutoConfiguration.class, DataSourceAutoConfiguration.class)) + .withBean("openTelemetry", OpenTelemetry.class, testing::getOpenTelemetry); } } diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/micrometer/MicrometerBridgeAutoConfigurationTest.java b/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/micrometer/MicrometerBridgeAutoConfigurationTest.java index 5a32e2951d98..db6fafffe33f 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/micrometer/MicrometerBridgeAutoConfigurationTest.java +++ b/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/micrometer/MicrometerBridgeAutoConfigurationTest.java @@ -5,54 +5,29 @@ package io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.micrometer; -import static org.assertj.core.api.Assertions.assertThat; - import io.micrometer.core.instrument.MeterRegistry; import io.opentelemetry.api.OpenTelemetry; -import io.opentelemetry.instrumentation.micrometer.v1_5.OpenTelemetryMeterRegistry; -import org.junit.jupiter.api.Test; +import io.opentelemetry.instrumentation.spring.autoconfigure.internal.AbstractMicrometerBridgeAutoConfigurationTest; import org.springframework.boot.actuate.autoconfigure.metrics.MetricsAutoConfiguration; import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.test.context.runner.ApplicationContextRunner; -class MicrometerBridgeAutoConfigurationTest { +class MicrometerBridgeAutoConfigurationTest extends AbstractMicrometerBridgeAutoConfigurationTest { private final ApplicationContextRunner runner = new ApplicationContextRunner() .withBean(OpenTelemetry.class, OpenTelemetry::noop) .withConfiguration(AutoConfigurations.of(MicrometerBridgeAutoConfiguration.class)); - @Test - void metricsEnabled() { - runner - .withConfiguration(AutoConfigurations.of(MetricsAutoConfiguration.class)) - .withPropertyValues("otel.instrumentation.micrometer.enabled = true") - .run( - context -> - assertThat(context.getBean("otelMeterRegistry", MeterRegistry.class)) - .isNotNull() - .isInstanceOf(OpenTelemetryMeterRegistry.class)); - } - - @Test - void metricsDisabledByDefault() { - runner - .withConfiguration(AutoConfigurations.of(MetricsAutoConfiguration.class)) - .run(context -> assertThat(context.containsBean("otelMeterRegistry")).isFalse()); + protected ApplicationContextRunner contextRunner() { + return runner; } - @Test - void metricsDisabled() { - runner - .withConfiguration(AutoConfigurations.of(MetricsAutoConfiguration.class)) - .withPropertyValues("otel.instrumentation.micrometer.enabled = false") - .run(context -> assertThat(context.containsBean("otelMeterRegistry")).isFalse()); + protected Class getMetricsAutoConfigurationClass() { + return MetricsAutoConfiguration.class; } - @Test - void noActuatorAutoConfiguration() { - runner - .withPropertyValues("otel.instrumentation.micrometer.enabled = true") - .run(context -> assertThat(context.containsBean("otelMeterRegistry")).isFalse()); + protected Class getMeterRegistryClass() { + return MeterRegistry.class; } } diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/testSpring4/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/jdbc/JdbcInstrumentationAutoConfigurationTest.java b/instrumentation/spring/spring-boot-autoconfigure/src/testSpring4/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/jdbc/JdbcInstrumentationAutoConfigurationTest.java index 98a7d547d70a..7f6d483c9912 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/testSpring4/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/jdbc/JdbcInstrumentationAutoConfigurationTest.java +++ b/instrumentation/spring/spring-boot-autoconfigure/src/testSpring4/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/jdbc/JdbcInstrumentationAutoConfigurationTest.java @@ -5,32 +5,29 @@ package io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.jdbc; -import static io.opentelemetry.instrumentation.testing.junit.db.SemconvStabilityUtil.maybeStable; -import static io.opentelemetry.semconv.incubating.DbIncubatingAttributes.DB_STATEMENT; -import static org.assertj.core.api.Assertions.assertThat; - import io.opentelemetry.api.OpenTelemetry; import io.opentelemetry.instrumentation.api.incubator.config.internal.InstrumentationConfig; +import io.opentelemetry.instrumentation.spring.autoconfigure.internal.AbstractJdbcInstrumentationAutoConfigurationTest; import io.opentelemetry.instrumentation.spring.autoconfigure.internal.properties.ConfigPropertiesBridge; +import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; import io.opentelemetry.instrumentation.testing.junit.LibraryInstrumentationExtension; import io.opentelemetry.sdk.autoconfigure.spi.internal.DefaultConfigProperties; -import java.sql.Connection; -import java.sql.Statement; import java.util.Collections; -import javax.sql.DataSource; -import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; -import org.springframework.aop.framework.Advised; -import org.springframework.aop.support.AopUtils; import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.jdbc.autoconfigure.DataSourceAutoConfiguration; import org.springframework.boot.test.context.runner.ApplicationContextRunner; -class JdbcInstrumentationAutoConfigurationTest { +class JdbcInstrumentationAutoConfigurationTest + extends AbstractJdbcInstrumentationAutoConfigurationTest { @RegisterExtension static final LibraryInstrumentationExtension testing = LibraryInstrumentationExtension.create(); + protected InstrumentationExtension testing() { + return testing; + } + private final ApplicationContextRunner runner = new ApplicationContextRunner() .withBean( @@ -44,30 +41,7 @@ class JdbcInstrumentationAutoConfigurationTest { DataSourceAutoConfiguration.class)) .withBean("openTelemetry", OpenTelemetry.class, testing::getOpenTelemetry); - @SuppressWarnings("deprecation") // using deprecated semconv - @Test - void statementSanitizerEnabledByDefault() { - runner.run( - context -> { - DataSource dataSource = context.getBean(DataSource.class); - - assertThat(AopUtils.isAopProxy(dataSource)).isTrue(); - assertThat(dataSource.getClass().getSimpleName()).isNotEqualTo("HikariDataSource"); - // unwrap the instrumented data source to get the original data source - Object original = ((Advised) dataSource).getTargetSource().getTarget(); - assertThat(AopUtils.isAopProxy(original)).isFalse(); - assertThat(original.getClass().getSimpleName()).isEqualTo("HikariDataSource"); - - try (Connection connection = dataSource.getConnection()) { - try (Statement statement = connection.createStatement()) { - statement.execute("SELECT 1"); - } - } - - testing.waitAndAssertTraces( - trace -> - trace.hasSpansSatisfyingExactly( - span -> span.hasAttribute(maybeStable(DB_STATEMENT), "SELECT ?"))); - }); + protected ApplicationContextRunner contextRunner() { + return runner; } } diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/testSpring4/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/kafka/KafkaInstrumentationAutoConfigurationTest.java b/instrumentation/spring/spring-boot-autoconfigure/src/testSpring4/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/kafka/KafkaInstrumentationAutoConfigurationTest.java index 2b60a084110f..a0c680817fa8 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/testSpring4/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/kafka/KafkaInstrumentationAutoConfigurationTest.java +++ b/instrumentation/spring/spring-boot-autoconfigure/src/testSpring4/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/kafka/KafkaInstrumentationAutoConfigurationTest.java @@ -10,6 +10,7 @@ import io.opentelemetry.api.OpenTelemetry; import io.opentelemetry.instrumentation.api.incubator.config.internal.InstrumentationConfig; +import io.opentelemetry.instrumentation.spring.autoconfigure.internal.AbstractKafkaInstrumentationAutoConfigurationTest; import io.opentelemetry.instrumentation.spring.autoconfigure.internal.properties.ConfigPropertiesBridge; import io.opentelemetry.sdk.autoconfigure.spi.internal.DefaultConfigProperties; import org.junit.jupiter.api.Test; @@ -18,7 +19,8 @@ import org.springframework.boot.test.context.runner.ApplicationContextRunner; import org.springframework.kafka.core.DefaultKafkaProducerFactory; -class KafkaInstrumentationAutoConfigurationTest { +class KafkaInstrumentationAutoConfigurationTest + extends AbstractKafkaInstrumentationAutoConfigurationTest { private final ApplicationContextRunner runner = new ApplicationContextRunner() @@ -26,9 +28,15 @@ class KafkaInstrumentationAutoConfigurationTest { InstrumentationConfig.class, () -> new ConfigPropertiesBridge(DefaultConfigProperties.createFromMap(emptyMap()))) .withConfiguration( - AutoConfigurations.of(KafkaInstrumentationSpringBoot4AutoConfiguration.class)) + AutoConfigurations.of( + KafkaInstrumentationAutoConfiguration.class, + KafkaInstrumentationSpringBoot4AutoConfiguration.class)) .withBean("openTelemetry", OpenTelemetry.class, OpenTelemetry::noop); + protected ApplicationContextRunner contextRunner() { + return runner; + } + @Test void defaultConfiguration() { runner.run( @@ -52,28 +60,4 @@ void defaultConfiguration() { assertThat(factory.getPostProcessors()).isNotEmpty(); }); } - - @Test - void instrumentationDisabled() { - runner - .withPropertyValues("otel.instrumentation.kafka.enabled=false") - .run( - context -> { - assertThat(context.containsBean("otelKafkaProducerFactoryCustomizer")).isFalse(); - assertThat(context.containsBean("otelKafkaListenerContainerFactoryBeanPostProcessor")) - .isFalse(); - }); - } - - @Test - void listenerInterceptorCanBeDisabled() { - runner - .withPropertyValues("otel.instrumentation.kafka.autoconfigure-interceptor=false") - .run( - context -> { - assertThat(context.containsBean("otelKafkaProducerFactoryCustomizer")).isTrue(); - assertThat(context.containsBean("otelKafkaListenerContainerFactoryBeanPostProcessor")) - .isFalse(); - }); - } } diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/testSpring4/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/micrometer/MicrometerBridgeAutoConfigurationTest.java b/instrumentation/spring/spring-boot-autoconfigure/src/testSpring4/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/micrometer/MicrometerBridgeAutoConfigurationTest.java index 0a15e8f66ff9..0f5d949ad3a8 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/testSpring4/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/micrometer/MicrometerBridgeAutoConfigurationTest.java +++ b/instrumentation/spring/spring-boot-autoconfigure/src/testSpring4/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/micrometer/MicrometerBridgeAutoConfigurationTest.java @@ -5,17 +5,14 @@ package io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.micrometer; -import static org.assertj.core.api.Assertions.assertThat; - import io.micrometer.core.instrument.MeterRegistry; import io.opentelemetry.api.OpenTelemetry; -import io.opentelemetry.instrumentation.micrometer.v1_5.OpenTelemetryMeterRegistry; -import org.junit.jupiter.api.Test; +import io.opentelemetry.instrumentation.spring.autoconfigure.internal.AbstractMicrometerBridgeAutoConfigurationTest; import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.micrometer.metrics.autoconfigure.MetricsAutoConfiguration; import org.springframework.boot.test.context.runner.ApplicationContextRunner; -class MicrometerBridgeAutoConfigurationTest { +class MicrometerBridgeAutoConfigurationTest extends AbstractMicrometerBridgeAutoConfigurationTest { private final ApplicationContextRunner runner = new ApplicationContextRunner() @@ -23,37 +20,15 @@ class MicrometerBridgeAutoConfigurationTest { .withConfiguration( AutoConfigurations.of(MicrometerBridgeSpringBoot4AutoConfiguration.class)); - @Test - void metricsEnabled() { - runner - .withConfiguration(AutoConfigurations.of(MetricsAutoConfiguration.class)) - .withPropertyValues("otel.instrumentation.micrometer.enabled = true") - .run( - context -> - assertThat(context.getBean("otelMeterRegistry", MeterRegistry.class)) - .isNotNull() - .isInstanceOf(OpenTelemetryMeterRegistry.class)); - } - - @Test - void metricsDisabledByDefault() { - runner - .withConfiguration(AutoConfigurations.of(MetricsAutoConfiguration.class)) - .run(context -> assertThat(context.containsBean("otelMeterRegistry")).isFalse()); + protected ApplicationContextRunner contextRunner() { + return runner; } - @Test - void metricsDisabled() { - runner - .withConfiguration(AutoConfigurations.of(MetricsAutoConfiguration.class)) - .withPropertyValues("otel.instrumentation.micrometer.enabled = false") - .run(context -> assertThat(context.containsBean("otelMeterRegistry")).isFalse()); + protected Class getMetricsAutoConfigurationClass() { + return MetricsAutoConfiguration.class; } - @Test - void noActuatorAutoConfiguration() { - runner - .withPropertyValues("otel.instrumentation.micrometer.enabled = true") - .run(context -> assertThat(context.containsBean("otelMeterRegistry")).isFalse()); + protected Class getMeterRegistryClass() { + return MeterRegistry.class; } } diff --git a/instrumentation/spring/spring-boot-autoconfigure/testing/build.gradle.kts b/instrumentation/spring/spring-boot-autoconfigure/testing/build.gradle.kts new file mode 100644 index 000000000000..ff92f5029de2 --- /dev/null +++ b/instrumentation/spring/spring-boot-autoconfigure/testing/build.gradle.kts @@ -0,0 +1,19 @@ +import org.gradle.kotlin.dsl.testImplementation + +plugins { + id("otel.library-instrumentation") + id("otel.java-conventions") +} + +val springBootVersion = "2.7.18" + +dependencies { + library("org.springframework.boot:spring-boot-starter-test:$springBootVersion") { + exclude("org.junit.vintage", "junit-vintage-engine") + } + implementation(project(":instrumentation:micrometer:micrometer-1.5:library")) + implementation("io.opentelemetry.javaagent:opentelemetry-testing-common") + implementation("io.opentelemetry:opentelemetry-sdk") + implementation("io.opentelemetry:opentelemetry-sdk-testing") + implementation("io.opentelemetry:opentelemetry-sdk-extension-autoconfigure-spi") +} diff --git a/instrumentation/spring/spring-boot-autoconfigure/testing/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/AbstractJdbcInstrumentationAutoConfigurationTest.java b/instrumentation/spring/spring-boot-autoconfigure/testing/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/AbstractJdbcInstrumentationAutoConfigurationTest.java new file mode 100644 index 000000000000..ead38ab6ba23 --- /dev/null +++ b/instrumentation/spring/spring-boot-autoconfigure/testing/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/AbstractJdbcInstrumentationAutoConfigurationTest.java @@ -0,0 +1,64 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.spring.autoconfigure.internal; + +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +import static org.assertj.core.api.Assertions.assertThat; + +import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; +import io.opentelemetry.instrumentation.testing.junit.db.SemconvStabilityUtil; +import io.opentelemetry.semconv.incubating.DbIncubatingAttributes; +import java.sql.Connection; +import java.sql.Statement; +import javax.sql.DataSource; +import org.junit.jupiter.api.Test; +import org.springframework.aop.framework.Advised; +import org.springframework.aop.support.AopUtils; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; + +public abstract class AbstractJdbcInstrumentationAutoConfigurationTest { + + protected abstract InstrumentationExtension testing(); + + protected abstract ApplicationContextRunner contextRunner(); + + @SuppressWarnings("deprecation") // using deprecated semconv + @Test + void statementSanitizerEnabledByDefault() { + contextRunner() + .run( + context -> { + DataSource dataSource = context.getBean(DataSource.class); + + assertThat(AopUtils.isAopProxy(dataSource)).isTrue(); + assertThat(dataSource.getClass().getSimpleName()).isNotEqualTo("HikariDataSource"); + // unwrap the instrumented data source to get the original data source + Object original = ((Advised) dataSource).getTargetSource().getTarget(); + assertThat(AopUtils.isAopProxy(original)).isFalse(); + assertThat(original.getClass().getSimpleName()).isEqualTo("HikariDataSource"); + + try (Connection connection = dataSource.getConnection()) { + try (Statement statement = connection.createStatement()) { + statement.execute("SELECT 1"); + } + } + + testing() + .waitAndAssertTraces( + trace -> + trace.hasSpansSatisfyingExactly( + span -> + span.hasAttribute( + SemconvStabilityUtil.maybeStable( + DbIncubatingAttributes.DB_STATEMENT), + "SELECT ?"))); + }); + } +} diff --git a/instrumentation/spring/spring-boot-autoconfigure/testing/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/AbstractKafkaInstrumentationAutoConfigurationTest.java b/instrumentation/spring/spring-boot-autoconfigure/testing/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/AbstractKafkaInstrumentationAutoConfigurationTest.java new file mode 100644 index 000000000000..9cc017eebfe9 --- /dev/null +++ b/instrumentation/spring/spring-boot-autoconfigure/testing/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/AbstractKafkaInstrumentationAutoConfigurationTest.java @@ -0,0 +1,39 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.spring.autoconfigure.internal; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; + +public abstract class AbstractKafkaInstrumentationAutoConfigurationTest { + protected abstract ApplicationContextRunner contextRunner(); + + @Test + void instrumentationDisabled() { + contextRunner() + .withPropertyValues("otel.instrumentation.kafka.enabled=false") + .run( + context -> { + assertThat(context.containsBean("otelKafkaProducerFactoryCustomizer")).isFalse(); + assertThat(context.containsBean("otelKafkaListenerContainerFactoryBeanPostProcessor")) + .isFalse(); + }); + } + + @Test + void listenerInterceptorCanBeDisabled() { + contextRunner() + .withPropertyValues("otel.instrumentation.kafka.autoconfigure-interceptor=false") + .run( + context -> { + assertThat(context.containsBean("otelKafkaProducerFactoryCustomizer")).isTrue(); + assertThat(context.containsBean("otelKafkaListenerContainerFactoryBeanPostProcessor")) + .isFalse(); + }); + } +} diff --git a/instrumentation/spring/spring-boot-autoconfigure/testing/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/AbstractMicrometerBridgeAutoConfigurationTest.java b/instrumentation/spring/spring-boot-autoconfigure/testing/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/AbstractMicrometerBridgeAutoConfigurationTest.java new file mode 100644 index 000000000000..7b14ae8d84b7 --- /dev/null +++ b/instrumentation/spring/spring-boot-autoconfigure/testing/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/AbstractMicrometerBridgeAutoConfigurationTest.java @@ -0,0 +1,56 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.spring.autoconfigure.internal; + +import static org.assertj.core.api.Assertions.assertThat; + +import io.opentelemetry.instrumentation.micrometer.v1_5.OpenTelemetryMeterRegistry; +import org.junit.jupiter.api.Test; +import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; + +public abstract class AbstractMicrometerBridgeAutoConfigurationTest { + + protected abstract ApplicationContextRunner contextRunner(); + + protected abstract Class getMetricsAutoConfigurationClass(); + + protected abstract Class getMeterRegistryClass(); + + @Test + void metricsEnabled() { + contextRunner() + .withConfiguration(AutoConfigurations.of(getMetricsAutoConfigurationClass())) + .withPropertyValues("otel.instrumentation.micrometer.enabled = true") + .run( + context -> + assertThat(context.getBean("otelMeterRegistry", getMeterRegistryClass())) + .isNotNull() + .isInstanceOf(OpenTelemetryMeterRegistry.class)); + } + + @Test + void metricsDisabledByDefault() { + contextRunner() + .withConfiguration(AutoConfigurations.of(getMetricsAutoConfigurationClass())) + .run(context -> assertThat(context.containsBean("otelMeterRegistry")).isFalse()); + } + + @Test + void metricsDisabled() { + contextRunner() + .withConfiguration(AutoConfigurations.of(getMetricsAutoConfigurationClass())) + .withPropertyValues("otel.instrumentation.micrometer.enabled = false") + .run(context -> assertThat(context.containsBean("otelMeterRegistry")).isFalse()); + } + + @Test + void noActuatorAutoConfiguration() { + contextRunner() + .withPropertyValues("otel.instrumentation.micrometer.enabled = true") + .run(context -> assertThat(context.containsBean("otelMeterRegistry")).isFalse()); + } +} diff --git a/settings.gradle.kts b/settings.gradle.kts index 9e520a25bd4e..e6ecc65d5878 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -82,7 +82,8 @@ develocity { } if (!gradle.startParameter.taskNames.contains("listTestsInPartition") && - !gradle.startParameter.taskNames.contains(":test-report:reportFlakyTests")) { + !gradle.startParameter.taskNames.contains(":test-report:reportFlakyTests") + ) { buildScanPublished { File("build-scan.txt").printWriter().use { writer -> writer.println(buildScanUri) @@ -612,6 +613,7 @@ include(":instrumentation:spark-2.3:javaagent") include(":instrumentation:spring:spring-batch-3.0:javaagent") include(":instrumentation:spring:spring-boot-actuator-autoconfigure-2.0:javaagent") include(":instrumentation:spring:spring-boot-autoconfigure") +include(":instrumentation:spring:spring-boot-autoconfigure:testing") include(":instrumentation:spring:spring-boot-resources:javaagent") include(":instrumentation:spring:spring-boot-resources:javaagent-unit-tests") include(":instrumentation:spring:spring-cloud-aws-3.0:javaagent") From 153195e26af485008f6cd9691957b148c160e7f4 Mon Sep 17 00:00:00 2001 From: Jay DeLuca Date: Tue, 2 Dec 2025 09:51:03 -0500 Subject: [PATCH 26/40] finish common testing module --- .../build.gradle.kts | 1 - .../web/RestTemplateBeanPostProcessor.java | 6 +- ...cInstrumentationAutoConfigurationTest.java | 27 +++--- ...MicrometerBridgeAutoConfigurationTest.java | 3 + ...cInstrumentationAutoConfigurationTest.java | 45 +++------ ...bInstrumentationAutoConfigurationTest.java | 79 ++-------------- ...cInstrumentationAutoConfigurationTest.java | 45 +++------ ...bInstrumentationAutoConfigurationTest.java | 79 ++-------------- .../testing/build.gradle.kts | 5 + ...cInstrumentationAutoConfigurationTest.java | 52 +++++++++++ ...bInstrumentationAutoConfigurationTest.java | 91 +++++++++++++++++++ 11 files changed, 208 insertions(+), 225 deletions(-) create mode 100644 instrumentation/spring/spring-boot-autoconfigure/testing/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/AbstractR2DbcInstrumentationAutoConfigurationTest.java create mode 100644 instrumentation/spring/spring-boot-autoconfigure/testing/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/AbstractSpringWebInstrumentationAutoConfigurationTest.java diff --git a/instrumentation/spring/spring-boot-autoconfigure/build.gradle.kts b/instrumentation/spring/spring-boot-autoconfigure/build.gradle.kts index 5ca4c737f92b..4fd3766ded96 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/build.gradle.kts +++ b/instrumentation/spring/spring-boot-autoconfigure/build.gradle.kts @@ -134,7 +134,6 @@ dependencies { add("javaSpring4CompileOnly", project(":instrumentation:spring:spring-kafka-2.7:library")) add("javaSpring4CompileOnly", project(":instrumentation:mongo:mongo-3.1:library")) add("javaSpring4CompileOnly", project(":instrumentation:micrometer:micrometer-1.5:library")) - add("javaSpring4CompileOnly", project(":instrumentation:spring:spring-boot-autoconfigure:testing")) } val latestDepTest = findProperty("testLatestDeps") as Boolean diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/web/RestTemplateBeanPostProcessor.java b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/web/RestTemplateBeanPostProcessor.java index 4db9b873fece..8ffe8f0c9b5a 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/web/RestTemplateBeanPostProcessor.java +++ b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/web/RestTemplateBeanPostProcessor.java @@ -11,7 +11,11 @@ import org.springframework.beans.factory.config.BeanPostProcessor; import org.springframework.web.client.RestTemplate; -final class RestTemplateBeanPostProcessor implements BeanPostProcessor { +/** + * This class is internal and is hence not for public use. Its APIs are unstable and can change at + * any time. + */ +public final class RestTemplateBeanPostProcessor implements BeanPostProcessor { private final ObjectProvider openTelemetryProvider; diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/jdbc/JdbcInstrumentationAutoConfigurationTest.java b/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/jdbc/JdbcInstrumentationAutoConfigurationTest.java index 4dda63522dbe..70d33a21c666 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/jdbc/JdbcInstrumentationAutoConfigurationTest.java +++ b/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/jdbc/JdbcInstrumentationAutoConfigurationTest.java @@ -24,20 +24,25 @@ class JdbcInstrumentationAutoConfigurationTest @RegisterExtension static final LibraryInstrumentationExtension testing = LibraryInstrumentationExtension.create(); + private final ApplicationContextRunner contextRunner = + new ApplicationContextRunner() + .withBean( + InstrumentationConfig.class, + () -> + new ConfigPropertiesBridge( + DefaultConfigProperties.createFromMap(Collections.emptyMap()))) + .withConfiguration( + AutoConfigurations.of( + JdbcInstrumentationAutoConfiguration.class, DataSourceAutoConfiguration.class)) + .withBean("openTelemetry", OpenTelemetry.class, testing::getOpenTelemetry); + + @Override protected InstrumentationExtension testing() { return testing; } - protected ApplicationContextRunner runner() { - return new ApplicationContextRunner() - .withBean( - InstrumentationConfig.class, - () -> - new ConfigPropertiesBridge( - DefaultConfigProperties.createFromMap(Collections.emptyMap()))) - .withConfiguration( - AutoConfigurations.of( - JdbcInstrumentationAutoConfiguration.class, DataSourceAutoConfiguration.class)) - .withBean("openTelemetry", OpenTelemetry.class, testing::getOpenTelemetry); + @Override + protected ApplicationContextRunner contextRunner() { + return contextRunner; } } diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/micrometer/MicrometerBridgeAutoConfigurationTest.java b/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/micrometer/MicrometerBridgeAutoConfigurationTest.java index db6fafffe33f..dd926c289315 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/micrometer/MicrometerBridgeAutoConfigurationTest.java +++ b/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/micrometer/MicrometerBridgeAutoConfigurationTest.java @@ -19,14 +19,17 @@ class MicrometerBridgeAutoConfigurationTest extends AbstractMicrometerBridgeAuto .withBean(OpenTelemetry.class, OpenTelemetry::noop) .withConfiguration(AutoConfigurations.of(MicrometerBridgeAutoConfiguration.class)); + @Override protected ApplicationContextRunner contextRunner() { return runner; } + @Override protected Class getMetricsAutoConfigurationClass() { return MetricsAutoConfiguration.class; } + @Override protected Class getMeterRegistryClass() { return MeterRegistry.class; } diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/r2dbc/R2DbcInstrumentationAutoConfigurationTest.java b/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/r2dbc/R2DbcInstrumentationAutoConfigurationTest.java index bed063c28d1f..212ea25a46a9 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/r2dbc/R2DbcInstrumentationAutoConfigurationTest.java +++ b/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/r2dbc/R2DbcInstrumentationAutoConfigurationTest.java @@ -5,28 +5,25 @@ package io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.r2dbc; -import static io.opentelemetry.instrumentation.testing.junit.db.SemconvStabilityUtil.maybeStable; -import static io.opentelemetry.semconv.incubating.DbIncubatingAttributes.DB_STATEMENT; - import io.opentelemetry.api.OpenTelemetry; import io.opentelemetry.instrumentation.api.incubator.config.internal.InstrumentationConfig; +import io.opentelemetry.instrumentation.spring.autoconfigure.internal.AbstractR2DbcInstrumentationAutoConfigurationTest; import io.opentelemetry.instrumentation.spring.autoconfigure.internal.properties.ConfigPropertiesBridge; import io.opentelemetry.instrumentation.testing.junit.LibraryInstrumentationExtension; import io.opentelemetry.sdk.autoconfigure.spi.internal.DefaultConfigProperties; import java.util.Collections; -import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.autoconfigure.r2dbc.R2dbcAutoConfiguration; import org.springframework.boot.test.context.runner.ApplicationContextRunner; -import org.springframework.r2dbc.core.DatabaseClient; -class R2DbcInstrumentationAutoConfigurationTest { +class R2DbcInstrumentationAutoConfigurationTest + extends AbstractR2DbcInstrumentationAutoConfigurationTest { @RegisterExtension static final LibraryInstrumentationExtension testing = LibraryInstrumentationExtension.create(); - private final ApplicationContextRunner runner = + private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() .withBean( InstrumentationConfig.class, @@ -38,31 +35,13 @@ class R2DbcInstrumentationAutoConfigurationTest { R2dbcInstrumentationAutoConfiguration.class, R2dbcAutoConfiguration.class)) .withBean("openTelemetry", OpenTelemetry.class, testing::getOpenTelemetry); - @SuppressWarnings("deprecation") // using deprecated semconv - @Test - void statementSanitizerEnabledByDefault() { - runner.run( - context -> { - DatabaseClient client = context.getBean(DatabaseClient.class); - client - .sql( - "CREATE TABLE IF NOT EXISTS player(id INT NOT NULL AUTO_INCREMENT, name VARCHAR(255), age INT, PRIMARY KEY (id))") - .fetch() - .all() - .blockLast(); - client.sql("SELECT * FROM player WHERE id = 1").fetch().all().blockLast(); - testing.waitAndAssertTraces( - trace -> - trace.hasSpansSatisfyingExactly( - span -> - span.hasAttribute( - maybeStable(DB_STATEMENT), - "CREATE TABLE IF NOT EXISTS player(id INT NOT NULL AUTO_INCREMENT, name VARCHAR(?), age INT, PRIMARY KEY (id))")), - trace -> - trace.hasSpansSatisfyingExactly( - span -> - span.hasAttribute( - maybeStable(DB_STATEMENT), "SELECT * FROM player WHERE id = ?"))); - }); + @Override + protected LibraryInstrumentationExtension testing() { + return testing; + } + + @Override + protected ApplicationContextRunner contextRunner() { + return contextRunner; } } diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/web/SpringWebInstrumentationAutoConfigurationTest.java b/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/web/SpringWebInstrumentationAutoConfigurationTest.java index 8e08dec0ea20..29ffe72d1cf4 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/web/SpringWebInstrumentationAutoConfigurationTest.java +++ b/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/web/SpringWebInstrumentationAutoConfigurationTest.java @@ -5,19 +5,18 @@ package io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.web; -import static org.assertj.core.api.Assertions.assertThat; - import io.opentelemetry.api.OpenTelemetry; import io.opentelemetry.instrumentation.api.incubator.config.internal.InstrumentationConfig; +import io.opentelemetry.instrumentation.spring.autoconfigure.internal.AbstractSpringWebInstrumentationAutoConfigurationTest; import io.opentelemetry.instrumentation.spring.autoconfigure.internal.properties.ConfigPropertiesBridge; 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; import org.springframework.web.client.RestTemplate; -class SpringWebInstrumentationAutoConfigurationTest { +class SpringWebInstrumentationAutoConfigurationTest + extends AbstractSpringWebInstrumentationAutoConfigurationTest { private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() @@ -31,74 +30,8 @@ class SpringWebInstrumentationAutoConfigurationTest { .withConfiguration( AutoConfigurations.of(SpringWebInstrumentationAutoConfiguration.class)); - /** - * Tests that users create {@link RestTemplate} bean is instrumented. - * - *

{@code
-   * @Bean public RestTemplate restTemplate() {
-   *     return new RestTemplate();
-   * }
-   * }
- */ - @Test - void instrumentationEnabled() { - contextRunner - .withPropertyValues("otel.instrumentation.spring-web.enabled=true") - .withPropertyValues("otel.instrumentation.common.default-enabled=false") - .run( - context -> { - assertThat( - context.getBean( - "otelRestTemplateBeanPostProcessor", RestTemplateBeanPostProcessor.class)) - .isNotNull(); - - assertThat( - context.getBean(RestTemplate.class).getInterceptors().stream() - .filter( - rti -> - rti.getClass() - .getName() - .startsWith("io.opentelemetry.instrumentation")) - .count()) - .isEqualTo(1); - }); - } - - @Test - void instrumentationDisabled() { - contextRunner - .withPropertyValues("otel.instrumentation.spring-web.enabled=false") - .run( - context -> - assertThat(context.containsBean("otelRestTemplateBeanPostProcessor")).isFalse()); - } - - @Test - void instrumentationDisabledButAllEnabled() { - contextRunner - .withPropertyValues("otel.instrumentation.spring-web.enabled=false") - .withPropertyValues("otel.instrumentation.common.default-enabled=true") - .run( - context -> - assertThat(context.containsBean("otelRestTemplateBeanPostProcessor")).isFalse()); - } - - @Test - void allInstrumentationDisabled() { - contextRunner - .withPropertyValues("otel.instrumentation.common.default-enabled=false") - .run( - context -> - assertThat(context.containsBean("otelRestTemplateBeanPostProcessor")).isFalse()); - } - - @Test - void defaultConfiguration() { - contextRunner.run( - context -> - assertThat( - context.getBean( - "otelRestTemplateBeanPostProcessor", RestTemplateBeanPostProcessor.class)) - .isNotNull()); + @Override + protected ApplicationContextRunner contextRunner() { + return contextRunner; } } diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/testSpring4/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/r2dbc/R2DbcInstrumentationAutoConfigurationTest.java b/instrumentation/spring/spring-boot-autoconfigure/src/testSpring4/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/r2dbc/R2DbcInstrumentationAutoConfigurationTest.java index 683bad231a0e..68538249e4ba 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/testSpring4/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/r2dbc/R2DbcInstrumentationAutoConfigurationTest.java +++ b/instrumentation/spring/spring-boot-autoconfigure/src/testSpring4/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/r2dbc/R2DbcInstrumentationAutoConfigurationTest.java @@ -5,28 +5,25 @@ package io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.r2dbc; -import static io.opentelemetry.instrumentation.testing.junit.db.SemconvStabilityUtil.maybeStable; -import static io.opentelemetry.semconv.incubating.DbIncubatingAttributes.DB_STATEMENT; - import io.opentelemetry.api.OpenTelemetry; import io.opentelemetry.instrumentation.api.incubator.config.internal.InstrumentationConfig; +import io.opentelemetry.instrumentation.spring.autoconfigure.internal.AbstractR2DbcInstrumentationAutoConfigurationTest; import io.opentelemetry.instrumentation.spring.autoconfigure.internal.properties.ConfigPropertiesBridge; import io.opentelemetry.instrumentation.testing.junit.LibraryInstrumentationExtension; import io.opentelemetry.sdk.autoconfigure.spi.internal.DefaultConfigProperties; import java.util.Collections; -import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.r2dbc.autoconfigure.R2dbcAutoConfiguration; import org.springframework.boot.test.context.runner.ApplicationContextRunner; -import org.springframework.r2dbc.core.DatabaseClient; -class R2DbcInstrumentationAutoConfigurationTest { +class R2DbcInstrumentationAutoConfigurationTest + extends AbstractR2DbcInstrumentationAutoConfigurationTest { @RegisterExtension static final LibraryInstrumentationExtension testing = LibraryInstrumentationExtension.create(); - private final ApplicationContextRunner runner = + private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() .withBean( InstrumentationConfig.class, @@ -38,31 +35,13 @@ class R2DbcInstrumentationAutoConfigurationTest { R2dbcInstrumentationAutoConfiguration.class, R2dbcAutoConfiguration.class)) .withBean("openTelemetry", OpenTelemetry.class, testing::getOpenTelemetry); - @SuppressWarnings("deprecation") // using deprecated semconv - @Test - void statementSanitizerEnabledByDefault() { - runner.run( - context -> { - DatabaseClient client = context.getBean(DatabaseClient.class); - client - .sql( - "CREATE TABLE IF NOT EXISTS player(id INT NOT NULL AUTO_INCREMENT, name VARCHAR(255), age INT, PRIMARY KEY (id))") - .fetch() - .all() - .blockLast(); - client.sql("SELECT * FROM player WHERE id = 1").fetch().all().blockLast(); - testing.waitAndAssertTraces( - trace -> - trace.hasSpansSatisfyingExactly( - span -> - span.hasAttribute( - maybeStable(DB_STATEMENT), - "CREATE TABLE IF NOT EXISTS player(id INT NOT NULL AUTO_INCREMENT, name VARCHAR(?), age INT, PRIMARY KEY (id))")), - trace -> - trace.hasSpansSatisfyingExactly( - span -> - span.hasAttribute( - maybeStable(DB_STATEMENT), "SELECT * FROM player WHERE id = ?"))); - }); + @Override + protected LibraryInstrumentationExtension testing() { + return testing; + } + + @Override + protected ApplicationContextRunner contextRunner() { + return contextRunner; } } diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/testSpring4/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/web/SpringWebInstrumentationAutoConfigurationTest.java b/instrumentation/spring/spring-boot-autoconfigure/src/testSpring4/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/web/SpringWebInstrumentationAutoConfigurationTest.java index c19b8dec242a..f9705f67b8f8 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/testSpring4/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/web/SpringWebInstrumentationAutoConfigurationTest.java +++ b/instrumentation/spring/spring-boot-autoconfigure/src/testSpring4/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/web/SpringWebInstrumentationAutoConfigurationTest.java @@ -5,19 +5,18 @@ package io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.web; -import static org.assertj.core.api.Assertions.assertThat; - import io.opentelemetry.api.OpenTelemetry; import io.opentelemetry.instrumentation.api.incubator.config.internal.InstrumentationConfig; +import io.opentelemetry.instrumentation.spring.autoconfigure.internal.AbstractSpringWebInstrumentationAutoConfigurationTest; import io.opentelemetry.instrumentation.spring.autoconfigure.internal.properties.ConfigPropertiesBridge; 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; import org.springframework.web.client.RestTemplate; -class SpringWebInstrumentationAutoConfigurationTest { +class SpringWebInstrumentationAutoConfigurationTest + extends AbstractSpringWebInstrumentationAutoConfigurationTest { private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() @@ -31,74 +30,8 @@ class SpringWebInstrumentationAutoConfigurationTest { .withConfiguration( AutoConfigurations.of(SpringWebInstrumentationSpringBoot4AutoConfiguration.class)); - /** - * Tests that users create {@link RestTemplate} bean is instrumented. - * - *
{@code
-   * @Bean public RestTemplate restTemplate() {
-   *     return new RestTemplate();
-   * }
-   * }
- */ - @Test - void instrumentationEnabled() { - contextRunner - .withPropertyValues("otel.instrumentation.spring-web.enabled=true") - .withPropertyValues("otel.instrumentation.common.default-enabled=false") - .run( - context -> { - assertThat( - context.getBean( - "otelRestTemplateBeanPostProcessor", RestTemplateBeanPostProcessor.class)) - .isNotNull(); - - assertThat( - context.getBean(RestTemplate.class).getInterceptors().stream() - .filter( - rti -> - rti.getClass() - .getName() - .startsWith("io.opentelemetry.instrumentation")) - .count()) - .isEqualTo(1); - }); - } - - @Test - void instrumentationDisabled() { - contextRunner - .withPropertyValues("otel.instrumentation.spring-web.enabled=false") - .run( - context -> - assertThat(context.containsBean("otelRestTemplateBeanPostProcessor")).isFalse()); - } - - @Test - void instrumentationDisabledButAllEnabled() { - contextRunner - .withPropertyValues("otel.instrumentation.spring-web.enabled=false") - .withPropertyValues("otel.instrumentation.common.default-enabled=true") - .run( - context -> - assertThat(context.containsBean("otelRestTemplateBeanPostProcessor")).isFalse()); - } - - @Test - void allInstrumentationDisabled() { - contextRunner - .withPropertyValues("otel.instrumentation.common.default-enabled=false") - .run( - context -> - assertThat(context.containsBean("otelRestTemplateBeanPostProcessor")).isFalse()); - } - - @Test - void defaultConfiguration() { - contextRunner.run( - context -> - assertThat( - context.getBean( - "otelRestTemplateBeanPostProcessor", RestTemplateBeanPostProcessor.class)) - .isNotNull()); + @Override + protected ApplicationContextRunner contextRunner() { + return contextRunner; } } diff --git a/instrumentation/spring/spring-boot-autoconfigure/testing/build.gradle.kts b/instrumentation/spring/spring-boot-autoconfigure/testing/build.gradle.kts index ff92f5029de2..318f2c8fe69d 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/testing/build.gradle.kts +++ b/instrumentation/spring/spring-boot-autoconfigure/testing/build.gradle.kts @@ -8,10 +8,15 @@ plugins { val springBootVersion = "2.7.18" dependencies { + compileOnly("org.springframework.boot:spring-boot-restclient:4.0.0") + library("org.springframework.boot:spring-boot-starter-test:$springBootVersion") { exclude("org.junit.vintage", "junit-vintage-engine") } + library("org.springframework.boot:spring-boot-starter-data-r2dbc:$springBootVersion") + implementation(project(":instrumentation:micrometer:micrometer-1.5:library")) + implementation(project(":instrumentation:spring:spring-boot-autoconfigure")) implementation("io.opentelemetry.javaagent:opentelemetry-testing-common") implementation("io.opentelemetry:opentelemetry-sdk") implementation("io.opentelemetry:opentelemetry-sdk-testing") diff --git a/instrumentation/spring/spring-boot-autoconfigure/testing/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/AbstractR2DbcInstrumentationAutoConfigurationTest.java b/instrumentation/spring/spring-boot-autoconfigure/testing/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/AbstractR2DbcInstrumentationAutoConfigurationTest.java new file mode 100644 index 000000000000..87765d6456f3 --- /dev/null +++ b/instrumentation/spring/spring-boot-autoconfigure/testing/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/AbstractR2DbcInstrumentationAutoConfigurationTest.java @@ -0,0 +1,52 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.spring.autoconfigure.internal; + +import static io.opentelemetry.instrumentation.testing.junit.db.SemconvStabilityUtil.maybeStable; +import static io.opentelemetry.semconv.incubating.DbIncubatingAttributes.DB_STATEMENT; + +import io.opentelemetry.instrumentation.testing.junit.LibraryInstrumentationExtension; +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; +import org.springframework.r2dbc.core.DatabaseClient; + +public abstract class AbstractR2DbcInstrumentationAutoConfigurationTest { + + protected abstract LibraryInstrumentationExtension testing(); + + protected abstract ApplicationContextRunner contextRunner(); + + @SuppressWarnings("deprecation") // using deprecated semconv + @Test + void statementSanitizerEnabledByDefault() { + contextRunner() + .run( + context -> { + DatabaseClient client = context.getBean(DatabaseClient.class); + client + .sql( + "CREATE TABLE IF NOT EXISTS player(id INT NOT NULL AUTO_INCREMENT, name VARCHAR(255), age INT, PRIMARY KEY (id))") + .fetch() + .all() + .blockLast(); + client.sql("SELECT * FROM player WHERE id = 1").fetch().all().blockLast(); + testing() + .waitAndAssertTraces( + trace -> + trace.hasSpansSatisfyingExactly( + span -> + span.hasAttribute( + maybeStable(DB_STATEMENT), + "CREATE TABLE IF NOT EXISTS player(id INT NOT NULL AUTO_INCREMENT, name VARCHAR(?), age INT, PRIMARY KEY (id))")), + trace -> + trace.hasSpansSatisfyingExactly( + span -> + span.hasAttribute( + maybeStable(DB_STATEMENT), + "SELECT * FROM player WHERE id = ?"))); + }); + } +} diff --git a/instrumentation/spring/spring-boot-autoconfigure/testing/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/AbstractSpringWebInstrumentationAutoConfigurationTest.java b/instrumentation/spring/spring-boot-autoconfigure/testing/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/AbstractSpringWebInstrumentationAutoConfigurationTest.java new file mode 100644 index 000000000000..08514d5ac4f1 --- /dev/null +++ b/instrumentation/spring/spring-boot-autoconfigure/testing/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/AbstractSpringWebInstrumentationAutoConfigurationTest.java @@ -0,0 +1,91 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.spring.autoconfigure.internal; + +import static org.assertj.core.api.Assertions.assertThat; + +import io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.web.RestTemplateBeanPostProcessor; +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; +import org.springframework.web.client.RestTemplate; + +public abstract class AbstractSpringWebInstrumentationAutoConfigurationTest { + + protected abstract ApplicationContextRunner contextRunner(); + + /** + * Tests that users create {@link RestTemplate} bean is instrumented. + * + *
{@code
+   * @Bean public RestTemplate restTemplate() {
+   *     return new RestTemplate();
+   * }
+   * }
+ */ + @Test + void instrumentationEnabled() { + contextRunner() + .withPropertyValues("otel.instrumentation.spring-web.enabled=true") + .withPropertyValues("otel.instrumentation.common.default-enabled=false") + .run( + context -> { + assertThat( + context.getBean( + "otelRestTemplateBeanPostProcessor", RestTemplateBeanPostProcessor.class)) + .isNotNull(); + + assertThat( + context.getBean(RestTemplate.class).getInterceptors().stream() + .filter( + rti -> + rti.getClass() + .getName() + .startsWith("io.opentelemetry.instrumentation")) + .count()) + .isEqualTo(1); + }); + } + + @Test + void instrumentationDisabled() { + contextRunner() + .withPropertyValues("otel.instrumentation.spring-web.enabled=false") + .run( + context -> + assertThat(context.containsBean("otelRestTemplateBeanPostProcessor")).isFalse()); + } + + @Test + void instrumentationDisabledButAllEnabled() { + contextRunner() + .withPropertyValues("otel.instrumentation.spring-web.enabled=false") + .withPropertyValues("otel.instrumentation.common.default-enabled=true") + .run( + context -> + assertThat(context.containsBean("otelRestTemplateBeanPostProcessor")).isFalse()); + } + + @Test + void allInstrumentationDisabled() { + contextRunner() + .withPropertyValues("otel.instrumentation.common.default-enabled=false") + .run( + context -> + assertThat(context.containsBean("otelRestTemplateBeanPostProcessor")).isFalse()); + } + + @Test + void defaultConfiguration() { + contextRunner() + .run( + context -> + assertThat( + context.getBean( + "otelRestTemplateBeanPostProcessor", + RestTemplateBeanPostProcessor.class)) + .isNotNull()); + } +} From e6a8ae439b3b376259c52ca98a1d15c64033217b Mon Sep 17 00:00:00 2001 From: Jay DeLuca Date: Tue, 2 Dec 2025 14:47:54 -0500 Subject: [PATCH 27/40] create spring 2 suite to avoid exlusions --- .../build.gradle.kts | 54 +++++++++++++------ ...cInstrumentationAutoConfigurationTest.java | 0 ...MicrometerBridgeAutoConfigurationTest.java | 0 ...cInstrumentationAutoConfigurationTest.java | 0 ...bInstrumentationAutoConfigurationTest.java | 0 ...cInstrumentationAutoConfigurationTest.java | 2 + ...aInstrumentationAutoConfigurationTest.java | 1 + ...MicrometerBridgeAutoConfigurationTest.java | 3 ++ 8 files changed, 43 insertions(+), 17 deletions(-) rename instrumentation/spring/spring-boot-autoconfigure/src/{test => testSpring2}/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/jdbc/JdbcInstrumentationAutoConfigurationTest.java (100%) rename instrumentation/spring/spring-boot-autoconfigure/src/{test => testSpring2}/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/micrometer/MicrometerBridgeAutoConfigurationTest.java (100%) rename instrumentation/spring/spring-boot-autoconfigure/src/{test => testSpring2}/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/r2dbc/R2DbcInstrumentationAutoConfigurationTest.java (100%) rename instrumentation/spring/spring-boot-autoconfigure/src/{test => testSpring2}/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/web/SpringWebInstrumentationAutoConfigurationTest.java (100%) diff --git a/instrumentation/spring/spring-boot-autoconfigure/build.gradle.kts b/instrumentation/spring/spring-boot-autoconfigure/build.gradle.kts index 4fd3766ded96..2deb4bfc215a 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/build.gradle.kts +++ b/instrumentation/spring/spring-boot-autoconfigure/build.gradle.kts @@ -187,6 +187,24 @@ testing { } } + val testSpring2 by registering(JvmTestSuite::class) { + dependencies { + implementation(project()) + implementation("io.opentelemetry:opentelemetry-sdk") + implementation("io.opentelemetry:opentelemetry-sdk-testing") + implementation("io.opentelemetry:opentelemetry-sdk-extension-autoconfigure") + implementation(project(":instrumentation-api")) + implementation(project(":instrumentation:micrometer:micrometer-1.5:library")) + implementation(project(":instrumentation:spring:spring-boot-autoconfigure:testing")) + implementation("org.springframework.boot:spring-boot-starter-test:$springBootVersion") { + exclude("org.junit.vintage", "junit-vintage-engine") + } + implementation("javax.servlet:javax.servlet-api:3.1.0") + runtimeOnly("com.h2database:h2:1.4.197") + runtimeOnly("io.r2dbc:r2dbc-h2:1.0.0.RELEASE") + } + } + val testSpring3 by registering(JvmTestSuite::class) { dependencies { implementation(project()) @@ -241,20 +259,20 @@ configurations.configureEach { if (name.contains("testLogbackMissing")) { exclude("ch.qos.logback", "logback-classic") } + // testSpring2: configure Spring Boot 3.x dependencies for latest dep testing + if (name == "testSpring2Implementation") { + dependencies { + add(name, "org.springframework.boot:spring-boot-starter-web:3.+") + add(name, "org.springframework.boot:spring-boot-starter-jdbc:3.+") + add(name, "org.springframework.boot:spring-boot-starter-actuator:3.+") + add(name, "org.springframework.boot:spring-boot-starter-data-r2dbc:3.+") + } + } } tasks { compileTestJava { options.compilerArgs.add("-parameters") - - // Exclude Spring Boot specific tests from compilation when testLatestDeps is true - // These tests are covered by testSpring4 suite - if (latestDepTest) { - exclude("**/micrometer/MicrometerBridgeAutoConfigurationTest.java") - exclude("**/r2dbc/R2DbcInstrumentationAutoConfigurationTest.java") - exclude("**/jdbc/JdbcInstrumentationAutoConfigurationTest.java") - exclude("**/web/SpringWebInstrumentationAutoConfigurationTest.java") - } } withType().configureEach { @@ -266,14 +284,6 @@ tasks { } test { - // Exclude Spring Boot specific tests when testLatestDeps is true - // These tests are covered by testSpring4 suite - if (latestDepTest) { - exclude("**/micrometer/MicrometerBridgeAutoConfigurationTest.class") - exclude("**/r2dbc/R2DbcInstrumentationAutoConfigurationTest.class") - exclude("**/jdbc/JdbcInstrumentationAutoConfigurationTest.class") - exclude("**/web/SpringWebInstrumentationAutoConfigurationTest.class") - } } named("compileJavaSpring3Java") { @@ -282,12 +292,22 @@ tasks { options.release.set(17) } + named("compileTestSpring2Java") { + sourceCompatibility = "17" + targetCompatibility = "17" + options.release.set(17) + } + named("compileTestSpring3Java") { sourceCompatibility = "17" targetCompatibility = "17" options.release.set(17) } + named("testSpring2") { + isEnabled = latestDepTest && testSpring3 + } + named("testSpring3") { isEnabled = testSpring3 } diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/jdbc/JdbcInstrumentationAutoConfigurationTest.java b/instrumentation/spring/spring-boot-autoconfigure/src/testSpring2/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/jdbc/JdbcInstrumentationAutoConfigurationTest.java similarity index 100% rename from instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/jdbc/JdbcInstrumentationAutoConfigurationTest.java rename to instrumentation/spring/spring-boot-autoconfigure/src/testSpring2/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/jdbc/JdbcInstrumentationAutoConfigurationTest.java diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/micrometer/MicrometerBridgeAutoConfigurationTest.java b/instrumentation/spring/spring-boot-autoconfigure/src/testSpring2/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/micrometer/MicrometerBridgeAutoConfigurationTest.java similarity index 100% rename from instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/micrometer/MicrometerBridgeAutoConfigurationTest.java rename to instrumentation/spring/spring-boot-autoconfigure/src/testSpring2/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/micrometer/MicrometerBridgeAutoConfigurationTest.java diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/r2dbc/R2DbcInstrumentationAutoConfigurationTest.java b/instrumentation/spring/spring-boot-autoconfigure/src/testSpring2/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/r2dbc/R2DbcInstrumentationAutoConfigurationTest.java similarity index 100% rename from instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/r2dbc/R2DbcInstrumentationAutoConfigurationTest.java rename to instrumentation/spring/spring-boot-autoconfigure/src/testSpring2/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/r2dbc/R2DbcInstrumentationAutoConfigurationTest.java diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/web/SpringWebInstrumentationAutoConfigurationTest.java b/instrumentation/spring/spring-boot-autoconfigure/src/testSpring2/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/web/SpringWebInstrumentationAutoConfigurationTest.java similarity index 100% rename from instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/web/SpringWebInstrumentationAutoConfigurationTest.java rename to instrumentation/spring/spring-boot-autoconfigure/src/testSpring2/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/web/SpringWebInstrumentationAutoConfigurationTest.java diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/testSpring4/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/jdbc/JdbcInstrumentationAutoConfigurationTest.java b/instrumentation/spring/spring-boot-autoconfigure/src/testSpring4/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/jdbc/JdbcInstrumentationAutoConfigurationTest.java index 7f6d483c9912..f7288a125a76 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/testSpring4/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/jdbc/JdbcInstrumentationAutoConfigurationTest.java +++ b/instrumentation/spring/spring-boot-autoconfigure/src/testSpring4/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/jdbc/JdbcInstrumentationAutoConfigurationTest.java @@ -24,6 +24,7 @@ class JdbcInstrumentationAutoConfigurationTest @RegisterExtension static final LibraryInstrumentationExtension testing = LibraryInstrumentationExtension.create(); + @Override protected InstrumentationExtension testing() { return testing; } @@ -41,6 +42,7 @@ protected InstrumentationExtension testing() { DataSourceAutoConfiguration.class)) .withBean("openTelemetry", OpenTelemetry.class, testing::getOpenTelemetry); + @Override protected ApplicationContextRunner contextRunner() { return runner; } diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/testSpring4/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/kafka/KafkaInstrumentationAutoConfigurationTest.java b/instrumentation/spring/spring-boot-autoconfigure/src/testSpring4/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/kafka/KafkaInstrumentationAutoConfigurationTest.java index a0c680817fa8..30fa586a6cba 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/testSpring4/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/kafka/KafkaInstrumentationAutoConfigurationTest.java +++ b/instrumentation/spring/spring-boot-autoconfigure/src/testSpring4/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/kafka/KafkaInstrumentationAutoConfigurationTest.java @@ -33,6 +33,7 @@ class KafkaInstrumentationAutoConfigurationTest KafkaInstrumentationSpringBoot4AutoConfiguration.class)) .withBean("openTelemetry", OpenTelemetry.class, OpenTelemetry::noop); + @Override protected ApplicationContextRunner contextRunner() { return runner; } diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/testSpring4/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/micrometer/MicrometerBridgeAutoConfigurationTest.java b/instrumentation/spring/spring-boot-autoconfigure/src/testSpring4/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/micrometer/MicrometerBridgeAutoConfigurationTest.java index 0f5d949ad3a8..1cf118d3ddbd 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/testSpring4/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/micrometer/MicrometerBridgeAutoConfigurationTest.java +++ b/instrumentation/spring/spring-boot-autoconfigure/src/testSpring4/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/micrometer/MicrometerBridgeAutoConfigurationTest.java @@ -20,14 +20,17 @@ class MicrometerBridgeAutoConfigurationTest extends AbstractMicrometerBridgeAuto .withConfiguration( AutoConfigurations.of(MicrometerBridgeSpringBoot4AutoConfiguration.class)); + @Override protected ApplicationContextRunner contextRunner() { return runner; } + @Override protected Class getMetricsAutoConfigurationClass() { return MetricsAutoConfiguration.class; } + @Override protected Class getMeterRegistryClass() { return MeterRegistry.class; } From 7ac8f914879ff470e0f15749e09ba84a1f093c6f Mon Sep 17 00:00:00 2001 From: Jay DeLuca Date: Tue, 2 Dec 2025 16:38:31 -0500 Subject: [PATCH 28/40] refactor smoke tests to avoid duplication for kafka and mongo, jfr assertions --- ...mentationSpringBoot4AutoConfiguration.java | 59 ++++++++++++++-- .../spring-boot-2/build.gradle.kts | 1 + .../KafkaSpringStarterSmokeTest.java | 14 +++- .../MongoSpringStarterSmokeTest.java | 19 ++++- .../spring-boot-3/build.gradle.kts | 1 + .../KafkaSpringStarterSmokeTest.java | 14 +++- .../MongoSpringStarterSmokeTest.java | 19 ++++- .../spring-boot-4/build.gradle.kts | 1 + .../KafkaSpringStarterSmokeTest.java | 69 ++----------------- .../MongoSpringStarterSmokeTest.java | 55 +++------------ .../smoketest/OtelSpringStarterSmokeTest.java | 36 ---------- ...bstractJvmKafkaSpringStarterSmokeTest.java | 13 ++-- ...tractJvmMongodbSpringStarterSmokeTest.java | 16 +++-- .../AbstractOtelSpringStarterSmokeTest.java | 33 ++++++++- 14 files changed, 182 insertions(+), 168 deletions(-) diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/main/javaSpring4/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/kafka/KafkaInstrumentationSpringBoot4AutoConfiguration.java b/instrumentation/spring/spring-boot-autoconfigure/src/main/javaSpring4/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/kafka/KafkaInstrumentationSpringBoot4AutoConfiguration.java index f334adc4fb8e..52c83ecb0a38 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/main/javaSpring4/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/kafka/KafkaInstrumentationSpringBoot4AutoConfiguration.java +++ b/instrumentation/spring/spring-boot-autoconfigure/src/main/javaSpring4/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/kafka/KafkaInstrumentationSpringBoot4AutoConfiguration.java @@ -6,28 +6,73 @@ package io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.kafka; import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.instrumentation.api.incubator.config.internal.InstrumentationConfig; import io.opentelemetry.instrumentation.kafkaclients.v2_6.KafkaTelemetry; import io.opentelemetry.instrumentation.spring.autoconfigure.internal.ConditionalOnEnabledInstrumentation; +import io.opentelemetry.instrumentation.spring.kafka.v2_7.SpringKafkaTelemetry; +import org.springframework.beans.factory.ObjectProvider; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; -import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.kafka.autoconfigure.DefaultKafkaProducerFactoryCustomizer; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.kafka.config.ConcurrentKafkaListenerContainerFactory; +import org.springframework.kafka.core.KafkaTemplate; /** * This class is internal and is hence not for public use. Its APIs are unstable and can change at * any time. */ @ConditionalOnEnabledInstrumentation(module = "kafka") -@ConditionalOnClass(DefaultKafkaProducerFactoryCustomizer.class) -@ConditionalOnMissingBean(name = "otelKafkaProducerFactoryCustomizer") +@ConditionalOnClass({KafkaTemplate.class, ConcurrentKafkaListenerContainerFactory.class}) @Configuration public class KafkaInstrumentationSpringBoot4AutoConfiguration { + // For error prone + public KafkaInstrumentationSpringBoot4AutoConfiguration() {} + + @Bean + static SpringKafkaTelemetry springKafkaTelemetrySpringBoot4( + ObjectProvider openTelemetryProvider, + ObjectProvider configProvider) { + return SpringKafkaTelemetry.builder(openTelemetryProvider.getObject()) + .setCaptureExperimentalSpanAttributes( + configProvider + .getObject() + .getBoolean("otel.instrumentation.kafka.experimental-span-attributes", false)) + .build(); + } + + // static to avoid "is not eligible for getting processed by all BeanPostProcessors" warning @Bean - DefaultKafkaProducerFactoryCustomizer otelKafkaProducerFactoryCustomizer( - OpenTelemetry openTelemetry) { - KafkaTelemetry kafkaTelemetry = KafkaTelemetry.create(openTelemetry); - return producerFactory -> producerFactory.addPostProcessor(kafkaTelemetry::wrap); + @ConditionalOnProperty( + name = "otel.instrumentation.kafka.autoconfigure-interceptor", + havingValue = "true", + matchIfMissing = true) + static ConcurrentKafkaListenerContainerFactoryPostProcessor + otelKafkaListenerContainerFactoryBeanPostProcessorSpringBoot4( + ObjectProvider openTelemetryProvider, + ObjectProvider configProvider) { + return new ConcurrentKafkaListenerContainerFactoryPostProcessor( + () -> springKafkaTelemetrySpringBoot4(openTelemetryProvider, configProvider)); + } + + @ConditionalOnClass(DefaultKafkaProducerFactoryCustomizer.class) + @Configuration + static class ProducerFactoryCustomizerConfiguration { + + @Bean + DefaultKafkaProducerFactoryCustomizer otelKafkaProducerFactoryCustomizerSpringBoot4( + ObjectProvider openTelemetryProvider, + ObjectProvider configProvider) { + KafkaTelemetry kafkaTelemetry = + KafkaTelemetry.builder(openTelemetryProvider.getObject()) + .setCaptureExperimentalSpanAttributes( + configProvider + .getObject() + .getBoolean("otel.instrumentation.kafka.experimental-span-attributes", false)) + .build(); + return producerFactory -> producerFactory.addPostProcessor(kafkaTelemetry::wrap); + } } } diff --git a/smoke-tests-otel-starter/spring-boot-2/build.gradle.kts b/smoke-tests-otel-starter/spring-boot-2/build.gradle.kts index 2bc0debea139..63c363f477d2 100644 --- a/smoke-tests-otel-starter/spring-boot-2/build.gradle.kts +++ b/smoke-tests-otel-starter/spring-boot-2/build.gradle.kts @@ -24,6 +24,7 @@ dependencies { testImplementation("org.testcontainers:testcontainers-kafka") testImplementation("org.testcontainers:testcontainers-mongodb") testImplementation("org.springframework.boot:spring-boot-starter-test") + testImplementation(project(":instrumentation:spring:spring-boot-autoconfigure")) } configurations.configureEach { diff --git a/smoke-tests-otel-starter/spring-boot-2/src/test/java/io/opentelemetry/spring/smoketest/KafkaSpringStarterSmokeTest.java b/smoke-tests-otel-starter/spring-boot-2/src/test/java/io/opentelemetry/spring/smoketest/KafkaSpringStarterSmokeTest.java index 5c8ad0f748e0..08e43db7d358 100644 --- a/smoke-tests-otel-starter/spring-boot-2/src/test/java/io/opentelemetry/spring/smoketest/KafkaSpringStarterSmokeTest.java +++ b/smoke-tests-otel-starter/spring-boot-2/src/test/java/io/opentelemetry/spring/smoketest/KafkaSpringStarterSmokeTest.java @@ -5,7 +5,19 @@ package io.opentelemetry.spring.smoketest; +import io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.kafka.KafkaInstrumentationAutoConfiguration; import org.junit.jupiter.api.condition.DisabledInNativeImage; +import org.springframework.boot.autoconfigure.kafka.KafkaAutoConfiguration; @DisabledInNativeImage // See GraalVmNativeKafkaSpringStarterSmokeTest for the GraalVM native test -class KafkaSpringStarterSmokeTest extends AbstractJvmKafkaSpringStarterSmokeTest {} +class KafkaSpringStarterSmokeTest extends AbstractJvmKafkaSpringStarterSmokeTest { + @Override + protected Class kafkaInstrumentationAutoConfigurationClass() { + return KafkaInstrumentationAutoConfiguration.class; + } + + @Override + protected Class kafkaAutoConfigurationClass() { + return KafkaAutoConfiguration.class; + } +} diff --git a/smoke-tests-otel-starter/spring-boot-2/src/test/java/io/opentelemetry/spring/smoketest/MongoSpringStarterSmokeTest.java b/smoke-tests-otel-starter/spring-boot-2/src/test/java/io/opentelemetry/spring/smoketest/MongoSpringStarterSmokeTest.java index fe97c8a1a18a..d99a59a63568 100644 --- a/smoke-tests-otel-starter/spring-boot-2/src/test/java/io/opentelemetry/spring/smoketest/MongoSpringStarterSmokeTest.java +++ b/smoke-tests-otel-starter/spring-boot-2/src/test/java/io/opentelemetry/spring/smoketest/MongoSpringStarterSmokeTest.java @@ -5,7 +5,24 @@ package io.opentelemetry.spring.smoketest; +import io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.mongo.MongoClientInstrumentationAutoConfiguration; import org.junit.jupiter.api.condition.DisabledInNativeImage; +import org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration; @DisabledInNativeImage // See GraalVmNativeMongodbSpringStarterSmokeTest for the GraalVM native test -class MongoSpringStarterSmokeTest extends AbstractJvmMongodbSpringStarterSmokeTest {} +class MongoSpringStarterSmokeTest extends AbstractJvmMongodbSpringStarterSmokeTest { + @Override + String getMongoUriProperty() { + return "spring.data.mongodb.uri"; + } + + @Override + Class mongoAutoConfigurationClass() { + return MongoAutoConfiguration.class; + } + + @Override + Class mongoInstrumentationAutoConfigurationClass() { + return MongoClientInstrumentationAutoConfiguration.class; + } +} diff --git a/smoke-tests-otel-starter/spring-boot-3/build.gradle.kts b/smoke-tests-otel-starter/spring-boot-3/build.gradle.kts index 7906bf5fabed..2041ab29cf3e 100644 --- a/smoke-tests-otel-starter/spring-boot-3/build.gradle.kts +++ b/smoke-tests-otel-starter/spring-boot-3/build.gradle.kts @@ -25,6 +25,7 @@ dependencies { testImplementation("org.testcontainers:testcontainers-kafka") testImplementation("org.testcontainers:testcontainers-mongodb") testImplementation("org.springframework.boot:spring-boot-starter-test") + testImplementation(project(":instrumentation:spring:spring-boot-autoconfigure")) val testLatestDeps = gradle.startParameter.projectProperties["testLatestDeps"] == "true" if (testLatestDeps) { diff --git a/smoke-tests-otel-starter/spring-boot-3/src/test/java/io/opentelemetry/spring/smoketest/KafkaSpringStarterSmokeTest.java b/smoke-tests-otel-starter/spring-boot-3/src/test/java/io/opentelemetry/spring/smoketest/KafkaSpringStarterSmokeTest.java index 5c8ad0f748e0..08e43db7d358 100644 --- a/smoke-tests-otel-starter/spring-boot-3/src/test/java/io/opentelemetry/spring/smoketest/KafkaSpringStarterSmokeTest.java +++ b/smoke-tests-otel-starter/spring-boot-3/src/test/java/io/opentelemetry/spring/smoketest/KafkaSpringStarterSmokeTest.java @@ -5,7 +5,19 @@ package io.opentelemetry.spring.smoketest; +import io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.kafka.KafkaInstrumentationAutoConfiguration; import org.junit.jupiter.api.condition.DisabledInNativeImage; +import org.springframework.boot.autoconfigure.kafka.KafkaAutoConfiguration; @DisabledInNativeImage // See GraalVmNativeKafkaSpringStarterSmokeTest for the GraalVM native test -class KafkaSpringStarterSmokeTest extends AbstractJvmKafkaSpringStarterSmokeTest {} +class KafkaSpringStarterSmokeTest extends AbstractJvmKafkaSpringStarterSmokeTest { + @Override + protected Class kafkaInstrumentationAutoConfigurationClass() { + return KafkaInstrumentationAutoConfiguration.class; + } + + @Override + protected Class kafkaAutoConfigurationClass() { + return KafkaAutoConfiguration.class; + } +} diff --git a/smoke-tests-otel-starter/spring-boot-3/src/test/java/io/opentelemetry/spring/smoketest/MongoSpringStarterSmokeTest.java b/smoke-tests-otel-starter/spring-boot-3/src/test/java/io/opentelemetry/spring/smoketest/MongoSpringStarterSmokeTest.java index fe97c8a1a18a..d99a59a63568 100644 --- a/smoke-tests-otel-starter/spring-boot-3/src/test/java/io/opentelemetry/spring/smoketest/MongoSpringStarterSmokeTest.java +++ b/smoke-tests-otel-starter/spring-boot-3/src/test/java/io/opentelemetry/spring/smoketest/MongoSpringStarterSmokeTest.java @@ -5,7 +5,24 @@ package io.opentelemetry.spring.smoketest; +import io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.mongo.MongoClientInstrumentationAutoConfiguration; import org.junit.jupiter.api.condition.DisabledInNativeImage; +import org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration; @DisabledInNativeImage // See GraalVmNativeMongodbSpringStarterSmokeTest for the GraalVM native test -class MongoSpringStarterSmokeTest extends AbstractJvmMongodbSpringStarterSmokeTest {} +class MongoSpringStarterSmokeTest extends AbstractJvmMongodbSpringStarterSmokeTest { + @Override + String getMongoUriProperty() { + return "spring.data.mongodb.uri"; + } + + @Override + Class mongoAutoConfigurationClass() { + return MongoAutoConfiguration.class; + } + + @Override + Class mongoInstrumentationAutoConfigurationClass() { + return MongoClientInstrumentationAutoConfiguration.class; + } +} diff --git a/smoke-tests-otel-starter/spring-boot-4/build.gradle.kts b/smoke-tests-otel-starter/spring-boot-4/build.gradle.kts index ae52b19cd1da..4bbd5bd6fe92 100644 --- a/smoke-tests-otel-starter/spring-boot-4/build.gradle.kts +++ b/smoke-tests-otel-starter/spring-boot-4/build.gradle.kts @@ -34,6 +34,7 @@ dependencies { testImplementation("org.testcontainers:testcontainers-junit-jupiter") testImplementation("org.testcontainers:testcontainers-kafka") testImplementation("org.testcontainers:testcontainers-mongodb") + testImplementation(project(":instrumentation:spring:spring-boot-autoconfigure")) } springBoot { diff --git a/smoke-tests-otel-starter/spring-boot-4/src/test/java/io/opentelemetry/spring/smoketest/KafkaSpringStarterSmokeTest.java b/smoke-tests-otel-starter/spring-boot-4/src/test/java/io/opentelemetry/spring/smoketest/KafkaSpringStarterSmokeTest.java index 40ddb3a290aa..be32e1fa84ed 100644 --- a/smoke-tests-otel-starter/spring-boot-4/src/test/java/io/opentelemetry/spring/smoketest/KafkaSpringStarterSmokeTest.java +++ b/smoke-tests-otel-starter/spring-boot-4/src/test/java/io/opentelemetry/spring/smoketest/KafkaSpringStarterSmokeTest.java @@ -5,76 +5,19 @@ package io.opentelemetry.spring.smoketest; -import io.opentelemetry.api.OpenTelemetry; -import io.opentelemetry.instrumentation.spring.autoconfigure.OpenTelemetryAutoConfiguration; import io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.kafka.KafkaInstrumentationSpringBoot4AutoConfiguration; -import io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.thread.ThreadDetailsAutoConfiguration; -import java.time.Duration; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; import org.junit.jupiter.api.condition.DisabledInNativeImage; -import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.kafka.autoconfigure.KafkaAutoConfiguration; -import org.springframework.boot.test.context.runner.ApplicationContextRunner; -import org.springframework.kafka.core.KafkaTemplate; -import org.testcontainers.containers.wait.strategy.Wait; -import org.testcontainers.kafka.KafkaContainer; -import org.testcontainers.utility.DockerImageName; @DisabledInNativeImage // See GraalVmNativeKafkaSpringStarterSmokeTest for the GraalVM native test -class KafkaSpringStarterSmokeTest extends AbstractKafkaSpringStarterSmokeTest { - static KafkaContainer kafka; - - private ApplicationContextRunner contextRunner; - - @BeforeAll - static void setUpKafka() { - kafka = - new KafkaContainer(DockerImageName.parse("apache/kafka:3.8.0")) - .withEnv("KAFKA_HEAP_OPTS", "-Xmx256m") - .waitingFor(Wait.forLogMessage(".*started \\(kafka.server.Kafka.*Server\\).*", 1)) - .withStartupTimeout(Duration.ofMinutes(1)); - kafka.start(); - } - - @AfterAll - static void tearDownKafka() { - kafka.stop(); - } - - @BeforeEach - void setUpContext() { - contextRunner = - new ApplicationContextRunner() - .withAllowBeanDefinitionOverriding(true) - .withConfiguration( - AutoConfigurations.of( - OpenTelemetryAutoConfiguration.class, - ThreadDetailsAutoConfiguration.class, - SpringSmokeOtelConfiguration.class, - KafkaAutoConfiguration.class, - KafkaInstrumentationSpringBoot4AutoConfiguration.class, - KafkaConfig.class)) - .withPropertyValues( - "otel.instrumentation.kafka.experimental-span-attributes=true", - "spring.kafka.bootstrap-servers=" + kafka.getBootstrapServers(), - "spring.kafka.consumer.auto-offset-reset=earliest", - "spring.kafka.consumer.linger-ms=10", - "spring.kafka.listener.idle-between-polls=1000", - "spring.kafka.producer.transaction-id-prefix=test-"); +class KafkaSpringStarterSmokeTest extends AbstractJvmKafkaSpringStarterSmokeTest { + @Override + protected Class kafkaInstrumentationAutoConfigurationClass() { + return KafkaInstrumentationSpringBoot4AutoConfiguration.class; } - @SuppressWarnings("unchecked") // we lose parameter types for the KafkaTemplate @Override - @Test - void shouldInstrumentProducerAndConsumer() { - contextRunner.run( - applicationContext -> { - testing = new SpringSmokeTestRunner(applicationContext.getBean(OpenTelemetry.class)); - kafkaTemplate = applicationContext.getBean(KafkaTemplate.class); - super.shouldInstrumentProducerAndConsumer(); - }); + protected Class kafkaAutoConfigurationClass() { + return KafkaAutoConfiguration.class; } } diff --git a/smoke-tests-otel-starter/spring-boot-4/src/test/java/io/opentelemetry/spring/smoketest/MongoSpringStarterSmokeTest.java b/smoke-tests-otel-starter/spring-boot-4/src/test/java/io/opentelemetry/spring/smoketest/MongoSpringStarterSmokeTest.java index f1df8dd3d5fb..be90f83ef9dd 100644 --- a/smoke-tests-otel-starter/spring-boot-4/src/test/java/io/opentelemetry/spring/smoketest/MongoSpringStarterSmokeTest.java +++ b/smoke-tests-otel-starter/spring-boot-4/src/test/java/io/opentelemetry/spring/smoketest/MongoSpringStarterSmokeTest.java @@ -5,61 +5,24 @@ package io.opentelemetry.spring.smoketest; -import com.mongodb.client.MongoClient; -import io.opentelemetry.api.OpenTelemetry; -import io.opentelemetry.instrumentation.spring.autoconfigure.OpenTelemetryAutoConfiguration; import io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.mongo.MongoClientInstrumentationSpringBoot4AutoConfiguration; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; import org.junit.jupiter.api.condition.DisabledInNativeImage; -import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.mongodb.autoconfigure.MongoAutoConfiguration; -import org.springframework.boot.test.context.runner.ApplicationContextRunner; -import org.testcontainers.junit.jupiter.Container; -import org.testcontainers.mongodb.MongoDBContainer; @DisabledInNativeImage // See GraalVmNativeMongodbSpringStarterSmokeTest for the GraalVM native test -class MongoSpringStarterSmokeTest extends AbstractMongodbSpringStarterSmokeTest { - - @Container static MongoDBContainer container; - - private ApplicationContextRunner contextRunner; - - @BeforeAll - static void setUpContainer() { - container = new MongoDBContainer("mongo:8.2.2"); - container.start(); - } - - @AfterAll - static void tearDownContainer() { - container.stop(); +class MongoSpringStarterSmokeTest extends AbstractJvmMongodbSpringStarterSmokeTest { + @Override + String getMongoUriProperty() { + return "spring.mongodb.uri"; } - @BeforeEach - void setUpContext() { - contextRunner = - new ApplicationContextRunner() - .withAllowBeanDefinitionOverriding(true) - .withConfiguration( - AutoConfigurations.of( - OpenTelemetryAutoConfiguration.class, - SpringSmokeOtelConfiguration.class, - MongoAutoConfiguration.class, - MongoClientInstrumentationSpringBoot4AutoConfiguration.class)) - .withPropertyValues("spring.mongodb.uri=" + container.getReplicaSetUrl()); + @Override + Class mongoAutoConfigurationClass() { + return MongoAutoConfiguration.class; } @Override - @Test - void mongodb() { - contextRunner.run( - applicationContext -> { - testing = new SpringSmokeTestRunner(applicationContext.getBean(OpenTelemetry.class)); - mongoClient = applicationContext.getBean(MongoClient.class); - super.mongodb(); - }); + Class mongoInstrumentationAutoConfigurationClass() { + return MongoClientInstrumentationSpringBoot4AutoConfiguration.class; } } diff --git a/smoke-tests-otel-starter/spring-boot-4/src/test/java/io/opentelemetry/spring/smoketest/OtelSpringStarterSmokeTest.java b/smoke-tests-otel-starter/spring-boot-4/src/test/java/io/opentelemetry/spring/smoketest/OtelSpringStarterSmokeTest.java index 53ac20caf237..29b9fb61b123 100644 --- a/smoke-tests-otel-starter/spring-boot-4/src/test/java/io/opentelemetry/spring/smoketest/OtelSpringStarterSmokeTest.java +++ b/smoke-tests-otel-starter/spring-boot-4/src/test/java/io/opentelemetry/spring/smoketest/OtelSpringStarterSmokeTest.java @@ -6,8 +6,6 @@ package io.opentelemetry.spring.smoketest; import java.net.URI; -import java.util.List; -import org.assertj.core.api.AbstractIterableAssert; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.restclient.RestTemplateBuilder; import org.springframework.boot.resttestclient.TestRestTemplate; @@ -55,38 +53,4 @@ void restClientCall(String path) { RestTemplate restTemplate = restTemplateBuilder.rootUri("http://localhost:" + port).build(); restTemplate.getForObject(path, String.class); } - - @Override - protected void assertAdditionalMetrics() { - if (!isFlightRecorderAvailable()) { - return; - } - - // JFR based metrics - for (String metric : - List.of( - "jvm.cpu.limit", - "jvm.buffer.count", - "jvm.class.count", - "jvm.cpu.context_switch", - "jvm.system.cpu.utilization", - "jvm.gc.duration", - "jvm.memory.init", - "jvm.memory.used", - "jvm.memory.allocation", - "jvm.network.io", - "jvm.thread.count")) { - testing.waitAndAssertMetrics( - "io.opentelemetry.runtime-telemetry-java17", metric, AbstractIterableAssert::isNotEmpty); - } - } - - private static boolean isFlightRecorderAvailable() { - try { - return (boolean) - Class.forName("jdk.jfr.FlightRecorder").getMethod("isAvailable").invoke(null); - } catch (ReflectiveOperationException exception) { - return false; - } - } } diff --git a/smoke-tests-otel-starter/spring-boot-common/src/main/java/io/opentelemetry/spring/smoketest/AbstractJvmKafkaSpringStarterSmokeTest.java b/smoke-tests-otel-starter/spring-boot-common/src/main/java/io/opentelemetry/spring/smoketest/AbstractJvmKafkaSpringStarterSmokeTest.java index f57a7c1738ab..8efd54d1d288 100644 --- a/smoke-tests-otel-starter/spring-boot-common/src/main/java/io/opentelemetry/spring/smoketest/AbstractJvmKafkaSpringStarterSmokeTest.java +++ b/smoke-tests-otel-starter/spring-boot-common/src/main/java/io/opentelemetry/spring/smoketest/AbstractJvmKafkaSpringStarterSmokeTest.java @@ -7,7 +7,6 @@ import io.opentelemetry.api.OpenTelemetry; import io.opentelemetry.instrumentation.spring.autoconfigure.OpenTelemetryAutoConfiguration; -import io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.kafka.KafkaInstrumentationAutoConfiguration; import io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.thread.ThreadDetailsAutoConfiguration; import java.time.Duration; import org.junit.jupiter.api.AfterAll; @@ -15,7 +14,6 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.boot.autoconfigure.AutoConfigurations; -import org.springframework.boot.autoconfigure.kafka.KafkaAutoConfiguration; import org.springframework.boot.test.context.runner.ApplicationContextRunner; import org.springframework.kafka.core.KafkaTemplate; import org.testcontainers.containers.wait.strategy.Wait; @@ -23,11 +21,16 @@ import org.testcontainers.utility.DockerImageName; /** Spring has a test container integration, but that doesn't work for Spring Boot 2 */ -public class AbstractJvmKafkaSpringStarterSmokeTest extends AbstractKafkaSpringStarterSmokeTest { +public abstract class AbstractJvmKafkaSpringStarterSmokeTest + extends AbstractKafkaSpringStarterSmokeTest { static KafkaContainer kafka; private ApplicationContextRunner contextRunner; + protected abstract Class kafkaInstrumentationAutoConfigurationClass(); + + protected abstract Class kafkaAutoConfigurationClass(); + @BeforeAll static void setUpKafka() { kafka = @@ -53,8 +56,8 @@ void setUpContext() { OpenTelemetryAutoConfiguration.class, ThreadDetailsAutoConfiguration.class, SpringSmokeOtelConfiguration.class, - KafkaAutoConfiguration.class, - KafkaInstrumentationAutoConfiguration.class, + kafkaAutoConfigurationClass(), + kafkaInstrumentationAutoConfigurationClass(), KafkaConfig.class)) .withPropertyValues( "otel.instrumentation.kafka.experimental-span-attributes=true", diff --git a/smoke-tests-otel-starter/spring-boot-common/src/main/java/io/opentelemetry/spring/smoketest/AbstractJvmMongodbSpringStarterSmokeTest.java b/smoke-tests-otel-starter/spring-boot-common/src/main/java/io/opentelemetry/spring/smoketest/AbstractJvmMongodbSpringStarterSmokeTest.java index 60d0cafa8b67..d403919d3996 100644 --- a/smoke-tests-otel-starter/spring-boot-common/src/main/java/io/opentelemetry/spring/smoketest/AbstractJvmMongodbSpringStarterSmokeTest.java +++ b/smoke-tests-otel-starter/spring-boot-common/src/main/java/io/opentelemetry/spring/smoketest/AbstractJvmMongodbSpringStarterSmokeTest.java @@ -8,25 +8,29 @@ import com.mongodb.client.MongoClient; import io.opentelemetry.api.OpenTelemetry; import io.opentelemetry.instrumentation.spring.autoconfigure.OpenTelemetryAutoConfiguration; -import io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.mongo.MongoClientInstrumentationAutoConfiguration; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.boot.autoconfigure.AutoConfigurations; -import org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration; import org.springframework.boot.test.context.runner.ApplicationContextRunner; import org.testcontainers.junit.jupiter.Container; import org.testcontainers.mongodb.MongoDBContainer; /** Spring has a test container integration, but that doesn't work for Spring Boot 2 */ -public class AbstractJvmMongodbSpringStarterSmokeTest +public abstract class AbstractJvmMongodbSpringStarterSmokeTest extends AbstractMongodbSpringStarterSmokeTest { @Container static MongoDBContainer container; private ApplicationContextRunner contextRunner; + abstract String getMongoUriProperty(); + + abstract Class mongoAutoConfigurationClass(); + + abstract Class mongoInstrumentationAutoConfigurationClass(); + @BeforeAll static void setUpContainer() { container = new MongoDBContainer("mongo:4.2"); @@ -47,9 +51,9 @@ void setUpContext() { AutoConfigurations.of( OpenTelemetryAutoConfiguration.class, SpringSmokeOtelConfiguration.class, - MongoAutoConfiguration.class, - MongoClientInstrumentationAutoConfiguration.class)) - .withPropertyValues("spring.data.mongodb.uri=" + container.getReplicaSetUrl()); + mongoAutoConfigurationClass(), + mongoInstrumentationAutoConfigurationClass())) + .withPropertyValues(getMongoUriProperty() + "=" + container.getReplicaSetUrl()); } @Override diff --git a/smoke-tests-otel-starter/spring-boot-common/src/main/java/io/opentelemetry/spring/smoketest/AbstractOtelSpringStarterSmokeTest.java b/smoke-tests-otel-starter/spring-boot-common/src/main/java/io/opentelemetry/spring/smoketest/AbstractOtelSpringStarterSmokeTest.java index 080a6c9e91cf..b7edf8025446 100644 --- a/smoke-tests-otel-starter/spring-boot-common/src/main/java/io/opentelemetry/spring/smoketest/AbstractOtelSpringStarterSmokeTest.java +++ b/smoke-tests-otel-starter/spring-boot-common/src/main/java/io/opentelemetry/spring/smoketest/AbstractOtelSpringStarterSmokeTest.java @@ -259,7 +259,38 @@ void shouldSendTelemetry() { } } - protected void assertAdditionalMetrics() {} + protected void assertAdditionalMetrics() { + if (!isFlightRecorderAvailable()) { + return; + } + + // JFR based metrics + for (String metric : + Arrays.asList( + "jvm.cpu.limit", + "jvm.buffer.count", + "jvm.class.count", + "jvm.cpu.context_switch", + "jvm.system.cpu.utilization", + "jvm.gc.duration", + "jvm.memory.init", + "jvm.memory.used", + "jvm.memory.allocation", + "jvm.network.io", + "jvm.thread.count")) { + testing.waitAndAssertMetrics( + "io.opentelemetry.runtime-telemetry-java17", metric, AbstractIterableAssert::isNotEmpty); + } + } + + private static boolean isFlightRecorderAvailable() { + try { + return (boolean) + Class.forName("jdk.jfr.FlightRecorder").getMethod("isAvailable").invoke(null); + } catch (ReflectiveOperationException exception) { + return false; + } + } @Test void databaseQuery() { From 59994b77f9534f5e8abed4a0987de1a2627c97dc Mon Sep 17 00:00:00 2001 From: Jay DeLuca Date: Tue, 2 Dec 2025 17:08:07 -0500 Subject: [PATCH 29/40] further reduce test duplication --- .fossa.yml | 3 + .../build.gradle.kts | 3 - ...mentationSpringBoot4AutoConfiguration.java | 8 +-- ...cInstrumentationAutoConfigurationTest.java | 23 +------ ...MicrometerBridgeAutoConfigurationTest.java | 11 +-- ...cInstrumentationAutoConfigurationTest.java | 23 +------ ...bInstrumentationAutoConfigurationTest.java | 23 +------ ...cInstrumentationAutoConfigurationTest.java | 24 +------ ...aInstrumentationAutoConfigurationTest.java | 22 +----- ...MicrometerBridgeAutoConfigurationTest.java | 12 +--- ...cInstrumentationAutoConfigurationTest.java | 23 +------ ...bInstrumentationAutoConfigurationTest.java | 23 +------ ...cInstrumentationAutoConfigurationTest.java | 65 +++++++++++------- ...aInstrumentationAutoConfigurationTest.java | 20 +++++- ...MicrometerBridgeAutoConfigurationTest.java | 16 +++-- ...cInstrumentationAutoConfigurationTest.java | 68 +++++++++++-------- ...bInstrumentationAutoConfigurationTest.java | 41 +++++++---- 17 files changed, 166 insertions(+), 242 deletions(-) diff --git a/.fossa.yml b/.fossa.yml index 64cb9c66e211..1d584ded4f7f 100644 --- a/.fossa.yml +++ b/.fossa.yml @@ -946,6 +946,9 @@ targets: - type: gradle path: ./ target: ':instrumentation:spring:spring-boot-actuator-autoconfigure-2.0:javaagent' + - type: gradle + path: ./ + target: ':instrumentation:spring:spring-boot-autoconfigure:testing' - type: gradle path: ./ target: ':instrumentation:spring:spring-boot-resources:javaagent' diff --git a/instrumentation/spring/spring-boot-autoconfigure/build.gradle.kts b/instrumentation/spring/spring-boot-autoconfigure/build.gradle.kts index 2deb4bfc215a..7aad519a2b17 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/build.gradle.kts +++ b/instrumentation/spring/spring-boot-autoconfigure/build.gradle.kts @@ -283,9 +283,6 @@ tasks { jvmArgs("-XX:+IgnoreUnrecognizedVMOptions") } - test { - } - named("compileJavaSpring3Java") { sourceCompatibility = "17" targetCompatibility = "17" diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/main/javaSpring4/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/kafka/KafkaInstrumentationSpringBoot4AutoConfiguration.java b/instrumentation/spring/spring-boot-autoconfigure/src/main/javaSpring4/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/kafka/KafkaInstrumentationSpringBoot4AutoConfiguration.java index 52c83ecb0a38..8f7b98289bde 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/main/javaSpring4/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/kafka/KafkaInstrumentationSpringBoot4AutoConfiguration.java +++ b/instrumentation/spring/spring-boot-autoconfigure/src/main/javaSpring4/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/kafka/KafkaInstrumentationSpringBoot4AutoConfiguration.java @@ -32,7 +32,7 @@ public class KafkaInstrumentationSpringBoot4AutoConfiguration { public KafkaInstrumentationSpringBoot4AutoConfiguration() {} @Bean - static SpringKafkaTelemetry springKafkaTelemetrySpringBoot4( + static SpringKafkaTelemetry getTelemetry( ObjectProvider openTelemetryProvider, ObjectProvider configProvider) { return SpringKafkaTelemetry.builder(openTelemetryProvider.getObject()) @@ -50,11 +50,11 @@ static SpringKafkaTelemetry springKafkaTelemetrySpringBoot4( havingValue = "true", matchIfMissing = true) static ConcurrentKafkaListenerContainerFactoryPostProcessor - otelKafkaListenerContainerFactoryBeanPostProcessorSpringBoot4( + otelKafkaListenerContainerFactoryBeanPostProcessor( ObjectProvider openTelemetryProvider, ObjectProvider configProvider) { return new ConcurrentKafkaListenerContainerFactoryPostProcessor( - () -> springKafkaTelemetrySpringBoot4(openTelemetryProvider, configProvider)); + () -> getTelemetry(openTelemetryProvider, configProvider)); } @ConditionalOnClass(DefaultKafkaProducerFactoryCustomizer.class) @@ -62,7 +62,7 @@ static SpringKafkaTelemetry springKafkaTelemetrySpringBoot4( static class ProducerFactoryCustomizerConfiguration { @Bean - DefaultKafkaProducerFactoryCustomizer otelKafkaProducerFactoryCustomizerSpringBoot4( + DefaultKafkaProducerFactoryCustomizer otelKafkaProducerFactoryCustomizer( ObjectProvider openTelemetryProvider, ObjectProvider configProvider) { KafkaTelemetry kafkaTelemetry = diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/testSpring2/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/jdbc/JdbcInstrumentationAutoConfigurationTest.java b/instrumentation/spring/spring-boot-autoconfigure/src/testSpring2/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/jdbc/JdbcInstrumentationAutoConfigurationTest.java index 70d33a21c666..6249446b849a 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/testSpring2/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/jdbc/JdbcInstrumentationAutoConfigurationTest.java +++ b/instrumentation/spring/spring-boot-autoconfigure/src/testSpring2/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/jdbc/JdbcInstrumentationAutoConfigurationTest.java @@ -5,18 +5,12 @@ package io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.jdbc; -import io.opentelemetry.api.OpenTelemetry; -import io.opentelemetry.instrumentation.api.incubator.config.internal.InstrumentationConfig; import io.opentelemetry.instrumentation.spring.autoconfigure.internal.AbstractJdbcInstrumentationAutoConfigurationTest; -import io.opentelemetry.instrumentation.spring.autoconfigure.internal.properties.ConfigPropertiesBridge; import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; import io.opentelemetry.instrumentation.testing.junit.LibraryInstrumentationExtension; -import io.opentelemetry.sdk.autoconfigure.spi.internal.DefaultConfigProperties; -import java.util.Collections; import org.junit.jupiter.api.extension.RegisterExtension; import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; -import org.springframework.boot.test.context.runner.ApplicationContextRunner; class JdbcInstrumentationAutoConfigurationTest extends AbstractJdbcInstrumentationAutoConfigurationTest { @@ -24,25 +18,14 @@ class JdbcInstrumentationAutoConfigurationTest @RegisterExtension static final LibraryInstrumentationExtension testing = LibraryInstrumentationExtension.create(); - private final ApplicationContextRunner contextRunner = - new ApplicationContextRunner() - .withBean( - InstrumentationConfig.class, - () -> - new ConfigPropertiesBridge( - DefaultConfigProperties.createFromMap(Collections.emptyMap()))) - .withConfiguration( - AutoConfigurations.of( - JdbcInstrumentationAutoConfiguration.class, DataSourceAutoConfiguration.class)) - .withBean("openTelemetry", OpenTelemetry.class, testing::getOpenTelemetry); - @Override protected InstrumentationExtension testing() { return testing; } @Override - protected ApplicationContextRunner contextRunner() { - return contextRunner; + protected AutoConfigurations autoConfigurations() { + return AutoConfigurations.of( + JdbcInstrumentationAutoConfiguration.class, DataSourceAutoConfiguration.class); } } diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/testSpring2/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/micrometer/MicrometerBridgeAutoConfigurationTest.java b/instrumentation/spring/spring-boot-autoconfigure/src/testSpring2/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/micrometer/MicrometerBridgeAutoConfigurationTest.java index dd926c289315..5d244c0584db 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/testSpring2/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/micrometer/MicrometerBridgeAutoConfigurationTest.java +++ b/instrumentation/spring/spring-boot-autoconfigure/src/testSpring2/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/micrometer/MicrometerBridgeAutoConfigurationTest.java @@ -6,22 +6,15 @@ package io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.micrometer; import io.micrometer.core.instrument.MeterRegistry; -import io.opentelemetry.api.OpenTelemetry; import io.opentelemetry.instrumentation.spring.autoconfigure.internal.AbstractMicrometerBridgeAutoConfigurationTest; import org.springframework.boot.actuate.autoconfigure.metrics.MetricsAutoConfiguration; import org.springframework.boot.autoconfigure.AutoConfigurations; -import org.springframework.boot.test.context.runner.ApplicationContextRunner; class MicrometerBridgeAutoConfigurationTest extends AbstractMicrometerBridgeAutoConfigurationTest { - private final ApplicationContextRunner runner = - new ApplicationContextRunner() - .withBean(OpenTelemetry.class, OpenTelemetry::noop) - .withConfiguration(AutoConfigurations.of(MicrometerBridgeAutoConfiguration.class)); - @Override - protected ApplicationContextRunner contextRunner() { - return runner; + protected AutoConfigurations autoConfigurations() { + return AutoConfigurations.of(MicrometerBridgeAutoConfiguration.class); } @Override diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/testSpring2/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/r2dbc/R2DbcInstrumentationAutoConfigurationTest.java b/instrumentation/spring/spring-boot-autoconfigure/src/testSpring2/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/r2dbc/R2DbcInstrumentationAutoConfigurationTest.java index 212ea25a46a9..5ec97f1d60b2 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/testSpring2/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/r2dbc/R2DbcInstrumentationAutoConfigurationTest.java +++ b/instrumentation/spring/spring-boot-autoconfigure/src/testSpring2/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/r2dbc/R2DbcInstrumentationAutoConfigurationTest.java @@ -5,17 +5,11 @@ package io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.r2dbc; -import io.opentelemetry.api.OpenTelemetry; -import io.opentelemetry.instrumentation.api.incubator.config.internal.InstrumentationConfig; import io.opentelemetry.instrumentation.spring.autoconfigure.internal.AbstractR2DbcInstrumentationAutoConfigurationTest; -import io.opentelemetry.instrumentation.spring.autoconfigure.internal.properties.ConfigPropertiesBridge; import io.opentelemetry.instrumentation.testing.junit.LibraryInstrumentationExtension; -import io.opentelemetry.sdk.autoconfigure.spi.internal.DefaultConfigProperties; -import java.util.Collections; import org.junit.jupiter.api.extension.RegisterExtension; import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.autoconfigure.r2dbc.R2dbcAutoConfiguration; -import org.springframework.boot.test.context.runner.ApplicationContextRunner; class R2DbcInstrumentationAutoConfigurationTest extends AbstractR2DbcInstrumentationAutoConfigurationTest { @@ -23,25 +17,14 @@ class R2DbcInstrumentationAutoConfigurationTest @RegisterExtension static final LibraryInstrumentationExtension testing = LibraryInstrumentationExtension.create(); - private final ApplicationContextRunner contextRunner = - new ApplicationContextRunner() - .withBean( - InstrumentationConfig.class, - () -> - new ConfigPropertiesBridge( - DefaultConfigProperties.createFromMap(Collections.emptyMap()))) - .withConfiguration( - AutoConfigurations.of( - R2dbcInstrumentationAutoConfiguration.class, R2dbcAutoConfiguration.class)) - .withBean("openTelemetry", OpenTelemetry.class, testing::getOpenTelemetry); - @Override protected LibraryInstrumentationExtension testing() { return testing; } @Override - protected ApplicationContextRunner contextRunner() { - return contextRunner; + protected AutoConfigurations autoConfigurations() { + return AutoConfigurations.of( + R2dbcInstrumentationAutoConfiguration.class, R2dbcAutoConfiguration.class); } } diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/testSpring2/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/web/SpringWebInstrumentationAutoConfigurationTest.java b/instrumentation/spring/spring-boot-autoconfigure/src/testSpring2/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/web/SpringWebInstrumentationAutoConfigurationTest.java index 29ffe72d1cf4..5bb6edcd0436 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/testSpring2/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/web/SpringWebInstrumentationAutoConfigurationTest.java +++ b/instrumentation/spring/spring-boot-autoconfigure/src/testSpring2/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/web/SpringWebInstrumentationAutoConfigurationTest.java @@ -5,33 +5,14 @@ package io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.web; -import io.opentelemetry.api.OpenTelemetry; -import io.opentelemetry.instrumentation.api.incubator.config.internal.InstrumentationConfig; import io.opentelemetry.instrumentation.spring.autoconfigure.internal.AbstractSpringWebInstrumentationAutoConfigurationTest; -import io.opentelemetry.instrumentation.spring.autoconfigure.internal.properties.ConfigPropertiesBridge; -import io.opentelemetry.sdk.autoconfigure.spi.internal.DefaultConfigProperties; -import java.util.Collections; import org.springframework.boot.autoconfigure.AutoConfigurations; -import org.springframework.boot.test.context.runner.ApplicationContextRunner; -import org.springframework.web.client.RestTemplate; class SpringWebInstrumentationAutoConfigurationTest extends AbstractSpringWebInstrumentationAutoConfigurationTest { - private final ApplicationContextRunner contextRunner = - new ApplicationContextRunner() - .withBean(OpenTelemetry.class, OpenTelemetry::noop) - .withBean( - InstrumentationConfig.class, - () -> - new ConfigPropertiesBridge( - DefaultConfigProperties.createFromMap(Collections.emptyMap()))) - .withBean(RestTemplate.class, RestTemplate::new) - .withConfiguration( - AutoConfigurations.of(SpringWebInstrumentationAutoConfiguration.class)); - @Override - protected ApplicationContextRunner contextRunner() { - return contextRunner; + protected AutoConfigurations autoConfigurations() { + return AutoConfigurations.of(SpringWebInstrumentationAutoConfiguration.class); } } diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/testSpring4/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/jdbc/JdbcInstrumentationAutoConfigurationTest.java b/instrumentation/spring/spring-boot-autoconfigure/src/testSpring4/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/jdbc/JdbcInstrumentationAutoConfigurationTest.java index f7288a125a76..443da8425b71 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/testSpring4/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/jdbc/JdbcInstrumentationAutoConfigurationTest.java +++ b/instrumentation/spring/spring-boot-autoconfigure/src/testSpring4/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/jdbc/JdbcInstrumentationAutoConfigurationTest.java @@ -5,18 +5,12 @@ package io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.jdbc; -import io.opentelemetry.api.OpenTelemetry; -import io.opentelemetry.instrumentation.api.incubator.config.internal.InstrumentationConfig; import io.opentelemetry.instrumentation.spring.autoconfigure.internal.AbstractJdbcInstrumentationAutoConfigurationTest; -import io.opentelemetry.instrumentation.spring.autoconfigure.internal.properties.ConfigPropertiesBridge; import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; import io.opentelemetry.instrumentation.testing.junit.LibraryInstrumentationExtension; -import io.opentelemetry.sdk.autoconfigure.spi.internal.DefaultConfigProperties; -import java.util.Collections; import org.junit.jupiter.api.extension.RegisterExtension; import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.jdbc.autoconfigure.DataSourceAutoConfiguration; -import org.springframework.boot.test.context.runner.ApplicationContextRunner; class JdbcInstrumentationAutoConfigurationTest extends AbstractJdbcInstrumentationAutoConfigurationTest { @@ -29,21 +23,9 @@ protected InstrumentationExtension testing() { return testing; } - private final ApplicationContextRunner runner = - new ApplicationContextRunner() - .withBean( - InstrumentationConfig.class, - () -> - new ConfigPropertiesBridge( - DefaultConfigProperties.createFromMap(Collections.emptyMap()))) - .withConfiguration( - AutoConfigurations.of( - JdbcInstrumentationSpringBoot4AutoConfiguration.class, - DataSourceAutoConfiguration.class)) - .withBean("openTelemetry", OpenTelemetry.class, testing::getOpenTelemetry); - @Override - protected ApplicationContextRunner contextRunner() { - return runner; + protected AutoConfigurations autoConfigurations() { + return AutoConfigurations.of( + JdbcInstrumentationSpringBoot4AutoConfiguration.class, DataSourceAutoConfiguration.class); } } diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/testSpring4/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/kafka/KafkaInstrumentationAutoConfigurationTest.java b/instrumentation/spring/spring-boot-autoconfigure/src/testSpring4/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/kafka/KafkaInstrumentationAutoConfigurationTest.java index 30fa586a6cba..ab4902c74709 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/testSpring4/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/kafka/KafkaInstrumentationAutoConfigurationTest.java +++ b/instrumentation/spring/spring-boot-autoconfigure/src/testSpring4/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/kafka/KafkaInstrumentationAutoConfigurationTest.java @@ -8,39 +8,23 @@ import static java.util.Collections.emptyMap; import static org.assertj.core.api.Assertions.assertThat; -import io.opentelemetry.api.OpenTelemetry; -import io.opentelemetry.instrumentation.api.incubator.config.internal.InstrumentationConfig; import io.opentelemetry.instrumentation.spring.autoconfigure.internal.AbstractKafkaInstrumentationAutoConfigurationTest; -import io.opentelemetry.instrumentation.spring.autoconfigure.internal.properties.ConfigPropertiesBridge; -import io.opentelemetry.sdk.autoconfigure.spi.internal.DefaultConfigProperties; import org.junit.jupiter.api.Test; import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.kafka.autoconfigure.DefaultKafkaProducerFactoryCustomizer; -import org.springframework.boot.test.context.runner.ApplicationContextRunner; import org.springframework.kafka.core.DefaultKafkaProducerFactory; class KafkaInstrumentationAutoConfigurationTest extends AbstractKafkaInstrumentationAutoConfigurationTest { - private final ApplicationContextRunner runner = - new ApplicationContextRunner() - .withBean( - InstrumentationConfig.class, - () -> new ConfigPropertiesBridge(DefaultConfigProperties.createFromMap(emptyMap()))) - .withConfiguration( - AutoConfigurations.of( - KafkaInstrumentationAutoConfiguration.class, - KafkaInstrumentationSpringBoot4AutoConfiguration.class)) - .withBean("openTelemetry", OpenTelemetry.class, OpenTelemetry::noop); - @Override - protected ApplicationContextRunner contextRunner() { - return runner; + protected AutoConfigurations autoConfigurations() { + return AutoConfigurations.of(KafkaInstrumentationSpringBoot4AutoConfiguration.class); } @Test void defaultConfiguration() { - runner.run( + contextRunner.run( context -> { assertThat(context.containsBean("otelKafkaProducerFactoryCustomizer")).isTrue(); assertThat(context.containsBean("otelKafkaListenerContainerFactoryBeanPostProcessor")) diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/testSpring4/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/micrometer/MicrometerBridgeAutoConfigurationTest.java b/instrumentation/spring/spring-boot-autoconfigure/src/testSpring4/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/micrometer/MicrometerBridgeAutoConfigurationTest.java index 1cf118d3ddbd..e8001b510eb6 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/testSpring4/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/micrometer/MicrometerBridgeAutoConfigurationTest.java +++ b/instrumentation/spring/spring-boot-autoconfigure/src/testSpring4/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/micrometer/MicrometerBridgeAutoConfigurationTest.java @@ -6,23 +6,15 @@ package io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.micrometer; import io.micrometer.core.instrument.MeterRegistry; -import io.opentelemetry.api.OpenTelemetry; import io.opentelemetry.instrumentation.spring.autoconfigure.internal.AbstractMicrometerBridgeAutoConfigurationTest; import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.micrometer.metrics.autoconfigure.MetricsAutoConfiguration; -import org.springframework.boot.test.context.runner.ApplicationContextRunner; class MicrometerBridgeAutoConfigurationTest extends AbstractMicrometerBridgeAutoConfigurationTest { - private final ApplicationContextRunner runner = - new ApplicationContextRunner() - .withBean(OpenTelemetry.class, OpenTelemetry::noop) - .withConfiguration( - AutoConfigurations.of(MicrometerBridgeSpringBoot4AutoConfiguration.class)); - @Override - protected ApplicationContextRunner contextRunner() { - return runner; + protected AutoConfigurations autoConfigurations() { + return AutoConfigurations.of(MicrometerBridgeSpringBoot4AutoConfiguration.class); } @Override diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/testSpring4/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/r2dbc/R2DbcInstrumentationAutoConfigurationTest.java b/instrumentation/spring/spring-boot-autoconfigure/src/testSpring4/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/r2dbc/R2DbcInstrumentationAutoConfigurationTest.java index 68538249e4ba..ba1c4140faa1 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/testSpring4/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/r2dbc/R2DbcInstrumentationAutoConfigurationTest.java +++ b/instrumentation/spring/spring-boot-autoconfigure/src/testSpring4/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/r2dbc/R2DbcInstrumentationAutoConfigurationTest.java @@ -5,17 +5,11 @@ package io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.r2dbc; -import io.opentelemetry.api.OpenTelemetry; -import io.opentelemetry.instrumentation.api.incubator.config.internal.InstrumentationConfig; import io.opentelemetry.instrumentation.spring.autoconfigure.internal.AbstractR2DbcInstrumentationAutoConfigurationTest; -import io.opentelemetry.instrumentation.spring.autoconfigure.internal.properties.ConfigPropertiesBridge; import io.opentelemetry.instrumentation.testing.junit.LibraryInstrumentationExtension; -import io.opentelemetry.sdk.autoconfigure.spi.internal.DefaultConfigProperties; -import java.util.Collections; import org.junit.jupiter.api.extension.RegisterExtension; import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.r2dbc.autoconfigure.R2dbcAutoConfiguration; -import org.springframework.boot.test.context.runner.ApplicationContextRunner; class R2DbcInstrumentationAutoConfigurationTest extends AbstractR2DbcInstrumentationAutoConfigurationTest { @@ -23,25 +17,14 @@ class R2DbcInstrumentationAutoConfigurationTest @RegisterExtension static final LibraryInstrumentationExtension testing = LibraryInstrumentationExtension.create(); - private final ApplicationContextRunner contextRunner = - new ApplicationContextRunner() - .withBean( - InstrumentationConfig.class, - () -> - new ConfigPropertiesBridge( - DefaultConfigProperties.createFromMap(Collections.emptyMap()))) - .withConfiguration( - AutoConfigurations.of( - R2dbcInstrumentationAutoConfiguration.class, R2dbcAutoConfiguration.class)) - .withBean("openTelemetry", OpenTelemetry.class, testing::getOpenTelemetry); - @Override protected LibraryInstrumentationExtension testing() { return testing; } @Override - protected ApplicationContextRunner contextRunner() { - return contextRunner; + protected AutoConfigurations autoConfigurations() { + return AutoConfigurations.of( + R2dbcInstrumentationAutoConfiguration.class, R2dbcAutoConfiguration.class); } } diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/testSpring4/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/web/SpringWebInstrumentationAutoConfigurationTest.java b/instrumentation/spring/spring-boot-autoconfigure/src/testSpring4/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/web/SpringWebInstrumentationAutoConfigurationTest.java index f9705f67b8f8..ac272af96341 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/testSpring4/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/web/SpringWebInstrumentationAutoConfigurationTest.java +++ b/instrumentation/spring/spring-boot-autoconfigure/src/testSpring4/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/web/SpringWebInstrumentationAutoConfigurationTest.java @@ -5,33 +5,14 @@ package io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.web; -import io.opentelemetry.api.OpenTelemetry; -import io.opentelemetry.instrumentation.api.incubator.config.internal.InstrumentationConfig; import io.opentelemetry.instrumentation.spring.autoconfigure.internal.AbstractSpringWebInstrumentationAutoConfigurationTest; -import io.opentelemetry.instrumentation.spring.autoconfigure.internal.properties.ConfigPropertiesBridge; -import io.opentelemetry.sdk.autoconfigure.spi.internal.DefaultConfigProperties; -import java.util.Collections; import org.springframework.boot.autoconfigure.AutoConfigurations; -import org.springframework.boot.test.context.runner.ApplicationContextRunner; -import org.springframework.web.client.RestTemplate; class SpringWebInstrumentationAutoConfigurationTest extends AbstractSpringWebInstrumentationAutoConfigurationTest { - private final ApplicationContextRunner contextRunner = - new ApplicationContextRunner() - .withBean(OpenTelemetry.class, OpenTelemetry::noop) - .withBean( - InstrumentationConfig.class, - () -> - new ConfigPropertiesBridge( - DefaultConfigProperties.createFromMap(Collections.emptyMap()))) - .withBean(RestTemplate.class, RestTemplate::new) - .withConfiguration( - AutoConfigurations.of(SpringWebInstrumentationSpringBoot4AutoConfiguration.class)); - @Override - protected ApplicationContextRunner contextRunner() { - return contextRunner; + protected AutoConfigurations autoConfigurations() { + return AutoConfigurations.of(SpringWebInstrumentationSpringBoot4AutoConfiguration.class); } } diff --git a/instrumentation/spring/spring-boot-autoconfigure/testing/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/AbstractJdbcInstrumentationAutoConfigurationTest.java b/instrumentation/spring/spring-boot-autoconfigure/testing/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/AbstractJdbcInstrumentationAutoConfigurationTest.java index ead38ab6ba23..2b2ce330a42e 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/testing/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/AbstractJdbcInstrumentationAutoConfigurationTest.java +++ b/instrumentation/spring/spring-boot-autoconfigure/testing/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/AbstractJdbcInstrumentationAutoConfigurationTest.java @@ -10,10 +10,15 @@ * SPDX-License-Identifier: Apache-2.0 */ +import static java.util.Collections.emptyMap; import static org.assertj.core.api.Assertions.assertThat; +import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.instrumentation.api.incubator.config.internal.InstrumentationConfig; +import io.opentelemetry.instrumentation.spring.autoconfigure.internal.properties.ConfigPropertiesBridge; import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; import io.opentelemetry.instrumentation.testing.junit.db.SemconvStabilityUtil; +import io.opentelemetry.sdk.autoconfigure.spi.internal.DefaultConfigProperties; import io.opentelemetry.semconv.incubating.DbIncubatingAttributes; import java.sql.Connection; import java.sql.Statement; @@ -21,44 +26,52 @@ import org.junit.jupiter.api.Test; import org.springframework.aop.framework.Advised; import org.springframework.aop.support.AopUtils; +import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.test.context.runner.ApplicationContextRunner; public abstract class AbstractJdbcInstrumentationAutoConfigurationTest { protected abstract InstrumentationExtension testing(); - protected abstract ApplicationContextRunner contextRunner(); + protected abstract AutoConfigurations autoConfigurations(); + + private final ApplicationContextRunner contextRunner = + new ApplicationContextRunner() + .withBean( + InstrumentationConfig.class, + () -> new ConfigPropertiesBridge(DefaultConfigProperties.createFromMap(emptyMap()))) + .withConfiguration(autoConfigurations()) + .withBean("openTelemetry", OpenTelemetry.class, testing()::getOpenTelemetry); @SuppressWarnings("deprecation") // using deprecated semconv @Test void statementSanitizerEnabledByDefault() { - contextRunner() - .run( - context -> { - DataSource dataSource = context.getBean(DataSource.class); + contextRunner.run( + context -> { + DataSource dataSource = context.getBean(DataSource.class); - assertThat(AopUtils.isAopProxy(dataSource)).isTrue(); - assertThat(dataSource.getClass().getSimpleName()).isNotEqualTo("HikariDataSource"); - // unwrap the instrumented data source to get the original data source - Object original = ((Advised) dataSource).getTargetSource().getTarget(); - assertThat(AopUtils.isAopProxy(original)).isFalse(); - assertThat(original.getClass().getSimpleName()).isEqualTo("HikariDataSource"); + assertThat(AopUtils.isAopProxy(dataSource)).isTrue(); + assertThat(dataSource.getClass().getSimpleName()).isNotEqualTo("HikariDataSource"); + // unwrap the instrumented data source to get the original data source + Object original = ((Advised) dataSource).getTargetSource().getTarget(); + assertThat(AopUtils.isAopProxy(original)).isFalse(); + assertThat(original.getClass().getSimpleName()).isEqualTo("HikariDataSource"); - try (Connection connection = dataSource.getConnection()) { - try (Statement statement = connection.createStatement()) { - statement.execute("SELECT 1"); - } - } + try (Connection connection = dataSource.getConnection()) { + try (Statement statement = connection.createStatement()) { + statement.execute("SELECT 1"); + } + } - testing() - .waitAndAssertTraces( - trace -> - trace.hasSpansSatisfyingExactly( - span -> - span.hasAttribute( - SemconvStabilityUtil.maybeStable( - DbIncubatingAttributes.DB_STATEMENT), - "SELECT ?"))); - }); + testing() + .waitAndAssertTraces( + trace -> + trace.hasSpansSatisfyingExactly( + span -> + span.hasAttribute( + SemconvStabilityUtil.maybeStable( + DbIncubatingAttributes.DB_STATEMENT), + "SELECT ?"))); + }); } } diff --git a/instrumentation/spring/spring-boot-autoconfigure/testing/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/AbstractKafkaInstrumentationAutoConfigurationTest.java b/instrumentation/spring/spring-boot-autoconfigure/testing/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/AbstractKafkaInstrumentationAutoConfigurationTest.java index 9cc017eebfe9..cc32a8502ef8 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/testing/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/AbstractKafkaInstrumentationAutoConfigurationTest.java +++ b/instrumentation/spring/spring-boot-autoconfigure/testing/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/AbstractKafkaInstrumentationAutoConfigurationTest.java @@ -5,17 +5,31 @@ package io.opentelemetry.instrumentation.spring.autoconfigure.internal; +import static java.util.Collections.emptyMap; import static org.assertj.core.api.Assertions.assertThat; +import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.instrumentation.api.incubator.config.internal.InstrumentationConfig; +import io.opentelemetry.instrumentation.spring.autoconfigure.internal.properties.ConfigPropertiesBridge; +import io.opentelemetry.sdk.autoconfigure.spi.internal.DefaultConfigProperties; import org.junit.jupiter.api.Test; +import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.test.context.runner.ApplicationContextRunner; public abstract class AbstractKafkaInstrumentationAutoConfigurationTest { - protected abstract ApplicationContextRunner contextRunner(); + protected abstract AutoConfigurations autoConfigurations(); + + protected final ApplicationContextRunner contextRunner = + new ApplicationContextRunner() + .withBean( + InstrumentationConfig.class, + () -> new ConfigPropertiesBridge(DefaultConfigProperties.createFromMap(emptyMap()))) + .withConfiguration(autoConfigurations()) + .withBean("openTelemetry", OpenTelemetry.class, OpenTelemetry::noop); @Test void instrumentationDisabled() { - contextRunner() + contextRunner .withPropertyValues("otel.instrumentation.kafka.enabled=false") .run( context -> { @@ -27,7 +41,7 @@ void instrumentationDisabled() { @Test void listenerInterceptorCanBeDisabled() { - contextRunner() + contextRunner .withPropertyValues("otel.instrumentation.kafka.autoconfigure-interceptor=false") .run( context -> { diff --git a/instrumentation/spring/spring-boot-autoconfigure/testing/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/AbstractMicrometerBridgeAutoConfigurationTest.java b/instrumentation/spring/spring-boot-autoconfigure/testing/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/AbstractMicrometerBridgeAutoConfigurationTest.java index 7b14ae8d84b7..da3909f83902 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/testing/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/AbstractMicrometerBridgeAutoConfigurationTest.java +++ b/instrumentation/spring/spring-boot-autoconfigure/testing/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/AbstractMicrometerBridgeAutoConfigurationTest.java @@ -7,6 +7,7 @@ import static org.assertj.core.api.Assertions.assertThat; +import io.opentelemetry.api.OpenTelemetry; import io.opentelemetry.instrumentation.micrometer.v1_5.OpenTelemetryMeterRegistry; import org.junit.jupiter.api.Test; import org.springframework.boot.autoconfigure.AutoConfigurations; @@ -14,15 +15,20 @@ public abstract class AbstractMicrometerBridgeAutoConfigurationTest { - protected abstract ApplicationContextRunner contextRunner(); + protected abstract AutoConfigurations autoConfigurations(); protected abstract Class getMetricsAutoConfigurationClass(); protected abstract Class getMeterRegistryClass(); + protected final ApplicationContextRunner contextRunner = + new ApplicationContextRunner() + .withBean(OpenTelemetry.class, OpenTelemetry::noop) + .withConfiguration(autoConfigurations()); + @Test void metricsEnabled() { - contextRunner() + contextRunner .withConfiguration(AutoConfigurations.of(getMetricsAutoConfigurationClass())) .withPropertyValues("otel.instrumentation.micrometer.enabled = true") .run( @@ -34,14 +40,14 @@ void metricsEnabled() { @Test void metricsDisabledByDefault() { - contextRunner() + contextRunner .withConfiguration(AutoConfigurations.of(getMetricsAutoConfigurationClass())) .run(context -> assertThat(context.containsBean("otelMeterRegistry")).isFalse()); } @Test void metricsDisabled() { - contextRunner() + contextRunner .withConfiguration(AutoConfigurations.of(getMetricsAutoConfigurationClass())) .withPropertyValues("otel.instrumentation.micrometer.enabled = false") .run(context -> assertThat(context.containsBean("otelMeterRegistry")).isFalse()); @@ -49,7 +55,7 @@ void metricsDisabled() { @Test void noActuatorAutoConfiguration() { - contextRunner() + contextRunner .withPropertyValues("otel.instrumentation.micrometer.enabled = true") .run(context -> assertThat(context.containsBean("otelMeterRegistry")).isFalse()); } diff --git a/instrumentation/spring/spring-boot-autoconfigure/testing/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/AbstractR2DbcInstrumentationAutoConfigurationTest.java b/instrumentation/spring/spring-boot-autoconfigure/testing/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/AbstractR2DbcInstrumentationAutoConfigurationTest.java index 87765d6456f3..a556d7e1f68c 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/testing/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/AbstractR2DbcInstrumentationAutoConfigurationTest.java +++ b/instrumentation/spring/spring-boot-autoconfigure/testing/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/AbstractR2DbcInstrumentationAutoConfigurationTest.java @@ -8,8 +8,14 @@ import static io.opentelemetry.instrumentation.testing.junit.db.SemconvStabilityUtil.maybeStable; import static io.opentelemetry.semconv.incubating.DbIncubatingAttributes.DB_STATEMENT; +import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.instrumentation.api.incubator.config.internal.InstrumentationConfig; +import io.opentelemetry.instrumentation.spring.autoconfigure.internal.properties.ConfigPropertiesBridge; import io.opentelemetry.instrumentation.testing.junit.LibraryInstrumentationExtension; +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; import org.springframework.r2dbc.core.DatabaseClient; @@ -17,36 +23,44 @@ public abstract class AbstractR2DbcInstrumentationAutoConfigurationTest { protected abstract LibraryInstrumentationExtension testing(); - protected abstract ApplicationContextRunner contextRunner(); + protected abstract AutoConfigurations autoConfigurations(); + + protected final ApplicationContextRunner contextRunner = + new ApplicationContextRunner() + .withBean( + InstrumentationConfig.class, + () -> + new ConfigPropertiesBridge( + DefaultConfigProperties.createFromMap(Collections.emptyMap()))) + .withConfiguration(autoConfigurations()) + .withBean("openTelemetry", OpenTelemetry.class, testing()::getOpenTelemetry); @SuppressWarnings("deprecation") // using deprecated semconv @Test void statementSanitizerEnabledByDefault() { - contextRunner() - .run( - context -> { - DatabaseClient client = context.getBean(DatabaseClient.class); - client - .sql( - "CREATE TABLE IF NOT EXISTS player(id INT NOT NULL AUTO_INCREMENT, name VARCHAR(255), age INT, PRIMARY KEY (id))") - .fetch() - .all() - .blockLast(); - client.sql("SELECT * FROM player WHERE id = 1").fetch().all().blockLast(); - testing() - .waitAndAssertTraces( - trace -> - trace.hasSpansSatisfyingExactly( - span -> - span.hasAttribute( - maybeStable(DB_STATEMENT), - "CREATE TABLE IF NOT EXISTS player(id INT NOT NULL AUTO_INCREMENT, name VARCHAR(?), age INT, PRIMARY KEY (id))")), - trace -> - trace.hasSpansSatisfyingExactly( - span -> - span.hasAttribute( - maybeStable(DB_STATEMENT), - "SELECT * FROM player WHERE id = ?"))); - }); + contextRunner.run( + context -> { + DatabaseClient client = context.getBean(DatabaseClient.class); + client + .sql( + "CREATE TABLE IF NOT EXISTS player(id INT NOT NULL AUTO_INCREMENT, name VARCHAR(255), age INT, PRIMARY KEY (id))") + .fetch() + .all() + .blockLast(); + client.sql("SELECT * FROM player WHERE id = 1").fetch().all().blockLast(); + testing() + .waitAndAssertTraces( + trace -> + trace.hasSpansSatisfyingExactly( + span -> + span.hasAttribute( + maybeStable(DB_STATEMENT), + "CREATE TABLE IF NOT EXISTS player(id INT NOT NULL AUTO_INCREMENT, name VARCHAR(?), age INT, PRIMARY KEY (id))")), + trace -> + trace.hasSpansSatisfyingExactly( + span -> + span.hasAttribute( + maybeStable(DB_STATEMENT), "SELECT * FROM player WHERE id = ?"))); + }); } } diff --git a/instrumentation/spring/spring-boot-autoconfigure/testing/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/AbstractSpringWebInstrumentationAutoConfigurationTest.java b/instrumentation/spring/spring-boot-autoconfigure/testing/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/AbstractSpringWebInstrumentationAutoConfigurationTest.java index 08514d5ac4f1..4b9a339d38b7 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/testing/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/AbstractSpringWebInstrumentationAutoConfigurationTest.java +++ b/instrumentation/spring/spring-boot-autoconfigure/testing/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/AbstractSpringWebInstrumentationAutoConfigurationTest.java @@ -7,14 +7,31 @@ import static org.assertj.core.api.Assertions.assertThat; +import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.instrumentation.api.incubator.config.internal.InstrumentationConfig; import io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.web.RestTemplateBeanPostProcessor; +import io.opentelemetry.instrumentation.spring.autoconfigure.internal.properties.ConfigPropertiesBridge; +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; import org.springframework.web.client.RestTemplate; public abstract class AbstractSpringWebInstrumentationAutoConfigurationTest { - protected abstract ApplicationContextRunner contextRunner(); + protected abstract AutoConfigurations autoConfigurations(); + + protected final ApplicationContextRunner contextRunner = + new ApplicationContextRunner() + .withBean(OpenTelemetry.class, OpenTelemetry::noop) + .withBean( + InstrumentationConfig.class, + () -> + new ConfigPropertiesBridge( + DefaultConfigProperties.createFromMap(Collections.emptyMap()))) + .withBean(RestTemplate.class, RestTemplate::new) + .withConfiguration(autoConfigurations()); /** * Tests that users create {@link RestTemplate} bean is instrumented. @@ -27,7 +44,7 @@ public abstract class AbstractSpringWebInstrumentationAutoConfigurationTest { */ @Test void instrumentationEnabled() { - contextRunner() + contextRunner .withPropertyValues("otel.instrumentation.spring-web.enabled=true") .withPropertyValues("otel.instrumentation.common.default-enabled=false") .run( @@ -51,7 +68,7 @@ void instrumentationEnabled() { @Test void instrumentationDisabled() { - contextRunner() + contextRunner .withPropertyValues("otel.instrumentation.spring-web.enabled=false") .run( context -> @@ -60,7 +77,7 @@ void instrumentationDisabled() { @Test void instrumentationDisabledButAllEnabled() { - contextRunner() + contextRunner .withPropertyValues("otel.instrumentation.spring-web.enabled=false") .withPropertyValues("otel.instrumentation.common.default-enabled=true") .run( @@ -70,7 +87,7 @@ void instrumentationDisabledButAllEnabled() { @Test void allInstrumentationDisabled() { - contextRunner() + contextRunner .withPropertyValues("otel.instrumentation.common.default-enabled=false") .run( context -> @@ -79,13 +96,11 @@ void allInstrumentationDisabled() { @Test void defaultConfiguration() { - contextRunner() - .run( - context -> - assertThat( - context.getBean( - "otelRestTemplateBeanPostProcessor", - RestTemplateBeanPostProcessor.class)) - .isNotNull()); + contextRunner.run( + context -> + assertThat( + context.getBean( + "otelRestTemplateBeanPostProcessor", RestTemplateBeanPostProcessor.class)) + .isNotNull()); } } From c7d5de81c0b7c3167302b7075960722da8ad5d09 Mon Sep 17 00:00:00 2001 From: Jay DeLuca Date: Tue, 2 Dec 2025 17:32:22 -0500 Subject: [PATCH 30/40] fix --- .../KafkaInstrumentationAutoConfiguration.java | 6 +++++- ...rumentationSpringBoot4AutoConfiguration.java | 6 +++++- ...fkaInstrumentationAutoConfigurationTest.java | 17 +++++++++++++++++ ...fkaInstrumentationAutoConfigurationTest.java | 3 +-- ...fkaInstrumentationAutoConfigurationTest.java | 13 ++++++++++++- 5 files changed, 40 insertions(+), 5 deletions(-) create mode 100644 instrumentation/spring/spring-boot-autoconfigure/src/testSpring2/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/kafka/KafkaInstrumentationAutoConfigurationTest.java rename instrumentation/spring/spring-boot-autoconfigure/testing/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/{ => instrumentation/kafka}/AbstractKafkaInstrumentationAutoConfigurationTest.java (86%) diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/kafka/KafkaInstrumentationAutoConfiguration.java b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/kafka/KafkaInstrumentationAutoConfiguration.java index 8e1e0bc6194d..08005d1fd9a7 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/kafka/KafkaInstrumentationAutoConfiguration.java +++ b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/kafka/KafkaInstrumentationAutoConfiguration.java @@ -24,7 +24,11 @@ * any time. */ @ConditionalOnEnabledInstrumentation(module = "kafka") -@ConditionalOnClass({KafkaTemplate.class, ConcurrentKafkaListenerContainerFactory.class}) +@ConditionalOnClass({ + KafkaTemplate.class, + ConcurrentKafkaListenerContainerFactory.class, + DefaultKafkaProducerFactoryCustomizer.class // package changed in Spring Boot 4 +}) @Configuration public class KafkaInstrumentationAutoConfiguration { diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/main/javaSpring4/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/kafka/KafkaInstrumentationSpringBoot4AutoConfiguration.java b/instrumentation/spring/spring-boot-autoconfigure/src/main/javaSpring4/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/kafka/KafkaInstrumentationSpringBoot4AutoConfiguration.java index 8f7b98289bde..89ad4a2b2599 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/main/javaSpring4/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/kafka/KafkaInstrumentationSpringBoot4AutoConfiguration.java +++ b/instrumentation/spring/spring-boot-autoconfigure/src/main/javaSpring4/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/kafka/KafkaInstrumentationSpringBoot4AutoConfiguration.java @@ -24,7 +24,11 @@ * any time. */ @ConditionalOnEnabledInstrumentation(module = "kafka") -@ConditionalOnClass({KafkaTemplate.class, ConcurrentKafkaListenerContainerFactory.class}) +@ConditionalOnClass({ + KafkaTemplate.class, + ConcurrentKafkaListenerContainerFactory.class, + DefaultKafkaProducerFactoryCustomizer.class // package changed from Spring Boot 4 +}) @Configuration public class KafkaInstrumentationSpringBoot4AutoConfiguration { diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/testSpring2/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/kafka/KafkaInstrumentationAutoConfigurationTest.java b/instrumentation/spring/spring-boot-autoconfigure/src/testSpring2/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/kafka/KafkaInstrumentationAutoConfigurationTest.java new file mode 100644 index 000000000000..22ca4d057004 --- /dev/null +++ b/instrumentation/spring/spring-boot-autoconfigure/src/testSpring2/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/kafka/KafkaInstrumentationAutoConfigurationTest.java @@ -0,0 +1,17 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.kafka; + +import org.springframework.boot.autoconfigure.AutoConfigurations; + +class KafkaInstrumentationAutoConfigurationTest + extends AbstractKafkaInstrumentationAutoConfigurationTest { + + @Override + protected AutoConfigurations autoConfigurations() { + return AutoConfigurations.of(KafkaInstrumentationAutoConfiguration.class); + } +} diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/testSpring4/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/kafka/KafkaInstrumentationAutoConfigurationTest.java b/instrumentation/spring/spring-boot-autoconfigure/src/testSpring4/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/kafka/KafkaInstrumentationAutoConfigurationTest.java index ab4902c74709..0f0d48024e5c 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/testSpring4/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/kafka/KafkaInstrumentationAutoConfigurationTest.java +++ b/instrumentation/spring/spring-boot-autoconfigure/src/testSpring4/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/kafka/KafkaInstrumentationAutoConfigurationTest.java @@ -8,7 +8,6 @@ import static java.util.Collections.emptyMap; import static org.assertj.core.api.Assertions.assertThat; -import io.opentelemetry.instrumentation.spring.autoconfigure.internal.AbstractKafkaInstrumentationAutoConfigurationTest; import org.junit.jupiter.api.Test; import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.kafka.autoconfigure.DefaultKafkaProducerFactoryCustomizer; @@ -23,7 +22,7 @@ protected AutoConfigurations autoConfigurations() { } @Test - void defaultConfiguration() { + void defaultConfigurationWithFactoryTesting() { contextRunner.run( context -> { assertThat(context.containsBean("otelKafkaProducerFactoryCustomizer")).isTrue(); diff --git a/instrumentation/spring/spring-boot-autoconfigure/testing/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/AbstractKafkaInstrumentationAutoConfigurationTest.java b/instrumentation/spring/spring-boot-autoconfigure/testing/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/kafka/AbstractKafkaInstrumentationAutoConfigurationTest.java similarity index 86% rename from instrumentation/spring/spring-boot-autoconfigure/testing/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/AbstractKafkaInstrumentationAutoConfigurationTest.java rename to instrumentation/spring/spring-boot-autoconfigure/testing/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/kafka/AbstractKafkaInstrumentationAutoConfigurationTest.java index cc32a8502ef8..d92438535772 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/testing/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/AbstractKafkaInstrumentationAutoConfigurationTest.java +++ b/instrumentation/spring/spring-boot-autoconfigure/testing/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/kafka/AbstractKafkaInstrumentationAutoConfigurationTest.java @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -package io.opentelemetry.instrumentation.spring.autoconfigure.internal; +package io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.kafka; import static java.util.Collections.emptyMap; import static org.assertj.core.api.Assertions.assertThat; @@ -17,6 +17,7 @@ import org.springframework.boot.test.context.runner.ApplicationContextRunner; public abstract class AbstractKafkaInstrumentationAutoConfigurationTest { + protected abstract AutoConfigurations autoConfigurations(); protected final ApplicationContextRunner contextRunner = @@ -50,4 +51,14 @@ void listenerInterceptorCanBeDisabled() { .isFalse(); }); } + + @Test + void defaultConfiguration() { + contextRunner.run( + context -> { + assertThat(context.containsBean("otelKafkaProducerFactoryCustomizer")).isTrue(); + assertThat(context.containsBean("otelKafkaListenerContainerFactoryBeanPostProcessor")) + .isTrue(); + }); + } } From 0a5806f12ae9c8c0e428d80d0e040cb34854c250 Mon Sep 17 00:00:00 2001 From: Jay DeLuca Date: Tue, 2 Dec 2025 20:01:49 -0500 Subject: [PATCH 31/40] add dependency --- .../spring/spring-boot-autoconfigure/build.gradle.kts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/instrumentation/spring/spring-boot-autoconfigure/build.gradle.kts b/instrumentation/spring/spring-boot-autoconfigure/build.gradle.kts index 7aad519a2b17..508715166358 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/build.gradle.kts +++ b/instrumentation/spring/spring-boot-autoconfigure/build.gradle.kts @@ -266,6 +266,9 @@ configurations.configureEach { add(name, "org.springframework.boot:spring-boot-starter-jdbc:3.+") add(name, "org.springframework.boot:spring-boot-starter-actuator:3.+") add(name, "org.springframework.boot:spring-boot-starter-data-r2dbc:3.+") + add(name, "org.springframework.kafka:spring-kafka:3.+") + add(name, project(":instrumentation:kafka:kafka-clients:kafka-clients-2.6:library")) + add(name, project(":instrumentation:spring:spring-kafka-2.7:library")) } } } From c8516d7856e9f4180694f58941dadd2beb4050d2 Mon Sep 17 00:00:00 2001 From: Jay DeLuca Date: Tue, 2 Dec 2025 20:48:10 -0500 Subject: [PATCH 32/40] japi cmp --- .../opentelemetry-spring-boot-autoconfigure.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/apidiffs/current_vs_latest/opentelemetry-spring-boot-autoconfigure.txt b/docs/apidiffs/current_vs_latest/opentelemetry-spring-boot-autoconfigure.txt index 7e15cee7de8c..9dfa328763a1 100644 --- a/docs/apidiffs/current_vs_latest/opentelemetry-spring-boot-autoconfigure.txt +++ b/docs/apidiffs/current_vs_latest/opentelemetry-spring-boot-autoconfigure.txt @@ -1,2 +1,2 @@ -Comparing source compatibility of opentelemetry-spring-boot-autoconfigure-2.23.0-SNAPSHOT.jar against opentelemetry-spring-boot-autoconfigure-2.22.0.jar +Comparing source compatibility of opentelemetry-spring-boot-autoconfigure-2.23.0-SNAPSHOT.jar against No changes. \ No newline at end of file From d88ceef1ad553e32e2a9930a90725c6fb1af0ab1 Mon Sep 17 00:00:00 2001 From: Jay DeLuca Date: Wed, 3 Dec 2025 12:56:51 -0500 Subject: [PATCH 33/40] fix kafka separation of configs --- .../build.gradle.kts | 2 +- ...KafkaInstrumentationAutoConfiguration.java | 15 ---- ...roducerFactoryCustomizerConfiguration.java | 31 +++++++ ...mentationSpringBoot4AutoConfiguration.java | 82 ------------------- ...oryCustomizerSpringBoot4Configuration.java | 30 +++++++ .../main/resources/META-INF/spring.factories | 2 + ...ot.autoconfigure.AutoConfiguration.imports | 1 - ...aInstrumentationAutoConfigurationTest.java | 26 +++++- ...aInstrumentationAutoConfigurationTest.java | 43 +++++----- .../testing/build.gradle.kts | 1 + ...aInstrumentationAutoConfigurationTest.java | 17 +++- .../KafkaSpringStarterSmokeTest.java | 6 +- .../KafkaSpringStarterSmokeTest.java | 6 +- .../KafkaSpringStarterSmokeTest.java | 6 +- ...bstractJvmKafkaSpringStarterSmokeTest.java | 6 +- 15 files changed, 138 insertions(+), 136 deletions(-) create mode 100644 instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/kafka/ProducerFactoryCustomizerConfiguration.java delete mode 100644 instrumentation/spring/spring-boot-autoconfigure/src/main/javaSpring4/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/kafka/KafkaInstrumentationSpringBoot4AutoConfiguration.java create mode 100644 instrumentation/spring/spring-boot-autoconfigure/src/main/javaSpring4/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/kafka/ProducerFactoryCustomizerSpringBoot4Configuration.java rename instrumentation/spring/spring-boot-autoconfigure/testing/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/{instrumentation/kafka => }/AbstractKafkaInstrumentationAutoConfigurationTest.java (81%) diff --git a/instrumentation/spring/spring-boot-autoconfigure/build.gradle.kts b/instrumentation/spring/spring-boot-autoconfigure/build.gradle.kts index 508715166358..ee8d6fb51b7b 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/build.gradle.kts +++ b/instrumentation/spring/spring-boot-autoconfigure/build.gradle.kts @@ -305,7 +305,7 @@ tasks { } named("testSpring2") { - isEnabled = latestDepTest && testSpring3 + isEnabled = testSpring3 } named("testSpring3") { diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/kafka/KafkaInstrumentationAutoConfiguration.java b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/kafka/KafkaInstrumentationAutoConfiguration.java index 08005d1fd9a7..b10308c31fc5 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/kafka/KafkaInstrumentationAutoConfiguration.java +++ b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/kafka/KafkaInstrumentationAutoConfiguration.java @@ -7,13 +7,11 @@ import io.opentelemetry.api.OpenTelemetry; import io.opentelemetry.instrumentation.api.incubator.config.internal.InstrumentationConfig; -import io.opentelemetry.instrumentation.kafkaclients.v2_6.KafkaTelemetry; import io.opentelemetry.instrumentation.spring.autoconfigure.internal.ConditionalOnEnabledInstrumentation; import io.opentelemetry.instrumentation.spring.kafka.v2_7.SpringKafkaTelemetry; import org.springframework.beans.factory.ObjectProvider; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.boot.autoconfigure.kafka.DefaultKafkaProducerFactoryCustomizer; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.kafka.config.ConcurrentKafkaListenerContainerFactory; @@ -27,7 +25,6 @@ @ConditionalOnClass({ KafkaTemplate.class, ConcurrentKafkaListenerContainerFactory.class, - DefaultKafkaProducerFactoryCustomizer.class // package changed in Spring Boot 4 }) @Configuration public class KafkaInstrumentationAutoConfiguration { @@ -60,16 +57,4 @@ static SpringKafkaTelemetry getTelemetry( return new ConcurrentKafkaListenerContainerFactoryPostProcessor( () -> getTelemetry(openTelemetryProvider, configProvider)); } - - @ConditionalOnClass(DefaultKafkaProducerFactoryCustomizer.class) - @Configuration - static class ProducerFactoryCustomizerConfiguration { - - @Bean - DefaultKafkaProducerFactoryCustomizer otelKafkaProducerFactoryCustomizer( - OpenTelemetry openTelemetry) { - KafkaTelemetry kafkaTelemetry = KafkaTelemetry.create(openTelemetry); - return producerFactory -> producerFactory.addPostProcessor(kafkaTelemetry::wrap); - } - } } diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/kafka/ProducerFactoryCustomizerConfiguration.java b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/kafka/ProducerFactoryCustomizerConfiguration.java new file mode 100644 index 000000000000..4d476f829547 --- /dev/null +++ b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/kafka/ProducerFactoryCustomizerConfiguration.java @@ -0,0 +1,31 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.kafka; + +import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.instrumentation.kafkaclients.v2_6.KafkaTelemetry; +import io.opentelemetry.instrumentation.spring.autoconfigure.internal.ConditionalOnEnabledInstrumentation; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.kafka.DefaultKafkaProducerFactoryCustomizer; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * This class is internal and is hence not for public use. Its APIs are unstable and can change at + * any time. + */ +@ConditionalOnEnabledInstrumentation(module = "kafka") +// package changed in Spring Boot 4 +@ConditionalOnClass({DefaultKafkaProducerFactoryCustomizer.class}) +@Configuration +public class ProducerFactoryCustomizerConfiguration { + @Bean + DefaultKafkaProducerFactoryCustomizer otelKafkaProducerFactoryCustomizer( + OpenTelemetry openTelemetry) { + KafkaTelemetry kafkaTelemetry = KafkaTelemetry.create(openTelemetry); + return producerFactory -> producerFactory.addPostProcessor(kafkaTelemetry::wrap); + } +} diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/main/javaSpring4/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/kafka/KafkaInstrumentationSpringBoot4AutoConfiguration.java b/instrumentation/spring/spring-boot-autoconfigure/src/main/javaSpring4/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/kafka/KafkaInstrumentationSpringBoot4AutoConfiguration.java deleted file mode 100644 index 89ad4a2b2599..000000000000 --- a/instrumentation/spring/spring-boot-autoconfigure/src/main/javaSpring4/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/kafka/KafkaInstrumentationSpringBoot4AutoConfiguration.java +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.kafka; - -import io.opentelemetry.api.OpenTelemetry; -import io.opentelemetry.instrumentation.api.incubator.config.internal.InstrumentationConfig; -import io.opentelemetry.instrumentation.kafkaclients.v2_6.KafkaTelemetry; -import io.opentelemetry.instrumentation.spring.autoconfigure.internal.ConditionalOnEnabledInstrumentation; -import io.opentelemetry.instrumentation.spring.kafka.v2_7.SpringKafkaTelemetry; -import org.springframework.beans.factory.ObjectProvider; -import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.boot.kafka.autoconfigure.DefaultKafkaProducerFactoryCustomizer; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.kafka.config.ConcurrentKafkaListenerContainerFactory; -import org.springframework.kafka.core.KafkaTemplate; - -/** - * This class is internal and is hence not for public use. Its APIs are unstable and can change at - * any time. - */ -@ConditionalOnEnabledInstrumentation(module = "kafka") -@ConditionalOnClass({ - KafkaTemplate.class, - ConcurrentKafkaListenerContainerFactory.class, - DefaultKafkaProducerFactoryCustomizer.class // package changed from Spring Boot 4 -}) -@Configuration -public class KafkaInstrumentationSpringBoot4AutoConfiguration { - - // For error prone - public KafkaInstrumentationSpringBoot4AutoConfiguration() {} - - @Bean - static SpringKafkaTelemetry getTelemetry( - ObjectProvider openTelemetryProvider, - ObjectProvider configProvider) { - return SpringKafkaTelemetry.builder(openTelemetryProvider.getObject()) - .setCaptureExperimentalSpanAttributes( - configProvider - .getObject() - .getBoolean("otel.instrumentation.kafka.experimental-span-attributes", false)) - .build(); - } - - // static to avoid "is not eligible for getting processed by all BeanPostProcessors" warning - @Bean - @ConditionalOnProperty( - name = "otel.instrumentation.kafka.autoconfigure-interceptor", - havingValue = "true", - matchIfMissing = true) - static ConcurrentKafkaListenerContainerFactoryPostProcessor - otelKafkaListenerContainerFactoryBeanPostProcessor( - ObjectProvider openTelemetryProvider, - ObjectProvider configProvider) { - return new ConcurrentKafkaListenerContainerFactoryPostProcessor( - () -> getTelemetry(openTelemetryProvider, configProvider)); - } - - @ConditionalOnClass(DefaultKafkaProducerFactoryCustomizer.class) - @Configuration - static class ProducerFactoryCustomizerConfiguration { - - @Bean - DefaultKafkaProducerFactoryCustomizer otelKafkaProducerFactoryCustomizer( - ObjectProvider openTelemetryProvider, - ObjectProvider configProvider) { - KafkaTelemetry kafkaTelemetry = - KafkaTelemetry.builder(openTelemetryProvider.getObject()) - .setCaptureExperimentalSpanAttributes( - configProvider - .getObject() - .getBoolean("otel.instrumentation.kafka.experimental-span-attributes", false)) - .build(); - return producerFactory -> producerFactory.addPostProcessor(kafkaTelemetry::wrap); - } - } -} diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/main/javaSpring4/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/kafka/ProducerFactoryCustomizerSpringBoot4Configuration.java b/instrumentation/spring/spring-boot-autoconfigure/src/main/javaSpring4/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/kafka/ProducerFactoryCustomizerSpringBoot4Configuration.java new file mode 100644 index 000000000000..b39059462d66 --- /dev/null +++ b/instrumentation/spring/spring-boot-autoconfigure/src/main/javaSpring4/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/kafka/ProducerFactoryCustomizerSpringBoot4Configuration.java @@ -0,0 +1,30 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.kafka; + +import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.instrumentation.kafkaclients.v2_6.KafkaTelemetry; +import io.opentelemetry.instrumentation.spring.autoconfigure.internal.ConditionalOnEnabledInstrumentation; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.kafka.autoconfigure.DefaultKafkaProducerFactoryCustomizer; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * This class is internal and is hence not for public use. Its APIs are unstable and can change at + * any time. + */ +@ConditionalOnEnabledInstrumentation(module = "kafka") +@ConditionalOnClass({DefaultKafkaProducerFactoryCustomizer.class}) // package changed in 4+ +@Configuration +public class ProducerFactoryCustomizerSpringBoot4Configuration { + @Bean + DefaultKafkaProducerFactoryCustomizer otelKafkaProducerFactoryCustomizer( + OpenTelemetry openTelemetry) { + KafkaTelemetry kafkaTelemetry = KafkaTelemetry.create(openTelemetry); + return producerFactory -> producerFactory.addPostProcessor(kafkaTelemetry::wrap); + } +} 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 19fb234190e5..e463dac38738 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 @@ -2,6 +2,8 @@ org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ io.opentelemetry.instrumentation.spring.autoconfigure.OpenTelemetryAutoConfiguration,\ io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.annotations.InstrumentationAnnotationsAutoConfiguration,\ io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.kafka.KafkaInstrumentationAutoConfiguration,\ +io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.kafka.ProducerFactoryCustomizerSpringBoot4Configuration,\ +io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.kafka.ProducerFactoryCustomizerConfiguration,\ io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.mongo.MongoClientInstrumentationAutoConfiguration,\ io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.mongo.MongoClientInstrumentationSpringBoot4AutoConfiguration,\ io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.logging.OpenTelemetryAppenderAutoConfiguration,\ 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 c2326c5a1dee..fbcd7986ae59 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 @@ -1,7 +1,6 @@ io.opentelemetry.instrumentation.spring.autoconfigure.OpenTelemetryAutoConfiguration io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.annotations.InstrumentationAnnotationsAutoConfiguration io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.kafka.KafkaInstrumentationAutoConfiguration -io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.kafka.KafkaInstrumentationSpringBoot4AutoConfiguration io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.mongo.MongoClientInstrumentationAutoConfiguration io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.mongo.MongoClientInstrumentationSpringBoot4AutoConfiguration io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.logging.OpenTelemetryAppenderAutoConfiguration diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/testSpring2/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/kafka/KafkaInstrumentationAutoConfigurationTest.java b/instrumentation/spring/spring-boot-autoconfigure/src/testSpring2/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/kafka/KafkaInstrumentationAutoConfigurationTest.java index 22ca4d057004..acd6506750c1 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/testSpring2/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/kafka/KafkaInstrumentationAutoConfigurationTest.java +++ b/instrumentation/spring/spring-boot-autoconfigure/src/testSpring2/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/kafka/KafkaInstrumentationAutoConfigurationTest.java @@ -5,13 +5,37 @@ package io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.kafka; +import static java.util.Collections.emptyMap; +import static org.assertj.core.api.Assertions.assertThat; + +import io.opentelemetry.instrumentation.spring.autoconfigure.internal.AbstractKafkaInstrumentationAutoConfigurationTest; import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.autoconfigure.kafka.DefaultKafkaProducerFactoryCustomizer; +import org.springframework.boot.test.context.assertj.AssertableApplicationContext; +import org.springframework.kafka.core.DefaultKafkaProducerFactory; class KafkaInstrumentationAutoConfigurationTest extends AbstractKafkaInstrumentationAutoConfigurationTest { @Override protected AutoConfigurations autoConfigurations() { - return AutoConfigurations.of(KafkaInstrumentationAutoConfiguration.class); + return AutoConfigurations.of( + KafkaInstrumentationAutoConfiguration.class, ProducerFactoryCustomizerConfiguration.class); + } + + @Override + protected void factoryTestAssertion(AssertableApplicationContext context) { + DefaultKafkaProducerFactoryCustomizer customizer = + context.getBean( + "otelKafkaProducerFactoryCustomizer", DefaultKafkaProducerFactoryCustomizer.class); + assertThat(customizer).isNotNull(); + + // Verify the customizer works by applying it to a producer factory + DefaultKafkaProducerFactory factory = + new DefaultKafkaProducerFactory<>(emptyMap()); + customizer.customize(factory); + + // Check that interceptors were added (the customizer adds a post processor) + assertThat(factory.getPostProcessors()).isNotEmpty(); } } diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/testSpring4/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/kafka/KafkaInstrumentationAutoConfigurationTest.java b/instrumentation/spring/spring-boot-autoconfigure/src/testSpring4/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/kafka/KafkaInstrumentationAutoConfigurationTest.java index 0f0d48024e5c..a267ef94a764 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/testSpring4/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/kafka/KafkaInstrumentationAutoConfigurationTest.java +++ b/instrumentation/spring/spring-boot-autoconfigure/src/testSpring4/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/kafka/KafkaInstrumentationAutoConfigurationTest.java @@ -8,9 +8,10 @@ import static java.util.Collections.emptyMap; import static org.assertj.core.api.Assertions.assertThat; -import org.junit.jupiter.api.Test; +import io.opentelemetry.instrumentation.spring.autoconfigure.internal.AbstractKafkaInstrumentationAutoConfigurationTest; import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.kafka.autoconfigure.DefaultKafkaProducerFactoryCustomizer; +import org.springframework.boot.test.context.assertj.AssertableApplicationContext; import org.springframework.kafka.core.DefaultKafkaProducerFactory; class KafkaInstrumentationAutoConfigurationTest @@ -18,30 +19,24 @@ class KafkaInstrumentationAutoConfigurationTest @Override protected AutoConfigurations autoConfigurations() { - return AutoConfigurations.of(KafkaInstrumentationSpringBoot4AutoConfiguration.class); + return AutoConfigurations.of( + KafkaInstrumentationAutoConfiguration.class, + ProducerFactoryCustomizerSpringBoot4Configuration.class); } - @Test - void defaultConfigurationWithFactoryTesting() { - contextRunner.run( - context -> { - assertThat(context.containsBean("otelKafkaProducerFactoryCustomizer")).isTrue(); - assertThat(context.containsBean("otelKafkaListenerContainerFactoryBeanPostProcessor")) - .isTrue(); - - DefaultKafkaProducerFactoryCustomizer customizer = - context.getBean( - "otelKafkaProducerFactoryCustomizer", - DefaultKafkaProducerFactoryCustomizer.class); - assertThat(customizer).isNotNull(); - - // Verify the customizer works by applying it to a producer factory - DefaultKafkaProducerFactory factory = - new DefaultKafkaProducerFactory<>(emptyMap()); - customizer.customize(factory); - - // Check that interceptors were added (the customizer adds a post processor) - assertThat(factory.getPostProcessors()).isNotEmpty(); - }); + @Override + protected void factoryTestAssertion(AssertableApplicationContext context) { + DefaultKafkaProducerFactoryCustomizer customizer = + context.getBean( + "otelKafkaProducerFactoryCustomizer", DefaultKafkaProducerFactoryCustomizer.class); + assertThat(customizer).isNotNull(); + + // Verify the customizer works by applying it to a producer factory + DefaultKafkaProducerFactory factory = + new DefaultKafkaProducerFactory<>(emptyMap()); + customizer.customize(factory); + + // Check that interceptors were added (the customizer adds a post processor) + assertThat(factory.getPostProcessors()).isNotEmpty(); } } diff --git a/instrumentation/spring/spring-boot-autoconfigure/testing/build.gradle.kts b/instrumentation/spring/spring-boot-autoconfigure/testing/build.gradle.kts index 318f2c8fe69d..601d1a556e77 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/testing/build.gradle.kts +++ b/instrumentation/spring/spring-boot-autoconfigure/testing/build.gradle.kts @@ -9,6 +9,7 @@ val springBootVersion = "2.7.18" dependencies { compileOnly("org.springframework.boot:spring-boot-restclient:4.0.0") + implementation("org.springframework.kafka:spring-kafka:2.9.0") library("org.springframework.boot:spring-boot-starter-test:$springBootVersion") { exclude("org.junit.vintage", "junit-vintage-engine") diff --git a/instrumentation/spring/spring-boot-autoconfigure/testing/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/kafka/AbstractKafkaInstrumentationAutoConfigurationTest.java b/instrumentation/spring/spring-boot-autoconfigure/testing/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/AbstractKafkaInstrumentationAutoConfigurationTest.java similarity index 81% rename from instrumentation/spring/spring-boot-autoconfigure/testing/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/kafka/AbstractKafkaInstrumentationAutoConfigurationTest.java rename to instrumentation/spring/spring-boot-autoconfigure/testing/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/AbstractKafkaInstrumentationAutoConfigurationTest.java index d92438535772..c4e0b40a127c 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/testing/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/kafka/AbstractKafkaInstrumentationAutoConfigurationTest.java +++ b/instrumentation/spring/spring-boot-autoconfigure/testing/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/AbstractKafkaInstrumentationAutoConfigurationTest.java @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -package io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.kafka; +package io.opentelemetry.instrumentation.spring.autoconfigure.internal; import static java.util.Collections.emptyMap; import static org.assertj.core.api.Assertions.assertThat; @@ -14,12 +14,15 @@ import io.opentelemetry.sdk.autoconfigure.spi.internal.DefaultConfigProperties; import org.junit.jupiter.api.Test; import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.test.context.assertj.AssertableApplicationContext; import org.springframework.boot.test.context.runner.ApplicationContextRunner; public abstract class AbstractKafkaInstrumentationAutoConfigurationTest { protected abstract AutoConfigurations autoConfigurations(); + protected abstract void factoryTestAssertion(AssertableApplicationContext context); + protected final ApplicationContextRunner contextRunner = new ApplicationContextRunner() .withBean( @@ -61,4 +64,16 @@ void defaultConfiguration() { .isTrue(); }); } + + @Test + void defaultConfigurationWithFactoryTesting() { + contextRunner.run( + context -> { + assertThat(context.containsBean("otelKafkaProducerFactoryCustomizer")).isTrue(); + assertThat(context.containsBean("otelKafkaListenerContainerFactoryBeanPostProcessor")) + .isTrue(); + + factoryTestAssertion(context); + }); + } } diff --git a/smoke-tests-otel-starter/spring-boot-2/src/test/java/io/opentelemetry/spring/smoketest/KafkaSpringStarterSmokeTest.java b/smoke-tests-otel-starter/spring-boot-2/src/test/java/io/opentelemetry/spring/smoketest/KafkaSpringStarterSmokeTest.java index 08e43db7d358..8d8761b688ac 100644 --- a/smoke-tests-otel-starter/spring-boot-2/src/test/java/io/opentelemetry/spring/smoketest/KafkaSpringStarterSmokeTest.java +++ b/smoke-tests-otel-starter/spring-boot-2/src/test/java/io/opentelemetry/spring/smoketest/KafkaSpringStarterSmokeTest.java @@ -5,15 +5,15 @@ package io.opentelemetry.spring.smoketest; -import io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.kafka.KafkaInstrumentationAutoConfiguration; +import io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.kafka.ProducerFactoryCustomizerConfiguration; import org.junit.jupiter.api.condition.DisabledInNativeImage; import org.springframework.boot.autoconfigure.kafka.KafkaAutoConfiguration; @DisabledInNativeImage // See GraalVmNativeKafkaSpringStarterSmokeTest for the GraalVM native test class KafkaSpringStarterSmokeTest extends AbstractJvmKafkaSpringStarterSmokeTest { @Override - protected Class kafkaInstrumentationAutoConfigurationClass() { - return KafkaInstrumentationAutoConfiguration.class; + protected Class kafkaProducerFactoryCustomizerClass() { + return ProducerFactoryCustomizerConfiguration.class; } @Override diff --git a/smoke-tests-otel-starter/spring-boot-3/src/test/java/io/opentelemetry/spring/smoketest/KafkaSpringStarterSmokeTest.java b/smoke-tests-otel-starter/spring-boot-3/src/test/java/io/opentelemetry/spring/smoketest/KafkaSpringStarterSmokeTest.java index 08e43db7d358..8d8761b688ac 100644 --- a/smoke-tests-otel-starter/spring-boot-3/src/test/java/io/opentelemetry/spring/smoketest/KafkaSpringStarterSmokeTest.java +++ b/smoke-tests-otel-starter/spring-boot-3/src/test/java/io/opentelemetry/spring/smoketest/KafkaSpringStarterSmokeTest.java @@ -5,15 +5,15 @@ package io.opentelemetry.spring.smoketest; -import io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.kafka.KafkaInstrumentationAutoConfiguration; +import io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.kafka.ProducerFactoryCustomizerConfiguration; import org.junit.jupiter.api.condition.DisabledInNativeImage; import org.springframework.boot.autoconfigure.kafka.KafkaAutoConfiguration; @DisabledInNativeImage // See GraalVmNativeKafkaSpringStarterSmokeTest for the GraalVM native test class KafkaSpringStarterSmokeTest extends AbstractJvmKafkaSpringStarterSmokeTest { @Override - protected Class kafkaInstrumentationAutoConfigurationClass() { - return KafkaInstrumentationAutoConfiguration.class; + protected Class kafkaProducerFactoryCustomizerClass() { + return ProducerFactoryCustomizerConfiguration.class; } @Override diff --git a/smoke-tests-otel-starter/spring-boot-4/src/test/java/io/opentelemetry/spring/smoketest/KafkaSpringStarterSmokeTest.java b/smoke-tests-otel-starter/spring-boot-4/src/test/java/io/opentelemetry/spring/smoketest/KafkaSpringStarterSmokeTest.java index be32e1fa84ed..f2b491893a16 100644 --- a/smoke-tests-otel-starter/spring-boot-4/src/test/java/io/opentelemetry/spring/smoketest/KafkaSpringStarterSmokeTest.java +++ b/smoke-tests-otel-starter/spring-boot-4/src/test/java/io/opentelemetry/spring/smoketest/KafkaSpringStarterSmokeTest.java @@ -5,15 +5,15 @@ package io.opentelemetry.spring.smoketest; -import io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.kafka.KafkaInstrumentationSpringBoot4AutoConfiguration; +import io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.kafka.ProducerFactoryCustomizerSpringBoot4Configuration; import org.junit.jupiter.api.condition.DisabledInNativeImage; import org.springframework.boot.kafka.autoconfigure.KafkaAutoConfiguration; @DisabledInNativeImage // See GraalVmNativeKafkaSpringStarterSmokeTest for the GraalVM native test class KafkaSpringStarterSmokeTest extends AbstractJvmKafkaSpringStarterSmokeTest { @Override - protected Class kafkaInstrumentationAutoConfigurationClass() { - return KafkaInstrumentationSpringBoot4AutoConfiguration.class; + protected Class kafkaProducerFactoryCustomizerClass() { + return ProducerFactoryCustomizerSpringBoot4Configuration.class; } @Override diff --git a/smoke-tests-otel-starter/spring-boot-common/src/main/java/io/opentelemetry/spring/smoketest/AbstractJvmKafkaSpringStarterSmokeTest.java b/smoke-tests-otel-starter/spring-boot-common/src/main/java/io/opentelemetry/spring/smoketest/AbstractJvmKafkaSpringStarterSmokeTest.java index 8efd54d1d288..88c58fb69ce8 100644 --- a/smoke-tests-otel-starter/spring-boot-common/src/main/java/io/opentelemetry/spring/smoketest/AbstractJvmKafkaSpringStarterSmokeTest.java +++ b/smoke-tests-otel-starter/spring-boot-common/src/main/java/io/opentelemetry/spring/smoketest/AbstractJvmKafkaSpringStarterSmokeTest.java @@ -7,6 +7,7 @@ import io.opentelemetry.api.OpenTelemetry; import io.opentelemetry.instrumentation.spring.autoconfigure.OpenTelemetryAutoConfiguration; +import io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.kafka.KafkaInstrumentationAutoConfiguration; import io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.thread.ThreadDetailsAutoConfiguration; import java.time.Duration; import org.junit.jupiter.api.AfterAll; @@ -27,7 +28,7 @@ public abstract class AbstractJvmKafkaSpringStarterSmokeTest private ApplicationContextRunner contextRunner; - protected abstract Class kafkaInstrumentationAutoConfigurationClass(); + protected abstract Class kafkaProducerFactoryCustomizerClass(); protected abstract Class kafkaAutoConfigurationClass(); @@ -57,7 +58,8 @@ void setUpContext() { ThreadDetailsAutoConfiguration.class, SpringSmokeOtelConfiguration.class, kafkaAutoConfigurationClass(), - kafkaInstrumentationAutoConfigurationClass(), + KafkaInstrumentationAutoConfiguration.class, + kafkaProducerFactoryCustomizerClass(), KafkaConfig.class)) .withPropertyValues( "otel.instrumentation.kafka.experimental-span-attributes=true", From 8f75f727e27c3469cf79456a5ed7d5d507188d6f Mon Sep 17 00:00:00 2001 From: Jay DeLuca Date: Wed, 3 Dec 2025 12:59:50 -0500 Subject: [PATCH 34/40] revert jApiCmd --- .../opentelemetry-spring-boot-autoconfigure.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/apidiffs/current_vs_latest/opentelemetry-spring-boot-autoconfigure.txt b/docs/apidiffs/current_vs_latest/opentelemetry-spring-boot-autoconfigure.txt index 9dfa328763a1..7e15cee7de8c 100644 --- a/docs/apidiffs/current_vs_latest/opentelemetry-spring-boot-autoconfigure.txt +++ b/docs/apidiffs/current_vs_latest/opentelemetry-spring-boot-autoconfigure.txt @@ -1,2 +1,2 @@ -Comparing source compatibility of opentelemetry-spring-boot-autoconfigure-2.23.0-SNAPSHOT.jar against +Comparing source compatibility of opentelemetry-spring-boot-autoconfigure-2.23.0-SNAPSHOT.jar against opentelemetry-spring-boot-autoconfigure-2.22.0.jar No changes. \ No newline at end of file From fc726fefa5b88a37a24475ec9da3679fb9173673 Mon Sep 17 00:00:00 2001 From: Jay DeLuca Date: Wed, 3 Dec 2025 13:44:50 -0500 Subject: [PATCH 35/40] fix conditionals --- .../kafka/ProducerFactoryCustomizerConfiguration.java | 6 +++++- .../ProducerFactoryCustomizerSpringBoot4Configuration.java | 6 +++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/kafka/ProducerFactoryCustomizerConfiguration.java b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/kafka/ProducerFactoryCustomizerConfiguration.java index 4d476f829547..ad7b3fc1ea46 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/kafka/ProducerFactoryCustomizerConfiguration.java +++ b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/kafka/ProducerFactoryCustomizerConfiguration.java @@ -12,6 +12,7 @@ import org.springframework.boot.autoconfigure.kafka.DefaultKafkaProducerFactoryCustomizer; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.kafka.core.DefaultKafkaProducerFactory; /** * This class is internal and is hence not for public use. Its APIs are unstable and can change at @@ -19,7 +20,10 @@ */ @ConditionalOnEnabledInstrumentation(module = "kafka") // package changed in Spring Boot 4 -@ConditionalOnClass({DefaultKafkaProducerFactoryCustomizer.class}) +@ConditionalOnClass({ + DefaultKafkaProducerFactoryCustomizer.class, + DefaultKafkaProducerFactory.class +}) @Configuration public class ProducerFactoryCustomizerConfiguration { @Bean diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/main/javaSpring4/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/kafka/ProducerFactoryCustomizerSpringBoot4Configuration.java b/instrumentation/spring/spring-boot-autoconfigure/src/main/javaSpring4/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/kafka/ProducerFactoryCustomizerSpringBoot4Configuration.java index b39059462d66..560d10812fdd 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/main/javaSpring4/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/kafka/ProducerFactoryCustomizerSpringBoot4Configuration.java +++ b/instrumentation/spring/spring-boot-autoconfigure/src/main/javaSpring4/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/kafka/ProducerFactoryCustomizerSpringBoot4Configuration.java @@ -12,13 +12,17 @@ import org.springframework.boot.kafka.autoconfigure.DefaultKafkaProducerFactoryCustomizer; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.kafka.core.DefaultKafkaProducerFactory; /** * This class is internal and is hence not for public use. Its APIs are unstable and can change at * any time. */ @ConditionalOnEnabledInstrumentation(module = "kafka") -@ConditionalOnClass({DefaultKafkaProducerFactoryCustomizer.class}) // package changed in 4+ +@ConditionalOnClass({ + DefaultKafkaProducerFactoryCustomizer.class, + DefaultKafkaProducerFactory.class +}) // package changed in 4+ @Configuration public class ProducerFactoryCustomizerSpringBoot4Configuration { @Bean From 0c6e263f9161246b667014af7e125c2d44a40042 Mon Sep 17 00:00:00 2001 From: Jay DeLuca Date: Thu, 4 Dec 2025 16:40:03 -0500 Subject: [PATCH 36/40] extending wrong test classes --- ...mentationSpringBoot4AutoConfiguration.java | 74 +++++++++++++++++++ ...alVmNativeKafkaSpringStarterSmokeTest.java | 4 +- ...alVmNativeKafkaSpringStarterSmokeTest.java | 4 +- 3 files changed, 78 insertions(+), 4 deletions(-) create mode 100644 instrumentation/spring/spring-boot-autoconfigure/src/main/javaSpring4/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/kafka/KafkaInstrumentationSpringBoot4AutoConfiguration.java diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/main/javaSpring4/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/kafka/KafkaInstrumentationSpringBoot4AutoConfiguration.java b/instrumentation/spring/spring-boot-autoconfigure/src/main/javaSpring4/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/kafka/KafkaInstrumentationSpringBoot4AutoConfiguration.java new file mode 100644 index 000000000000..9a63b9b6035e --- /dev/null +++ b/instrumentation/spring/spring-boot-autoconfigure/src/main/javaSpring4/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/kafka/KafkaInstrumentationSpringBoot4AutoConfiguration.java @@ -0,0 +1,74 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.kafka; + +import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.instrumentation.api.incubator.config.internal.InstrumentationConfig; +import io.opentelemetry.instrumentation.kafkaclients.v2_6.KafkaTelemetry; +import io.opentelemetry.instrumentation.spring.autoconfigure.internal.ConditionalOnEnabledInstrumentation; +import io.opentelemetry.instrumentation.spring.kafka.v2_7.SpringKafkaTelemetry; +import org.springframework.beans.factory.ObjectProvider; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.kafka.autoconfigure.DefaultKafkaProducerFactoryCustomizer; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.kafka.config.ConcurrentKafkaListenerContainerFactory; +import org.springframework.kafka.core.KafkaTemplate; + +/** + * This class is internal and is hence not for public use. Its APIs are unstable and can change at + * any time. + */ +@ConditionalOnEnabledInstrumentation(module = "kafka") +@ConditionalOnClass({ + KafkaTemplate.class, + ConcurrentKafkaListenerContainerFactory.class, + DefaultKafkaProducerFactoryCustomizer.class +}) +@ConditionalOnMissingBean(name = "otelKafkaProducerFactoryCustomizer") +@ConditionalOnMissingClass( + // Spring Boot 2 & 3 + "org.springframework.boot.autoconfigure.kafka.DefaultKafkaProducerFactoryCustomizer") +@Configuration +public class KafkaInstrumentationSpringBoot4AutoConfiguration { + + @Bean + DefaultKafkaProducerFactoryCustomizer otelKafkaProducerFactoryCustomizer( + OpenTelemetry openTelemetry) { + KafkaTelemetry kafkaTelemetry = KafkaTelemetry.create(openTelemetry); + return producerFactory -> producerFactory.addPostProcessor(kafkaTelemetry::wrap); + } + + @Bean + static SpringKafkaTelemetry getTelemetry( + ObjectProvider openTelemetryProvider, + ObjectProvider configProvider) { + return SpringKafkaTelemetry.builder(openTelemetryProvider.getObject()) + .setCaptureExperimentalSpanAttributes( + configProvider + .getObject() + .getBoolean("otel.instrumentation.kafka.experimental-span-attributes", false)) + .build(); + } + + // static to avoid "is not eligible for getting processed by all BeanPostProcessors" warning + @Bean + @ConditionalOnProperty( + name = "otel.instrumentation.kafka.autoconfigure-interceptor", + havingValue = "true", + matchIfMissing = true) + @ConditionalOnMissingBean + static ConcurrentKafkaListenerContainerFactoryPostProcessor + otelKafkaListenerContainerFactoryBeanPostProcessor( + ObjectProvider openTelemetryProvider, + ObjectProvider configProvider) { + return new ConcurrentKafkaListenerContainerFactoryPostProcessor( + () -> getTelemetry(openTelemetryProvider, configProvider)); + } +} diff --git a/smoke-tests-otel-starter/spring-boot-3/src/test/java/io/opentelemetry/spring/smoketest/GraalVmNativeKafkaSpringStarterSmokeTest.java b/smoke-tests-otel-starter/spring-boot-3/src/test/java/io/opentelemetry/spring/smoketest/GraalVmNativeKafkaSpringStarterSmokeTest.java index aeb23c97f371..d60755b211e7 100644 --- a/smoke-tests-otel-starter/spring-boot-3/src/test/java/io/opentelemetry/spring/smoketest/GraalVmNativeKafkaSpringStarterSmokeTest.java +++ b/smoke-tests-otel-starter/spring-boot-3/src/test/java/io/opentelemetry/spring/smoketest/GraalVmNativeKafkaSpringStarterSmokeTest.java @@ -27,6 +27,6 @@ AbstractKafkaSpringStarterSmokeTest.KafkaConfig.class }, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) -@EnabledInNativeImage // see AbstractJvmKafkaSpringStarterSmokeTest for the JVM test +@EnabledInNativeImage @RequiresDockerCompose -class GraalVmNativeKafkaSpringStarterSmokeTest extends AbstractKafkaSpringStarterSmokeTest {} +class GraalVmNativeKafkaSpringStarterSmokeTest extends KafkaSpringStarterSmokeTest {} diff --git a/smoke-tests-otel-starter/spring-boot-4/src/test/java/io/opentelemetry/spring/smoketest/GraalVmNativeKafkaSpringStarterSmokeTest.java b/smoke-tests-otel-starter/spring-boot-4/src/test/java/io/opentelemetry/spring/smoketest/GraalVmNativeKafkaSpringStarterSmokeTest.java index aeb23c97f371..d60755b211e7 100644 --- a/smoke-tests-otel-starter/spring-boot-4/src/test/java/io/opentelemetry/spring/smoketest/GraalVmNativeKafkaSpringStarterSmokeTest.java +++ b/smoke-tests-otel-starter/spring-boot-4/src/test/java/io/opentelemetry/spring/smoketest/GraalVmNativeKafkaSpringStarterSmokeTest.java @@ -27,6 +27,6 @@ AbstractKafkaSpringStarterSmokeTest.KafkaConfig.class }, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) -@EnabledInNativeImage // see AbstractJvmKafkaSpringStarterSmokeTest for the JVM test +@EnabledInNativeImage @RequiresDockerCompose -class GraalVmNativeKafkaSpringStarterSmokeTest extends AbstractKafkaSpringStarterSmokeTest {} +class GraalVmNativeKafkaSpringStarterSmokeTest extends KafkaSpringStarterSmokeTest {} From 3b18b9450a163c74d6124ccef1ce586f69a6c7f6 Mon Sep 17 00:00:00 2001 From: Lauri Tulmin Date: Fri, 5 Dec 2025 14:52:58 +0200 Subject: [PATCH 37/40] fix jApiCmp --- .../testing/build.gradle.kts | 23 ++++++++----------- 1 file changed, 9 insertions(+), 14 deletions(-) diff --git a/instrumentation/spring/spring-boot-autoconfigure/testing/build.gradle.kts b/instrumentation/spring/spring-boot-autoconfigure/testing/build.gradle.kts index 601d1a556e77..c00480a8fa28 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/testing/build.gradle.kts +++ b/instrumentation/spring/spring-boot-autoconfigure/testing/build.gradle.kts @@ -1,7 +1,4 @@ -import org.gradle.kotlin.dsl.testImplementation - plugins { - id("otel.library-instrumentation") id("otel.java-conventions") } @@ -9,17 +6,15 @@ val springBootVersion = "2.7.18" dependencies { compileOnly("org.springframework.boot:spring-boot-restclient:4.0.0") - implementation("org.springframework.kafka:spring-kafka:2.9.0") + compileOnly("org.springframework.kafka:spring-kafka:2.9.0") - library("org.springframework.boot:spring-boot-starter-test:$springBootVersion") { - exclude("org.junit.vintage", "junit-vintage-engine") - } - library("org.springframework.boot:spring-boot-starter-data-r2dbc:$springBootVersion") + compileOnly("org.springframework.boot:spring-boot-starter-test:$springBootVersion") + compileOnly("org.springframework.boot:spring-boot-starter-data-r2dbc:$springBootVersion") - implementation(project(":instrumentation:micrometer:micrometer-1.5:library")) - implementation(project(":instrumentation:spring:spring-boot-autoconfigure")) - implementation("io.opentelemetry.javaagent:opentelemetry-testing-common") - implementation("io.opentelemetry:opentelemetry-sdk") - implementation("io.opentelemetry:opentelemetry-sdk-testing") - implementation("io.opentelemetry:opentelemetry-sdk-extension-autoconfigure-spi") + compileOnly(project(":instrumentation:micrometer:micrometer-1.5:library")) + compileOnly(project(":instrumentation:spring:spring-boot-autoconfigure")) + compileOnly("io.opentelemetry.javaagent:opentelemetry-testing-common") + compileOnly("io.opentelemetry:opentelemetry-sdk") + compileOnly("io.opentelemetry:opentelemetry-sdk-testing") + compileOnly("io.opentelemetry:opentelemetry-sdk-extension-autoconfigure-spi") } From 24aac9aca819256939f58d3c097a2aaa41aecad0 Mon Sep 17 00:00:00 2001 From: Lauri Tulmin Date: Fri, 5 Dec 2025 15:56:04 +0200 Subject: [PATCH 38/40] update fossa configuration --- .fossa.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.fossa.yml b/.fossa.yml index ff03053f1f52..7422e7ce8718 100644 --- a/.fossa.yml +++ b/.fossa.yml @@ -952,9 +952,6 @@ targets: - type: gradle path: ./ target: ':instrumentation:spring:spring-boot-actuator-autoconfigure-2.0:javaagent' - - type: gradle - path: ./ - target: ':instrumentation:spring:spring-boot-autoconfigure:testing' - type: gradle path: ./ target: ':instrumentation:spring:spring-boot-resources:javaagent' From 879cddebb0de18cb775946161d455e6c64571d8b Mon Sep 17 00:00:00 2001 From: Lauri Tulmin Date: Fri, 5 Dec 2025 16:58:17 +0200 Subject: [PATCH 39/40] review --- .../build.gradle.kts | 48 +++++------- ...mentationSpringBoot4AutoConfiguration.java | 74 ------------------- ...cInstrumentationAutoConfigurationTest.java | 15 +--- ...alVmNativeKafkaSpringStarterSmokeTest.java | 4 +- ...alVmNativeKafkaSpringStarterSmokeTest.java | 4 +- 5 files changed, 27 insertions(+), 118 deletions(-) delete mode 100644 instrumentation/spring/spring-boot-autoconfigure/src/main/javaSpring4/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/kafka/KafkaInstrumentationSpringBoot4AutoConfiguration.java diff --git a/instrumentation/spring/spring-boot-autoconfigure/build.gradle.kts b/instrumentation/spring/spring-boot-autoconfigure/build.gradle.kts index 85c8b3e34a59..2e71d2a2100d 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/build.gradle.kts +++ b/instrumentation/spring/spring-boot-autoconfigure/build.gradle.kts @@ -196,9 +196,15 @@ testing { implementation(project(":instrumentation-api")) implementation(project(":instrumentation:micrometer:micrometer-1.5:library")) implementation(project(":instrumentation:spring:spring-boot-autoconfigure:testing")) - implementation("org.springframework.boot:spring-boot-starter-test:$springBootVersion") { - exclude("org.junit.vintage", "junit-vintage-engine") - } + // configure Spring Boot 3.x dependencies for latest dep testing + val version = if (latestDepTest) "3.+" else springBootVersion + implementation("org.springframework.boot:spring-boot-starter-test:$version") + implementation("org.springframework.boot:spring-boot-starter-actuator:$version") + implementation("org.springframework.boot:spring-boot-starter-web:$version") + implementation("org.springframework.boot:spring-boot-starter-jdbc:$version") + implementation("org.springframework.boot:spring-boot-starter-data-r2dbc:$version") + val springKafkaVersion = if (latestDepTest) "3.+" else "2.9.0" + implementation("org.springframework.kafka:spring-kafka:$springKafkaVersion") implementation("javax.servlet:javax.servlet-api:3.1.0") runtimeOnly("com.h2database:h2:1.4.197") runtimeOnly("io.r2dbc:r2dbc-h2:1.0.0.RELEASE") @@ -208,14 +214,13 @@ testing { val testSpring3 by registering(JvmTestSuite::class) { dependencies { implementation(project()) - implementation("org.springframework.boot:spring-boot-starter-web:3.2.4") + val version = if (latestDepTest) "3.+" else "3.2.4" + implementation("org.springframework.boot:spring-boot-starter-web:$version") implementation("io.opentelemetry:opentelemetry-sdk-extension-autoconfigure") implementation(project(":instrumentation:spring:spring-web:spring-web-3.1:library")) implementation(project(":instrumentation:spring:spring-webmvc:spring-webmvc-6.0:library")) implementation("jakarta.servlet:jakarta.servlet-api:5.0.0") - implementation("org.springframework.boot:spring-boot-starter-test:3.2.4") { - exclude("org.junit.vintage", "junit-vintage-engine") - } + implementation("org.springframework.boot:spring-boot-starter-test:$version") } } @@ -223,20 +228,19 @@ testing { dependencies { implementation(project()) implementation("io.opentelemetry:opentelemetry-sdk-extension-autoconfigure") - implementation("org.springframework.boot:spring-boot-starter-jdbc:4.0.0") - implementation("org.springframework.boot:spring-boot-restclient:4.0.0") - implementation("org.springframework.boot:spring-boot-starter-kafka:4.0.0") - implementation("org.springframework.boot:spring-boot-starter-actuator:4.0.0") - implementation("org.springframework.boot:spring-boot-starter-data-r2dbc:4.0.0") - implementation("org.springframework.boot:spring-boot-starter-micrometer-metrics:4.0.0") + val version = if (latestDepTest) "latest.release" else "4.0.0" + implementation("org.springframework.boot:spring-boot-starter-jdbc:$version") + implementation("org.springframework.boot:spring-boot-restclient:$version") + implementation("org.springframework.boot:spring-boot-starter-kafka:$version") + implementation("org.springframework.boot:spring-boot-starter-actuator:$version") + implementation("org.springframework.boot:spring-boot-starter-data-r2dbc:$version") + implementation("org.springframework.boot:spring-boot-starter-micrometer-metrics:$version") implementation("io.opentelemetry:opentelemetry-sdk") implementation("io.opentelemetry:opentelemetry-sdk-testing") implementation(project(":instrumentation-api")) implementation(project(":instrumentation:micrometer:micrometer-1.5:library")) implementation(project(":instrumentation:spring:spring-boot-autoconfigure:testing")) - implementation("org.springframework.boot:spring-boot-starter-test:4.0.0") { - exclude("org.junit.vintage", "junit-vintage-engine") - } + implementation("org.springframework.boot:spring-boot-starter-test:$version") runtimeOnly("com.h2database:h2:1.4.197") runtimeOnly("io.r2dbc:r2dbc-h2:1.0.0.RELEASE") } @@ -260,18 +264,6 @@ configurations.configureEach { if (name.contains("testLogbackMissing")) { exclude("ch.qos.logback", "logback-classic") } - // testSpring2: configure Spring Boot 3.x dependencies for latest dep testing - if (name == "testSpring2Implementation") { - dependencies { - add(name, "org.springframework.boot:spring-boot-starter-web:3.+") - add(name, "org.springframework.boot:spring-boot-starter-jdbc:3.+") - add(name, "org.springframework.boot:spring-boot-starter-actuator:3.+") - add(name, "org.springframework.boot:spring-boot-starter-data-r2dbc:3.+") - add(name, "org.springframework.kafka:spring-kafka:3.+") - add(name, project(":instrumentation:kafka:kafka-clients:kafka-clients-2.6:library")) - add(name, project(":instrumentation:spring:spring-kafka-2.7:library")) - } - } } tasks { diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/main/javaSpring4/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/kafka/KafkaInstrumentationSpringBoot4AutoConfiguration.java b/instrumentation/spring/spring-boot-autoconfigure/src/main/javaSpring4/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/kafka/KafkaInstrumentationSpringBoot4AutoConfiguration.java deleted file mode 100644 index 9a63b9b6035e..000000000000 --- a/instrumentation/spring/spring-boot-autoconfigure/src/main/javaSpring4/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/kafka/KafkaInstrumentationSpringBoot4AutoConfiguration.java +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.kafka; - -import io.opentelemetry.api.OpenTelemetry; -import io.opentelemetry.instrumentation.api.incubator.config.internal.InstrumentationConfig; -import io.opentelemetry.instrumentation.kafkaclients.v2_6.KafkaTelemetry; -import io.opentelemetry.instrumentation.spring.autoconfigure.internal.ConditionalOnEnabledInstrumentation; -import io.opentelemetry.instrumentation.spring.kafka.v2_7.SpringKafkaTelemetry; -import org.springframework.beans.factory.ObjectProvider; -import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; -import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; -import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.boot.kafka.autoconfigure.DefaultKafkaProducerFactoryCustomizer; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.kafka.config.ConcurrentKafkaListenerContainerFactory; -import org.springframework.kafka.core.KafkaTemplate; - -/** - * This class is internal and is hence not for public use. Its APIs are unstable and can change at - * any time. - */ -@ConditionalOnEnabledInstrumentation(module = "kafka") -@ConditionalOnClass({ - KafkaTemplate.class, - ConcurrentKafkaListenerContainerFactory.class, - DefaultKafkaProducerFactoryCustomizer.class -}) -@ConditionalOnMissingBean(name = "otelKafkaProducerFactoryCustomizer") -@ConditionalOnMissingClass( - // Spring Boot 2 & 3 - "org.springframework.boot.autoconfigure.kafka.DefaultKafkaProducerFactoryCustomizer") -@Configuration -public class KafkaInstrumentationSpringBoot4AutoConfiguration { - - @Bean - DefaultKafkaProducerFactoryCustomizer otelKafkaProducerFactoryCustomizer( - OpenTelemetry openTelemetry) { - KafkaTelemetry kafkaTelemetry = KafkaTelemetry.create(openTelemetry); - return producerFactory -> producerFactory.addPostProcessor(kafkaTelemetry::wrap); - } - - @Bean - static SpringKafkaTelemetry getTelemetry( - ObjectProvider openTelemetryProvider, - ObjectProvider configProvider) { - return SpringKafkaTelemetry.builder(openTelemetryProvider.getObject()) - .setCaptureExperimentalSpanAttributes( - configProvider - .getObject() - .getBoolean("otel.instrumentation.kafka.experimental-span-attributes", false)) - .build(); - } - - // static to avoid "is not eligible for getting processed by all BeanPostProcessors" warning - @Bean - @ConditionalOnProperty( - name = "otel.instrumentation.kafka.autoconfigure-interceptor", - havingValue = "true", - matchIfMissing = true) - @ConditionalOnMissingBean - static ConcurrentKafkaListenerContainerFactoryPostProcessor - otelKafkaListenerContainerFactoryBeanPostProcessor( - ObjectProvider openTelemetryProvider, - ObjectProvider configProvider) { - return new ConcurrentKafkaListenerContainerFactoryPostProcessor( - () -> getTelemetry(openTelemetryProvider, configProvider)); - } -} diff --git a/instrumentation/spring/spring-boot-autoconfigure/testing/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/AbstractJdbcInstrumentationAutoConfigurationTest.java b/instrumentation/spring/spring-boot-autoconfigure/testing/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/AbstractJdbcInstrumentationAutoConfigurationTest.java index 2b2ce330a42e..8c7b05f14e96 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/testing/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/AbstractJdbcInstrumentationAutoConfigurationTest.java +++ b/instrumentation/spring/spring-boot-autoconfigure/testing/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/AbstractJdbcInstrumentationAutoConfigurationTest.java @@ -5,11 +5,8 @@ package io.opentelemetry.instrumentation.spring.autoconfigure.internal; -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - +import static io.opentelemetry.instrumentation.testing.junit.db.SemconvStabilityUtil.maybeStable; +import static io.opentelemetry.semconv.incubating.DbIncubatingAttributes.DB_STATEMENT; import static java.util.Collections.emptyMap; import static org.assertj.core.api.Assertions.assertThat; @@ -17,9 +14,7 @@ import io.opentelemetry.instrumentation.api.incubator.config.internal.InstrumentationConfig; import io.opentelemetry.instrumentation.spring.autoconfigure.internal.properties.ConfigPropertiesBridge; import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; -import io.opentelemetry.instrumentation.testing.junit.db.SemconvStabilityUtil; import io.opentelemetry.sdk.autoconfigure.spi.internal.DefaultConfigProperties; -import io.opentelemetry.semconv.incubating.DbIncubatingAttributes; import java.sql.Connection; import java.sql.Statement; import javax.sql.DataSource; @@ -67,11 +62,7 @@ void statementSanitizerEnabledByDefault() { .waitAndAssertTraces( trace -> trace.hasSpansSatisfyingExactly( - span -> - span.hasAttribute( - SemconvStabilityUtil.maybeStable( - DbIncubatingAttributes.DB_STATEMENT), - "SELECT ?"))); + span -> span.hasAttribute(maybeStable(DB_STATEMENT), "SELECT ?"))); }); } } diff --git a/smoke-tests-otel-starter/spring-boot-3/src/test/java/io/opentelemetry/spring/smoketest/GraalVmNativeKafkaSpringStarterSmokeTest.java b/smoke-tests-otel-starter/spring-boot-3/src/test/java/io/opentelemetry/spring/smoketest/GraalVmNativeKafkaSpringStarterSmokeTest.java index d60755b211e7..aeb23c97f371 100644 --- a/smoke-tests-otel-starter/spring-boot-3/src/test/java/io/opentelemetry/spring/smoketest/GraalVmNativeKafkaSpringStarterSmokeTest.java +++ b/smoke-tests-otel-starter/spring-boot-3/src/test/java/io/opentelemetry/spring/smoketest/GraalVmNativeKafkaSpringStarterSmokeTest.java @@ -27,6 +27,6 @@ AbstractKafkaSpringStarterSmokeTest.KafkaConfig.class }, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) -@EnabledInNativeImage +@EnabledInNativeImage // see AbstractJvmKafkaSpringStarterSmokeTest for the JVM test @RequiresDockerCompose -class GraalVmNativeKafkaSpringStarterSmokeTest extends KafkaSpringStarterSmokeTest {} +class GraalVmNativeKafkaSpringStarterSmokeTest extends AbstractKafkaSpringStarterSmokeTest {} diff --git a/smoke-tests-otel-starter/spring-boot-4/src/test/java/io/opentelemetry/spring/smoketest/GraalVmNativeKafkaSpringStarterSmokeTest.java b/smoke-tests-otel-starter/spring-boot-4/src/test/java/io/opentelemetry/spring/smoketest/GraalVmNativeKafkaSpringStarterSmokeTest.java index d60755b211e7..aeb23c97f371 100644 --- a/smoke-tests-otel-starter/spring-boot-4/src/test/java/io/opentelemetry/spring/smoketest/GraalVmNativeKafkaSpringStarterSmokeTest.java +++ b/smoke-tests-otel-starter/spring-boot-4/src/test/java/io/opentelemetry/spring/smoketest/GraalVmNativeKafkaSpringStarterSmokeTest.java @@ -27,6 +27,6 @@ AbstractKafkaSpringStarterSmokeTest.KafkaConfig.class }, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) -@EnabledInNativeImage +@EnabledInNativeImage // see AbstractJvmKafkaSpringStarterSmokeTest for the JVM test @RequiresDockerCompose -class GraalVmNativeKafkaSpringStarterSmokeTest extends KafkaSpringStarterSmokeTest {} +class GraalVmNativeKafkaSpringStarterSmokeTest extends AbstractKafkaSpringStarterSmokeTest {} From 41879d32474fe960e63de43dc721b3a9b5d9fb1a Mon Sep 17 00:00:00 2001 From: Jay DeLuca Date: Fri, 5 Dec 2025 11:14:38 -0500 Subject: [PATCH 40/40] fix graal tests --- .github/graal-native-docker-compose.yaml | 4 ++-- .../kafka/KafkaInstrumentationAutoConfiguration.java | 1 - .../kafka/ProducerFactoryCustomizerConfiguration.java | 5 ++++- .../ProducerFactoryCustomizerSpringBoot4Configuration.java | 5 ++++- ...ingframework.boot.autoconfigure.AutoConfiguration.imports | 2 ++ 5 files changed, 12 insertions(+), 5 deletions(-) diff --git a/.github/graal-native-docker-compose.yaml b/.github/graal-native-docker-compose.yaml index dcc394866a50..331ee62df8af 100644 --- a/.github/graal-native-docker-compose.yaml +++ b/.github/graal-native-docker-compose.yaml @@ -5,7 +5,7 @@ services: - "27017:27017" zookeeper: - image: confluentinc/cp-zookeeper:6.2.10 + image: confluentinc/cp-zookeeper:7.7.7 environment: ZOOKEEPER_CLIENT_PORT: 2181 ZOOKEEPER_TICK_TIME: 2000 @@ -13,7 +13,7 @@ services: - "22181:2181" kafka: - image: confluentinc/cp-kafka:6.2.10 + image: confluentinc/cp-kafka:7.7.7 ports: - 9094:9094 depends_on: diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/kafka/KafkaInstrumentationAutoConfiguration.java b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/kafka/KafkaInstrumentationAutoConfiguration.java index b10308c31fc5..94c005095fe5 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/kafka/KafkaInstrumentationAutoConfiguration.java +++ b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/kafka/KafkaInstrumentationAutoConfiguration.java @@ -29,7 +29,6 @@ @Configuration public class KafkaInstrumentationAutoConfiguration { - // For error prone public KafkaInstrumentationAutoConfiguration() {} @Bean diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/kafka/ProducerFactoryCustomizerConfiguration.java b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/kafka/ProducerFactoryCustomizerConfiguration.java index ad7b3fc1ea46..1e0807333055 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/kafka/ProducerFactoryCustomizerConfiguration.java +++ b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/kafka/ProducerFactoryCustomizerConfiguration.java @@ -26,8 +26,11 @@ }) @Configuration public class ProducerFactoryCustomizerConfiguration { + + public ProducerFactoryCustomizerConfiguration() {} + @Bean - DefaultKafkaProducerFactoryCustomizer otelKafkaProducerFactoryCustomizer( + static DefaultKafkaProducerFactoryCustomizer otelKafkaProducerFactoryCustomizer( OpenTelemetry openTelemetry) { KafkaTelemetry kafkaTelemetry = KafkaTelemetry.create(openTelemetry); return producerFactory -> producerFactory.addPostProcessor(kafkaTelemetry::wrap); diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/main/javaSpring4/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/kafka/ProducerFactoryCustomizerSpringBoot4Configuration.java b/instrumentation/spring/spring-boot-autoconfigure/src/main/javaSpring4/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/kafka/ProducerFactoryCustomizerSpringBoot4Configuration.java index 560d10812fdd..d40a3945395d 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/main/javaSpring4/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/kafka/ProducerFactoryCustomizerSpringBoot4Configuration.java +++ b/instrumentation/spring/spring-boot-autoconfigure/src/main/javaSpring4/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/kafka/ProducerFactoryCustomizerSpringBoot4Configuration.java @@ -25,8 +25,11 @@ }) // package changed in 4+ @Configuration public class ProducerFactoryCustomizerSpringBoot4Configuration { + + public ProducerFactoryCustomizerSpringBoot4Configuration() {} + @Bean - DefaultKafkaProducerFactoryCustomizer otelKafkaProducerFactoryCustomizer( + static DefaultKafkaProducerFactoryCustomizer otelKafkaProducerFactoryCustomizer( OpenTelemetry openTelemetry) { KafkaTelemetry kafkaTelemetry = KafkaTelemetry.create(openTelemetry); return producerFactory -> producerFactory.addPostProcessor(kafkaTelemetry::wrap); 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 fbcd7986ae59..e6f50403a574 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 @@ -1,6 +1,8 @@ io.opentelemetry.instrumentation.spring.autoconfigure.OpenTelemetryAutoConfiguration io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.annotations.InstrumentationAnnotationsAutoConfiguration io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.kafka.KafkaInstrumentationAutoConfiguration +io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.kafka.ProducerFactoryCustomizerSpringBoot4Configuration +io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.kafka.ProducerFactoryCustomizerConfiguration io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.mongo.MongoClientInstrumentationAutoConfiguration io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.mongo.MongoClientInstrumentationSpringBoot4AutoConfiguration io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.logging.OpenTelemetryAppenderAutoConfiguration