From e81ba2645039cbf17bcd190ca3ee1a4c0df06e91 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20Vav=C5=99=C3=ADk?= Date: Wed, 8 Mar 2023 15:02:26 +0100 Subject: [PATCH 1/2] Use a property to enable OTEL for JDBC & replace driver with datasource --- docs/src/main/asciidoc/opentelemetry.adoc | 11 +- .../agroal/deployment/AgroalProcessor.java | 25 ++++ ...TelemetryJDBCInstrumentationBuildItem.java | 9 ++ extensions/agroal/runtime/pom.xml | 7 ++ .../runtime/AgroalOpenTelemetryRecorder.java | 24 ++++ .../DataSourceJdbcBuildTimeConfig.java | 6 + .../runtime/DataSourceJdbcRuntimeConfig.java | 6 + .../quarkus/agroal/runtime/DataSources.java | 16 +++ .../OpenTelemetryAgroalDataSource.java | 66 ++++++++++ extensions/opentelemetry/deployment/pom.xml | 16 ++- ...lemetryDriverJdbcDataSourcesBuildItem.java | 18 --- .../deployment/OpenTelemetryProcessor.java | 54 +++----- .../DevServicesOpenTelemetryProcessor.java | 44 ------- ...enTelemetryDevServicesDatasourcesTest.java | 115 ++++++++++++++---- ...etryJdbcInstrumentationValidationTest.java | 47 +++++++ .../runtime/OpenTelemetryRecorder.java | 31 ----- .../src/main/resources/application.properties | 9 +- .../it/opentelemetry/Db2LifecycleManager.java | 2 +- .../MariaDbLifecycleManager.java | 2 +- .../opentelemetry/OracleLifecycleManager.java | 2 +- .../PostgreSqlLifecycleManager.java | 2 +- 21 files changed, 336 insertions(+), 176 deletions(-) create mode 100644 extensions/agroal/deployment/src/main/java/io/quarkus/agroal/deployment/OpenTelemetryJDBCInstrumentationBuildItem.java create mode 100644 extensions/agroal/runtime/src/main/java/io/quarkus/agroal/runtime/AgroalOpenTelemetryRecorder.java create mode 100644 extensions/agroal/runtime/src/main/java/io/quarkus/agroal/runtime/OpenTelemetryAgroalDataSource.java delete mode 100644 extensions/opentelemetry/deployment/src/main/java/io/quarkus/opentelemetry/deployment/OpenTelemetryDriverJdbcDataSourcesBuildItem.java delete mode 100644 extensions/opentelemetry/deployment/src/main/java/io/quarkus/opentelemetry/deployment/dev/DevServicesOpenTelemetryProcessor.java create mode 100644 extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/OpenTelemetryJdbcInstrumentationValidationTest.java diff --git a/docs/src/main/asciidoc/opentelemetry.adoc b/docs/src/main/asciidoc/opentelemetry.adoc index ea75097b5a4de..d8ebeee542f75 100644 --- a/docs/src/main/asciidoc/opentelemetry.adoc +++ b/docs/src/main/asciidoc/opentelemetry.adoc @@ -243,15 +243,16 @@ The https://github.com/open-telemetry/opentelemetry-java-instrumentation/tree/ma implementation("io.opentelemetry.instrumentation:opentelemetry-jdbc") ---- -As it uses a dedicated JDBC driver, you must configure your datasource and Hibernate ORM to use it. +As it uses a dedicated JDBC datasource wrapper, you must enable JDBC tracing for your datasource: [source, properties] ---- +# enable tracing +quarkus.datasource.jdbc.telemetry=true + +# configure datasource quarkus.datasource.db-kind=postgresql -# add ':otel' to your database URL -quarkus.datasource.jdbc.url=jdbc:otel:postgresql://localhost:5432/mydatabase -# use the 'OpenTelemetryDriver' instead of the one for your database -quarkus.datasource.jdbc.driver=io.opentelemetry.instrumentation.jdbc.OpenTelemetryDriver +quarkus.datasource.jdbc.url=jdbc:postgresql://localhost:5432/mydatabase ---- == Additional configuration diff --git a/extensions/agroal/deployment/src/main/java/io/quarkus/agroal/deployment/AgroalProcessor.java b/extensions/agroal/deployment/src/main/java/io/quarkus/agroal/deployment/AgroalProcessor.java index 404c5e95efc62..3f15b6fb6bfa2 100644 --- a/extensions/agroal/deployment/src/main/java/io/quarkus/agroal/deployment/AgroalProcessor.java +++ b/extensions/agroal/deployment/src/main/java/io/quarkus/agroal/deployment/AgroalProcessor.java @@ -1,5 +1,7 @@ package io.quarkus.agroal.deployment; +import static io.quarkus.deployment.Capability.OPENTELEMETRY_TRACER; + import java.sql.Driver; import java.util.ArrayList; import java.util.HashMap; @@ -20,6 +22,7 @@ import io.agroal.api.AgroalDataSource; import io.agroal.api.AgroalPoolInterceptor; import io.quarkus.agroal.DataSource; +import io.quarkus.agroal.runtime.AgroalOpenTelemetryRecorder; import io.quarkus.agroal.runtime.AgroalRecorder; import io.quarkus.agroal.runtime.DataSourceJdbcBuildTimeConfig; import io.quarkus.agroal.runtime.DataSourceSupport; @@ -77,8 +80,10 @@ void build( List jdbcDriverBuildItems, BuildProducer reflectiveClass, BuildProducer resource, + Capabilities capabilities, BuildProducer sslNativeSupport, BuildProducer aggregatedConfig, + BuildProducer otelInstrumentationActiveProducer, CurateOutcomeBuildItem curateOutcomeBuildItem) throws Exception { if (dataSourcesBuildTimeConfig.driver.isPresent() || dataSourcesBuildTimeConfig.url.isPresent()) { throw new ConfigurationException( @@ -96,6 +101,7 @@ void build( return; } + boolean otelJdbcInstrumentationActive = false; for (AggregatedDataSourceBuildTimeConfigBuildItem aggregatedDataSourceBuildTimeConfig : aggregatedDataSourceBuildTimeConfigs) { validateBuildTimeConfig(aggregatedDataSourceBuildTimeConfig); @@ -105,6 +111,10 @@ void build( DataSources.TRACING_DRIVER_CLASSNAME)); } + if (aggregatedDataSourceBuildTimeConfig.getJdbcConfig().telemetry && !otelJdbcInstrumentationActive) { + otelJdbcInstrumentationActive = true; + } + reflectiveClass .produce(new ReflectiveClassBuildItem(true, false, aggregatedDataSourceBuildTimeConfig.getResolvedDriverClass())); @@ -112,6 +122,12 @@ void build( aggregatedConfig.produce(aggregatedDataSourceBuildTimeConfig); } + if (otelJdbcInstrumentationActive && capabilities.isPresent(OPENTELEMETRY_TRACER)) { + // at least one datasource is using OpenTelemetry JDBC instrumentation, + // therefore we need to prepare OpenTelemetry data source wrapper + otelInstrumentationActiveProducer.produce(new OpenTelemetryJDBCInstrumentationBuildItem()); + } + // For now, we can't push the security providers to Agroal so we need to include // the service file inside the image. Hopefully, we will get an entry point to // resolve them at build time and push them to Agroal soon. @@ -200,6 +216,8 @@ void generateDataSourceSupportBean(AgroalRecorder recorder, List aggregatedBuildTimeConfigBuildItems, SslNativeConfigBuildItem sslNativeConfig, Capabilities capabilities, + Optional otelInstrumentationActive, + AgroalOpenTelemetryRecorder agroalOpenTelemetryRecorder, BuildProducer additionalBeans, BuildProducer syntheticBeanBuildItemBuildProducer, BuildProducer unremovableBeans) { @@ -210,6 +228,13 @@ void generateDataSourceSupportBean(AgroalRecorder recorder, return; } + if (otelInstrumentationActive.isPresent()) { + // prepare OpenTelemetry datasource wrapper + // this code must only run when the OpenTelemetry is active + // to avoid native failures + agroalOpenTelemetryRecorder.prepareOpenTelemetryAgroalDatasource(); + } + // make a DataSourceProducer bean additionalBeans.produce(AdditionalBeanBuildItem.builder().addBeanClasses(DataSources.class).setUnremovable() .setDefaultScope(DotNames.SINGLETON).build()); diff --git a/extensions/agroal/deployment/src/main/java/io/quarkus/agroal/deployment/OpenTelemetryJDBCInstrumentationBuildItem.java b/extensions/agroal/deployment/src/main/java/io/quarkus/agroal/deployment/OpenTelemetryJDBCInstrumentationBuildItem.java new file mode 100644 index 0000000000000..0499ba6e023b9 --- /dev/null +++ b/extensions/agroal/deployment/src/main/java/io/quarkus/agroal/deployment/OpenTelemetryJDBCInstrumentationBuildItem.java @@ -0,0 +1,9 @@ +package io.quarkus.agroal.deployment; + +import io.quarkus.builder.item.SimpleBuildItem; + +/** + * Purely marker build item that tells us to prepare OpenTelemetry JDBC instrumentation. + */ +final class OpenTelemetryJDBCInstrumentationBuildItem extends SimpleBuildItem { +} diff --git a/extensions/agroal/runtime/pom.xml b/extensions/agroal/runtime/pom.xml index 1721b08c751c9..f05c3cbd0ba1b 100644 --- a/extensions/agroal/runtime/pom.xml +++ b/extensions/agroal/runtime/pom.xml @@ -71,6 +71,13 @@ test + + + io.opentelemetry.instrumentation + opentelemetry-jdbc + true + + diff --git a/extensions/agroal/runtime/src/main/java/io/quarkus/agroal/runtime/AgroalOpenTelemetryRecorder.java b/extensions/agroal/runtime/src/main/java/io/quarkus/agroal/runtime/AgroalOpenTelemetryRecorder.java new file mode 100644 index 0000000000000..b9700e5f5ae2e --- /dev/null +++ b/extensions/agroal/runtime/src/main/java/io/quarkus/agroal/runtime/AgroalOpenTelemetryRecorder.java @@ -0,0 +1,24 @@ +package io.quarkus.agroal.runtime; + +import java.util.function.Function; + +import io.agroal.api.AgroalDataSource; +import io.quarkus.runtime.annotations.Recorder; + +@Recorder +public class AgroalOpenTelemetryRecorder { + + /** + * Tell {@link DataSources} to use the OpenTelemetry datasource wrapper for + * data sources with activated telemetry. We need to set this way only when + * optional OpenTelemetry JDBC instrumentation dependency is present to avoid native failures. + */ + public void prepareOpenTelemetryAgroalDatasource() { + DataSources.setOpenTelemetryDatasourceTransformer(new Function() { + @Override + public AgroalDataSource apply(AgroalDataSource agroalDataSource) { + return new OpenTelemetryAgroalDataSource(agroalDataSource); + } + }); + } +} diff --git a/extensions/agroal/runtime/src/main/java/io/quarkus/agroal/runtime/DataSourceJdbcBuildTimeConfig.java b/extensions/agroal/runtime/src/main/java/io/quarkus/agroal/runtime/DataSourceJdbcBuildTimeConfig.java index ee29da2f80a70..e9595b34c9003 100644 --- a/extensions/agroal/runtime/src/main/java/io/quarkus/agroal/runtime/DataSourceJdbcBuildTimeConfig.java +++ b/extensions/agroal/runtime/src/main/java/io/quarkus/agroal/runtime/DataSourceJdbcBuildTimeConfig.java @@ -43,4 +43,10 @@ public class DataSourceJdbcBuildTimeConfig { */ @ConfigItem(defaultValue = "false") public boolean tracing = false; + + /** + * Enable OpenTelemetry JDBC instrumentation. + */ + @ConfigItem(defaultValue = "false") + public boolean telemetry = false; } diff --git a/extensions/agroal/runtime/src/main/java/io/quarkus/agroal/runtime/DataSourceJdbcRuntimeConfig.java b/extensions/agroal/runtime/src/main/java/io/quarkus/agroal/runtime/DataSourceJdbcRuntimeConfig.java index 1c07a501cf75d..105c9b87da0e5 100644 --- a/extensions/agroal/runtime/src/main/java/io/quarkus/agroal/runtime/DataSourceJdbcRuntimeConfig.java +++ b/extensions/agroal/runtime/src/main/java/io/quarkus/agroal/runtime/DataSourceJdbcRuntimeConfig.java @@ -145,4 +145,10 @@ public class DataSourceJdbcRuntimeConfig { @ConfigItem public DataSourceJdbcTracingRuntimeConfig tracing = new DataSourceJdbcTracingRuntimeConfig(); + /** + * Enable OpenTelemetry JDBC instrumentation. + */ + @ConfigItem(name = "telemetry.enabled", defaultValueDocumentation = "false if quarkus.datasource.jdbc.telemetry=false and true if quarkus.datasource.jdbc.telemetry=true") + public Optional telemetry; + } diff --git a/extensions/agroal/runtime/src/main/java/io/quarkus/agroal/runtime/DataSources.java b/extensions/agroal/runtime/src/main/java/io/quarkus/agroal/runtime/DataSources.java index fe497d2ce0bf2..62ea0fc49e7bc 100644 --- a/extensions/agroal/runtime/src/main/java/io/quarkus/agroal/runtime/DataSources.java +++ b/extensions/agroal/runtime/src/main/java/io/quarkus/agroal/runtime/DataSources.java @@ -7,6 +7,7 @@ import java.util.Collection; import java.util.Iterator; import java.util.Map; +import java.util.Objects; import java.util.ServiceLoader; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; @@ -68,6 +69,7 @@ public class DataSources { public static final String TRACING_DRIVER_CLASSNAME = "io.opentracing.contrib.jdbc.TracingDriver"; private static final String JDBC_URL_PREFIX = "jdbc:"; private static final String JDBC_TRACING_URL_PREFIX = "jdbc:tracing:"; + private static volatile Function OTEL_DATASOURCE_TRANSFORMER = null; private final DataSourcesBuildTimeConfig dataSourcesBuildTimeConfig; private final DataSourcesRuntimeConfig dataSourcesRuntimeConfig; @@ -252,6 +254,12 @@ public AgroalDataSource doCreateDataSource(String dataSourceName) { dataSource.setPoolInterceptors(interceptorList); } + if (dataSourceJdbcBuildTimeConfig.telemetry && dataSourceJdbcRuntimeConfig.telemetry.orElse(true)) { + // active OpenTelemetry JDBC instrumentation by wrapping AgroalDatasource + // use the transformer as we can't reference optional OpenTelemetry classes here + dataSource = OTEL_DATASOURCE_TRANSFORMER.apply(dataSource); + } + return dataSource; } @@ -445,4 +453,12 @@ public void stop() { } } + /** + * Set a function that will wrap {@link AgroalDataSource} with the OpenTelemetry datasource. + */ + static void setOpenTelemetryDatasourceTransformer(Function otelDatasourceTransformer) { + Objects.requireNonNull(otelDatasourceTransformer); + OTEL_DATASOURCE_TRANSFORMER = otelDatasourceTransformer; + } + } diff --git a/extensions/agroal/runtime/src/main/java/io/quarkus/agroal/runtime/OpenTelemetryAgroalDataSource.java b/extensions/agroal/runtime/src/main/java/io/quarkus/agroal/runtime/OpenTelemetryAgroalDataSource.java new file mode 100644 index 0000000000000..599be567bcd22 --- /dev/null +++ b/extensions/agroal/runtime/src/main/java/io/quarkus/agroal/runtime/OpenTelemetryAgroalDataSource.java @@ -0,0 +1,66 @@ +package io.quarkus.agroal.runtime; + +import java.sql.SQLException; +import java.sql.ShardingKeyBuilder; +import java.util.Collection; +import java.util.List; + +import io.agroal.api.AgroalDataSource; +import io.agroal.api.AgroalDataSourceMetrics; +import io.agroal.api.AgroalPoolInterceptor; +import io.agroal.api.configuration.AgroalDataSourceConfiguration; +import io.opentelemetry.api.GlobalOpenTelemetry; +import io.opentelemetry.instrumentation.jdbc.datasource.OpenTelemetryDataSource; + +/** + * The {@link AgroalDataSource} wrapper that actives OpenTelemetry JDBC instrumentation. + */ +public class OpenTelemetryAgroalDataSource extends OpenTelemetryDataSource implements AgroalDataSource { + + private final AgroalDataSource delegate; + + public OpenTelemetryAgroalDataSource(AgroalDataSource delegate) { + super(delegate, GlobalOpenTelemetry.get()); + this.delegate = delegate; + } + + @Override + public boolean isHealthy(boolean newConnection) throws SQLException { + return delegate.isHealthy(newConnection); + } + + @Override + public AgroalDataSourceConfiguration getConfiguration() { + return delegate.getConfiguration(); + } + + @Override + public AgroalDataSourceMetrics getMetrics() { + return delegate.getMetrics(); + } + + @Override + public void flush(FlushMode mode) { + delegate.flush(mode); + } + + @Override + public void setPoolInterceptors(Collection interceptors) { + delegate.setPoolInterceptors(interceptors); + } + + @Override + public List getPoolInterceptors() { + return delegate.getPoolInterceptors(); + } + + @Override + public ShardingKeyBuilder createShardingKeyBuilder() throws SQLException { + return delegate.createShardingKeyBuilder(); + } + + @Override + public void close() { + delegate.close(); + } +} diff --git a/extensions/opentelemetry/deployment/pom.xml b/extensions/opentelemetry/deployment/pom.xml index 69fc0a0f28833..e76dd1b9c5252 100644 --- a/extensions/opentelemetry/deployment/pom.xml +++ b/extensions/opentelemetry/deployment/pom.xml @@ -111,11 +111,6 @@ quarkus-agroal-deployment test - - io.quarkus - quarkus-jdbc-h2-deployment - test - io.opentelemetry.instrumentation opentelemetry-jdbc @@ -137,6 +132,17 @@ opentelemetry-aws-xray test + + io.quarkus + quarkus-jdbc-h2-deployment + test + + + + + io.opentelemetry + opentelemetry-sdk-testing + diff --git a/extensions/opentelemetry/deployment/src/main/java/io/quarkus/opentelemetry/deployment/OpenTelemetryDriverJdbcDataSourcesBuildItem.java b/extensions/opentelemetry/deployment/src/main/java/io/quarkus/opentelemetry/deployment/OpenTelemetryDriverJdbcDataSourcesBuildItem.java deleted file mode 100644 index 543fe2d36ebc1..0000000000000 --- a/extensions/opentelemetry/deployment/src/main/java/io/quarkus/opentelemetry/deployment/OpenTelemetryDriverJdbcDataSourcesBuildItem.java +++ /dev/null @@ -1,18 +0,0 @@ -package io.quarkus.opentelemetry.deployment; - -import java.util.List; - -import io.quarkus.agroal.spi.JdbcDataSourceBuildItem; -import io.quarkus.builder.item.SimpleBuildItem; - -/** - * Contains list of all {@link io.quarkus.agroal.spi.JdbcDataSourceBuildItem} using OpenTelemetryDriver. - */ -public final class OpenTelemetryDriverJdbcDataSourcesBuildItem extends SimpleBuildItem { - - public final List jdbcDataSources; - - OpenTelemetryDriverJdbcDataSourcesBuildItem(List jdbcDataSources) { - this.jdbcDataSources = List.copyOf(jdbcDataSources); - } -} diff --git a/extensions/opentelemetry/deployment/src/main/java/io/quarkus/opentelemetry/deployment/OpenTelemetryProcessor.java b/extensions/opentelemetry/deployment/src/main/java/io/quarkus/opentelemetry/deployment/OpenTelemetryProcessor.java index ee63ae6a3a647..e7d21976e4053 100644 --- a/extensions/opentelemetry/deployment/src/main/java/io/quarkus/opentelemetry/deployment/OpenTelemetryProcessor.java +++ b/extensions/opentelemetry/deployment/src/main/java/io/quarkus/opentelemetry/deployment/OpenTelemetryProcessor.java @@ -2,7 +2,6 @@ import static io.quarkus.opentelemetry.runtime.OpenTelemetryRecorder.OPEN_TELEMETRY_DRIVER; -import java.util.ArrayList; import java.util.List; import java.util.Optional; import java.util.Set; @@ -20,16 +19,13 @@ import io.opentelemetry.instrumentation.annotations.WithSpan; import io.opentelemetry.sdk.trace.SdkTracerProvider; import io.quarkus.agroal.spi.JdbcDataSourceBuildItem; -import io.quarkus.agroal.spi.JdbcDriverBuildItem; import io.quarkus.arc.deployment.AdditionalBeanBuildItem; import io.quarkus.arc.deployment.AnnotationsTransformerBuildItem; import io.quarkus.arc.deployment.InterceptorBindingRegistrarBuildItem; +import io.quarkus.arc.deployment.ValidationPhaseBuildItem.ValidationErrorBuildItem; import io.quarkus.arc.processor.AnnotationsTransformer; import io.quarkus.arc.processor.InterceptorBindingRegistrar; import io.quarkus.datasource.common.runtime.DataSourceUtil; -import io.quarkus.datasource.common.runtime.DatabaseKind; -import io.quarkus.deployment.Capabilities; -import io.quarkus.deployment.Capability; import io.quarkus.deployment.annotations.BuildProducer; import io.quarkus.deployment.annotations.BuildStep; import io.quarkus.deployment.annotations.BuildSteps; @@ -47,6 +43,7 @@ import io.quarkus.opentelemetry.runtime.tracing.intrumentation.InstrumentationRecorder; import io.quarkus.runtime.LaunchMode; import io.quarkus.runtime.RuntimeValue; +import io.quarkus.runtime.configuration.ConfigurationException; import io.quarkus.vertx.core.deployment.CoreVertxBuildItem; @BuildSteps(onlyIf = OpenTelemetryEnabled.class) @@ -168,18 +165,21 @@ void storeVertxOnContextStorage(OpenTelemetryRecorder recorder, CoreVertxBuildIt } @BuildStep - void collectAllJdbcDataSourcesUsingOTelDriver(BuildProducer resultProducer, - List jdbcDataSources) { - final List result = new ArrayList<>(); + void validateDataSourcesWithEnabledTelemetry(List jdbcDataSources, + BuildProducer validationErrors) { for (JdbcDataSourceBuildItem dataSource : jdbcDataSources) { - // if the datasource is explicitly configured to use the OTel driver... - if (dataSourceUsesOTelJdbcDriver(dataSource.getName())) { - result.add(dataSource); + final String dataSourceName = dataSource.getName(); + + // verify that no datasource is using OpenTelemetryDriver as that is not supported anymore + if (dataSourceUsesOTelJdbcDriver(dataSourceName)) { + validationErrors.produce( + new ValidationErrorBuildItem( + new ConfigurationException( + String.format( + "Data source '%s' is using unsupported JDBC driver '%s', please active JDBC instrumentation with the 'quarkus.datasource.jdbc.telemetry=true' configuration property instead", + dataSourceName, OPEN_TELEMETRY_DRIVER)))); } } - if (!result.isEmpty()) { - resultProducer.produce(new OpenTelemetryDriverJdbcDataSourcesBuildItem(result)); - } } private static boolean dataSourceUsesOTelJdbcDriver(String dataSourceName) { @@ -193,30 +193,4 @@ private static boolean dataSourceUsesOTelJdbcDriver(String dataSourceName) { return false; } - /** - * 'OracleDriver' register itself as driver in static initialization block, however we don't want to - * force runtime initialization for compatibility reasons, for more information please check: - * io.quarkus.jdbc.oracle.deployment.OracleMetadataOverrides#runtimeInitializeDriver - */ - @BuildStep - @Record(ExecutionTime.RUNTIME_INIT) - void registerOracleDriver(Optional otJdbcDataSourcesBuildItem, - List driverBuildItems, Capabilities capabilities, OpenTelemetryRecorder recorder) { - // check if there are data sources using OT driver and jdbc-oracle extension is present - if (otJdbcDataSourcesBuildItem.isPresent() && capabilities.isPresent(Capability.JDBC_ORACLE)) { - for (JdbcDataSourceBuildItem jdbcDataSource : otJdbcDataSourcesBuildItem.get().jdbcDataSources) { - if (jdbcDataSource.getDbKind().equals(DatabaseKind.ORACLE)) { - // now we know there is Oracle JDBC datasource - // let's find Oracle driver - for (JdbcDriverBuildItem driverBuildItem : driverBuildItems) { - if (DatabaseKind.ORACLE.equals(driverBuildItem.getDbKind())) { - recorder.registerJdbcDriver(driverBuildItem.getDriverClass()); - break; - } - } - break; - } - } - } - } } diff --git a/extensions/opentelemetry/deployment/src/main/java/io/quarkus/opentelemetry/deployment/dev/DevServicesOpenTelemetryProcessor.java b/extensions/opentelemetry/deployment/src/main/java/io/quarkus/opentelemetry/deployment/dev/DevServicesOpenTelemetryProcessor.java deleted file mode 100644 index 1ec00718187e6..0000000000000 --- a/extensions/opentelemetry/deployment/src/main/java/io/quarkus/opentelemetry/deployment/dev/DevServicesOpenTelemetryProcessor.java +++ /dev/null @@ -1,44 +0,0 @@ -package io.quarkus.opentelemetry.deployment.dev; - -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Optional; - -import io.quarkus.agroal.spi.JdbcDataSourceBuildItem; -import io.quarkus.datasource.common.runtime.DataSourceUtil; -import io.quarkus.deployment.IsNormal; -import io.quarkus.deployment.annotations.BuildProducer; -import io.quarkus.deployment.annotations.BuildStep; -import io.quarkus.deployment.annotations.BuildSteps; -import io.quarkus.deployment.builditem.DevServicesAdditionalConfigBuildItem; -import io.quarkus.deployment.dev.devservices.GlobalDevServicesConfig; -import io.quarkus.opentelemetry.deployment.OpenTelemetryDriverJdbcDataSourcesBuildItem; -import io.quarkus.opentelemetry.deployment.OpenTelemetryEnabled; - -@BuildSteps(onlyIfNot = IsNormal.class, onlyIf = { OpenTelemetryEnabled.class, GlobalDevServicesConfig.Enabled.class }) -public class DevServicesOpenTelemetryProcessor { - - @BuildStep - void devServicesDatasources(Optional otJdbcDataSourcesBuildItem, - BuildProducer devServicesAdditionalConfig) { - if (otJdbcDataSourcesBuildItem.isPresent()) { - // found datasources explicitly configured to use the OTel driver - for (JdbcDataSourceBuildItem dataSource : otJdbcDataSourcesBuildItem.get().jdbcDataSources) { - List urlPropertyKeys = DataSourceUtil.dataSourcePropertyKeys(dataSource.getName(), "jdbc.url"); - devServicesAdditionalConfig.produce(new DevServicesAdditionalConfigBuildItem(devServicesConfig -> { - Map overrides = new HashMap<>(); - for (String key : urlPropertyKeys) { - String devServicesUrl = devServicesConfig.get(key); - // ... and if the same datasource uses dev services... - if (devServicesUrl != null) { - // ... then we rewrite the jdbc url to add the otel prefix. - overrides.put(key, devServicesUrl.replaceFirst("jdbc:", "jdbc:otel:")); - } - } - return overrides; - })); - } - } - } -} diff --git a/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/OpenTelemetryDevServicesDatasourcesTest.java b/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/OpenTelemetryDevServicesDatasourcesTest.java index 3d9ade017fcd4..7565910a8a625 100644 --- a/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/OpenTelemetryDevServicesDatasourcesTest.java +++ b/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/OpenTelemetryDevServicesDatasourcesTest.java @@ -1,11 +1,20 @@ package io.quarkus.opentelemetry.deployment; -import javax.sql.DataSource; +import java.sql.Connection; +import java.sql.SQLException; +import java.sql.Statement; +import jakarta.annotation.PostConstruct; +import jakarta.enterprise.context.ApplicationScoped; import jakarta.inject.Inject; +import jakarta.inject.Singleton; +import jakarta.transaction.Transactional; import jakarta.ws.rs.GET; +import jakarta.ws.rs.POST; import jakarta.ws.rs.Path; import jakarta.ws.rs.PathParam; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.core.MediaType; import jakarta.ws.rs.core.Response; import org.hamcrest.Matchers; @@ -13,51 +22,109 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; +import io.agroal.api.AgroalDataSource; +import io.opentelemetry.api.common.AttributeKey; +import io.opentelemetry.sdk.testing.exporter.InMemorySpanExporter; import io.quarkus.test.QuarkusDevModeTest; import io.restassured.RestAssured; -import io.smallrye.config.SmallRyeConfig; public class OpenTelemetryDevServicesDatasourcesTest { + @RegisterExtension final static QuarkusDevModeTest TEST = new QuarkusDevModeTest() .withApplicationRoot((jar) -> jar - .addClasses(DevResource.class) - .add(new StringAsset( - "quarkus.datasource.db-kind=h2\n" + - "quarkus.datasource.jdbc.driver=io.opentelemetry.instrumentation.jdbc.OpenTelemetryDriver"), + .addClasses(HelloResource.class, InMemorySpanExporterProducer.class) + .add(new StringAsset("quarkus.datasource.db-kind=h2\n" + + "quarkus.datasource.jdbc.telemetry=true\n"), "application.properties")); @Test void devDatasource() { - RestAssured.when().get("/config/{name}", "quarkus.datasource.jdbc.url").then() - .statusCode(200) - .body(Matchers.startsWith("jdbc:otel:h2")); + + final String greeting1 = "Hello World"; + createGreeting(greeting1); + verifyNumOfInsertedTraces(1); // Test a change in resources that disables OTEL - TEST.modifyResourceFile("application.properties", s -> "quarkus.datasource.db-kind=h2\n"); - RestAssured.when().get("/config/{name}", "quarkus.datasource.jdbc.url").then() - .statusCode(200) - .body(Matchers.startsWith("jdbc:h2")); + TEST.modifyResourceFile("application.properties", + s -> s + "quarkus.datasource.jdbc.telemetry.enabled=false\n"); + final String greeting2 = "Hi"; + createGreeting(greeting2); + verifyNumOfInsertedTraces(0); // Test a change in resources that enables OTEL - TEST.modifyResourceFile("application.properties", s -> "quarkus.datasource.db-kind=h2\n" + - "quarkus.datasource.jdbc.driver=io.opentelemetry.instrumentation.jdbc.OpenTelemetryDriver"); - RestAssured.when().get("/config/{name}", "quarkus.datasource.jdbc.url").then() + TEST.modifyResourceFile("application.properties", s -> s.replace("quarkus.datasource.jdbc.telemetry.enabled=false", + "quarkus.datasource.jdbc.telemetry.enabled=true")); + final String greeting3 = "Hey"; + createGreeting(greeting3); + verifyNumOfInsertedTraces(1); + } + + private void verifyNumOfInsertedTraces(int insertCount) { + RestAssured.get("/hello/greetings-insert-count").then().statusCode(200) + .body(Matchers.is(Integer.toString(insertCount))); + } + + private void createGreeting(String greeting) { + RestAssured.when().post("/hello/{greeting}", greeting).then() .statusCode(200) - .body(Matchers.startsWith("jdbc:otel:h2")); + .body(Matchers.is(greeting)); } - @Path("/config") - public static class DevResource { + @Path("/hello") + public static class HelloResource { + @Inject - SmallRyeConfig config; + InMemorySpanExporter exporter; + @Inject - DataSource dataSource; + AgroalDataSource dataSource; + + private int idGenerator = 0; + + @Produces(MediaType.TEXT_PLAIN) + @Transactional + @POST + @Path("{greeting}") + public Response createGreeting(@PathParam("greeting") String greeting) { + try (Statement stmt = dataSource.getConnection().createStatement()) { + stmt.executeUpdate(String.format("INSERT INTO greeting VALUES('%d', '%s')", ++idGenerator, greeting)); + } catch (SQLException exception) { + throw new IllegalStateException(exception); + } + return Response.ok(greeting).build(); + } @GET - @Path("{name}") - public Response get(@PathParam("name") String name) { - return Response.ok().entity(config.getRawValue(name)).build(); + @Path("/greetings-insert-count") + public long greetingsInsertCount() { + return exporter.getFinishedSpanItems().stream() + .filter(span -> "INSERT".equals(span.getAttributes().get(AttributeKey.stringKey("db.operation")))).count(); + } + + @PostConstruct + public void setup() throws Exception { + + try (Connection con = dataSource.getConnection()) { + try (Statement statement = con.createStatement()) { + try { + statement.execute("DROP TABLE greeting"); + } catch (Exception ignored) { + + } + statement.execute("CREATE TABLE greeting (id int, greeting varchar)"); + } + } + } + } + + @ApplicationScoped + static class InMemorySpanExporterProducer { + + @jakarta.enterprise.inject.Produces + @Singleton + InMemorySpanExporter inMemorySpanExporter() { + return InMemorySpanExporter.create(); } } } diff --git a/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/OpenTelemetryJdbcInstrumentationValidationTest.java b/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/OpenTelemetryJdbcInstrumentationValidationTest.java new file mode 100644 index 0000000000000..6874513b18382 --- /dev/null +++ b/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/OpenTelemetryJdbcInstrumentationValidationTest.java @@ -0,0 +1,47 @@ +package io.quarkus.opentelemetry.deployment; + +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; + +import org.jboss.shrinkwrap.api.asset.StringAsset; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.runtime.configuration.ConfigurationException; +import io.quarkus.test.QuarkusUnitTest; + +public class OpenTelemetryJdbcInstrumentationValidationTest { + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest() + .withApplicationRoot((jar) -> jar + .addAsResource(new StringAsset( + "quarkus.datasource.db-kind=h2\n" + + "quarkus.datasource.jdbc.driver=io.opentelemetry.instrumentation.jdbc.OpenTelemetryDriver\n"), + "application.properties")) + .assertException(t -> { + Throwable e = t; + ConfigurationException te = null; + while (e != null) { + if (e instanceof ConfigurationException) { + te = (ConfigurationException) e; + break; + } + e = e.getCause(); + } + if (te == null) { + fail("No configuration exception thrown: " + t); + } + assertTrue(te.getMessage().contains("Data source '' is using unsupported JDBC driver"), + te.getMessage()); + assertTrue(te.getMessage().contains("io.opentelemetry.instrumentation.jdbc.OpenTelemetryDriver"), + te.getMessage()); + assertTrue(te.getMessage().contains("quarkus.datasource.jdbc.telemetry=true"), te.getMessage()); + }); + + @Test + public void testValidation() { + fail(); + } + +} diff --git a/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/OpenTelemetryRecorder.java b/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/OpenTelemetryRecorder.java index 8fb318b8cabce..e470f8c976672 100644 --- a/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/OpenTelemetryRecorder.java +++ b/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/OpenTelemetryRecorder.java @@ -1,11 +1,7 @@ package io.quarkus.opentelemetry.runtime; -import java.lang.reflect.InvocationTargetException; -import java.sql.Driver; import java.util.function.Supplier; -import org.jboss.logging.Logger; - import io.opentelemetry.api.GlobalOpenTelemetry; import io.opentelemetry.api.OpenTelemetry; import io.opentelemetry.context.ContextStorage; @@ -21,7 +17,6 @@ public class OpenTelemetryRecorder { public static final String OPEN_TELEMETRY_DRIVER = "io.opentelemetry.instrumentation.jdbc.OpenTelemetryDriver"; - private static final Logger LOG = Logger.getLogger(OpenTelemetryRecorder.class); /* STATIC INIT */ public void resetGlobalOpenTelemetryForDevMode() { @@ -54,30 +49,4 @@ public void storeVertxOnContextStorage(Supplier vertx) { QuarkusContextStorage.vertx = vertx.get(); } - public void registerJdbcDriver(String driverClass) { - try { - var constructors = Class - .forName(driverClass, true, Thread.currentThread().getContextClassLoader()) - .getConstructors(); - if (constructors.length == 1) { - // create driver - Driver driver = ((Driver) constructors[0].newInstance()); - // register the driver with OpenTelemetryDriver - Class - .forName(OPEN_TELEMETRY_DRIVER, true, Thread.currentThread().getContextClassLoader()) - .getMethod("addDriverCandidate", Driver.class) - .invoke(null, driver); - } else { - // drivers should have default constructor - LOG.warn(String.format( - "Class '%s' has more than one constructor and won't be registered as driver. JDBC instrumentation might not work properly in native mode.", - driverClass)); - } - } catch (InvocationTargetException | InstantiationException | IllegalAccessException | NoSuchMethodException - | ClassNotFoundException e) { - LOG.warn(String.format( - "Failed to register '%s' driver. JDBC instrumentation might not work properly in native mode.", - driverClass)); - } - } } diff --git a/integration-tests/opentelemetry-jdbc-instrumentation/src/main/resources/application.properties b/integration-tests/opentelemetry-jdbc-instrumentation/src/main/resources/application.properties index b7cf71fcb5ac3..e26c2fced8a45 100644 --- a/integration-tests/opentelemetry-jdbc-instrumentation/src/main/resources/application.properties +++ b/integration-tests/opentelemetry-jdbc-instrumentation/src/main/resources/application.properties @@ -7,7 +7,6 @@ quarkus.datasource.devservices.enabled=false quarkus.devservices.enabled=false # JDBC instrumentation setting -driver=io.opentelemetry.instrumentation.jdbc.OpenTelemetryDriver model-base-dir=io.quarkus.it.opentelemetry.model. # Oracle data source @@ -15,29 +14,29 @@ quarkus.hibernate-orm.oracle.datasource=oracle quarkus.hibernate-orm.oracle.packages=${model-base-dir}oracle quarkus.hibernate-orm.oracle.database.generation=none quarkus.datasource.oracle.db-kind=oracle -quarkus.datasource.oracle.jdbc.driver=${driver} quarkus.datasource.oracle.jdbc.max-size=1 +quarkus.datasource.oracle.jdbc.telemetry=true # MariaDB data source quarkus.hibernate-orm.mariadb.datasource=mariadb quarkus.hibernate-orm.mariadb.packages=${model-base-dir}mariadb quarkus.hibernate-orm.mariadb.database.generation=none quarkus.datasource.mariadb.db-kind=mariadb -quarkus.datasource.mariadb.jdbc.driver=${driver} quarkus.datasource.mariadb.jdbc.max-size=1 +quarkus.datasource.mariadb.jdbc.telemetry=true # PostgreSQL data source quarkus.hibernate-orm.postgresql.datasource=postgresql quarkus.hibernate-orm.postgresql.packages=${model-base-dir}pg quarkus.hibernate-orm.postgresql.database.generation=none quarkus.datasource.postgresql.db-kind=postgresql -quarkus.datasource.postgresql.jdbc.driver=${driver} quarkus.datasource.postgresql.jdbc.max-size=1 +quarkus.datasource.postgresql.jdbc.telemetry=true # Db2 data source quarkus.hibernate-orm.db2.datasource=db2 quarkus.hibernate-orm.db2.packages=${model-base-dir}db2 quarkus.hibernate-orm.db2.database.generation=none quarkus.datasource.db2.db-kind=db2 -quarkus.datasource.db2.jdbc.driver=${driver} quarkus.datasource.db2.jdbc.max-size=1 +quarkus.datasource.db2.jdbc.telemetry=true diff --git a/integration-tests/opentelemetry-jdbc-instrumentation/src/test/java/io/quarkus/it/opentelemetry/Db2LifecycleManager.java b/integration-tests/opentelemetry-jdbc-instrumentation/src/test/java/io/quarkus/it/opentelemetry/Db2LifecycleManager.java index 66b7c934d89d0..d5e4a52407e41 100644 --- a/integration-tests/opentelemetry-jdbc-instrumentation/src/test/java/io/quarkus/it/opentelemetry/Db2LifecycleManager.java +++ b/integration-tests/opentelemetry-jdbc-instrumentation/src/test/java/io/quarkus/it/opentelemetry/Db2LifecycleManager.java @@ -22,7 +22,7 @@ public Map start() { Map properties = new HashMap<>(); properties.put("quarkus.datasource.db2.jdbc.url", - String.format("jdbc:otel:db2://%s:%s/%s", db2Container.getHost(), + String.format("jdbc:db2://%s:%s/%s", db2Container.getHost(), db2Container.getFirstMappedPort(), QUARKUS)); properties.put("quarkus.datasource.db2.password", QUARKUS); properties.put("quarkus.datasource.db2.username", QUARKUS); diff --git a/integration-tests/opentelemetry-jdbc-instrumentation/src/test/java/io/quarkus/it/opentelemetry/MariaDbLifecycleManager.java b/integration-tests/opentelemetry-jdbc-instrumentation/src/test/java/io/quarkus/it/opentelemetry/MariaDbLifecycleManager.java index 5523144296ce4..6325338571caf 100644 --- a/integration-tests/opentelemetry-jdbc-instrumentation/src/test/java/io/quarkus/it/opentelemetry/MariaDbLifecycleManager.java +++ b/integration-tests/opentelemetry-jdbc-instrumentation/src/test/java/io/quarkus/it/opentelemetry/MariaDbLifecycleManager.java @@ -22,7 +22,7 @@ public Map start() { Map properties = new HashMap<>(); properties.put("quarkus.datasource.mariadb.jdbc.url", - String.format("jdbc:otel:mariadb://%s:%s/%s", mariaDbContainer.getHost(), + String.format("jdbc:mariadb://%s:%s/%s", mariaDbContainer.getHost(), mariaDbContainer.getFirstMappedPort(), QUARKUS)); properties.put("quarkus.datasource.mariadb.password", QUARKUS); properties.put("quarkus.datasource.mariadb.username", QUARKUS); diff --git a/integration-tests/opentelemetry-jdbc-instrumentation/src/test/java/io/quarkus/it/opentelemetry/OracleLifecycleManager.java b/integration-tests/opentelemetry-jdbc-instrumentation/src/test/java/io/quarkus/it/opentelemetry/OracleLifecycleManager.java index aa3e22a328e30..aab9c7dbd5f21 100644 --- a/integration-tests/opentelemetry-jdbc-instrumentation/src/test/java/io/quarkus/it/opentelemetry/OracleLifecycleManager.java +++ b/integration-tests/opentelemetry-jdbc-instrumentation/src/test/java/io/quarkus/it/opentelemetry/OracleLifecycleManager.java @@ -10,7 +10,7 @@ public class OracleLifecycleManager implements QuarkusTestResourceLifecycleManag @Override public Map start() { Map properties = new HashMap<>(); - properties.put("quarkus.datasource.oracle.jdbc.url", "jdbc:otel:oracle:thin:@localhost:1521/XE"); + properties.put("quarkus.datasource.oracle.jdbc.url", "jdbc:oracle:thin:@localhost:1521/XE"); properties.put("quarkus.datasource.oracle.password", "quarkus"); properties.put("quarkus.datasource.oracle.username", "SYSTEM"); properties.put("quarkus.hibernate-orm.oracle.database.generation", "drop-and-create"); diff --git a/integration-tests/opentelemetry-jdbc-instrumentation/src/test/java/io/quarkus/it/opentelemetry/PostgreSqlLifecycleManager.java b/integration-tests/opentelemetry-jdbc-instrumentation/src/test/java/io/quarkus/it/opentelemetry/PostgreSqlLifecycleManager.java index 1a1103a410623..762fa477746a0 100644 --- a/integration-tests/opentelemetry-jdbc-instrumentation/src/test/java/io/quarkus/it/opentelemetry/PostgreSqlLifecycleManager.java +++ b/integration-tests/opentelemetry-jdbc-instrumentation/src/test/java/io/quarkus/it/opentelemetry/PostgreSqlLifecycleManager.java @@ -22,7 +22,7 @@ public Map start() { Map properties = new HashMap<>(); properties.put("quarkus.datasource.postgresql.jdbc.url", - String.format("jdbc:otel:postgresql://%s:%s/%s", postgresContainer.getHost(), + String.format("jdbc:postgresql://%s:%s/%s", postgresContainer.getHost(), postgresContainer.getFirstMappedPort(), QUARKUS)); properties.put("quarkus.datasource.postgresql.password", QUARKUS); properties.put("quarkus.datasource.postgresql.username", QUARKUS); From 956f31adcdf8100595b009c83d5a1c0f4b24b638 Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Wed, 8 Mar 2023 12:48:54 +0100 Subject: [PATCH 2/2] Use a bean for telemetry wrapping to avoid making DataSources mutable We had a hard time making it immutable so let's not break this contract. We had a lot of race conditions before doing so. --- docs/src/main/asciidoc/opentelemetry.adoc | 2 +- .../agroal/deployment/AgroalProcessor.java | 20 +++++--------- ...TelemetryJDBCInstrumentationBuildItem.java | 9 ------- .../runtime/AgroalOpenTelemetryRecorder.java | 24 ----------------- .../runtime/AgroalOpenTelemetryWrapper.java | 13 ++++++++++ .../quarkus/agroal/runtime/DataSources.java | 26 +++++++------------ .../OpenTelemetryAgroalDataSource.java | 2 +- 7 files changed, 31 insertions(+), 65 deletions(-) delete mode 100644 extensions/agroal/deployment/src/main/java/io/quarkus/agroal/deployment/OpenTelemetryJDBCInstrumentationBuildItem.java delete mode 100644 extensions/agroal/runtime/src/main/java/io/quarkus/agroal/runtime/AgroalOpenTelemetryRecorder.java create mode 100644 extensions/agroal/runtime/src/main/java/io/quarkus/agroal/runtime/AgroalOpenTelemetryWrapper.java diff --git a/docs/src/main/asciidoc/opentelemetry.adoc b/docs/src/main/asciidoc/opentelemetry.adoc index d8ebeee542f75..bd194a4e4b445 100644 --- a/docs/src/main/asciidoc/opentelemetry.adoc +++ b/docs/src/main/asciidoc/opentelemetry.adoc @@ -243,7 +243,7 @@ The https://github.com/open-telemetry/opentelemetry-java-instrumentation/tree/ma implementation("io.opentelemetry.instrumentation:opentelemetry-jdbc") ---- -As it uses a dedicated JDBC datasource wrapper, you must enable JDBC tracing for your datasource: +As it uses a dedicated JDBC datasource wrapper, you must enable telemetry for your datasource: [source, properties] ---- diff --git a/extensions/agroal/deployment/src/main/java/io/quarkus/agroal/deployment/AgroalProcessor.java b/extensions/agroal/deployment/src/main/java/io/quarkus/agroal/deployment/AgroalProcessor.java index 3f15b6fb6bfa2..41f48aef08c8e 100644 --- a/extensions/agroal/deployment/src/main/java/io/quarkus/agroal/deployment/AgroalProcessor.java +++ b/extensions/agroal/deployment/src/main/java/io/quarkus/agroal/deployment/AgroalProcessor.java @@ -22,7 +22,6 @@ import io.agroal.api.AgroalDataSource; import io.agroal.api.AgroalPoolInterceptor; import io.quarkus.agroal.DataSource; -import io.quarkus.agroal.runtime.AgroalOpenTelemetryRecorder; import io.quarkus.agroal.runtime.AgroalRecorder; import io.quarkus.agroal.runtime.DataSourceJdbcBuildTimeConfig; import io.quarkus.agroal.runtime.DataSourceSupport; @@ -83,7 +82,7 @@ void build( Capabilities capabilities, BuildProducer sslNativeSupport, BuildProducer aggregatedConfig, - BuildProducer otelInstrumentationActiveProducer, + BuildProducer additionalBeans, CurateOutcomeBuildItem curateOutcomeBuildItem) throws Exception { if (dataSourcesBuildTimeConfig.driver.isPresent() || dataSourcesBuildTimeConfig.url.isPresent()) { throw new ConfigurationException( @@ -111,7 +110,7 @@ void build( DataSources.TRACING_DRIVER_CLASSNAME)); } - if (aggregatedDataSourceBuildTimeConfig.getJdbcConfig().telemetry && !otelJdbcInstrumentationActive) { + if (aggregatedDataSourceBuildTimeConfig.getJdbcConfig().telemetry) { otelJdbcInstrumentationActive = true; } @@ -124,8 +123,10 @@ void build( if (otelJdbcInstrumentationActive && capabilities.isPresent(OPENTELEMETRY_TRACER)) { // at least one datasource is using OpenTelemetry JDBC instrumentation, - // therefore we need to prepare OpenTelemetry data source wrapper - otelInstrumentationActiveProducer.produce(new OpenTelemetryJDBCInstrumentationBuildItem()); + // therefore we register the OpenTelemetry data source wrapper bean + additionalBeans.produce(new AdditionalBeanBuildItem.Builder() + .addBeanClass("io.quarkus.agroal.runtime.AgroalOpenTelemetryWrapper") + .setDefaultScope(DotNames.SINGLETON).build()); } // For now, we can't push the security providers to Agroal so we need to include @@ -216,8 +217,6 @@ void generateDataSourceSupportBean(AgroalRecorder recorder, List aggregatedBuildTimeConfigBuildItems, SslNativeConfigBuildItem sslNativeConfig, Capabilities capabilities, - Optional otelInstrumentationActive, - AgroalOpenTelemetryRecorder agroalOpenTelemetryRecorder, BuildProducer additionalBeans, BuildProducer syntheticBeanBuildItemBuildProducer, BuildProducer unremovableBeans) { @@ -228,13 +227,6 @@ void generateDataSourceSupportBean(AgroalRecorder recorder, return; } - if (otelInstrumentationActive.isPresent()) { - // prepare OpenTelemetry datasource wrapper - // this code must only run when the OpenTelemetry is active - // to avoid native failures - agroalOpenTelemetryRecorder.prepareOpenTelemetryAgroalDatasource(); - } - // make a DataSourceProducer bean additionalBeans.produce(AdditionalBeanBuildItem.builder().addBeanClasses(DataSources.class).setUnremovable() .setDefaultScope(DotNames.SINGLETON).build()); diff --git a/extensions/agroal/deployment/src/main/java/io/quarkus/agroal/deployment/OpenTelemetryJDBCInstrumentationBuildItem.java b/extensions/agroal/deployment/src/main/java/io/quarkus/agroal/deployment/OpenTelemetryJDBCInstrumentationBuildItem.java deleted file mode 100644 index 0499ba6e023b9..0000000000000 --- a/extensions/agroal/deployment/src/main/java/io/quarkus/agroal/deployment/OpenTelemetryJDBCInstrumentationBuildItem.java +++ /dev/null @@ -1,9 +0,0 @@ -package io.quarkus.agroal.deployment; - -import io.quarkus.builder.item.SimpleBuildItem; - -/** - * Purely marker build item that tells us to prepare OpenTelemetry JDBC instrumentation. - */ -final class OpenTelemetryJDBCInstrumentationBuildItem extends SimpleBuildItem { -} diff --git a/extensions/agroal/runtime/src/main/java/io/quarkus/agroal/runtime/AgroalOpenTelemetryRecorder.java b/extensions/agroal/runtime/src/main/java/io/quarkus/agroal/runtime/AgroalOpenTelemetryRecorder.java deleted file mode 100644 index b9700e5f5ae2e..0000000000000 --- a/extensions/agroal/runtime/src/main/java/io/quarkus/agroal/runtime/AgroalOpenTelemetryRecorder.java +++ /dev/null @@ -1,24 +0,0 @@ -package io.quarkus.agroal.runtime; - -import java.util.function.Function; - -import io.agroal.api.AgroalDataSource; -import io.quarkus.runtime.annotations.Recorder; - -@Recorder -public class AgroalOpenTelemetryRecorder { - - /** - * Tell {@link DataSources} to use the OpenTelemetry datasource wrapper for - * data sources with activated telemetry. We need to set this way only when - * optional OpenTelemetry JDBC instrumentation dependency is present to avoid native failures. - */ - public void prepareOpenTelemetryAgroalDatasource() { - DataSources.setOpenTelemetryDatasourceTransformer(new Function() { - @Override - public AgroalDataSource apply(AgroalDataSource agroalDataSource) { - return new OpenTelemetryAgroalDataSource(agroalDataSource); - } - }); - } -} diff --git a/extensions/agroal/runtime/src/main/java/io/quarkus/agroal/runtime/AgroalOpenTelemetryWrapper.java b/extensions/agroal/runtime/src/main/java/io/quarkus/agroal/runtime/AgroalOpenTelemetryWrapper.java new file mode 100644 index 0000000000000..4902b301aa596 --- /dev/null +++ b/extensions/agroal/runtime/src/main/java/io/quarkus/agroal/runtime/AgroalOpenTelemetryWrapper.java @@ -0,0 +1,13 @@ +package io.quarkus.agroal.runtime; + +import java.util.function.Function; + +import io.agroal.api.AgroalDataSource; + +public class AgroalOpenTelemetryWrapper implements Function { + + @Override + public AgroalDataSource apply(AgroalDataSource originalDataSource) { + return new OpenTelemetryAgroalDataSource(originalDataSource); + } +} diff --git a/extensions/agroal/runtime/src/main/java/io/quarkus/agroal/runtime/DataSources.java b/extensions/agroal/runtime/src/main/java/io/quarkus/agroal/runtime/DataSources.java index 62ea0fc49e7bc..e1efb946d8f50 100644 --- a/extensions/agroal/runtime/src/main/java/io/quarkus/agroal/runtime/DataSources.java +++ b/extensions/agroal/runtime/src/main/java/io/quarkus/agroal/runtime/DataSources.java @@ -7,7 +7,6 @@ import java.util.Collection; import java.util.Iterator; import java.util.Map; -import java.util.Objects; import java.util.ServiceLoader; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; @@ -69,7 +68,6 @@ public class DataSources { public static final String TRACING_DRIVER_CLASSNAME = "io.opentracing.contrib.jdbc.TracingDriver"; private static final String JDBC_URL_PREFIX = "jdbc:"; private static final String JDBC_TRACING_URL_PREFIX = "jdbc:tracing:"; - private static volatile Function OTEL_DATASOURCE_TRANSFORMER = null; private final DataSourcesBuildTimeConfig dataSourcesBuildTimeConfig; private final DataSourcesRuntimeConfig dataSourcesRuntimeConfig; @@ -81,6 +79,7 @@ public class DataSources { private final TransactionSynchronizationRegistry transactionSynchronizationRegistry; private final DataSourceSupport dataSourceSupport; private final Instance agroalPoolInterceptors; + private final Instance agroalOpenTelemetryWrapper; private final ConcurrentMap dataSources = new ConcurrentHashMap<>(); @@ -90,8 +89,10 @@ public DataSources(DataSourcesBuildTimeConfig dataSourcesBuildTimeConfig, TransactionManagerConfiguration transactionRuntimeConfig, TransactionManager transactionManager, XAResourceRecoveryRegistry xaResourceRecoveryRegistry, - TransactionSynchronizationRegistry transactionSynchronizationRegistry, DataSourceSupport dataSourceSupport, - @Any Instance agroalPoolInterceptors) { + TransactionSynchronizationRegistry transactionSynchronizationRegistry, + DataSourceSupport dataSourceSupport, + @Any Instance agroalPoolInterceptors, + Instance agroalOpenTelemetryWrapper) { this.dataSourcesBuildTimeConfig = dataSourcesBuildTimeConfig; this.dataSourcesRuntimeConfig = dataSourcesRuntimeConfig; this.dataSourcesJdbcBuildTimeConfig = dataSourcesJdbcBuildTimeConfig; @@ -102,6 +103,7 @@ public DataSources(DataSourcesBuildTimeConfig dataSourcesBuildTimeConfig, this.transactionSynchronizationRegistry = transactionSynchronizationRegistry; this.dataSourceSupport = dataSourceSupport; this.agroalPoolInterceptors = agroalPoolInterceptors; + this.agroalOpenTelemetryWrapper = agroalOpenTelemetryWrapper; } /** @@ -129,6 +131,7 @@ public AgroalDataSource apply(String s) { }); } + @SuppressWarnings("resource") public AgroalDataSource doCreateDataSource(String dataSourceName) { if (!dataSourceSupport.entries.containsKey(dataSourceName)) { throw new IllegalArgumentException("No datasource named '" + dataSourceName + "' exists"); @@ -255,9 +258,9 @@ public AgroalDataSource doCreateDataSource(String dataSourceName) { } if (dataSourceJdbcBuildTimeConfig.telemetry && dataSourceJdbcRuntimeConfig.telemetry.orElse(true)) { - // active OpenTelemetry JDBC instrumentation by wrapping AgroalDatasource - // use the transformer as we can't reference optional OpenTelemetry classes here - dataSource = OTEL_DATASOURCE_TRANSFORMER.apply(dataSource); + // activate OpenTelemetry JDBC instrumentation by wrapping AgroalDatasource + // use an optional CDI bean as we can't reference optional OpenTelemetry classes here + dataSource = agroalOpenTelemetryWrapper.get().apply(dataSource); } return dataSource; @@ -452,13 +455,4 @@ public void stop() { } } } - - /** - * Set a function that will wrap {@link AgroalDataSource} with the OpenTelemetry datasource. - */ - static void setOpenTelemetryDatasourceTransformer(Function otelDatasourceTransformer) { - Objects.requireNonNull(otelDatasourceTransformer); - OTEL_DATASOURCE_TRANSFORMER = otelDatasourceTransformer; - } - } diff --git a/extensions/agroal/runtime/src/main/java/io/quarkus/agroal/runtime/OpenTelemetryAgroalDataSource.java b/extensions/agroal/runtime/src/main/java/io/quarkus/agroal/runtime/OpenTelemetryAgroalDataSource.java index 599be567bcd22..9b5210fd79237 100644 --- a/extensions/agroal/runtime/src/main/java/io/quarkus/agroal/runtime/OpenTelemetryAgroalDataSource.java +++ b/extensions/agroal/runtime/src/main/java/io/quarkus/agroal/runtime/OpenTelemetryAgroalDataSource.java @@ -13,7 +13,7 @@ import io.opentelemetry.instrumentation.jdbc.datasource.OpenTelemetryDataSource; /** - * The {@link AgroalDataSource} wrapper that actives OpenTelemetry JDBC instrumentation. + * The {@link AgroalDataSource} wrapper that activates OpenTelemetry JDBC instrumentation. */ public class OpenTelemetryAgroalDataSource extends OpenTelemetryDataSource implements AgroalDataSource {