Skip to content

Commit 003cdee

Browse files
committed
OpenTelemetry JDBC instrumentation - fix Oracle and DB2 in native mode
1 parent bd66b86 commit 003cdee

32 files changed

+1234
-21
lines changed

.github/native-tests.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@
117117
{
118118
"category": "Misc4",
119119
"timeout": 75,
120-
"test-modules": "picocli-native, gradle, micrometer-mp-metrics, micrometer-prometheus, logging-json, jaxp, jaxb, opentelemetry, webjars-locator",
120+
"test-modules": "picocli-native, gradle, micrometer-mp-metrics, micrometer-prometheus, logging-json, jaxp, jaxb, opentelemetry, opentelemetry-jdbc-instrumentation, webjars-locator",
121121
"os-name": "ubuntu-latest"
122122
},
123123
{

core/deployment/src/main/java/io/quarkus/deployment/Capability.java

+1
Original file line numberDiff line numberDiff line change
@@ -137,4 +137,5 @@ public interface Capability {
137137

138138
String SMALLRYE_REACTIVE_MESSAGING = QUARKUS_PREFIX + "smallrye.reactive.messaging";
139139
String REDIS_CLIENT = QUARKUS_PREFIX + "redis";
140+
String JDBC_ORACLE = QUARKUS_PREFIX + "jdbc.oracle";
140141
}

extensions/jdbc/jdbc-db2/deployment/src/main/java/io/quarkus/jdbc/db2/deployment/JDBCDB2Processor.java

+13-1
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,15 @@
1616
import io.quarkus.deployment.builditem.SslNativeConfigBuildItem;
1717
import io.quarkus.deployment.builditem.nativeimage.JPMSExportBuildItem;
1818
import io.quarkus.deployment.builditem.nativeimage.NativeImageConfigBuildItem;
19+
import io.quarkus.deployment.builditem.nativeimage.ReflectiveClassBuildItem;
1920
import io.quarkus.deployment.builditem.nativeimage.ServiceProviderBuildItem;
2021
import io.quarkus.jdbc.db2.runtime.DB2AgroalConnectionConfigurer;
2122
import io.quarkus.jdbc.db2.runtime.DB2ServiceBindingConverter;
2223

2324
public class JDBCDB2Processor {
2425

26+
private static final String DB2_DRIVER_CLASS = "com.ibm.db2.jcc.DB2Driver";
27+
2528
@BuildStep
2629
FeatureBuildItem feature() {
2730
return new FeatureBuildItem(Feature.JDBC_DB2);
@@ -30,7 +33,7 @@ FeatureBuildItem feature() {
3033
@BuildStep
3134
void registerDriver(BuildProducer<JdbcDriverBuildItem> jdbcDriver,
3235
SslNativeConfigBuildItem sslNativeConfigBuildItem) {
33-
jdbcDriver.produce(new JdbcDriverBuildItem(DatabaseKind.DB2, "com.ibm.db2.jcc.DB2Driver",
36+
jdbcDriver.produce(new JdbcDriverBuildItem(DatabaseKind.DB2, DB2_DRIVER_CLASS,
3437
"com.ibm.db2.jcc.DB2XADataSource"));
3538
}
3639

@@ -51,6 +54,15 @@ void configureAgroalConnection(BuildProducer<AdditionalBeanBuildItem> additional
5154
}
5255
}
5356

57+
@BuildStep
58+
void registerDriverForReflection(BuildProducer<ReflectiveClassBuildItem> reflectiveClass) {
59+
//Not strictly necessary when using Agroal, as it also registers
60+
//any JDBC driver being configured explicitly through its configuration.
61+
//We register it for the sake of people not using Agroal,
62+
//for example when the driver is used with OpenTelemetry JDBC instrumentation.
63+
reflectiveClass.produce(new ReflectiveClassBuildItem(false, false, DB2_DRIVER_CLASS));
64+
}
65+
5466
@BuildStep
5567
NativeImageConfigBuildItem build() {
5668
// The DB2 JDBC driver has been updated with conditional checks for the

extensions/jdbc/jdbc-oracle/runtime/pom.xml

+5
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,11 @@
4343
<plugin>
4444
<groupId>io.quarkus</groupId>
4545
<artifactId>quarkus-extension-maven-plugin</artifactId>
46+
<configuration>
47+
<capabilities>
48+
<provides>io.quarkus.jdbc.oracle</provides>
49+
</capabilities>
50+
</configuration>
4651
</plugin>
4752
<plugin>
4853
<artifactId>maven-compiler-plugin</artifactId>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package io.quarkus.opentelemetry.deployment;
2+
3+
import java.util.List;
4+
5+
import io.quarkus.agroal.spi.JdbcDataSourceBuildItem;
6+
import io.quarkus.builder.item.SimpleBuildItem;
7+
8+
/**
9+
* Contains list of all {@link io.quarkus.agroal.spi.JdbcDataSourceBuildItem} using OpenTelemetryDriver.
10+
*/
11+
public final class OpenTelemetryDriverJdbcDataSourcesBuildItem extends SimpleBuildItem {
12+
13+
public final List<JdbcDataSourceBuildItem> jdbcDataSources;
14+
15+
OpenTelemetryDriverJdbcDataSourcesBuildItem(List<JdbcDataSourceBuildItem> jdbcDataSources) {
16+
this.jdbcDataSources = List.copyOf(jdbcDataSources);
17+
}
18+
}

extensions/opentelemetry/deployment/src/main/java/io/quarkus/opentelemetry/deployment/OpenTelemetryProcessor.java

+64
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,15 @@
11
package io.quarkus.opentelemetry.deployment;
22

3+
import static io.quarkus.opentelemetry.runtime.OpenTelemetryRecorder.OPEN_TELEMETRY_DRIVER;
4+
5+
import java.util.ArrayList;
36
import java.util.List;
47
import java.util.Optional;
58
import java.util.Set;
69
import java.util.stream.Collectors;
710

11+
import org.eclipse.microprofile.config.ConfigProvider;
12+
import org.eclipse.microprofile.config.ConfigValue;
813
import org.jboss.jandex.AnnotationInstance;
914
import org.jboss.jandex.AnnotationTarget;
1015
import org.jboss.jandex.AnnotationValue;
@@ -14,11 +19,17 @@
1419
import io.opentelemetry.instrumentation.annotations.SpanAttribute;
1520
import io.opentelemetry.instrumentation.annotations.WithSpan;
1621
import io.opentelemetry.sdk.trace.SdkTracerProvider;
22+
import io.quarkus.agroal.spi.JdbcDataSourceBuildItem;
23+
import io.quarkus.agroal.spi.JdbcDriverBuildItem;
1724
import io.quarkus.arc.deployment.AdditionalBeanBuildItem;
1825
import io.quarkus.arc.deployment.AnnotationsTransformerBuildItem;
1926
import io.quarkus.arc.deployment.InterceptorBindingRegistrarBuildItem;
2027
import io.quarkus.arc.processor.AnnotationsTransformer;
2128
import io.quarkus.arc.processor.InterceptorBindingRegistrar;
29+
import io.quarkus.datasource.common.runtime.DataSourceUtil;
30+
import io.quarkus.datasource.common.runtime.DatabaseKind;
31+
import io.quarkus.deployment.Capabilities;
32+
import io.quarkus.deployment.Capability;
2233
import io.quarkus.deployment.annotations.BuildProducer;
2334
import io.quarkus.deployment.annotations.BuildStep;
2435
import io.quarkus.deployment.annotations.BuildSteps;
@@ -155,4 +166,57 @@ void createOpenTelemetry(
155166
void storeVertxOnContextStorage(OpenTelemetryRecorder recorder, CoreVertxBuildItem vertx) {
156167
recorder.storeVertxOnContextStorage(vertx.getVertx());
157168
}
169+
170+
@BuildStep
171+
void collectAllJdbcDataSourcesUsingOTelDriver(BuildProducer<OpenTelemetryDriverJdbcDataSourcesBuildItem> resultProducer,
172+
List<JdbcDataSourceBuildItem> jdbcDataSources) {
173+
final List<JdbcDataSourceBuildItem> result = new ArrayList<>();
174+
for (JdbcDataSourceBuildItem dataSource : jdbcDataSources) {
175+
// if the datasource is explicitly configured to use the OTel driver...
176+
if (dataSourceUsesOTelJdbcDriver(dataSource.getName())) {
177+
result.add(dataSource);
178+
}
179+
}
180+
if (!result.isEmpty()) {
181+
resultProducer.produce(new OpenTelemetryDriverJdbcDataSourcesBuildItem(result));
182+
}
183+
}
184+
185+
private static boolean dataSourceUsesOTelJdbcDriver(String dataSourceName) {
186+
List<String> driverPropertyKeys = DataSourceUtil.dataSourcePropertyKeys(dataSourceName, "jdbc.driver");
187+
for (String driverPropertyKey : driverPropertyKeys) {
188+
ConfigValue explicitlyConfiguredDriverValue = ConfigProvider.getConfig().getConfigValue(driverPropertyKey);
189+
if (explicitlyConfiguredDriverValue.getValue() != null) {
190+
return explicitlyConfiguredDriverValue.getValue().equals(OPEN_TELEMETRY_DRIVER);
191+
}
192+
}
193+
return false;
194+
}
195+
196+
/**
197+
* 'OracleDriver' register itself as driver in static initialization block, however we don't want to
198+
* force runtime initialization for compatibility reasons, for more information please check:
199+
* io.quarkus.jdbc.oracle.deployment.OracleMetadataOverrides#runtimeInitializeDriver
200+
*/
201+
@BuildStep
202+
@Record(ExecutionTime.RUNTIME_INIT)
203+
void registerOracleDriver(Optional<OpenTelemetryDriverJdbcDataSourcesBuildItem> otJdbcDataSourcesBuildItem,
204+
List<JdbcDriverBuildItem> driverBuildItems, Capabilities capabilities, OpenTelemetryRecorder recorder) {
205+
// check if there are data sources using OT driver and jdbc-oracle extension is present
206+
if (otJdbcDataSourcesBuildItem.isPresent() && capabilities.isPresent(Capability.JDBC_ORACLE)) {
207+
for (JdbcDataSourceBuildItem jdbcDataSource : otJdbcDataSourcesBuildItem.get().jdbcDataSources) {
208+
if (jdbcDataSource.getDbKind().equals(DatabaseKind.ORACLE)) {
209+
// now we know there is Oracle JDBC datasource
210+
// let's find Oracle driver
211+
for (JdbcDriverBuildItem driverBuildItem : driverBuildItems) {
212+
if (DatabaseKind.ORACLE.equals(driverBuildItem.getDbKind())) {
213+
recorder.registerJdbcDriver(driverBuildItem.getDriverClass());
214+
break;
215+
}
216+
}
217+
break;
218+
}
219+
}
220+
}
221+
}
158222
}

extensions/opentelemetry/deployment/src/main/java/io/quarkus/opentelemetry/deployment/dev/DevServicesOpenTelemetryProcessor.java

+6-19
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,7 @@
33
import java.util.HashMap;
44
import java.util.List;
55
import java.util.Map;
6-
7-
import org.eclipse.microprofile.config.ConfigProvider;
8-
import org.eclipse.microprofile.config.ConfigValue;
6+
import java.util.Optional;
97

108
import io.quarkus.agroal.spi.JdbcDataSourceBuildItem;
119
import io.quarkus.datasource.common.runtime.DataSourceUtil;
@@ -15,17 +13,18 @@
1513
import io.quarkus.deployment.annotations.BuildSteps;
1614
import io.quarkus.deployment.builditem.DevServicesAdditionalConfigBuildItem;
1715
import io.quarkus.deployment.dev.devservices.GlobalDevServicesConfig;
16+
import io.quarkus.opentelemetry.deployment.OpenTelemetryDriverJdbcDataSourcesBuildItem;
1817
import io.quarkus.opentelemetry.deployment.OpenTelemetryEnabled;
1918

2019
@BuildSteps(onlyIfNot = IsNormal.class, onlyIf = { OpenTelemetryEnabled.class, GlobalDevServicesConfig.Enabled.class })
2120
public class DevServicesOpenTelemetryProcessor {
2221

2322
@BuildStep
24-
void devServicesDatasources(List<JdbcDataSourceBuildItem> jdbcDataSources,
23+
void devServicesDatasources(Optional<OpenTelemetryDriverJdbcDataSourcesBuildItem> otJdbcDataSourcesBuildItem,
2524
BuildProducer<DevServicesAdditionalConfigBuildItem> devServicesAdditionalConfig) {
26-
for (JdbcDataSourceBuildItem dataSource : jdbcDataSources) {
27-
// if the datasource is explicitly configured to use the OTel driver...
28-
if (dataSourceUsesOTelJdbcDriver(dataSource.getName())) {
25+
if (otJdbcDataSourcesBuildItem.isPresent()) {
26+
// found datasources explicitly configured to use the OTel driver
27+
for (JdbcDataSourceBuildItem dataSource : otJdbcDataSourcesBuildItem.get().jdbcDataSources) {
2928
List<String> urlPropertyKeys = DataSourceUtil.dataSourcePropertyKeys(dataSource.getName(), "jdbc.url");
3029
devServicesAdditionalConfig.produce(new DevServicesAdditionalConfigBuildItem(devServicesConfig -> {
3130
Map<String, String> overrides = new HashMap<>();
@@ -42,16 +41,4 @@ void devServicesDatasources(List<JdbcDataSourceBuildItem> jdbcDataSources,
4241
}
4342
}
4443
}
45-
46-
private boolean dataSourceUsesOTelJdbcDriver(String dataSourceName) {
47-
List<String> driverPropertyKeys = DataSourceUtil.dataSourcePropertyKeys(dataSourceName, "jdbc.driver");
48-
for (String driverPropertyKey : driverPropertyKeys) {
49-
ConfigValue explicitlyConfiguredDriverValue = ConfigProvider.getConfig().getConfigValue(driverPropertyKey);
50-
if (explicitlyConfiguredDriverValue.getValue() != null) {
51-
return explicitlyConfiguredDriverValue.getValue()
52-
.equals("io.opentelemetry.instrumentation.jdbc.OpenTelemetryDriver");
53-
}
54-
}
55-
return false;
56-
}
5744
}

extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/OpenTelemetryRecorder.java

+34
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
package io.quarkus.opentelemetry.runtime;
22

3+
import java.lang.reflect.InvocationTargetException;
4+
import java.sql.Driver;
35
import java.util.function.Supplier;
46

7+
import org.jboss.logging.Logger;
8+
59
import io.opentelemetry.api.GlobalOpenTelemetry;
610
import io.opentelemetry.api.OpenTelemetry;
711
import io.opentelemetry.context.ContextStorage;
@@ -16,6 +20,9 @@
1620
@Recorder
1721
public class OpenTelemetryRecorder {
1822

23+
public static final String OPEN_TELEMETRY_DRIVER = "io.opentelemetry.instrumentation.jdbc.OpenTelemetryDriver";
24+
private static final Logger LOG = Logger.getLogger(OpenTelemetryRecorder.class);
25+
1926
/* STATIC INIT */
2027
public void resetGlobalOpenTelemetryForDevMode() {
2128
GlobalOpenTelemetry.resetForTest();
@@ -46,4 +53,31 @@ public void eagerlyCreateContextStorage() {
4653
public void storeVertxOnContextStorage(Supplier<Vertx> vertx) {
4754
QuarkusContextStorage.vertx = vertx.get();
4855
}
56+
57+
public void registerJdbcDriver(String driverClass) {
58+
try {
59+
var constructors = Class
60+
.forName(driverClass, true, Thread.currentThread().getContextClassLoader())
61+
.getConstructors();
62+
if (constructors.length == 1) {
63+
// create driver
64+
Driver driver = ((Driver) constructors[0].newInstance());
65+
// register the driver with OpenTelemetryDriver
66+
Class
67+
.forName(OPEN_TELEMETRY_DRIVER, true, Thread.currentThread().getContextClassLoader())
68+
.getMethod("addDriverCandidate", Driver.class)
69+
.invoke(null, driver);
70+
} else {
71+
// drivers should have default constructor
72+
LOG.warn(String.format(
73+
"Class '%s' has more than one constructor and won't be registered as driver. JDBC instrumentation might not work properly in native mode.",
74+
driverClass));
75+
}
76+
} catch (InvocationTargetException | InstantiationException | IllegalAccessException | NoSuchMethodException
77+
| ClassNotFoundException e) {
78+
LOG.warn(String.format(
79+
"Failed to register '%s' driver. JDBC instrumentation might not work properly in native mode.",
80+
driverClass));
81+
}
82+
}
4983
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
# OpenTelemetry JDBC instrumentation example
2+
3+
## Running the tests
4+
5+
6+
To run the tests in a standard JVM with an Oracle, PostgreSQL and MariaDB databases started as a Docker containers, you can run the following command:
7+
8+
```
9+
mvn verify -Dtest-containers -Dstart-containers
10+
```
11+
12+
To also test as a native image, add `-Dnative`:
13+
14+
```
15+
mvn verify -Dtest-containers -Dstart-containers -Dnative
16+
```
17+
18+
You can also run tests with a specific database image, just set the following parameters:
19+
20+
- `oracle.image` for Oracle
21+
- `postgres.image` for PostgreSQL
22+
- `mariadb.image` for MariaDB
23+
- `db2.image` for Db2
24+
25+
For example to run tests with the latest PostgreSQL database image, you can run the following command:
26+
27+
```
28+
mvn verify -Dtest-containers -Dstart-containers -Dpostgres.image=docker.io/postgres:latest
29+
```
30+
31+
Unfortunately booting DB2 is slow and needs to set a generous timeout, therefore the DB2 test is disabled by default.
32+
You can enable it with `enable-db2` system property like this:
33+
34+
```
35+
mvn verify -Dtest-containers -Dstart-containers -Denable-db2
36+
```

0 commit comments

Comments
 (0)