Browse Source

Make the JDBC driver config work with the OTel starter (#9625)

Co-authored-by: Trask Stalnaker <trask.stalnaker@gmail.com>
Jean Bisutti 1 year ago
parent
commit
e6ec4f52ef

+ 4 - 0
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)`.

+ 34 - 2
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.SQLException;
 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 @@ public final class OpenTelemetryDriver implements Driver {
     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<Driver> 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 final class OpenTelemetryDriver implements Driver {
 
     DbInfo dbInfo = JdbcConnectionUrlParser.parse(realUrl, info);
 
-    return new OpenTelemetryConnection(connection, dbInfo, statementInstrumenter());
+    Instrumenter<DbRequest, Void> statementInstrumenter =
+        JdbcInstrumenterFactory.createStatementInstrumenter(openTelemetry);
+    return new OpenTelemetryConnection(connection, dbInfo, statementInstrumenter);
   }
 
   @Override

+ 1 - 0
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")

+ 17 - 7
instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/OpenTelemetryAutoConfiguration.java

@@ -132,20 +132,30 @@ public class OpenTelemetryAutoConfiguration {
     }
 
     @Bean
+    // If you change the bean name, also change it in the OpenTelemetryJdbcDriverAutoConfiguration
+    // class
     public OpenTelemetry openTelemetry(
         ObjectProvider<ContextPropagators> propagatorsProvider,
         SdkTracerProvider tracerProvider,
         SdkMeterProvider meterProvider,
-        SdkLoggerProvider loggerProvider) {
+        SdkLoggerProvider loggerProvider,
+        ObjectProvider<List<OpenTelemetryInjector>> 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<OpenTelemetryInjector> openTelemetryInjectors =
+          openTelemetryConsumerProvider.getIfAvailable(() -> Collections.emptyList());
+      openTelemetryInjectors.forEach(consumer -> consumer.accept(openTelemetry));
+
+      return openTelemetry;
     }
   }
 

+ 12 - 0
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<OpenTelemetry> {}

+ 39 - 0
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");
+    };
+  }
+}

+ 1 - 0
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,\

+ 1 - 0
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

+ 5 - 0
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)
+  }
 }

+ 2 - 0
smoke-tests-otel-starter/src/main/java/io/opentelemetry/spring/smoketest/DatasourceConfig.java

@@ -11,8 +11,10 @@ import javax.sql.DataSource;
 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

+ 4 - 0
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

+ 14 - 0
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 {}