Skip to content

Commit e81ba26

Browse files
committed
Use a property to enable OTEL for JDBC & replace driver with datasource
1 parent 885291b commit e81ba26

File tree

21 files changed

+336
-176
lines changed

21 files changed

+336
-176
lines changed

docs/src/main/asciidoc/opentelemetry.adoc

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -243,15 +243,16 @@ The https://github.com/open-telemetry/opentelemetry-java-instrumentation/tree/ma
243243
implementation("io.opentelemetry.instrumentation:opentelemetry-jdbc")
244244
----
245245

246-
As it uses a dedicated JDBC driver, you must configure your datasource and Hibernate ORM to use it.
246+
As it uses a dedicated JDBC datasource wrapper, you must enable JDBC tracing for your datasource:
247247

248248
[source, properties]
249249
----
250+
# enable tracing
251+
quarkus.datasource.jdbc.telemetry=true
252+
253+
# configure datasource
250254
quarkus.datasource.db-kind=postgresql
251-
# add ':otel' to your database URL
252-
quarkus.datasource.jdbc.url=jdbc:otel:postgresql://localhost:5432/mydatabase
253-
# use the 'OpenTelemetryDriver' instead of the one for your database
254-
quarkus.datasource.jdbc.driver=io.opentelemetry.instrumentation.jdbc.OpenTelemetryDriver
255+
quarkus.datasource.jdbc.url=jdbc:postgresql://localhost:5432/mydatabase
255256
----
256257

257258
== Additional configuration

extensions/agroal/deployment/src/main/java/io/quarkus/agroal/deployment/AgroalProcessor.java

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package io.quarkus.agroal.deployment;
22

