diff --git a/instrumentation/jdbc/library/README.md b/instrumentation/jdbc/library/README.md index 4b940e8b4ad1..63e4083cd3c1 100644 --- a/instrumentation/jdbc/library/README.md +++ b/instrumentation/jdbc/library/README.md @@ -68,3 +68,7 @@ public class DataSourceConfig { 1. Activate tracing for JDBC connections by setting `jdbc:otel:` prefix to the JDBC URL, e.g. `jdbc:otel:h2:mem:test`. 2. Set the driver class to `io.opentelemetry.instrumentation.jdbc.OpenTelemetryDriver`. + +3. Inject `OpenTelemetry` into `io.opentelemetry.instrumentation.jdbc.OpenTelemetryDriver` _before the initialization of the database connection pool_. +You can do this with the `void setOpenTelemetry(OpenTelemetry openTelemetry)` method of `io.opentelemetry.instrumentation.jdbc.OpenTelemetryDriver`. +Another way is to use `OpenTelemetryDriver.install(OpenTelemetry openTelemetry)`. diff --git a/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/OpenTelemetryDriver.java b/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/OpenTelemetryDriver.java index 5b6646ced2c6..8f0e32d63f93 100644 --- a/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/OpenTelemetryDriver.java +++ b/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/OpenTelemetryDriver.java @@ -21,10 +21,13 @@ package io.opentelemetry.instrumentation.jdbc; import static io.opentelemetry.instrumentation.jdbc.internal.JdbcInstrumenterFactory.INSTRUMENTATION_NAME; -import static io.opentelemetry.instrumentation.jdbc.internal.JdbcSingletons.statementInstrumenter; +import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; import io.opentelemetry.instrumentation.api.internal.EmbeddedInstrumentationProperties; +import io.opentelemetry.instrumentation.jdbc.internal.DbRequest; import io.opentelemetry.instrumentation.jdbc.internal.JdbcConnectionUrlParser; +import io.opentelemetry.instrumentation.jdbc.internal.JdbcInstrumenterFactory; import io.opentelemetry.instrumentation.jdbc.internal.OpenTelemetryConnection; import io.opentelemetry.instrumentation.jdbc.internal.dbinfo.DbInfo; import java.sql.Connection; @@ -35,6 +38,7 @@ import java.sql.SQLFeatureNotSupportedException; import java.util.Collection; import java.util.Collections; +import java.util.Enumeration; import java.util.List; import java.util.Properties; import java.util.concurrent.CopyOnWriteArrayList; @@ -48,6 +52,8 @@ public final class OpenTelemetryDriver implements Driver { // visible for testing static final OpenTelemetryDriver INSTANCE = new OpenTelemetryDriver(); + private volatile OpenTelemetry openTelemetry = OpenTelemetry.noop(); + private static final int MAJOR_VERSION; private static final int MINOR_VERSION; @@ -192,6 +198,30 @@ private static int[] parseInstrumentationVersion() { return new int[] {0, 0}; } + /** + * Installs the {@link OpenTelemetry} instance on the {@code OpenTelemetryDriver}. OpenTelemetry + * has to be set before the initialization of the database connection pool. + */ + public static void install(OpenTelemetry openTelemetry) { + Enumeration drivers = DriverManager.getDrivers(); + while (drivers.hasMoreElements()) { + Driver driver = drivers.nextElement(); + if (driver instanceof io.opentelemetry.instrumentation.jdbc.OpenTelemetryDriver) { + OpenTelemetryDriver openTelemetryDriver = (OpenTelemetryDriver) driver; + openTelemetryDriver.setOpenTelemetry(openTelemetry); + } + } + } + + /** + * Configures the {@link OpenTelemetry}. See {@link #install(OpenTelemetry)} for simple + * installation option. OpenTelemetry has to be set before the initialization of the database + * connection pool. + */ + public void setOpenTelemetry(OpenTelemetry openTelemetry) { + this.openTelemetry = openTelemetry; + } + @Nullable @Override public Connection connect(String url, Properties info) throws SQLException { @@ -212,7 +242,9 @@ public Connection connect(String url, Properties info) throws SQLException { DbInfo dbInfo = JdbcConnectionUrlParser.parse(realUrl, info); - return new OpenTelemetryConnection(connection, dbInfo, statementInstrumenter()); + Instrumenter statementInstrumenter = + JdbcInstrumenterFactory.createStatementInstrumenter(openTelemetry); + return new OpenTelemetryConnection(connection, dbInfo, statementInstrumenter); } @Override diff --git a/instrumentation/spring/spring-boot-autoconfigure/build.gradle.kts b/instrumentation/spring/spring-boot-autoconfigure/build.gradle.kts index 27283a684df6..9ac7f41691b4 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/build.gradle.kts +++ b/instrumentation/spring/spring-boot-autoconfigure/build.gradle.kts @@ -29,6 +29,7 @@ dependencies { compileOnly("org.apache.logging.log4j:log4j-core:2.17.0") implementation(project(":instrumentation:logback:logback-appender-1.0:library")) compileOnly("ch.qos.logback:logback-classic:1.0.0") + implementation(project(":instrumentation:jdbc:library")) library("org.springframework.kafka:spring-kafka:2.9.0") library("org.springframework.boot:spring-boot-starter-actuator:$springBootVersion") diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/OpenTelemetryAutoConfiguration.java b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/OpenTelemetryAutoConfiguration.java index 369a9fd22607..c8fe868964e8 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/OpenTelemetryAutoConfiguration.java +++ b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/OpenTelemetryAutoConfiguration.java @@ -132,20 +132,30 @@ public Resource otelResource( } @Bean + // If you change the bean name, also change it in the OpenTelemetryJdbcDriverAutoConfiguration + // class public OpenTelemetry openTelemetry( ObjectProvider propagatorsProvider, SdkTracerProvider tracerProvider, SdkMeterProvider meterProvider, - SdkLoggerProvider loggerProvider) { + SdkLoggerProvider loggerProvider, + ObjectProvider> openTelemetryConsumerProvider) { ContextPropagators propagators = propagatorsProvider.getIfAvailable(ContextPropagators::noop); - return OpenTelemetrySdk.builder() - .setTracerProvider(tracerProvider) - .setMeterProvider(meterProvider) - .setLoggerProvider(loggerProvider) - .setPropagators(propagators) - .build(); + OpenTelemetrySdk openTelemetry = + OpenTelemetrySdk.builder() + .setTracerProvider(tracerProvider) + .setMeterProvider(meterProvider) + .setLoggerProvider(loggerProvider) + .setPropagators(propagators) + .build(); + + List openTelemetryInjectors = + openTelemetryConsumerProvider.getIfAvailable(() -> Collections.emptyList()); + openTelemetryInjectors.forEach(consumer -> consumer.accept(openTelemetry)); + + return openTelemetry; } } diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/OpenTelemetryInjector.java b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/OpenTelemetryInjector.java new file mode 100644 index 000000000000..3cde2c2987ac --- /dev/null +++ b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/OpenTelemetryInjector.java @@ -0,0 +1,12 @@ +/* + * 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 {} diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/instrumentation/jdbc/OpenTelemetryJdbcDriverAutoConfiguration.java b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/instrumentation/jdbc/OpenTelemetryJdbcDriverAutoConfiguration.java new file mode 100644 index 000000000000..b47ec4c4de4f --- /dev/null +++ b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/instrumentation/jdbc/OpenTelemetryJdbcDriverAutoConfiguration.java @@ -0,0 +1,39 @@ +/* + * 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 org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.config.BeanFactoryPostProcessor; +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.Configuration; + +@ConditionalOnClass(OpenTelemetryDriver.class) +@ConditionalOnProperty( + name = "spring.datasource.driver-class-name", + havingValue = "io.opentelemetry.instrumentation.jdbc.OpenTelemetryDriver") +@Configuration(proxyBeanMethods = false) +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"); + }; + } +} 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 887ab1cfc287..ffecdfe5c22c 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories +++ b/instrumentation/spring/spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories @@ -9,6 +9,7 @@ io.opentelemetry.instrumentation.spring.autoconfigure.exporters.zipkin.ZipkinSpa 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.micrometer.MicrometerBridgeAutoConfiguration,\ io.opentelemetry.instrumentation.spring.autoconfigure.instrumentation.web.SpringWebInstrumentationAutoConfiguration,\ io.opentelemetry.instrumentation.spring.autoconfigure.instrumentation.webflux.SpringWebfluxInstrumentationAutoConfiguration,\ 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 5f7c5e05d0d6..7bfbc0dbc8f5 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 @@ -8,6 +8,7 @@ io.opentelemetry.instrumentation.spring.autoconfigure.exporters.zipkin.ZipkinSpa 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.micrometer.MicrometerBridgeAutoConfiguration io.opentelemetry.instrumentation.spring.autoconfigure.instrumentation.web.SpringWebInstrumentationAutoConfiguration io.opentelemetry.instrumentation.spring.autoconfigure.instrumentation.webflux.SpringWebfluxInstrumentationAutoConfiguration diff --git a/smoke-tests-otel-starter/build.gradle.kts b/smoke-tests-otel-starter/build.gradle.kts index f9472c4cf4cd..76256681491c 100644 --- a/smoke-tests-otel-starter/build.gradle.kts +++ b/smoke-tests-otel-starter/build.gradle.kts @@ -59,4 +59,9 @@ graalvmNative { buildArgs.add("--initialize-at-build-time=org.junit.platform.launcher.core.LauncherConfig") buildArgs.add("--initialize-at-build-time=org.junit.jupiter.engine.config.InstantiatingConfigurationParameterConverter") } + + tasks.test { + useJUnitPlatform() + setForkEvery(1) + } } diff --git a/smoke-tests-otel-starter/src/main/java/io/opentelemetry/spring/smoketest/DatasourceConfig.java b/smoke-tests-otel-starter/src/main/java/io/opentelemetry/spring/smoketest/DatasourceConfig.java index 2ac4ae8e7107..218e89a02b42 100644 --- a/smoke-tests-otel-starter/src/main/java/io/opentelemetry/spring/smoketest/DatasourceConfig.java +++ b/smoke-tests-otel-starter/src/main/java/io/opentelemetry/spring/smoketest/DatasourceConfig.java @@ -11,8 +11,10 @@ import org.apache.commons.dbcp2.BasicDataSource; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Profile; @Configuration +@Profile("!jdbc-driver-config") public class DatasourceConfig { @Bean diff --git a/smoke-tests-otel-starter/src/main/resources/application-jdbc-driver-config.properties b/smoke-tests-otel-starter/src/main/resources/application-jdbc-driver-config.properties new file mode 100644 index 000000000000..658635ec6fc6 --- /dev/null +++ b/smoke-tests-otel-starter/src/main/resources/application-jdbc-driver-config.properties @@ -0,0 +1,4 @@ +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 diff --git a/smoke-tests-otel-starter/src/test/java/io/opentelemetry/smoketest/OtelSpringStarterJdbcDriverConfigSmokeTest.java b/smoke-tests-otel-starter/src/test/java/io/opentelemetry/smoketest/OtelSpringStarterJdbcDriverConfigSmokeTest.java new file mode 100644 index 000000000000..0aeb9840072a --- /dev/null +++ b/smoke-tests-otel-starter/src/test/java/io/opentelemetry/smoketest/OtelSpringStarterJdbcDriverConfigSmokeTest.java @@ -0,0 +1,14 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.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 = "jdbc-driver-config") +class OtelSpringStarterJdbcDriverConfigSmokeTest extends OtelSpringStarterSmokeTest {}