Skip to content
45 changes: 45 additions & 0 deletions java-bigquery/google-cloud-bigquery-jdbc/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,10 @@
<relocation>
<pattern>io</pattern>
<shadedPattern>com.google.bqjdbc.shaded.io</shadedPattern>
<excludes>
<exclude>io.opentelemetry.api.*</exclude>
<exclude>io.opentelemetry.context.*</exclude>
</excludes>
</relocation>
</relocations>
<filters>
Expand Down Expand Up @@ -162,6 +166,22 @@
<artifactId>google-cloud-bigquery-parent</artifactId>
<version>2.63.0-SNAPSHOT</version><!-- {x-version-update:google-cloud-bigquery:current} -->
</parent>

<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>2.0.16</version>
</dependency>
<dependency>
<groupId>com.google.re2j</groupId>
<artifactId>re2j</artifactId>
<version>1.8</version>
</dependency>
</dependencies>
</dependencyManagement>

<dependencies>
<dependency>
<groupId>com.google.cloud</groupId>
Expand Down Expand Up @@ -277,6 +297,31 @@
<artifactId>httpcore5</artifactId>
</dependency>

<!-- OpenTelemetry APIs (unshaded) -->
<dependency>
<groupId>io.opentelemetry</groupId>
<artifactId>opentelemetry-api</artifactId>
</dependency>
<dependency>
<groupId>io.opentelemetry</groupId>
<artifactId>opentelemetry-context</artifactId>
</dependency>

<!-- OpenTelemetry SDK & Exporters (shaded) -->
<dependency>
<groupId>io.opentelemetry</groupId>
<artifactId>opentelemetry-sdk</artifactId>
</dependency>
<dependency>
<groupId>com.google.cloud.opentelemetry</groupId>
<artifactId>exporter-trace</artifactId>
<version>0.33.0</version>
</dependency>
<dependency>
<groupId>io.opentelemetry</groupId>
<artifactId>opentelemetry-sdk-trace</artifactId>
</dependency>