3+
import static io.quarkus.deployment.Capability.OPENTELEMETRY_TRACER;
4+
35
import java.sql.Driver;
46
import java.util.ArrayList;
57
import java.util.HashMap;
@@ -20,6 +22,7 @@
2022
import io.agroal.api.AgroalDataSource;
2123
import io.agroal.api.AgroalPoolInterceptor;
2224
import io.quarkus.agroal.DataSource;
25+
import io.quarkus.agroal.runtime.AgroalOpenTelemetryRecorder;
2326
import io.quarkus.agroal.runtime.AgroalRecorder;
2427
import io.quarkus.agroal.runtime.DataSourceJdbcBuildTimeConfig;
2528
import io.quarkus.agroal.runtime.DataSourceSupport;
@@ -77,8 +80,10 @@ void build(
7780
List<JdbcDriverBuildItem> jdbcDriverBuildItems,
7881
BuildProducer<ReflectiveClassBuildItem> reflectiveClass,
7982
BuildProducer<NativeImageResourceBuildItem> resource,
83+
Capabilities capabilities,
8084
BuildProducer<ExtensionSslNativeSupportBuildItem> sslNativeSupport,
8185
BuildProducer<AggregatedDataSourceBuildTimeConfigBuildItem> aggregatedConfig,
86+
BuildProducer<OpenTelemetryJDBCInstrumentationBuildItem> otelInstrumentationActiveProducer,
8287
CurateOutcomeBuildItem curateOutcomeBuildItem) throws Exception {
8388
if (dataSourcesBuildTimeConfig.driver.isPresent() || dataSourcesBuildTimeConfig.url.isPresent()) {
8489
throw new ConfigurationException(
@@ -96,6 +101,7 @@ void build(
96101
return;
97102
}
98103

104+
boolean otelJdbcInstrumentationActive = false;
99105
for (AggregatedDataSourceBuildTimeConfigBuildItem aggregatedDataSourceBuildTimeConfig : aggregatedDataSourceBuildTimeConfigs) {
100106
validateBuildTimeConfig(aggregatedDataSourceBuildTimeConfig);
101107

@@ -105,13 +111,23 @@ void build(
105111
DataSources.TRACING_DRIVER_CLASSNAME));
106112
}
107113

114+
if (aggregatedDataSourceBuildTimeConfig.getJdbcConfig().telemetry && !otelJdbcInstrumentationActive) {
115+
otelJdbcInstrumentationActive = true;
116+
}
117+
108118
reflectiveClass
109119
.produce(new ReflectiveClassBuildItem(true, false,
110120
aggregatedDataSourceBuildTimeConfig.getResolvedDriverClass()));
111121

112122
aggregatedConfig.produce(aggregatedDataSourceBuildTimeConfig);
113123
}
114124

125+
if (otelJdbcInstrumentationActive && capabilities.isPresent(OPENTELEMETRY_TRACER)) {
126+
// at least one datasource is using OpenTelemetry JDBC instrumentation,
127+
// therefore we need to prepare OpenTelemetry data source wrapper
128+
otelInstrumentationActiveProducer.produce(new OpenTelemetryJDBCInstrumentationBuildItem());
129+
}
130+
115131
// For now, we can't push the security providers to Agroal so we need to include
116132
// the service file inside the image. Hopefully, we will get an entry point to
117133
// resolve them at build time and push them to Agroal soon.
@@ -200,6 +216,8 @@ void generateDataSourceSupportBean(AgroalRecorder recorder,
200216
List<AggregatedDataSourceBuildTimeConfigBuildItem> aggregatedBuildTimeConfigBuildItems,
201217
SslNativeConfigBuildItem sslNativeConfig,
202218
Capabilities capabilities,
219+
Optional<OpenTelemetryJDBCInstrumentationBuildItem> otelInstrumentationActive,
220+
AgroalOpenTelemetryRecorder agroalOpenTelemetryRecorder,
203221
BuildProducer<AdditionalBeanBuildItem> additionalBeans,
204222
BuildProducer<SyntheticBeanBuildItem> syntheticBeanBuildItemBuildProducer,
205223
BuildProducer<UnremovableBeanBuildItem> unremovableBeans) {
@@ -210,6 +228,13 @@ void generateDataSourceSupportBean(AgroalRecorder recorder,
210228
return;
211229
}
212230

231+
if (otelInstrumentationActive.isPresent()) {
232+
// prepare OpenTelemetry datasource wrapper
233+
// this code must only run when the OpenTelemetry is active
234+
// to avoid native failures
235+
agroalOpenTelemetryRecorder.prepareOpenTelemetryAgroalDatasource();
236+
}
237+
213238
// make a DataSourceProducer bean
214239
additionalBeans.produce(AdditionalBeanBuildItem.builder().addBeanClasses(DataSources.class).setUnremovable()
215240
.setDefaultScope(DotNames.SINGLETON).build());
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package io.quarkus.agroal.deployment;
2+
3+
import io.quarkus.builder.item.SimpleBuildItem;
4+
5+
/**
6+
* Purely marker build item that tells us to prepare OpenTelemetry JDBC instrumentation.
7+
*/
8+
final class OpenTelemetryJDBCInstrumentationBuildItem extends SimpleBuildItem {
9+
}

extensions/agroal/runtime/pom.xml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,13 @@
7171
<scope>test</scope>
7272
</dependency>
7373

74+
<!-- Required for OpenTelemetry JDBC instrumentation -->
75+
<dependency>
76+
<groupId>io.opentelemetry.instrumentation</groupId>
77+
<artifactId>opentelemetry-jdbc</artifactId>
78+
<optional>true</optional>
79+
</dependency>
80+
7481
</dependencies>
7582

7683
<build>
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package io.quarkus.agroal.runtime;
2+
3+
import java.util.function.Function;
4+
5+
import io.agroal.api.AgroalDataSource;
6+
import io.quarkus.runtime.annotations.Recorder;
7+
8+
@Recorder
9+
public class AgroalOpenTelemetryRecorder {
10+
11+
/**
12+
* Tell {@link DataSources} to use the OpenTelemetry datasource wrapper for
13+
* data sources with activated telemetry. We need to set this way only when
14+
* optional OpenTelemetry JDBC instrumentation dependency is present to avoid native failures.
15+
*/
16+
public void prepareOpenTelemetryAgroalDatasource() {
17+
DataSources.setOpenTelemetryDatasourceTransformer(new Function<AgroalDataSource, AgroalDataSource>() {
18+
@Override
19+
public AgroalDataSource apply(AgroalDataSource agroalDataSource) {
20+
return new OpenTelemetryAgroalDataSource(agroalDataSource);
21+
}
22+
});
23+
}
24+
}

extensions/agroal/runtime/src/main/java/io/quarkus/agroal/runtime/DataSourceJdbcBuildTimeConfig.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,4 +43,10 @@ public class DataSourceJdbcBuildTimeConfig {
4343
*/
4444
@ConfigItem(defaultValue = "false")
4545
public boolean tracing = false;
46+
47+
/**
48+
* Enable OpenTelemetry JDBC instrumentation.
49+
*/
50+
@ConfigItem(defaultValue = "false")
51+
public boolean telemetry = false;
4652
}

extensions/agroal/runtime/src/main/java/io/quarkus/agroal/runtime/DataSourceJdbcRuntimeConfig.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,4 +145,10 @@ public class DataSourceJdbcRuntimeConfig {
145145
@ConfigItem
146146
public DataSourceJdbcTracingRuntimeConfig tracing = new DataSourceJdbcTracingRuntimeConfig();
147147

148+
/**
149+
* Enable OpenTelemetry JDBC instrumentation.
150+
*/
151+
@ConfigItem(name = "telemetry.enabled", defaultValueDocumentation = "false if quarkus.datasource.jdbc.telemetry=false and true if quarkus.datasource.jdbc.telemetry=true")
152+
public Optional<Boolean> telemetry;
153+
148154
}

extensions/agroal/runtime/src/main/java/io/quarkus/agroal/runtime/DataSources.java

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import java.util.Collection;
88
import java.util.Iterator;
99
import java.util.Map;
10+
import java.util.Objects;
1011
import java.util.ServiceLoader;
1112
import java.util.concurrent.ConcurrentHashMap;
1213
import java.util.concurrent.ConcurrentMap;
@@ -68,6 +69,7 @@ public class DataSources {
6869
public static final String TRACING_DRIVER_CLASSNAME = "io.opentracing.contrib.jdbc.TracingDriver";
6970
private static final String JDBC_URL_PREFIX = "jdbc:";
7071
private static final String JDBC_TRACING_URL_PREFIX = "jdbc:tracing:";
72+
private static volatile Function<AgroalDataSource, AgroalDataSource> OTEL_DATASOURCE_TRANSFORMER = null;
7173

7274
private final DataSourcesBuildTimeConfig dataSourcesBuildTimeConfig;
7375
private final DataSourcesRuntimeConfig dataSourcesRuntimeConfig;
@@ -252,6 +254,12 @@ public AgroalDataSource doCreateDataSource(String dataSourceName) {
252254
dataSource.setPoolInterceptors(interceptorList);
253255
}
254256

257+
if (dataSourceJdbcBuildTimeConfig.telemetry && dataSourceJdbcRuntimeConfig.telemetry.orElse(true)) {
258+
// active OpenTelemetry JDBC instrumentation by wrapping AgroalDatasource
259+
// use the transformer as we can't reference optional OpenTelemetry classes here
260+
dataSource = OTEL_DATASOURCE_TRANSFORMER.apply(dataSource);
261+
}
262+
255263
return dataSource;
256264
}
257265

@@ -445,4 +453,12 @@ public void stop() {
445453
}
446454
}
447455

456+
/**
457+
* Set a function that will wrap {@link AgroalDataSource} with the OpenTelemetry datasource.
458+
*/
459+
static void setOpenTelemetryDatasourceTransformer(Function<AgroalDataSource, AgroalDataSource> otelDatasourceTransformer) {
460+
Objects.requireNonNull(otelDatasourceTransformer);
461+
OTEL_DATASOURCE_TRANSFORMER = otelDatasourceTransformer;
462+
}
463+
448464
}
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
package io.quarkus.agroal.runtime;
2+
3+
import java.sql.SQLException;
4+
import java.sql.ShardingKeyBuilder;
5+
import java.util.Collection;
6+
import java.util.List;
7+
8+
import io.agroal.api.AgroalDataSource;
9+
import io.agroal.api.AgroalDataSourceMetrics;
10+
import io.agroal.api.AgroalPoolInterceptor;
11+
import io.agroal.api.configuration.AgroalDataSourceConfiguration;
12+
import io.opentelemetry.api.GlobalOpenTelemetry;
13+
import io.opentelemetry.instrumentation.jdbc.datasource.OpenTelemetryDataSource;
14+
15+
/**
16+
* The {@link AgroalDataSource} wrapper that actives OpenTelemetry JDBC instrumentation.
17+
*/
18+
public class OpenTelemetryAgroalDataSource extends OpenTelemetryDataSource implements AgroalDataSource {
19+
20+
private final AgroalDataSource delegate;
21+
22+
public OpenTelemetryAgroalDataSource(AgroalDataSource delegate) {
23+
super(delegate, GlobalOpenTelemetry.get());
24+
this.delegate = delegate;
25+
}
26+
27+
@Override
28+
public boolean isHealthy(boolean newConnection) throws SQLException {
29+
return delegate.isHealthy(newConnection);
30+
}
31+
32+
@Override
33+
public AgroalDataSourceConfiguration getConfiguration() {
34+
return delegate.getConfiguration();
35+
}
36+
37+
@Override
38+
public AgroalDataSourceMetrics getMetrics() {
39+
return delegate.getMetrics();
40+
}
41+
42+
@Override
43+
public void flush(FlushMode mode) {
44+
delegate.flush(mode);
45+
}
46+
47+
@Override
48+
public void setPoolInterceptors(Collection<? extends AgroalPoolInterceptor> interceptors) {
49+
delegate.setPoolInterceptors(interceptors);
50+
}
51+
52+
@Override
53+
public List<AgroalPoolInterceptor> getPoolInterceptors() {
54+
return delegate.getPoolInterceptors();
55+
}
56+
57+
@Override
58+
public ShardingKeyBuilder createShardingKeyBuilder() throws SQLException {
59+
return delegate.createShardingKeyBuilder();
60+
}
61+
62+
@Override
63+
public void close() {
64+
delegate.close();
65+
}
66+
}

