Add automatic JDBC instrumentation to the OTel Spring starter (#11258)

This commit is contained in:
Jean Bisutti 2024-05-02 19:29:21 +02:00 committed by GitHub
parent 7aca36f089
commit a9a97c0518
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 618 additions and 76 deletions

View File

@ -22,7 +22,6 @@ import io.opentelemetry.sdk.autoconfigure.spi.ResourceProvider;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.ConfigurationPropertiesBinding;
@ -86,8 +85,7 @@ public class OpenTelemetryAutoConfiguration {
OtlpExporterProperties otlpExporterProperties,
OtelResourceProperties resourceProperties,
PropagationProperties propagationProperties,
OpenTelemetrySdkComponentLoader componentLoader,
ObjectProvider<OpenTelemetryInjector> openTelemetryConsumerProvider) {
OpenTelemetrySdkComponentLoader componentLoader) {
OpenTelemetry openTelemetry =
AutoConfigureUtil.setComponentLoader(
@ -104,8 +102,6 @@ public class OpenTelemetryAutoConfiguration {
.build()
.getOpenTelemetrySdk();
openTelemetryConsumerProvider.forEach(consumer -> consumer.accept(openTelemetry));
return openTelemetry;
}
}

View File

@ -1,12 +0,0 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.instrumentation.spring.autoconfigure;
import io.opentelemetry.api.OpenTelemetry;
import java.util.function.Consumer;
/** To inject an OpenTelemetry bean into non-Spring components */
public interface OpenTelemetryInjector extends Consumer<OpenTelemetry> {}

View File

@ -0,0 +1,41 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.instrumentation.spring.autoconfigure.instrumentation.jdbc;
import com.google.errorprone.annotations.CanIgnoreReturnValue;
import io.opentelemetry.api.OpenTelemetry;
import io.opentelemetry.instrumentation.jdbc.datasource.JdbcTelemetry;
import javax.sql.DataSource;
import org.springframework.aop.scope.ScopedProxyUtils;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.core.Ordered;
final class DataSourcePostProcessor implements BeanPostProcessor, Ordered {
private final ObjectProvider<OpenTelemetry> openTelemetryProvider;
DataSourcePostProcessor(ObjectProvider<OpenTelemetry> openTelemetryProvider) {
this.openTelemetryProvider = openTelemetryProvider;
}
@CanIgnoreReturnValue
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) {
// Exclude scoped proxy beans to avoid double wrapping
if (bean instanceof DataSource && !ScopedProxyUtils.isScopedTarget(beanName)) {
DataSource dataSource = (DataSource) bean;
return JdbcTelemetry.create(openTelemetryProvider.getObject()).wrap(dataSource);
}
return bean;
}
// To be one of the first bean post-processors to be executed
@Override
public int getOrder() {
return Ordered.LOWEST_PRECEDENCE - 20;
}
}

View File

@ -0,0 +1,36 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.instrumentation.spring.autoconfigure.instrumentation.jdbc;
import io.opentelemetry.api.OpenTelemetry;
import io.opentelemetry.instrumentation.spring.autoconfigure.internal.SdkEnabled;
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.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
@ConditionalOnProperty(name = "otel.instrumentation.jdbc.enabled", matchIfMissing = true)
@AutoConfiguration(after = DataSourceAutoConfiguration.class)
@ConditionalOnBean({DataSource.class})
@Conditional(SdkEnabled.class)
@Configuration(proxyBeanMethods = false)
public class JdbcInstrumentationAutoconfiguration {
// For error prone
public JdbcInstrumentationAutoconfiguration() {}
@Bean
// static to avoid "is not eligible for getting processed by all BeanPostProcessors" warning
static DataSourcePostProcessor dataSourcePostProcessor(
ObjectProvider<OpenTelemetry> openTelemetryProvider) {
return new DataSourcePostProcessor(openTelemetryProvider);
}
}

View File

@ -1,47 +0,0 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.instrumentation.spring.autoconfigure.instrumentation.jdbc;
import io.opentelemetry.instrumentation.jdbc.OpenTelemetryDriver;
import io.opentelemetry.instrumentation.spring.autoconfigure.OpenTelemetryInjector;
import io.opentelemetry.instrumentation.spring.autoconfigure.internal.SdkEnabled;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
@ConditionalOnClass(OpenTelemetryDriver.class)
@ConditionalOnProperty(
name = "spring.datasource.driver-class-name",
havingValue = "io.opentelemetry.instrumentation.jdbc.OpenTelemetryDriver")
@Conditional(SdkEnabled.class)
@Configuration(proxyBeanMethods = false)
@AutoConfigureAfter(
name = "org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration")
@ConditionalOnBean(name = "org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration")
public class OpenTelemetryJdbcDriverAutoConfiguration {
@Bean
OpenTelemetryInjector injectOtelIntoJdbcDriver() {
return openTelemetry -> OpenTelemetryDriver.install(openTelemetry);
}
// To be sure OpenTelemetryDriver knows the OpenTelemetry bean before the initialization of the
// database connection pool
// See org.springframework.boot.autoconfigure.jdbc.DataSourceConfiguration and
// io.opentelemetry.instrumentation.spring.autoconfigure.OpenTelemetryAutoConfiguration
@Bean
BeanFactoryPostProcessor openTelemetryBeanCreatedBeforeDatasourceBean() {
return configurableBeanFactory -> {
BeanDefinition dataSourceBean = configurableBeanFactory.getBeanDefinition("dataSource");
dataSourceBean.setDependsOn("openTelemetry");
};
}
}

View File

@ -3,7 +3,7 @@ io.opentelemetry.instrumentation.spring.autoconfigure.OpenTelemetryAutoConfigura
io.opentelemetry.instrumentation.spring.autoconfigure.instrumentation.annotations.InstrumentationAnnotationsAutoConfiguration,\
io.opentelemetry.instrumentation.spring.autoconfigure.instrumentation.kafka.KafkaInstrumentationAutoConfiguration,\
io.opentelemetry.instrumentation.spring.autoconfigure.instrumentation.logging.OpenTelemetryAppenderAutoConfiguration,\
io.opentelemetry.instrumentation.spring.autoconfigure.instrumentation.jdbc.OpenTelemetryJdbcDriverAutoConfiguration,\
io.opentelemetry.instrumentation.spring.autoconfigure.instrumentation.jdbc.JdbcInstrumentationAutoconfiguration,\
io.opentelemetry.instrumentation.spring.autoconfigure.instrumentation.micrometer.MicrometerBridgeAutoConfiguration,\
io.opentelemetry.instrumentation.spring.autoconfigure.instrumentation.web.SpringWebInstrumentationAutoConfiguration,\
io.opentelemetry.instrumentation.spring.autoconfigure.instrumentation.webflux.SpringWebfluxInstrumentationAutoConfiguration,\

View File

@ -2,7 +2,7 @@ io.opentelemetry.instrumentation.spring.autoconfigure.OpenTelemetryAutoConfigura
io.opentelemetry.instrumentation.spring.autoconfigure.instrumentation.annotations.InstrumentationAnnotationsAutoConfiguration
io.opentelemetry.instrumentation.spring.autoconfigure.instrumentation.kafka.KafkaInstrumentationAutoConfiguration
io.opentelemetry.instrumentation.spring.autoconfigure.instrumentation.logging.OpenTelemetryAppenderAutoConfiguration
io.opentelemetry.instrumentation.spring.autoconfigure.instrumentation.jdbc.OpenTelemetryJdbcDriverAutoConfiguration
io.opentelemetry.instrumentation.spring.autoconfigure.instrumentation.jdbc.JdbcInstrumentationAutoconfiguration
io.opentelemetry.instrumentation.spring.autoconfigure.instrumentation.micrometer.MicrometerBridgeAutoConfiguration
io.opentelemetry.instrumentation.spring.autoconfigure.instrumentation.web.SpringWebInstrumentationAutoConfiguration
io.opentelemetry.instrumentation.spring.autoconfigure.instrumentation.webflux.SpringWebfluxInstrumentationAutoConfiguration

View File

@ -15,7 +15,6 @@ dependencies {
implementation("org.springframework.boot:spring-boot-starter-data-jdbc")
runtimeOnly("com.h2database:h2")
implementation("org.apache.commons:commons-dbcp2")
implementation(project(":instrumentation:jdbc:library"))
implementation("org.springframework.kafka:spring-kafka") // not tested here, just make sure there are no warnings when it's included
implementation("io.opentelemetry:opentelemetry-extension-trace-propagators")
implementation(project(":instrumentation:spring:starters:spring-boot-starter"))

View File

@ -6,7 +6,6 @@
package io.opentelemetry.spring.smoketest;
import io.opentelemetry.api.OpenTelemetry;
import io.opentelemetry.instrumentation.jdbc.datasource.JdbcTelemetry;
import javax.sql.DataSource;
import org.apache.commons.dbcp2.BasicDataSource;
import org.springframework.context.annotation.Bean;
@ -14,7 +13,7 @@ import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
@Configuration
@Profile("!jdbc-driver-config")
@Profile("user-has-defined-datasource-bean")
public class DatasourceConfig {
@Bean
@ -24,6 +23,6 @@ public class DatasourceConfig {
dataSource.setUrl("jdbc:h2:mem:db");
dataSource.setUsername("username");
dataSource.setPassword("pwd");
return JdbcTelemetry.create(openTelemetry).wrap(dataSource);
return dataSource;
}
}

View File

@ -11,4 +11,538 @@
],
"name": "org.apache.commons.pool2.impl.DefaultEvictionPolicy"
}
,
{
"condition": {
"typeReachable": "com.zaxxer.hikari.util.ConcurrentBag"
},
"name": "[Lcom.zaxxer.hikari.util.ConcurrentBag$IConcurrentBagEntry;"
},
{
"condition": {
"typeReachable": "com.zaxxer.hikari.pool.PoolEntry"
},
"name": "[Ljava.sql.Statement;"
},
{
"condition": {
"typeReachable": "com.zaxxer.hikari.util.FastList"
},
"name": "[Ljava.sql.Statement;"
},
{
"condition": {
"typeReachable": "com.zaxxer.hikari.HikariConfig"
},
"name": "com.zaxxer.hikari.HikariConfig",
"allDeclaredFields": true,
"queryAllPublicMethods": true,
"methods": [
{
"name": "setAutoCommit",
"parameterTypes": [
"boolean"
]
},
{
"name": "setConnectionTestQuery",
"parameterTypes": [
"java.lang.String"
]
},
{
"name": "setDataSourceClassName",
"parameterTypes": [
"java.lang.String"
]
},
{
"name": "setMinimumIdle",
"parameterTypes": [
"int"
]
}
]
},
{
"condition": {
"typeReachable": "com.zaxxer.hikari.HikariJNDIFactory"
},
"name": "com.zaxxer.hikari.HikariConfig",
"queryAllPublicMethods": true,
"methods": [
{
"name": "setDataSourceJNDI",
"parameterTypes": [
"java.lang.String"
]
},
{
"name": "setDriverClassName",
"parameterTypes": [
"java.lang.String"
]
},
{
"name": "setJdbcUrl",
"parameterTypes": [
"java.lang.String"
]
},
{
"name": "setMaxLifetime",
"parameterTypes": [
"long"
]
},
{
"name": "setMaximumPoolSize",
"parameterTypes": [
"int"
]
},
{
"name": "setPassword",
"parameterTypes": [
"java.lang.String"
]
},
{
"name": "setUsername",
"parameterTypes": [
"java.lang.String"
]
}
]
},
{
"condition": {
"typeReachable": "com.zaxxer.hikari.hibernate.HikariConfigurationUtil"
},
"name": "com.zaxxer.hikari.HikariConfig",
"queryAllPublicMethods": true,
"methods": [
{
"name": "setAutoCommit",
"parameterTypes": [
"boolean"
]
},
{
"name": "setConnectionTestQuery",
"parameterTypes": [
"java.lang.String"
]
},
{
"name": "setDataSourceClassName",
"parameterTypes": [
"java.lang.String"
]
}
]
},
{
"condition": {
"typeReachable": "com.zaxxer.hikari.util.PropertyElf"
},
"name": "com.zaxxer.hikari.HikariConfig",
"methods": [
{
"name": "getCatalog",
"parameterTypes": []
},
{
"name": "getConnectionInitSql",
"parameterTypes": []
},
{
"name": "getConnectionTestQuery",
"parameterTypes": []
},
{
"name": "getConnectionTimeout",
"parameterTypes": []
},
{
"name": "getDataSource",
"parameterTypes": []
},
{
"name": "getDataSourceClassName",
"parameterTypes": []
},
{
"name": "getDataSourceJNDI",
"parameterTypes": []
},
{
"name": "getDataSourceProperties",
"parameterTypes": []
},
{
"name": "getDriverClassName",
"parameterTypes": []
},
{
"name": "getExceptionOverrideClassName",
"parameterTypes": []
},
{
"name": "getHealthCheckProperties",
"parameterTypes": []
},
{
"name": "getHealthCheckRegistry",
"parameterTypes": []
},
{
"name": "getIdleTimeout",
"parameterTypes": []
},
{
"name": "getInitializationFailTimeout",
"parameterTypes": []
},
{
"name": "getJdbcUrl",
"parameterTypes": []
},
{
"name": "getKeepaliveTime",
"parameterTypes": []
},
{
"name": "getLeakDetectionThreshold",
"parameterTypes": []
},
{
"name": "getMaxLifetime",
"parameterTypes": []
},
{
"name": "getMaximumPoolSize",
"parameterTypes": []
},
{
"name": "getMetricRegistry",
"parameterTypes": []
},
{
"name": "getMetricsTrackerFactory",
"parameterTypes": []
},
{
"name": "getMinimumIdle",
"parameterTypes": []
},
{
"name": "getPassword",
"parameterTypes": []
},
{
"name": "getPoolName",
"parameterTypes": []
},
{
"name": "getScheduledExecutor",
"parameterTypes": []
},
{
"name": "getScheduledExecutorService",
"parameterTypes": []
},
{
"name": "getSchema",
"parameterTypes": []
},
{
"name": "getThreadFactory",
"parameterTypes": []
},
{
"name": "getTransactionIsolation",
"parameterTypes": []
},
{
"name": "getUsername",
"parameterTypes": []
},
{
"name": "getValidationTimeout",
"parameterTypes": []
},
{
"name": "isAllowPoolSuspension",
"parameterTypes": []
},
{
"name": "isAutoCommit",
"parameterTypes": []
},
{
"name": "isInitializationFailFast",
"parameterTypes": []
},
{
"name": "isIsolateInternalQueries",
"parameterTypes": []
},
{
"name": "isJdbc4ConnectionTest",
"parameterTypes": []
},
{
"name": "isReadOnly",
"parameterTypes": []
},
{
"name": "isRegisterMbeans",
"parameterTypes": []
}
],
"queriedMethods": [
{
"name": "setAllowPoolSuspension",
"parameterTypes": [
"boolean"
]
},
{
"name": "setAutoCommit",
"parameterTypes": [
"boolean"
]
},
{
"name": "setCatalog",
"parameterTypes": [
"java.lang.String"
]
},
{
"name": "setConnectionInitSql",
"parameterTypes": [
"java.lang.String"
]
},
{
"name": "setConnectionTestQuery",
"parameterTypes": [
"java.lang.String"
]
},
{
"name": "setConnectionTimeout",
"parameterTypes": [
"long"
]
},
{
"name": "setDataSource",
"parameterTypes": [
"javax.sql.DataSource"
]
},
{
"name": "setDataSourceClassName",
"parameterTypes": [
"java.lang.String"
]
},
{
"name": "setDataSourceJNDI",
"parameterTypes": [
"java.lang.String"
]
},
{
"name": "setDataSourceProperties",
"parameterTypes": [
"java.util.Properties"
]
},
{
"name": "setDriverClassName",
"parameterTypes": [
"java.lang.String"
]
},
{
"name": "setExceptionOverrideClassName",
"parameterTypes": [
"java.lang.String"
]
},
{
"name": "setHealthCheckProperties",
"parameterTypes": [
"java.util.Properties"
]
},
{
"name": "setHealthCheckRegistry",
"parameterTypes": [
"java.lang.Object"
]
},
{
"name": "setIdleTimeout",
"parameterTypes": [
"long"
]
},
{
"name": "setInitializationFailTimeout",
"parameterTypes": [
"long"
]
},
{
"name": "setInitializationFailFast",
"parameterTypes": [
"boolean"
]
},
{
"name": "setIsolateInternalQueries",
"parameterTypes": [
"boolean"
]
},
{
"name": "setJdbc4ConnectionTest",
"parameterTypes": [
"boolean"
]
},
{
"name": "setJdbcUrl",
"parameterTypes": [
"java.lang.String"
]
},
{
"name": "setKeepaliveTime",
"parameterTypes": [
"long"
]
},
{
"name": "setLeakDetectionThreshold",
"parameterTypes": [
"long"
]
},
{
"name": "setMaxLifetime",
"parameterTypes": [
"long"
]
},
{
"name": "setMaximumPoolSize",
"parameterTypes": [
"int"
]
},
{
"name": "setMetricRegistry",
"parameterTypes": [
"java.lang.Object"
]
},
{
"name": "setMetricsTrackerFactory",
"parameterTypes": [
"com.zaxxer.hikari.metrics.MetricsTrackerFactory"
]
},
{
"name": "setMinimumIdle",
"parameterTypes": [
"int"
]
},
{
"name": "setPassword",
"parameterTypes": [
"java.lang.String"
]
},
{
"name": "setPoolName",
"parameterTypes": [
"java.lang.String"
]
},
{
"name": "setReadOnly",
"parameterTypes": [
"boolean"
]
},
{
"name": "setRegisterMbeans",
"parameterTypes": [
"boolean"
]
},
{
"name": "setScheduledExecutor",
"parameterTypes": [
"java.util.concurrent.ScheduledExecutorService"
]
},
{
"name": "setScheduledExecutorService",
"parameterTypes": [
"java.util.concurrent.ScheduledThreadPoolExecutor"
]
},
{
"name": "setSchema",
"parameterTypes": [
"java.lang.String"
]
},
{
"name": "setThreadFactory",
"parameterTypes": [
"java.util.concurrent.ThreadFactory"
]
},
{
"name": "setTransactionIsolation",
"parameterTypes": [
"java.lang.String"
]
},
{
"name": "setUsername",
"parameterTypes": [
"java.lang.String"
]
},
{
"name": "setValidationTimeout",
"parameterTypes": [
"long"
]
}
]
},
{
"condition": {
"typeReachable": "com.zaxxer.hikari.util.UtilityElf"
},
"name": "java.sql.Connection",
"fields": []
},
{
"condition": {
"typeReachable": "com.zaxxer.hikari.util.Sequence$Factory"
},
"name": "java.util.concurrent.atomic.LongAdder"
}
]

View File

@ -1,4 +0,0 @@
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.url=jdbc:otel:h2:mem:db
spring.datasource.driver-class-name=io.opentelemetry.instrumentation.jdbc.OpenTelemetryDriver

View File

@ -10,5 +10,5 @@ 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 = "jdbc-driver-config")
class OtelSpringStarterJdbcDriverConfigSmokeTest extends OtelSpringStarterSmokeTest {}
@ActiveProfiles(value = "user-has-defined-datasource-bean")
class OtelSpringStarterUserDataSourceBeanTest extends OtelSpringStarterSmokeTest {}