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/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 55992570b55f..5540d553414a 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 diff --git a/instrumentation/spring/spring-boot-autoconfigure/build.gradle.kts b/instrumentation/spring/spring-boot-autoconfigure/build.gradle.kts index 7cb26e130c46..2e71d2a2100d 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 { @@ -99,6 +107,9 @@ 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") // needed for the Spring Boot 3 support implementation(project(":instrumentation:spring:spring-webmvc:spring-webmvc-6.0:library")) @@ -110,15 +121,19 @@ 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")) - // 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 + // Spring Boot 4 + add("javaSpring4CompileOnly", files(sourceSets.main.get().output.classesDirs)) + 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", "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")) + add("javaSpring4CompileOnly", project(":instrumentation:micrometer:micrometer-1.5:library")) } val latestDepTest = findProperty("testLatestDeps") as Boolean @@ -172,17 +187,62 @@ 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")) + // 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") + } + } + 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") + } + } + + val testSpring4 by registering(JvmTestSuite::class) { + dependencies { + implementation(project()) + implementation("io.opentelemetry:opentelemetry-sdk-extension-autoconfigure") + 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:$version") + runtimeOnly("com.h2database:h2:1.4.197") + runtimeOnly("io.r2dbc:r2dbc-h2:1.0.0.RELEASE") } } @@ -225,22 +285,50 @@ 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 = testSpring3 + } + named("testSpring3") { 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/jdbc/JdbcInstrumentationAutoConfiguration.java b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/jdbc/JdbcInstrumentationAutoConfiguration.java index 1732c3be178d..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 @@ -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.ConditionalOnClass; import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -23,6 +24,7 @@ @ConditionalOnEnabledInstrumentation(module = "jdbc") @AutoConfiguration(after = DataSourceAutoConfiguration.class) @ConditionalOnBean({DataSource.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..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 @@ -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,17 +25,11 @@ @ConditionalOnClass({ KafkaTemplate.class, ConcurrentKafkaListenerContainerFactory.class, - DefaultKafkaProducerFactoryCustomizer.class }) @Configuration public class KafkaInstrumentationAutoConfiguration { - @Bean - DefaultKafkaProducerFactoryCustomizer otelKafkaProducerFactoryCustomizer( - OpenTelemetry openTelemetry) { - KafkaTelemetry kafkaTelemetry = KafkaTelemetry.create(openTelemetry); - return producerFactory -> producerFactory.addPostProcessor(kafkaTelemetry::wrap); - } + public KafkaInstrumentationAutoConfiguration() {} @Bean static SpringKafkaTelemetry getTelemetry( 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..1e0807333055 --- /dev/null +++ b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/kafka/ProducerFactoryCustomizerConfiguration.java @@ -0,0 +1,38 @@ +/* + * 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; +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") +// package changed in Spring Boot 4 +@ConditionalOnClass({ + DefaultKafkaProducerFactoryCustomizer.class, + DefaultKafkaProducerFactory.class +}) +@Configuration +public class ProducerFactoryCustomizerConfiguration { + + public ProducerFactoryCustomizerConfiguration() {} + + @Bean + 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/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/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/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..ab8ad9cdf99f --- /dev/null +++ b/instrumentation/spring/spring-boot-autoconfigure/src/main/javaSpring4/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/jdbc/JdbcInstrumentationSpringBoot4AutoConfiguration.java @@ -0,0 +1,41 @@ +/* + * 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.ConditionalOnClass; +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}) +@Configuration(proxyBeanMethods = false) +@ConditionalOnClass(DataSourceAutoConfiguration.class) +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/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..d40a3945395d --- /dev/null +++ b/instrumentation/spring/spring-boot-autoconfigure/src/main/javaSpring4/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/kafka/ProducerFactoryCustomizerSpringBoot4Configuration.java @@ -0,0 +1,37 @@ +/* + * 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; +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, + DefaultKafkaProducerFactory.class +}) // package changed in 4+ +@Configuration +public class ProducerFactoryCustomizerSpringBoot4Configuration { + + public ProducerFactoryCustomizerSpringBoot4Configuration() {} + + @Bean + 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/micrometer/MicrometerBridgeSpringBoot4AutoConfiguration.java b/instrumentation/spring/spring-boot-autoconfigure/src/main/javaSpring4/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/micrometer/MicrometerBridgeSpringBoot4AutoConfiguration.java new file mode 100644 index 000000000000..f3b381d01fbe --- /dev/null +++ b/instrumentation/spring/spring-boot-autoconfigure/src/main/javaSpring4/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/micrometer/MicrometerBridgeSpringBoot4AutoConfiguration.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.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; + +/** + * 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, MetricsAutoConfiguration.class}) +@Configuration +public class MicrometerBridgeSpringBoot4AutoConfiguration { + + @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/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..09dcf88b3480 --- /dev/null +++ b/instrumentation/spring/spring-boot-autoconfigure/src/main/javaSpring4/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/mongo/MongoClientInstrumentationSpringBoot4AutoConfiguration.java @@ -0,0 +1,40 @@ +/* + * 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.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") +@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/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.factories b/instrumentation/spring/spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories index aa3f1300ec31..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,7 +2,10 @@ 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,\ 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 e8c5e8a83d7a..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,12 +1,18 @@ 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 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 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/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 new file mode 100644 index 000000000000..6249446b849a --- /dev/null +++ b/instrumentation/spring/spring-boot-autoconfigure/src/testSpring2/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/jdbc/JdbcInstrumentationAutoConfigurationTest.java @@ -0,0 +1,31 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.jdbc; + +import io.opentelemetry.instrumentation.spring.autoconfigure.internal.AbstractJdbcInstrumentationAutoConfigurationTest; +import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; +import io.opentelemetry.instrumentation.testing.junit.LibraryInstrumentationExtension; +import org.junit.jupiter.api.extension.RegisterExtension; +import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; + +class JdbcInstrumentationAutoConfigurationTest + extends AbstractJdbcInstrumentationAutoConfigurationTest { + + @RegisterExtension + static final LibraryInstrumentationExtension testing = LibraryInstrumentationExtension.create(); + + @Override + protected InstrumentationExtension testing() { + return testing; + } + + @Override + 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/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..acd6506750c1 --- /dev/null +++ b/instrumentation/spring/spring-boot-autoconfigure/src/testSpring2/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/kafka/KafkaInstrumentationAutoConfigurationTest.java @@ -0,0 +1,41 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +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, 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/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 new file mode 100644 index 000000000000..5d244c0584db --- /dev/null +++ b/instrumentation/spring/spring-boot-autoconfigure/src/testSpring2/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/micrometer/MicrometerBridgeAutoConfigurationTest.java @@ -0,0 +1,29 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.micrometer; + +import io.micrometer.core.instrument.MeterRegistry; +import io.opentelemetry.instrumentation.spring.autoconfigure.internal.AbstractMicrometerBridgeAutoConfigurationTest; +import org.springframework.boot.actuate.autoconfigure.metrics.MetricsAutoConfiguration; +import org.springframework.boot.autoconfigure.AutoConfigurations; + +class MicrometerBridgeAutoConfigurationTest extends AbstractMicrometerBridgeAutoConfigurationTest { + + @Override + protected AutoConfigurations autoConfigurations() { + return AutoConfigurations.of(MicrometerBridgeAutoConfiguration.class); + } + + @Override + protected Class getMetricsAutoConfigurationClass() { + return MetricsAutoConfiguration.class; + } + + @Override + protected Class getMeterRegistryClass() { + return MeterRegistry.class; + } +} 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 new file mode 100644 index 000000000000..5ec97f1d60b2 --- /dev/null +++ b/instrumentation/spring/spring-boot-autoconfigure/src/testSpring2/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/r2dbc/R2DbcInstrumentationAutoConfigurationTest.java @@ -0,0 +1,30 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.r2dbc; + +import io.opentelemetry.instrumentation.spring.autoconfigure.internal.AbstractR2DbcInstrumentationAutoConfigurationTest; +import io.opentelemetry.instrumentation.testing.junit.LibraryInstrumentationExtension; +import org.junit.jupiter.api.extension.RegisterExtension; +import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.autoconfigure.r2dbc.R2dbcAutoConfiguration; + +class R2DbcInstrumentationAutoConfigurationTest + extends AbstractR2DbcInstrumentationAutoConfigurationTest { + + @RegisterExtension + static final LibraryInstrumentationExtension testing = LibraryInstrumentationExtension.create(); + + @Override + protected LibraryInstrumentationExtension testing() { + return testing; + } + + @Override + 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 new file mode 100644 index 000000000000..5bb6edcd0436 --- /dev/null +++ b/instrumentation/spring/spring-boot-autoconfigure/src/testSpring2/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/web/SpringWebInstrumentationAutoConfigurationTest.java @@ -0,0 +1,18 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.web; + +import io.opentelemetry.instrumentation.spring.autoconfigure.internal.AbstractSpringWebInstrumentationAutoConfigurationTest; +import org.springframework.boot.autoconfigure.AutoConfigurations; + +class SpringWebInstrumentationAutoConfigurationTest + extends AbstractSpringWebInstrumentationAutoConfigurationTest { + + @Override + 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 new file mode 100644 index 000000000000..443da8425b71 --- /dev/null +++ b/instrumentation/spring/spring-boot-autoconfigure/src/testSpring4/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/jdbc/JdbcInstrumentationAutoConfigurationTest.java @@ -0,0 +1,31 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.jdbc; + +import io.opentelemetry.instrumentation.spring.autoconfigure.internal.AbstractJdbcInstrumentationAutoConfigurationTest; +import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; +import io.opentelemetry.instrumentation.testing.junit.LibraryInstrumentationExtension; +import org.junit.jupiter.api.extension.RegisterExtension; +import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.jdbc.autoconfigure.DataSourceAutoConfiguration; + +class JdbcInstrumentationAutoConfigurationTest + extends AbstractJdbcInstrumentationAutoConfigurationTest { + + @RegisterExtension + static final LibraryInstrumentationExtension testing = LibraryInstrumentationExtension.create(); + + @Override + protected InstrumentationExtension testing() { + return testing; + } + + @Override + 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 new file mode 100644 index 000000000000..a267ef94a764 --- /dev/null +++ b/instrumentation/spring/spring-boot-autoconfigure/src/testSpring4/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/kafka/KafkaInstrumentationAutoConfigurationTest.java @@ -0,0 +1,42 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +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.kafka.autoconfigure.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, + ProducerFactoryCustomizerSpringBoot4Configuration.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/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..e8001b510eb6 --- /dev/null +++ b/instrumentation/spring/spring-boot-autoconfigure/src/testSpring4/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/micrometer/MicrometerBridgeAutoConfigurationTest.java @@ -0,0 +1,29 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.micrometer; + +import io.micrometer.core.instrument.MeterRegistry; +import io.opentelemetry.instrumentation.spring.autoconfigure.internal.AbstractMicrometerBridgeAutoConfigurationTest; +import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.micrometer.metrics.autoconfigure.MetricsAutoConfiguration; + +class MicrometerBridgeAutoConfigurationTest extends AbstractMicrometerBridgeAutoConfigurationTest { + + @Override + protected AutoConfigurations autoConfigurations() { + return AutoConfigurations.of(MicrometerBridgeSpringBoot4AutoConfiguration.class); + } + + @Override + protected Class getMetricsAutoConfigurationClass() { + return MetricsAutoConfiguration.class; + } + + @Override + protected Class getMeterRegistryClass() { + return MeterRegistry.class; + } +} 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..ba1c4140faa1 --- /dev/null +++ b/instrumentation/spring/spring-boot-autoconfigure/src/testSpring4/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/r2dbc/R2DbcInstrumentationAutoConfigurationTest.java @@ -0,0 +1,30 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.r2dbc; + +import io.opentelemetry.instrumentation.spring.autoconfigure.internal.AbstractR2DbcInstrumentationAutoConfigurationTest; +import io.opentelemetry.instrumentation.testing.junit.LibraryInstrumentationExtension; +import org.junit.jupiter.api.extension.RegisterExtension; +import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.r2dbc.autoconfigure.R2dbcAutoConfiguration; + +class R2DbcInstrumentationAutoConfigurationTest + extends AbstractR2DbcInstrumentationAutoConfigurationTest { + + @RegisterExtension + static final LibraryInstrumentationExtension testing = LibraryInstrumentationExtension.create(); + + @Override + protected LibraryInstrumentationExtension testing() { + return testing; + } + + @Override + 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 new file mode 100644 index 000000000000..ac272af96341 --- /dev/null +++ b/instrumentation/spring/spring-boot-autoconfigure/src/testSpring4/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/web/SpringWebInstrumentationAutoConfigurationTest.java @@ -0,0 +1,18 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.web; + +import io.opentelemetry.instrumentation.spring.autoconfigure.internal.AbstractSpringWebInstrumentationAutoConfigurationTest; +import org.springframework.boot.autoconfigure.AutoConfigurations; + +class SpringWebInstrumentationAutoConfigurationTest + extends AbstractSpringWebInstrumentationAutoConfigurationTest { + + @Override + protected AutoConfigurations autoConfigurations() { + return AutoConfigurations.of(SpringWebInstrumentationSpringBoot4AutoConfiguration.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..c00480a8fa28 --- /dev/null +++ b/instrumentation/spring/spring-boot-autoconfigure/testing/build.gradle.kts @@ -0,0 +1,20 @@ +plugins { + id("otel.java-conventions") +} + +val springBootVersion = "2.7.18" + +dependencies { + compileOnly("org.springframework.boot:spring-boot-restclient:4.0.0") + compileOnly("org.springframework.kafka:spring-kafka:2.9.0") + + compileOnly("org.springframework.boot:spring-boot-starter-test:$springBootVersion") + compileOnly("org.springframework.boot:spring-boot-starter-data-r2dbc:$springBootVersion") + + 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") +} 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/testing/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/AbstractJdbcInstrumentationAutoConfigurationTest.java similarity index 67% 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/testing/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/AbstractJdbcInstrumentationAutoConfigurationTest.java index a81051228f98..8c7b05f14e96 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/testing/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/AbstractJdbcInstrumentationAutoConfigurationTest.java @@ -3,50 +3,45 @@ * SPDX-License-Identifier: Apache-2.0 */ -package io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.jdbc; +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 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.LibraryInstrumentationExtension; +import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; 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 { +public abstract class AbstractJdbcInstrumentationAutoConfigurationTest { - @RegisterExtension - static final LibraryInstrumentationExtension testing = LibraryInstrumentationExtension.create(); + protected abstract InstrumentationExtension testing(); - private final ApplicationContextRunner runner = + protected abstract AutoConfigurations autoConfigurations(); + + 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); + () -> new ConfigPropertiesBridge(DefaultConfigProperties.createFromMap(emptyMap()))) + .withConfiguration(autoConfigurations()) + .withBean("openTelemetry", OpenTelemetry.class, testing()::getOpenTelemetry); @SuppressWarnings("deprecation") // using deprecated semconv @Test void statementSanitizerEnabledByDefault() { - runner.run( + contextRunner.run( context -> { DataSource dataSource = context.getBean(DataSource.class); @@ -63,10 +58,11 @@ void statementSanitizerEnabledByDefault() { } } - testing.waitAndAssertTraces( - trace -> - trace.hasSpansSatisfyingExactly( - span -> span.hasAttribute(maybeStable(DB_STATEMENT), "SELECT ?"))); + testing() + .waitAndAssertTraces( + trace -> + trace.hasSpansSatisfyingExactly( + span -> span.hasAttribute(maybeStable(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..c4e0b40a127c --- /dev/null +++ b/instrumentation/spring/spring-boot-autoconfigure/testing/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/AbstractKafkaInstrumentationAutoConfigurationTest.java @@ -0,0 +1,79 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +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.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( + InstrumentationConfig.class, + () -> new ConfigPropertiesBridge(DefaultConfigProperties.createFromMap(emptyMap()))) + .withConfiguration(autoConfigurations()) + .withBean("openTelemetry", OpenTelemetry.class, OpenTelemetry::noop); + + @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(); + }); + } + + @Test + void defaultConfiguration() { + contextRunner.run( + context -> { + assertThat(context.containsBean("otelKafkaProducerFactoryCustomizer")).isTrue(); + assertThat(context.containsBean("otelKafkaListenerContainerFactoryBeanPostProcessor")) + .isTrue(); + }); + } + + @Test + void defaultConfigurationWithFactoryTesting() { + contextRunner.run( + context -> { + assertThat(context.containsBean("otelKafkaProducerFactoryCustomizer")).isTrue(); + assertThat(context.containsBean("otelKafkaListenerContainerFactoryBeanPostProcessor")) + .isTrue(); + + factoryTestAssertion(context); + }); + } +} 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/testing/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/AbstractMicrometerBridgeAutoConfigurationTest.java similarity index 67% 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/testing/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/AbstractMicrometerBridgeAutoConfigurationTest.java index 5a32e2951d98..da3909f83902 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/testing/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/AbstractMicrometerBridgeAutoConfigurationTest.java @@ -3,55 +3,59 @@ * SPDX-License-Identifier: Apache-2.0 */ -package io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.micrometer; +package io.opentelemetry.instrumentation.spring.autoconfigure.internal; 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 { +public abstract class AbstractMicrometerBridgeAutoConfigurationTest { - private final ApplicationContextRunner runner = + 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.of(MicrometerBridgeAutoConfiguration.class)); + .withConfiguration(autoConfigurations()); @Test void metricsEnabled() { - runner - .withConfiguration(AutoConfigurations.of(MetricsAutoConfiguration.class)) + contextRunner + .withConfiguration(AutoConfigurations.of(getMetricsAutoConfigurationClass())) .withPropertyValues("otel.instrumentation.micrometer.enabled = true") .run( context -> - assertThat(context.getBean("otelMeterRegistry", MeterRegistry.class)) + assertThat(context.getBean("otelMeterRegistry", getMeterRegistryClass())) .isNotNull() .isInstanceOf(OpenTelemetryMeterRegistry.class)); } @Test void metricsDisabledByDefault() { - runner - .withConfiguration(AutoConfigurations.of(MetricsAutoConfiguration.class)) + contextRunner + .withConfiguration(AutoConfigurations.of(getMetricsAutoConfigurationClass())) .run(context -> assertThat(context.containsBean("otelMeterRegistry")).isFalse()); } @Test void metricsDisabled() { - runner - .withConfiguration(AutoConfigurations.of(MetricsAutoConfiguration.class)) + contextRunner + .withConfiguration(AutoConfigurations.of(getMetricsAutoConfigurationClass())) .withPropertyValues("otel.instrumentation.micrometer.enabled = false") .run(context -> assertThat(context.containsBean("otelMeterRegistry")).isFalse()); } @Test void noActuatorAutoConfiguration() { - runner + contextRunner .withPropertyValues("otel.instrumentation.micrometer.enabled = true") .run(context -> assertThat(context.containsBean("otelMeterRegistry")).isFalse()); } 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/testing/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/AbstractR2DbcInstrumentationAutoConfigurationTest.java similarity index 60% 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/testing/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/AbstractR2DbcInstrumentationAutoConfigurationTest.java index bed063c28d1f..a556d7e1f68c 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/testing/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/AbstractR2DbcInstrumentationAutoConfigurationTest.java @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -package io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.r2dbc; +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; @@ -15,33 +15,30 @@ 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 { +public abstract class AbstractR2DbcInstrumentationAutoConfigurationTest { - @RegisterExtension - static final LibraryInstrumentationExtension testing = LibraryInstrumentationExtension.create(); + protected abstract LibraryInstrumentationExtension testing(); - private final ApplicationContextRunner runner = + protected abstract AutoConfigurations autoConfigurations(); + + protected 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); + .withConfiguration(autoConfigurations()) + .withBean("openTelemetry", OpenTelemetry.class, testing()::getOpenTelemetry); @SuppressWarnings("deprecation") // using deprecated semconv @Test void statementSanitizerEnabledByDefault() { - runner.run( + contextRunner.run( context -> { DatabaseClient client = context.getBean(DatabaseClient.class); client @@ -51,18 +48,19 @@ void statementSanitizerEnabledByDefault() { .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 = ?"))); + 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/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/web/SpringWebInstrumentationAutoConfigurationTest.java b/instrumentation/spring/spring-boot-autoconfigure/testing/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/AbstractSpringWebInstrumentationAutoConfigurationTest.java similarity index 90% 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/testing/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/AbstractSpringWebInstrumentationAutoConfigurationTest.java index 8e08dec0ea20..4b9a339d38b7 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/testing/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/AbstractSpringWebInstrumentationAutoConfigurationTest.java @@ -3,12 +3,13 @@ * SPDX-License-Identifier: Apache-2.0 */ -package io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.web; +package io.opentelemetry.instrumentation.spring.autoconfigure.internal; 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; @@ -17,9 +18,11 @@ import org.springframework.boot.test.context.runner.ApplicationContextRunner; import org.springframework.web.client.RestTemplate; -class SpringWebInstrumentationAutoConfigurationTest { +public abstract class AbstractSpringWebInstrumentationAutoConfigurationTest { - private final ApplicationContextRunner contextRunner = + protected abstract AutoConfigurations autoConfigurations(); + + protected final ApplicationContextRunner contextRunner = new ApplicationContextRunner() .withBean(OpenTelemetry.class, OpenTelemetry::noop) .withBean( @@ -28,8 +31,7 @@ class SpringWebInstrumentationAutoConfigurationTest { new ConfigPropertiesBridge( DefaultConfigProperties.createFromMap(Collections.emptyMap()))) .withBean(RestTemplate.class, RestTemplate::new) - .withConfiguration( - AutoConfigurations.of(SpringWebInstrumentationAutoConfiguration.class)); + .withConfiguration(autoConfigurations()); /** * Tests that users create {@link RestTemplate} bean is instrumented. diff --git a/settings.gradle.kts b/settings.gradle.kts index c175394c2dd4..f3a564f2f16a 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.+") } } @@ -81,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) @@ -161,6 +163,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") @@ -613,6 +616,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") 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..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,7 +5,19 @@ package io.opentelemetry.spring.smoketest; +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 {} +class KafkaSpringStarterSmokeTest extends AbstractJvmKafkaSpringStarterSmokeTest { + @Override + protected Class kafkaProducerFactoryCustomizerClass() { + return ProducerFactoryCustomizerConfiguration.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-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/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..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,7 +5,19 @@ package io.opentelemetry.spring.smoketest; +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 {} +class KafkaSpringStarterSmokeTest extends AbstractJvmKafkaSpringStarterSmokeTest { + @Override + protected Class kafkaProducerFactoryCustomizerClass() { + return ProducerFactoryCustomizerConfiguration.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-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 new file mode 100644 index 000000000000..4bbd5bd6fe92 --- /dev/null +++ b/smoke-tests-otel-starter/spring-boot-4/build.gradle.kts @@ -0,0 +1,104 @@ +plugins { + id("otel.java-conventions") + alias(springBoot40.plugins.versions) + id("org.graalvm.buildtools.native") +} + +description = "smoke-tests-otel-starter-spring-boot-4" + +otelJava { + minJavaVersionSupported.set(JavaVersion.VERSION_17) +} + +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") + 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("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") + 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") + testImplementation(project(":instrumentation:spring:spring-boot-autoconfigure")) +} + +springBoot { + mainClass = "io.opentelemetry.spring.smoketest.OtelSpringStarterSmokeTestApplication" +} + +// Disable -Werror for Spring Framework 7.0 compatibility +tasks.withType().configureEach { + options.compilerArgs.removeAll(listOf("-Werror")) +} + +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) + } + + // 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 { + // 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 +} 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/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..9391bec94f58 --- /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 +@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 new file mode 100644 index 000000000000..f2b491893a16 --- /dev/null +++ b/smoke-tests-otel-starter/spring-boot-4/src/test/java/io/opentelemetry/spring/smoketest/KafkaSpringStarterSmokeTest.java @@ -0,0 +1,23 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.spring.smoketest; + +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 kafkaProducerFactoryCustomizerClass() { + return ProducerFactoryCustomizerSpringBoot4Configuration.class; + } + + @Override + 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 new file mode 100644 index 000000000000..be90f83ef9dd --- /dev/null +++ b/smoke-tests-otel-starter/spring-boot-4/src/test/java/io/opentelemetry/spring/smoketest/MongoSpringStarterSmokeTest.java @@ -0,0 +1,28 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.spring.smoketest; + +import io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.mongo.MongoClientInstrumentationSpringBoot4AutoConfiguration; +import org.junit.jupiter.api.condition.DisabledInNativeImage; +import org.springframework.boot.mongodb.autoconfigure.MongoAutoConfiguration; + +@DisabledInNativeImage // See GraalVmNativeMongodbSpringStarterSmokeTest for the GraalVM native test +class MongoSpringStarterSmokeTest extends AbstractJvmMongodbSpringStarterSmokeTest { + @Override + String getMongoUriProperty() { + return "spring.mongodb.uri"; + } + + @Override + Class mongoAutoConfigurationClass() { + return MongoAutoConfiguration.class; + } + + @Override + Class mongoInstrumentationAutoConfigurationClass() { + return MongoClientInstrumentationSpringBoot4AutoConfiguration.class; + } +} 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/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..29b9fb61b123 --- /dev/null +++ b/smoke-tests-otel-starter/spring-boot-4/src/test/java/io/opentelemetry/spring/smoketest/OtelSpringStarterSmokeTest.java @@ -0,0 +1,56 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.spring.smoketest; + +import java.net.URI; +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 = { + 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", + "logging.level.org.springframework.boot.autoconfigure=DEBUG", + }) +@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); + } +} 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(); + } +} 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..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 @@ -15,7 +15,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 +22,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 kafkaProducerFactoryCustomizerClass(); + + protected abstract Class kafkaAutoConfigurationClass(); + @BeforeAll static void setUpKafka() { kafka = @@ -53,8 +57,9 @@ void setUpContext() { OpenTelemetryAutoConfiguration.class, ThreadDetailsAutoConfiguration.class, SpringSmokeOtelConfiguration.class, - KafkaAutoConfiguration.class, + kafkaAutoConfigurationClass(), KafkaInstrumentationAutoConfiguration.class, + kafkaProducerFactoryCustomizerClass(), 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 a3d7bfc41e1d..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 @@ -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( @@ -272,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() { @@ -301,8 +319,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 +334,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 -> 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;