Skip to content

Commit a33f3fe

Browse files
feat(jdbc): basic OpenTelemetry tracing integration for BigQuery JDBC Statement (#12124)
b/491239772 b/491239773 ### Changes - New connection properties: `enableGcpTraceExporter` (Boolean, default: false) and `enableGcpLogExporter` (Boolean, default: false) - `customOpenTelemetry` (Instance): Programmatic injection of a custom SDK (User Application-Managed setup) via `BigQueryDataSource.setCustomOpenTelemetry()` - Added the core initialization logic for `OpenTelemetry`. During connection setup, it evaluates whether tracing is enabled and constructs an OpenTelemetry Tracer instance. Then, it passes this newly minted tracer strictly downward into the core `BigQueryOptions.Builder` via `.setOpenTelemetryTracer()` - Intercepted the execution functions (`execute`, `executeQuery`, `executeLargeUpdate`, `executeBatch`) to spawn child spans wrapping each database call.
1 parent 3750661 commit a33f3fe

9 files changed

Lines changed: 550 additions & 181 deletions

File tree

java-bigquery/google-cloud-bigquery-jdbc/pom.xml

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,17 @@
133133
<relocation>
134134
<pattern>io</pattern>
135135
<shadedPattern>com.google.bqjdbc.shaded.io</shadedPattern>
136+
<excludes>
137+
<!--
138+
OpenTelemetry API and Context must remain unshaded to ensure interoperability.
139+
Shading these would prevent the driver from participating in the application's
140+
existing tracing context. We are aware that unshaded dependencies can lead to
141+
version mismatches, but this is a necessary trade-off for the OpenTelemetry
142+
integration to function correctly across different applications.
143+
-->
144+
<exclude>io.opentelemetry.api.*</exclude>
145+
<exclude>io.opentelemetry.context.*</exclude>
146+
</excludes>
136147
</relocation>
137148
</relocations>
138149
<filters>
@@ -277,6 +288,16 @@
277288
<artifactId>httpcore5</artifactId>
278289
</dependency>
279290

291+
<!-- OpenTelemetry APIs (unshaded) -->
292+
<dependency>
293+
<groupId>io.opentelemetry</groupId>
294+
<artifactId>opentelemetry-api</artifactId>
295+
</dependency>
296+
<dependency>
297+
<groupId>io.opentelemetry</groupId>
298+
<artifactId>opentelemetry-context</artifactId>
299+
</dependency>
300+
280301
<!-- Test Dependencies -->
281302
<dependency>
282303
<groupId>com.google.truth</groupId>

java-bigquery/google-cloud-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryConnection.java

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@
4141
import com.google.cloud.bigquery.storage.v1.BigQueryWriteClient;
4242
import com.google.cloud.bigquery.storage.v1.BigQueryWriteSettings;
4343
import com.google.cloud.http.HttpTransportOptions;
44+
import io.opentelemetry.api.OpenTelemetry;
45+
import io.opentelemetry.api.trace.Tracer;
4446
import java.io.IOException;
4547
import java.io.InputStream;
4648
import java.sql.CallableStatement;
@@ -138,6 +140,11 @@ public class BigQueryConnection extends BigQueryNoOpsConnection {
138140
Long connectionPoolSize;
139141
Long listenerPoolSize;
140142
String partnerToken;
143+
Boolean enableGcpTraceExporter;
144+
Boolean enableGcpLogExporter;
145+
OpenTelemetry customOpenTelemetry;
146+
Tracer tracer =
147+
OpenTelemetry.noop().getTracer(BigQueryJdbcOpenTelemetry.INSTRUMENTATION_SCOPE_NAME);
141148
DatabaseMetaData databaseMetaData;
142149

143150
BigQueryConnection(String url) throws IOException {
@@ -243,6 +250,9 @@ public class BigQueryConnection extends BigQueryNoOpsConnection {
243250
this.connectionPoolSize = ds.getConnectionPoolSize();
244251
this.listenerPoolSize = ds.getListenerPoolSize();
245252
this.partnerToken = ds.getPartnerToken();
253+
this.enableGcpTraceExporter = ds.getEnableGcpTraceExporter();
254+
this.enableGcpLogExporter = ds.getEnableGcpLogExporter();
255+
this.customOpenTelemetry = ds.getCustomOpenTelemetry();
246256

247257
this.headerProvider = createHeaderProvider();
248258
this.bigQuery = getBigQueryConnection();
@@ -939,6 +949,14 @@ private BigQuery getBigQueryConnection() {
939949
bigQueryOptions.setTransportOptions(this.httpTransportOptions);
940950
}
941951

952+
OpenTelemetry openTelemetry =
953+
BigQueryJdbcOpenTelemetry.getOpenTelemetry(
954+
this.enableGcpTraceExporter, this.enableGcpLogExporter, this.customOpenTelemetry);
955+
if (this.enableGcpTraceExporter || this.customOpenTelemetry != null) {
956+
this.tracer = BigQueryJdbcOpenTelemetry.getTracer(openTelemetry);
957+
bigQueryOptions.setOpenTelemetryTracer(this.tracer);
958+
}
959+
942960
BigQueryOptions options = bigQueryOptions.setHeaderProvider(this.headerProvider).build();
943961
options.setDefaultJobCreationMode(
944962
this.useStatelessQueryMode
@@ -987,6 +1005,13 @@ private BigQueryReadClient getBigQueryReadClientConnection() throws IOException
9871005

9881006
bigQueryReadSettings.setTransportChannelProvider(activeProvider);
9891007

1008+
OpenTelemetry openTelemetry =
1009+
BigQueryJdbcOpenTelemetry.getOpenTelemetry(
1010+
this.enableGcpTraceExporter, this.enableGcpLogExporter, this.customOpenTelemetry);
1011+
if (this.enableGcpTraceExporter || this.customOpenTelemetry != null) {
1012+
bigQueryReadSettings.setOpenTelemetryTracerProvider(openTelemetry.getTracerProvider());
1013+
}
1014+
9901015
return BigQueryReadClient.create(bigQueryReadSettings.build());
9911016
}
9921017

@@ -1087,4 +1112,8 @@ public CallableStatement prepareCall(
10871112
}
10881113
return prepareCall(sql);
10891114
}
1115+
1116+
public Tracer getTracer() {
1117+
return this.tracer;
1118+
}
10901119
}

java-bigquery/google-cloud-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryDriver.java

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import com.google.cloud.bigquery.exception.BigQueryJdbcRuntimeException;
2121
import io.grpc.LoadBalancerRegistry;
2222
import io.grpc.internal.PickFirstLoadBalancerProvider;
23+
import io.opentelemetry.api.OpenTelemetry;
2324
import java.io.IOException;
2425
import java.sql.Connection;
2526
import java.sql.Driver;
@@ -121,16 +122,22 @@ public Connection connect(String url, Properties info) throws SQLException {
121122
LOG.finest("++enter++");
122123
try {
123124
if (acceptsURL(url)) {
124-
// strip 'jdbc:' from the URL, add any extra properties
125+
Properties connectInfo = info == null ? new Properties() : (Properties) info.clone();
126+
Object customOpenTelemetryObj = connectInfo.remove("customOpenTelemetry");
127+
125128
String connectionUri =
126-
BigQueryJdbcUrlUtility.appendPropertiesToURL(url.substring(5), this.toString(), info);
129+
BigQueryJdbcUrlUtility.appendPropertiesToURL(
130+
url.substring(5), this.toString(), connectInfo);
127131
try {
128132
BigQueryJdbcUrlUtility.parseUrl(connectionUri);
129133
} catch (BigQueryJdbcRuntimeException e) {
130134
throw new BigQueryJdbcException(e.getMessage(), e);
131135
}
132136

133137
DataSource ds = DataSource.fromUrl(connectionUri);
138+
if (customOpenTelemetryObj instanceof OpenTelemetry) {
139+
ds.setCustomOpenTelemetry((OpenTelemetry) customOpenTelemetryObj);
140+
}
134141

135142
// LogLevel
136143
String logLevelStr = ds.getLogLevel();
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
/*
2+
* Copyright 2026 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.google.cloud.bigquery.jdbc;
18+
19+
import io.opentelemetry.api.OpenTelemetry;
20+
import io.opentelemetry.api.trace.Tracer;
21+
22+
public class BigQueryJdbcOpenTelemetry {
23+
24+
static final String INSTRUMENTATION_SCOPE_NAME = "com.google.cloud.bigquery.jdbc";
25+
26+
private BigQueryJdbcOpenTelemetry() {}
27+
28+
/**
29+
* Initializes or returns the OpenTelemetry instance based on hybrid logic. Prefer
30+
* customOpenTelemetry if provided; fallback to an auto-configured GCP exporter if requested.
31+
*/
32+
public static OpenTelemetry getOpenTelemetry(
33+
boolean enableGcpTraceExporter,
34+
boolean enableGcpLogExporter,
35+
OpenTelemetry customOpenTelemetry) {
36+
if (customOpenTelemetry != null) {
37+
return customOpenTelemetry;
38+
}
39+
40+
if (enableGcpTraceExporter || enableGcpLogExporter) {
41+
// TODO(b/491238299): Initialize and return GCP-specific auto-configured SDK
42+
return OpenTelemetry.noop();
43+
}
44+
45+
return OpenTelemetry.noop();
46+
}
47+
48+
/** Gets a Tracer for the JDBC driver instrumentation scope. */
49+
public static Tracer getTracer(OpenTelemetry openTelemetry) {
50+
return openTelemetry.getTracer(INSTRUMENTATION_SCOPE_NAME);
51+
}
52+
}

java-bigquery/google-cloud-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryJdbcUrlUtility.java

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,10 @@ protected boolean removeEldestEntry(Map.Entry<String, Map<String, String>> eldes
162162
static final int DEFAULT_SWA_APPEND_ROW_COUNT_VALUE = 1000;
163163
static final String SWA_ACTIVATION_ROW_COUNT_PROPERTY_NAME = "SWA_ActivationRowCount";
164164
static final int DEFAULT_SWA_ACTIVATION_ROW_COUNT_VALUE = 3;
165+
static final String ENABLE_GCP_TRACE_EXPORTER_PROPERTY_NAME = "enableGcpTraceExporter";
166+
static final boolean DEFAULT_ENABLE_GCP_TRACE_EXPORTER_VALUE = false;
167+
static final String ENABLE_GCP_LOG_EXPORTER_PROPERTY_NAME = "enableGcpLogExporter";
168+
static final boolean DEFAULT_ENABLE_GCP_LOG_EXPORTER_VALUE = false;
165169
private static final BigQueryJdbcCustomLogger LOG =
166170
new BigQueryJdbcCustomLogger(BigQueryJdbcUrlUtility.class.getName());
167171
static final String FILTER_TABLES_ON_DEFAULT_DATASET_PROPERTY_NAME =
@@ -607,6 +611,18 @@ protected boolean removeEldestEntry(Map.Entry<String, Map<String, String>> eldes
607611
.setDescription(
608612
"Reason for the request, which is passed as the x-goog-request-reason"
609613
+ " header.")
614+
.build(),
615+
BigQueryConnectionProperty.newBuilder()
616+
.setName(ENABLE_GCP_TRACE_EXPORTER_PROPERTY_NAME)
617+
.setDescription(
618+
"Enables or disables GCP OpenTelemetry Trace exporter. Disabled by default.")
619+
.setDefaultValue(String.valueOf(DEFAULT_ENABLE_GCP_TRACE_EXPORTER_VALUE))
620+
.build(),
621+
BigQueryConnectionProperty.newBuilder()
622+
.setName(ENABLE_GCP_LOG_EXPORTER_PROPERTY_NAME)
623+
.setDescription(
624+
"Enables or disables GCP OpenTelemetry Log exporter. Disabled by default.")
625+
.setDefaultValue(String.valueOf(DEFAULT_ENABLE_GCP_LOG_EXPORTER_VALUE))
610626
.build())));
611627

612628
private static final List<String> NETWORK_PROPERTIES =

0 commit comments

Comments
 (0)