Skip to content
Open
Show file tree
Hide file tree
Changes from 25 commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
5b1236d
autoconfigure and smoke
jaydeluca Nov 25, 2025
38d6dc4
progress
jaydeluca Nov 25, 2025
ba32a8d
kafka
jaydeluca Nov 26, 2025
6c13ecb
kafka working
jaydeluca Nov 26, 2025
330cd89
most things working
jaydeluca Nov 26, 2025
f100658
fix more tests, exclude tests from latestdeps
jaydeluca Nov 26, 2025
9c6f987
micrometer fix
jaydeluca Nov 26, 2025
79629d2
add kafka test
jaydeluca Nov 26, 2025
30d0e2c
native and another micrometer issue
jaydeluca Nov 26, 2025
be14d94
fix codeql
jaydeluca Nov 26, 2025
6757a63
fix test on java 25
jaydeluca Nov 27, 2025
25b980b
Merge branch 'main' into spring-boot-4-autoconfigure
jaydeluca Nov 27, 2025
6562745
ignore transient kafka startup errors
jaydeluca Nov 27, 2025
7e75966
Merge branch 'spring-boot-4-autoconfigure' of github.com:jaydeluca/op…
jaydeluca Nov 27, 2025
f37f691
Merge branch 'main' into spring-boot-4-autoconfigure
jaydeluca Nov 27, 2025
a37c8a5
register both mongo configs
jaydeluca Nov 27, 2025
a406382
more smoke test changes
jaydeluca Nov 27, 2025
3113936
test fix
jaydeluca Nov 27, 2025
f86e06e
missing mongo
jaydeluca Nov 27, 2025
bc3519d
avoid using common to fix dependency issues
jaydeluca Nov 27, 2025
fcf6bce
add propagators and app file
jaydeluca Nov 27, 2025
cbc8b1e
only run native tests on java 25+
jaydeluca Nov 28, 2025
1a83d20
cleanup
jaydeluca Nov 28, 2025
c2d5230
cleanup
jaydeluca Nov 28, 2025
9ce1a80
Merge branch 'main' into spring-boot-4-autoconfigure
jaydeluca Nov 28, 2025
d17fed1
fix comment line breaks
jaydeluca Nov 28, 2025
36c991a
switch to using conditionalOnClass
jaydeluca Nov 29, 2025
10fef8c
abstract incompatible class
jaydeluca Nov 29, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/codeql.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -100,6 +108,8 @@ dependencies {
testImplementation("io.opentelemetry:opentelemetry-exporter-zipkin")
testImplementation(project(":instrumentation-annotations"))

latestDepTestLibrary("org.springframework.boot:spring-boot-starter-micrometer-metrics:latest.release")

// needed for the Spring Boot 3 support
implementation(project(":instrumentation:spring:spring-webmvc:spring-webmvc-6.0:library"))

Expand All @@ -110,15 +120,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
Expand Down Expand Up @@ -186,6 +200,28 @@ testing {
}
}

val testSpring4 by registering(JvmTestSuite::class) {
dependencies {
implementation(project())
implementation("io.opentelemetry:opentelemetry-sdk-extension-autoconfigure")
implementation("org.springframework.boot:spring-boot-starter-jdbc:4.0.0")
implementation("org.springframework.boot:spring-boot-restclient:4.0.0")
implementation("org.springframework.boot:spring-boot-starter-kafka:4.0.0")
implementation("org.springframework.boot:spring-boot-starter-actuator:4.0.0")
implementation("org.springframework.boot:spring-boot-starter-data-r2dbc:4.0.0")
implementation("org.springframework.boot:spring-boot-starter-micrometer-metrics:4.0.0")
implementation("io.opentelemetry:opentelemetry-sdk")
implementation("io.opentelemetry:opentelemetry-sdk-testing")
implementation(project(":instrumentation-api"))
implementation(project(":instrumentation:micrometer:micrometer-1.5:library"))
implementation("org.springframework.boot:spring-boot-starter-test:4.0.0") {
exclude("org.junit.vintage", "junit-vintage-engine")
}
runtimeOnly("com.h2database:h2:1.4.197")
runtimeOnly("io.r2dbc:r2dbc-h2:1.0.0.RELEASE")
}
}

