Skip to content

Commit

Permalink
Merge pull request #31567 from michalvavrik/feature/opentelemetry-tra…
Browse files Browse the repository at this point in the history
…cing-enabled

Use a property to enable OpenTelemetry for JDBC and use the OpenTelemetry datasource instead of the OpenTelemetry driver
  • Loading branch information
gsmet committed Mar 14, 2023
2 parents 912fc9e + 956f31a commit 8312e06
Show file tree
Hide file tree
Showing 20 changed files with 305 additions and 179 deletions.
11 changes: 6 additions & 5 deletions docs/src/main/asciidoc/opentelemetry.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -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 telemetry 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
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -77,8 +79,10 @@ void build(
List<JdbcDriverBuildItem> jdbcDriverBuildItems,
BuildProducer<ReflectiveClassBuildItem> reflectiveClass,
BuildProducer<NativeImageResourceBuildItem> resource,
Capabilities capabilities,
BuildProducer<ExtensionSslNativeSupportBuildItem> sslNativeSupport,
BuildProducer<AggregatedDataSourceBuildTimeConfigBuildItem> aggregatedConfig,
BuildProducer<AdditionalBeanBuildItem> additionalBeans,
CurateOutcomeBuildItem curateOutcomeBuildItem) throws Exception {
if (dataSourcesBuildTimeConfig.driver.isPresent() || dataSourcesBuildTimeConfig.url.isPresent()) {
throw new ConfigurationException(
Expand All @@ -96,6 +100,7 @@ void build(
return;
}

boolean otelJdbcInstrumentationActive = false;
for (AggregatedDataSourceBuildTimeConfigBuildItem aggregatedDataSourceBuildTimeConfig : aggregatedDataSourceBuildTimeConfigs) {
validateBuildTimeConfig(aggregatedDataSourceBuildTimeConfig);

Expand All @@ -105,13 +110,25 @@ void build(
DataSources.TRACING_DRIVER_CLASSNAME));
}

if (aggregatedDataSourceBuildTimeConfig.getJdbcConfig().telemetry) {
otelJdbcInstrumentationActive = true;
}

reflectiveClass
.produce(new ReflectiveClassBuildItem(true, false,
aggregatedDataSourceBuildTimeConfig.getResolvedDriverClass()));

aggregatedConfig.produce(aggregatedDataSourceBuildTimeConfig);
}

if (otelJdbcInstrumentationActive && capabilities.isPresent(OPENTELEMETRY_TRACER)) {
// at least one datasource is using OpenTelemetry JDBC instrumentation,
// 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
// 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.
Expand Down
7 changes: 7 additions & 0 deletions extensions/agroal/runtime/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,13 @@
<scope>test</scope>
</dependency>

<!-- Required for OpenTelemetry JDBC instrumentation -->
<dependency>
<groupId>io.opentelemetry.instrumentation</groupId>
<artifactId>opentelemetry-jdbc</artifactId>
<optional>true</optional>
</dependency>

</dependencies>

<build>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package io.quarkus.agroal.runtime;

import java.util.function.Function;

import io.agroal.api.AgroalDataSource;

public class AgroalOpenTelemetryWrapper implements Function<AgroalDataSource, AgroalDataSource> {

@Override
public AgroalDataSource apply(AgroalDataSource originalDataSource) {
return new OpenTelemetryAgroalDataSource(originalDataSource);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Original file line number Diff line number Diff line change
Expand Up @@ -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<Boolean> telemetry;

}
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ public class DataSources {
private final TransactionSynchronizationRegistry transactionSynchronizationRegistry;
private final DataSourceSupport dataSourceSupport;
private final Instance<AgroalPoolInterceptor> agroalPoolInterceptors;
private final Instance<AgroalOpenTelemetryWrapper> agroalOpenTelemetryWrapper;

private final ConcurrentMap<String, AgroalDataSource> dataSources = new ConcurrentHashMap<>();

Expand All @@ -88,8 +89,10 @@ public DataSources(DataSourcesBuildTimeConfig dataSourcesBuildTimeConfig,
TransactionManagerConfiguration transactionRuntimeConfig,
TransactionManager transactionManager,
XAResourceRecoveryRegistry xaResourceRecoveryRegistry,
TransactionSynchronizationRegistry transactionSynchronizationRegistry, DataSourceSupport dataSourceSupport,
@Any Instance<AgroalPoolInterceptor> agroalPoolInterceptors) {
TransactionSynchronizationRegistry transactionSynchronizationRegistry,
DataSourceSupport dataSourceSupport,
@Any Instance<AgroalPoolInterceptor> agroalPoolInterceptors,
Instance<AgroalOpenTelemetryWrapper> agroalOpenTelemetryWrapper) {
this.dataSourcesBuildTimeConfig = dataSourcesBuildTimeConfig;
this.dataSourcesRuntimeConfig = dataSourcesRuntimeConfig;
this.dataSourcesJdbcBuildTimeConfig = dataSourcesJdbcBuildTimeConfig;
Expand All @@ -100,6 +103,7 @@ public DataSources(DataSourcesBuildTimeConfig dataSourcesBuildTimeConfig,
this.transactionSynchronizationRegistry = transactionSynchronizationRegistry;
this.dataSourceSupport = dataSourceSupport;
this.agroalPoolInterceptors = agroalPoolInterceptors;
this.agroalOpenTelemetryWrapper = agroalOpenTelemetryWrapper;
}

/**
Expand Down Expand Up @@ -127,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");
Expand Down Expand Up @@ -252,6 +257,12 @@ public AgroalDataSource doCreateDataSource(String dataSourceName) {
dataSource.setPoolInterceptors(interceptorList);
}

if (dataSourceJdbcBuildTimeConfig.telemetry && dataSourceJdbcRuntimeConfig.telemetry.orElse(true)) {
// 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;
}

Expand Down Expand Up @@ -444,5 +455,4 @@ public void stop() {
}
}
}

}
Original file line number Diff line number Diff line change
@@ -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 activates 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<? extends AgroalPoolInterceptor> interceptors) {
delegate.setPoolInterceptors(interceptors);
}

@Override
public List<AgroalPoolInterceptor> getPoolInterceptors() {
return delegate.getPoolInterceptors();
}

@Override
public ShardingKeyBuilder createShardingKeyBuilder() throws SQLException {
return delegate.createShardingKeyBuilder();
}

@Override
public void close() {
delegate.close();
}
}
16 changes: 11 additions & 5 deletions extensions/opentelemetry/deployment/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -111,11 +111,6 @@
<artifactId>quarkus-agroal-deployment</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-jdbc-h2-deployment</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.opentelemetry.instrumentation</groupId>
<artifactId>opentelemetry-jdbc</artifactId>
Expand All @@ -137,6 +132,17 @@
<artifactId>opentelemetry-aws-xray</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-jdbc-h2-deployment</artifactId>
<scope>test</scope>
</dependency>

<!-- Needed for InMemorySpanExporter to verify captured traces -->
<dependency>
<groupId>io.opentelemetry</groupId>
<artifactId>opentelemetry-sdk-testing</artifactId>
</dependency>
</dependencies>

<build>
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand All @@ -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)
Expand Down Expand Up @@ -168,18 +165,21 @@ void storeVertxOnContextStorage(OpenTelemetryRecorder recorder, CoreVertxBuildIt
}

@BuildStep
void collectAllJdbcDataSourcesUsingOTelDriver(BuildProducer<OpenTelemetryDriverJdbcDataSourcesBuildItem> resultProducer,
List<JdbcDataSourceBuildItem> jdbcDataSources) {
final List<JdbcDataSourceBuildItem> result = new ArrayList<>();
void validateDataSourcesWithEnabledTelemetry(List<JdbcDataSourceBuildItem> jdbcDataSources,
BuildProducer<ValidationErrorBuildItem> 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) {
Expand All @@ -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<OpenTelemetryDriverJdbcDataSourcesBuildItem> otJdbcDataSourcesBuildItem,
List<JdbcDriverBuildItem> 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;
}
}
}
}
}
Loading

0 comments on commit 8312e06

Please sign in to comment.