extensions/opentelemetry/deployment/pom.xml

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -111,11 +111,6 @@
111111
<artifactId>quarkus-agroal-deployment</artifactId>
112112
<scope>test</scope>
113113
</dependency>
114-
<dependency>
115-
<groupId>io.quarkus</groupId>
116-
<artifactId>quarkus-jdbc-h2-deployment</artifactId>
117-
<scope>test</scope>
118-
</dependency>
119114
<dependency>
120115
<groupId>io.opentelemetry.instrumentation</groupId>
121116
<artifactId>opentelemetry-jdbc</artifactId>
@@ -137,6 +132,17 @@
137132
<artifactId>opentelemetry-aws-xray</artifactId>
138133
<scope>test</scope>
139134
</dependency>
135+
<dependency>
136+
<groupId>io.quarkus</groupId>
137+
<artifactId>quarkus-jdbc-h2-deployment</artifactId>
138+
<scope>test</scope>
139+
</dependency>
140+
141+
<!-- Needed for InMemorySpanExporter to verify captured traces -->
142+
<dependency>
143+
<groupId>io.opentelemetry</groupId>
144+
<artifactId>opentelemetry-sdk-testing</artifactId>
145+
</dependency>
140146
</dependencies>
141147

142148
<build>

0 commit comments

Comments
 (0)