<!-- Test Dependencies -->
<dependency>
<groupId>com.google.truth</groupId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@
import com.google.cloud.bigquery.storage.v1.BigQueryWriteClient;
import com.google.cloud.bigquery.storage.v1.BigQueryWriteSettings;
import com.google.cloud.http.HttpTransportOptions;
import io.opentelemetry.api.OpenTelemetry;
import io.opentelemetry.api.trace.Tracer;
import java.io.IOException;
import java.io.InputStream;
import java.sql.CallableStatement;
Expand Down Expand Up @@ -138,6 +140,9 @@ public class BigQueryConnection extends BigQueryNoOpsConnection {
Long connectionPoolSize;
Long listenerPoolSize;
String partnerToken;
boolean enableOpenTelemetry;
String openTelemetryExporter;
Tracer tracer = OpenTelemetry.noop().getTracer("");

BigQueryConnection(String url) throws IOException {
this(url, DataSource.fromUrl(url));
Expand Down Expand Up @@ -242,6 +247,8 @@ public class BigQueryConnection extends BigQueryNoOpsConnection {
this.connectionPoolSize = ds.getConnectionPoolSize();
this.listenerPoolSize = ds.getListenerPoolSize();
this.partnerToken = ds.getPartnerToken();
this.enableOpenTelemetry = ds.getEnableOpenTelemetry();
this.openTelemetryExporter = ds.getOpenTelemetryExporter();

this.headerProvider = createHeaderProvider();
this.bigQuery = getBigQueryConnection();
Expand Down Expand Up @@ -935,6 +942,15 @@ private BigQuery getBigQueryConnection() {
bigQueryOptions.setTransportOptions(this.httpTransportOptions);
}

OpenTelemetry openTelemetry =
BigQueryJdbcOpenTelemetry.getOpenTelemetry(
this.enableOpenTelemetry, this.openTelemetryExporter);
if (this.enableOpenTelemetry) {
this.tracer = BigQueryJdbcOpenTelemetry.getTracer(openTelemetry);
bigQueryOptions.setOpenTelemetryTracer(this.tracer);
BigQueryJdbcOpenTelemetry.attachLoggingBridge();
}

BigQueryOptions options = bigQueryOptions.setHeaderProvider(this.headerProvider).build();
options.setDefaultJobCreationMode(
this.useStatelessQueryMode
Expand Down Expand Up @@ -1083,4 +1099,8 @@ public CallableStatement prepareCall(
}
return prepareCall(sql);
}

public Tracer getTracer() {
return this.tracer;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
/*
* Copyright 2026 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.google.cloud.bigquery.jdbc;

import com.google.cloud.opentelemetry.trace.TraceConfiguration;
import com.google.cloud.opentelemetry.trace.TraceExporter;
import io.opentelemetry.api.GlobalOpenTelemetry;
import io.opentelemetry.api.OpenTelemetry;
import io.opentelemetry.api.trace.Tracer;
import io.opentelemetry.sdk.OpenTelemetrySdk;
import io.opentelemetry.sdk.trace.SdkTracerProvider;
import io.opentelemetry.sdk.trace.export.BatchSpanProcessor;
import io.opentelemetry.sdk.trace.export.SpanExporter;
import java.io.IOException;

public class BigQueryJdbcOpenTelemetry {

private static final BigQueryJdbcCustomLogger LOG =
new BigQueryJdbcCustomLogger(BigQueryJdbcOpenTelemetry.class.getName());
private static final Object lock = new Object();
private static volatile OpenTelemetrySdk autoConfiguredOpenTelemetry;
private static volatile boolean initialized = false;
private static final String INSTRUMENTATION_SCOPE_NAME = "com.google.cloud.bigquery.jdbc";

/**
* Initializes or returns the OpenTelemetry instance based on hybrid logic. Prefer
* GlobalOpenTelemetry; fallback to an auto-configured GCP exporter if requested.
*/
public static OpenTelemetry getOpenTelemetry(boolean enableOpenTelemetry, String exporterType) {
if (!enableOpenTelemetry) {
return OpenTelemetry.noop();
}

OpenTelemetry globalOtel = GlobalOpenTelemetry.get();
if ("gcp".equalsIgnoreCase(exporterType)) {
return getAutoConfiguredOpenTelemetry();
}

return globalOtel;
}

/** Gets a Tracer for the JDBC driver instrumentation scope. */
public static Tracer getTracer(OpenTelemetry openTelemetry) {
return openTelemetry.getTracer(INSTRUMENTATION_SCOPE_NAME);
}

/**
* TODO(b/491245568): Attaches the OpenTelemetry logging bridge to the root
* java.util.logging.Logger. This is currently a no-op due to shading issues with
* `opentelemetry-appender-jul`.
*/
public static void attachLoggingBridge() {
// No-op for now.
Comment on lines +66 to +67
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please avoid no-op functions. It doesn't implement any existing interface, so this function can be added when it performas some meaningful action.

}

private static OpenTelemetry getAutoConfiguredOpenTelemetry() {
if (!initialized) {
synchronized (lock) {
if (!initialized) {
try {
autoConfiguredOpenTelemetry = initGcpOpenTelemetry();
} catch (Exception e) {
LOG.warning("Failed to initialize OpenTelemetry with GCP exporter: " + e.getMessage());
autoConfiguredOpenTelemetry = null;
} finally {
initialized = true;
}
}
}
}
return autoConfiguredOpenTelemetry != null ? autoConfiguredOpenTelemetry : OpenTelemetry.noop();
}

private static OpenTelemetrySdk initGcpOpenTelemetry() throws IOException {
LOG.info("Initializing BigQuery JDBC standalone OpenTelemetry SDK with GCP exporter.");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd separate generic tracer support & default tracer. e.g. if custom tracer is provided, what is the purpose of that function?


SpanExporter traceExporter;
try {
TraceConfiguration configuration = TraceConfiguration.builder().build();
traceExporter = TraceExporter.createWithConfiguration(configuration);
} catch (Exception e) {
throw new RuntimeException(
"Could not create TraceExporter. Ensure exporter-trace is on classpath.", e);
}

SdkTracerProvider tracerProvider =
SdkTracerProvider.builder()
.addSpanProcessor(BatchSpanProcessor.builder(traceExporter).build())
.build();

OpenTelemetrySdk sdk = OpenTelemetrySdk.builder().setTracerProvider(tracerProvider).build();

Runtime.getRuntime()
.addShutdownHook(
new Thread(
() -> {
try {
tracerProvider.close();
} catch (Exception e) {
LOG.warning("Error closing tracer provider: " + e.getMessage());
}
}));

return sdk;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,10 @@ protected boolean removeEldestEntry(Map.Entry<String, Map<String, String>> eldes
static final int DEFAULT_SWA_APPEND_ROW_COUNT_VALUE = 1000;
static final String SWA_ACTIVATION_ROW_COUNT_PROPERTY_NAME = "SWA_ActivationRowCount";
static final int DEFAULT_SWA_ACTIVATION_ROW_COUNT_VALUE = 3;
static final String ENABLE_OPENTELEMETRY_PROPERTY_NAME = "EnableOpenTelemetry";
static final boolean DEFAULT_ENABLE_OPENTELEMETRY_VALUE = false;
static final String OPENTELEMETRY_EXPORTER_PROPERTY_NAME = "OpenTelemetryExporter";
static final String DEFAULT_OPENTELEMETRY_EXPORTER_VALUE = "none";
private static final BigQueryJdbcCustomLogger LOG =
new BigQueryJdbcCustomLogger(BigQueryJdbcUrlUtility.class.getName());
static final String FILTER_TABLES_ON_DEFAULT_DATASET_PROPERTY_NAME =
Expand Down Expand Up @@ -607,6 +611,18 @@ protected boolean removeEldestEntry(Map.Entry<String, Map<String, String>> eldes
.setDescription(
"Reason for the request, which is passed as the x-goog-request-reason"
+ " header.")
.build(),
BigQueryConnectionProperty.newBuilder()
.setName(ENABLE_OPENTELEMETRY_PROPERTY_NAME)
.setDescription(
"Enables or disables OpenTelemetry features in the Driver. Disabled by default.")
.setDefaultValue(String.valueOf(DEFAULT_ENABLE_OPENTELEMETRY_VALUE))
.build(),
BigQueryConnectionProperty.newBuilder()
.setName(OPENTELEMETRY_EXPORTER_PROPERTY_NAME)
.setDescription(
"Specifies the auto-configured OpenTelemetry exporter (e.g., gcp).")
.setDefaultValue(DEFAULT_OPENTELEMETRY_EXPORTER_VALUE)
.build())));

private static final List<String> NETWORK_PROPERTIES =
Expand Down
Loading
Loading