val testDeclarativeConfig by registering(JvmTestSuite::class) {
dependencies {
implementation(project())
Expand All @@ -208,6 +244,15 @@ configurations.configureEach {
tasks {
compileTestJava {
options.compilerArgs.add("-parameters")

// Exclude Spring Boot specific tests from compilation when testLatestDeps is true
// These tests are covered by testSpring4 suite
if (latestDepTest) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sometimes when we have a separate suite for new version we limit the latest dep in base version to 1.+ and in version 2 suite either set the version to 2.0 or latest.release depending on the latest dep flag. Would that also work here? Or maybe it would help if these tests were moved to a separate suite and the main test directory would only contain tests that work on all versions?

exclude("**/micrometer/MicrometerBridgeAutoConfigurationTest.java")
exclude("**/r2dbc/R2DbcInstrumentationAutoConfigurationTest.java")
exclude("**/jdbc/JdbcInstrumentationAutoConfigurationTest.java")
exclude("**/web/SpringWebInstrumentationAutoConfigurationTest.java")
}
}

withType<Test>().configureEach {
Expand All @@ -218,6 +263,17 @@ tasks {
jvmArgs("-XX:+IgnoreUnrecognizedVMOptions")
}

test {
// Exclude Spring Boot specific tests when testLatestDeps is true
// These tests are covered by testSpring4 suite
if (latestDepTest) {
exclude("**/micrometer/MicrometerBridgeAutoConfigurationTest.class")
exclude("**/r2dbc/R2DbcInstrumentationAutoConfigurationTest.class")
exclude("**/jdbc/JdbcInstrumentationAutoConfigurationTest.class")
exclude("**/web/SpringWebInstrumentationAutoConfigurationTest.class")
}
}

named<JavaCompile>("compileJavaSpring3Java") {
sourceCompatibility = "17"
targetCompatibility = "17"
Expand All @@ -234,12 +290,30 @@ tasks {
isEnabled = testSpring3
}

named<JavaCompile>("compileJavaSpring4Java") {
sourceCompatibility = "17"
targetCompatibility = "17"
options.release.set(17)
}

named<JavaCompile>("compileTestSpring4Java") {
sourceCompatibility = "17"
targetCompatibility = "17"
options.release.set(17)
}

named<Test>("testSpring4") {
isEnabled = testSpring3 // same condition as Spring 3 (requires Java 17+)
}

named<Jar>("jar") {
from(sourceSets["javaSpring3"].output)
from(sourceSets["javaSpring4"].output)
}

named<Jar>("sourcesJar") {
from(sourceSets["javaSpring3"].java)
from(sourceSets["javaSpring4"].java)
}

val testStableSemconv by registering(Test::class) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
Expand All @@ -23,6 +24,8 @@
@ConditionalOnEnabledInstrumentation(module = "jdbc")
@AutoConfiguration(after = DataSourceAutoConfiguration.class)
@ConditionalOnBean({DataSource.class})
@ConditionalOnMissingClass(
"org.springframework.boot.jdbc.autoconfigure.DataSourceAutoConfiguration") // Spring Boot 4+
@Configuration(proxyBeanMethods = false)
public class JdbcInstrumentationAutoConfiguration {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import io.opentelemetry.instrumentation.spring.kafka.v2_7.SpringKafkaTelemetry;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.kafka.DefaultKafkaProducerFactoryCustomizer;
import org.springframework.context.annotation.Bean;
Expand All @@ -29,6 +30,9 @@
ConcurrentKafkaListenerContainerFactory.class,
DefaultKafkaProducerFactoryCustomizer.class
})
@ConditionalOnMissingClass(
"org.springframework.boot.kafka.autoconfigure.DefaultKafkaProducerFactoryCustomizer") // Spring
// Boot 4+
@Configuration
public class KafkaInstrumentationAutoConfiguration {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import io.opentelemetry.instrumentation.spring.autoconfigure.internal.ConditionalOnEnabledInstrumentation;
import io.opentelemetry.instrumentation.spring.autoconfigure.internal.properties.InstrumentationConfigUtil;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass;
import org.springframework.boot.autoconfigure.mongo.MongoClientSettingsBuilderCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
Expand All @@ -22,6 +23,10 @@
*/
@ConditionalOnClass({MongoClientSettings.class, MongoClientSettingsBuilderCustomizer.class})
@ConditionalOnEnabledInstrumentation(module = "mongo")
@ConditionalOnMissingClass(
"org.springframework.boot.mongodb.autoconfigure.MongoClientSettingsBuilderCustomizer") // Spring
// Boot
// 4+
@Configuration
public class MongoClientInstrumentationAutoConfiguration {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import io.opentelemetry.instrumentation.spring.autoconfigure.internal.ConditionalOnEnabledInstrumentation;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass;
import org.springframework.boot.web.client.RestTemplateCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
Expand All @@ -25,6 +26,8 @@
*/
@ConditionalOnEnabledInstrumentation(module = "spring-web")
@ConditionalOnClass({RestTemplate.class, RestTemplateCustomizer.class})
@ConditionalOnMissingClass(
"org.springframework.boot.restclient.RestTemplateCustomizer") // Spring Boot 4+
@Configuration
public class SpringWebInstrumentationAutoConfiguration {

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.jdbc;

import io.opentelemetry.api.OpenTelemetry;
import io.opentelemetry.instrumentation.api.incubator.config.internal.InstrumentationConfig;
import io.opentelemetry.instrumentation.spring.autoconfigure.internal.ConditionalOnEnabledInstrumentation;
import javax.sql.DataSource;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass;
import org.springframework.boot.jdbc.autoconfigure.DataSourceAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
* This class is internal and is hence not for public use. Its APIs are unstable and can change at
* any time.
*/
@ConditionalOnEnabledInstrumentation(module = "jdbc")
@AutoConfiguration(after = DataSourceAutoConfiguration.class)
@ConditionalOnBean({DataSource.class})
@Configuration(proxyBeanMethods = false)
@ConditionalOnMissingClass(
"org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration") // Spring Boot 2 & 3
public class JdbcInstrumentationSpringBoot4AutoConfiguration {

// For error prone
public JdbcInstrumentationSpringBoot4AutoConfiguration() {}

@Bean
// static to avoid "is not eligible for getting processed by all BeanPostProcessors" warning
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not really for this PR but I suspect that (proxyBeanMethods = false) has the same effect as making the method static. If I remember correctly the warning was triggered by proxying the configuration class methods.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you're right - that would be a nice follow-up PR

static DataSourcePostProcessor dataSourcePostProcessor(
ObjectProvider<OpenTelemetry> openTelemetryProvider,
ObjectProvider<InstrumentationConfig> configProvider) {
return new DataSourcePostProcessor(openTelemetryProvider, configProvider);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.kafka;

import io.opentelemetry.api.OpenTelemetry;
import io.opentelemetry.instrumentation.api.incubator.config.internal.InstrumentationConfig;
import io.opentelemetry.instrumentation.kafkaclients.v2_6.KafkaTelemetry;
import io.opentelemetry.instrumentation.spring.autoconfigure.internal.ConditionalOnEnabledInstrumentation;
import io.opentelemetry.instrumentation.spring.kafka.v2_7.SpringKafkaTelemetry;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.kafka.autoconfigure.DefaultKafkaProducerFactoryCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.kafka.config.ConcurrentKafkaListenerContainerFactory;
import org.springframework.kafka.core.KafkaTemplate;

/**
* This class is internal and is hence not for public use. Its APIs are unstable and can change at
* any time.
*/
@ConditionalOnEnabledInstrumentation(module = "kafka")
@ConditionalOnClass({
KafkaTemplate.class,
ConcurrentKafkaListenerContainerFactory.class,
DefaultKafkaProducerFactoryCustomizer.class
})
@ConditionalOnMissingBean(name = "otelKafkaProducerFactoryCustomizer")
@ConditionalOnMissingClass(
"org.springframework.boot.autoconfigure.kafka.DefaultKafkaProducerFactoryCustomizer") // Spring
// Boot 2
// & 3
@Configuration
public class KafkaInstrumentationSpringBoot4AutoConfiguration {

@Bean
DefaultKafkaProducerFactoryCustomizer otelKafkaProducerFactoryCustomizer(
OpenTelemetry openTelemetry) {
KafkaTelemetry kafkaTelemetry = KafkaTelemetry.create(openTelemetry);
return producerFactory -> producerFactory.addPostProcessor(kafkaTelemetry::wrap);
}
Comment on lines +37 to +42
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

actually only this bean is different for spring boot 4. Wondering whether it would make sense to move this bean into separate (maybe nested?) configuration class so that the other beans wouldn't need to be copy-pasted. Or is that too much effort?

Copy link
Member

@zeitlinger zeitlinger Dec 1, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the imports are also different for the others

image


@Bean
static SpringKafkaTelemetry getTelemetry(
ObjectProvider<OpenTelemetry> openTelemetryProvider,
ObjectProvider<InstrumentationConfig> configProvider) {
return SpringKafkaTelemetry.builder(openTelemetryProvider.getObject())
.setCaptureExperimentalSpanAttributes(
configProvider
.getObject()
.getBoolean("otel.instrumentation.kafka.experimental-span-attributes", false))
.build();
}

// static to avoid "is not eligible for getting processed by all BeanPostProcessors" warning
@Bean
@ConditionalOnProperty(
name = "otel.instrumentation.kafka.autoconfigure-interceptor",
havingValue = "true",
matchIfMissing = true)
@ConditionalOnMissingBean
static ConcurrentKafkaListenerContainerFactoryPostProcessor
otelKafkaListenerContainerFactoryBeanPostProcessor(
ObjectProvider<OpenTelemetry> openTelemetryProvider,
ObjectProvider<InstrumentationConfig> configProvider) {
return new ConcurrentKafkaListenerContainerFactoryPostProcessor(
() -> getTelemetry(openTelemetryProvider, configProvider));
}
}
Original file line number Diff line number Diff line change
@@ -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();
}
}
Loading
Loading