Skip to content

Commit 6229229

Browse files
feat(bigquery-jdbc): add globalOtel support (#13282)
b/517124136 This PR introduces support for opting into the global OpenTelemetry instance via connection properties in the BigQuery JDBC driver. It also establishes a clear hierarchy of precedence for various OpenTelemetry configuration flags to ensure predictable behavior ## Key Changes ### 1. Global OpenTelemetry Support * Added a new connection property `useGlobalOpenTelemetry` (default: `false`). * When set to `true`, the driver will explicitly use the globally registered OpenTelemetry instance (`GlobalOpenTelemetry.get()`) for both tracing and logging. ### 2. Precedence Rules for Telemetry Flags Implemented the following hierarchy of precedence in `BigQueryConnection`: 1. **`customOpenTelemetry`** (Provided via `DataSource`): Highest precedence. Overrides everything else. 2. **`useGlobalOpenTelemetry`**: Second precedence. Overrides driver-managed GCP fallback flags. 3. **`enableGcpTraceExporter` / `enableGcpLogExporter`**: Lowest precedence. Used only if above are false/null. ### 3. Code Modifications * **`BigQueryJdbcUrlUtility.java`**: Added property definition and added it to valid properties. * **`DataSource.java`**: Added field, getter/setter, and URL parsing support for the new property. * **`BigQueryConnection.java`**: Updated constructor to read the new property and implemented the precedence logic for both tracer initialization and logging client registration. * **`BigQueryJdbcOpenTelemetry.java`**: Updated `getOpenTelemetry()` to handle the `useGlobalOpenTelemetry` flag. ### 4. Testing * **`BigQueryJdbcOpenTelemetryTest.java`**: Added unit test to verify that `useGlobalOpenTelemetry` returns the global instance. * **`BigQueryConnectionTest.java`**: Added a comprehensive parameterized test (`testOpenTelemetryPrecedenceHierarchy`) covering all 8 combinations of the 4 flags to verify the correct behavior of `OpenTelemetry` resolution and logging client creation.
1 parent aef912c commit 6229229

6 files changed

Lines changed: 209 additions & 16 deletions

File tree

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

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,7 @@ public class BigQueryConnection extends BigQueryNoOpsConnection {
216216
boolean enableGcpTraceExporter;
217217
boolean enableGcpLogExporter;
218218
OpenTelemetry customOpenTelemetry;
219+
boolean useGlobalOpenTelemetry;
219220
private OpenTelemetry openTelemetry;
220221
private Context otelContext;
221222
Tracer tracer =
@@ -367,6 +368,7 @@ public class BigQueryConnection extends BigQueryNoOpsConnection {
367368
this.enableGcpTraceExporter = ds.getEnableGcpTraceExporter();
368369
this.enableGcpLogExporter = ds.getEnableGcpLogExporter();
369370
this.customOpenTelemetry = ds.getCustomOpenTelemetry();
371+
this.useGlobalOpenTelemetry = ds.getUseGlobalOpenTelemetry();
370372
this.openTelemetry = getOpenTelemetryInstance();
371373
this.bigQuery = getBigQueryConnection();
372374
}
@@ -1038,7 +1040,6 @@ void removeStatement(Statement statement) {
10381040
}
10391041

10401042
private OpenTelemetry getOpenTelemetryInstance() {
1041-
boolean hasCustomOtel = this.customOpenTelemetry != null;
10421043

10431044
String effectiveProjectId =
10441045
(this.gcpTelemetryProjectId != null) ? this.gcpTelemetryProjectId : this.catalog;
@@ -1048,22 +1049,27 @@ private OpenTelemetry getOpenTelemetryInstance() {
10481049

10491050
OpenTelemetry openTelemetry =
10501051
BigQueryJdbcOpenTelemetry.getOpenTelemetry(
1052+
this.useGlobalOpenTelemetry,
10511053
this.enableGcpTraceExporter,
10521054
this.enableGcpLogExporter,
10531055
this.customOpenTelemetry,
10541056
effectiveCredentials,
10551057
effectiveProjectId);
10561058

1059+
boolean hasExternalOtel = this.customOpenTelemetry != null || this.useGlobalOpenTelemetry;
10571060
Logging localLoggingClient = null;
1058-
if (this.enableGcpLogExporter && !hasCustomOtel) {
1061+
if (this.enableGcpLogExporter && !hasExternalOtel) {
10591062
localLoggingClient =
10601063
BigQueryJdbcOpenTelemetry.createLoggingClient(
10611064
true, null, effectiveCredentials, effectiveProjectId, this.credentials);
10621065
}
10631066

1064-
if (this.enableGcpLogExporter || hasCustomOtel) {
1067+
if (this.enableGcpLogExporter || hasExternalOtel) {
10651068
BigQueryJdbcOpenTelemetry.registerConnection(
1066-
this.connectionId, openTelemetry, localLoggingClient, this.enableGcpLogExporter);
1069+
this.connectionId,
1070+
openTelemetry,
1071+
localLoggingClient,
1072+
this.enableGcpLogExporter && !hasExternalOtel);
10671073
}
10681074

10691075
return openTelemetry;
@@ -1128,7 +1134,9 @@ private BigQuery getBigQueryConnection() {
11281134
if (this.httpTransportOptions != null) {
11291135
bigQueryOptions.setTransportOptions(this.httpTransportOptions);
11301136
}
1131-
if (this.enableGcpTraceExporter || this.customOpenTelemetry != null) {
1137+
if (this.enableGcpTraceExporter
1138+
|| this.customOpenTelemetry != null
1139+
|| this.useGlobalOpenTelemetry) {
11321140
Tracer sdkTracer = this.openTelemetry.getTracer(BigQueryJdbcOpenTelemetry.BIGQUERY_NAMESPACE);
11331141
bigQueryOptions.setOpenTelemetryTracer(sdkTracer);
11341142
this.tracer =

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import com.google.cloud.logging.Logging;
2222
import com.google.cloud.logging.LoggingOptions;
2323
import com.google.common.hash.Hashing;
24+
import io.opentelemetry.api.GlobalOpenTelemetry;
2425
import io.opentelemetry.api.OpenTelemetry;
2526
import io.opentelemetry.api.baggage.Baggage;
2627
import io.opentelemetry.api.trace.Span;
@@ -245,6 +246,7 @@ private static String getCredentialsIdentifier(String credentials) {
245246
* customOpenTelemetry if provided; fallback to an auto-configured GCP exporter if requested.
246247
*/
247248
public static OpenTelemetry getOpenTelemetry(
249+
boolean useGlobalOpenTelemetry,
248250
boolean enableGcpTraceExporter,
249251
boolean enableGcpLogExporter,
250252
OpenTelemetry customOpenTelemetry,
@@ -255,6 +257,10 @@ public static OpenTelemetry getOpenTelemetry(
255257
return customOpenTelemetry;
256258
}
257259

260+
if (useGlobalOpenTelemetry) {
261+
return GlobalOpenTelemetry.get();
262+
}
263+
258264
// NOTE: Currently, tracing only fully supports Application Default Credentials (ADC).
259265
// Once b/503721589 is completed, Service Account (SA) will work as well.
260266
if (!enableGcpTraceExporter && !enableGcpLogExporter) {

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,8 @@ protected boolean removeEldestEntry(Map.Entry<String, Map<String, String>> eldes
168168
static final boolean DEFAULT_ENABLE_GCP_TRACE_EXPORTER_VALUE = false;
169169
static final String ENABLE_GCP_LOG_EXPORTER_PROPERTY_NAME = "enableGcpLogExporter";
170170
static final boolean DEFAULT_ENABLE_GCP_LOG_EXPORTER_VALUE = false;
171+
static final String USE_GLOBAL_OTEL_PROPERTY_NAME = "useGlobalOpenTelemetry";
172+
static final boolean DEFAULT_USE_GLOBAL_OTEL_VALUE = false;
171173
private static final BigQueryJdbcCustomLogger LOG =
172174
new BigQueryJdbcCustomLogger(BigQueryJdbcUrlUtility.class.getName());
173175
static final String FILTER_TABLES_ON_DEFAULT_DATASET_PROPERTY_NAME =
@@ -638,6 +640,12 @@ protected boolean removeEldestEntry(Map.Entry<String, Map<String, String>> eldes
638640
BigQueryConnectionProperty.newBuilder()
639641
.setName(GCP_TELEMETRY_PROJECT_ID_PROPERTY_NAME)
640642
.setDescription("GCP Project ID for OTel exporter.")
643+
.build(),
644+
BigQueryConnectionProperty.newBuilder()
645+
.setName(USE_GLOBAL_OTEL_PROPERTY_NAME)
646+
.setDescription(
647+
"Enables usage of the Global OpenTelemetry instance when true. Default is false.")
648+
.setDefaultValue(String.valueOf(DEFAULT_USE_GLOBAL_OTEL_VALUE))
641649
.build())));
642650

643651
private static final List<String> NETWORK_PROPERTIES =

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

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,7 @@ public class DataSource implements javax.sql.DataSource {
123123
private boolean enableGcpLogExporter =
124124
BigQueryJdbcUrlUtility.DEFAULT_ENABLE_GCP_LOG_EXPORTER_VALUE;
125125
private OpenTelemetry customOpenTelemetry;
126+
private boolean useGlobalOpenTelemetry = BigQueryJdbcUrlUtility.DEFAULT_USE_GLOBAL_OTEL_VALUE;
126127

127128
// Make sure the JDBC driver class is loaded.
128129
static {
@@ -358,6 +359,12 @@ public class DataSource implements javax.sql.DataSource {
358359
ds.setEnableGcpLogExporter(
359360
BigQueryJdbcUrlUtility.convertIntToBoolean(
360361
val, BigQueryJdbcUrlUtility.ENABLE_GCP_LOG_EXPORTER_PROPERTY_NAME)))
362+
.put(
363+
BigQueryJdbcUrlUtility.USE_GLOBAL_OTEL_PROPERTY_NAME,
364+
(ds, val) ->
365+
ds.setUseGlobalOpenTelemetry(
366+
BigQueryJdbcUrlUtility.convertIntToBoolean(
367+
val, BigQueryJdbcUrlUtility.USE_GLOBAL_OTEL_PROPERTY_NAME)))
361368
.build();
362369

363370
public static DataSource fromUrl(String url) {
@@ -675,6 +682,11 @@ Properties createProperties() {
675682
BigQueryJdbcUrlUtility.ENABLE_GCP_LOG_EXPORTER_PROPERTY_NAME,
676683
String.valueOf(this.enableGcpLogExporter));
677684
}
685+
if (this.useGlobalOpenTelemetry) {
686+
connectionProperties.setProperty(
687+
BigQueryJdbcUrlUtility.USE_GLOBAL_OTEL_PROPERTY_NAME,
688+
String.valueOf(this.useGlobalOpenTelemetry));
689+
}
678690
return connectionProperties;
679691
}
680692

@@ -832,6 +844,14 @@ public void setCustomOpenTelemetry(OpenTelemetry customOpenTelemetry) {
832844
this.customOpenTelemetry = customOpenTelemetry;
833845
}
834846

847+
public boolean getUseGlobalOpenTelemetry() {
848+
return useGlobalOpenTelemetry;
849+
}
850+
851+
public void setUseGlobalOpenTelemetry(boolean useGlobalOpenTelemetry) {
852+
this.useGlobalOpenTelemetry = useGlobalOpenTelemetry;
853+
}
854+
835855
public void setHighThroughputMinTableSize(Integer highThroughputMinTableSize) {
836856
if (highThroughputMinTableSize != null) {
837857
validateNonNegative(

java-bigquery-jdbc/src/test/java/com/google/cloud/bigquery/jdbc/BigQueryConnectionTest.java

Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,15 +17,27 @@
1717
package com.google.cloud.bigquery.jdbc;
1818

1919
import static org.junit.jupiter.api.Assertions.*;
20+
import static org.mockito.ArgumentMatchers.any;
21+
import static org.mockito.ArgumentMatchers.anyBoolean;
22+
import static org.mockito.ArgumentMatchers.anyString;
23+
import static org.mockito.ArgumentMatchers.eq;
24+
import static org.mockito.ArgumentMatchers.isNull;
25+
import static org.mockito.Mockito.mock;
26+
import static org.mockito.Mockito.mockStatic;
27+
import static org.mockito.Mockito.never;
28+
import static org.mockito.Mockito.when;
2029

2130
import com.google.api.gax.grpc.InstantiatingGrpcChannelProvider;
2231
import com.google.api.gax.rpc.HeaderProvider;
2332
import com.google.api.gax.rpc.TransportChannelProvider;
33+
import com.google.auth.oauth2.GoogleCredentials;
2434
import com.google.cloud.bigquery.BigQuery;
2535
import com.google.cloud.bigquery.QueryJobConfiguration.JobCreationMode;
2636
import com.google.cloud.bigquery.exception.BigQueryJdbcException;
2737
import com.google.cloud.bigquery.storage.v1.BigQueryReadClient;
2838
import com.google.cloud.bigquery.storage.v1.BigQueryWriteClient;
39+
import com.google.cloud.logging.Logging;
40+
import io.opentelemetry.api.OpenTelemetry;
2941
import io.opentelemetry.api.trace.Span;
3042
import io.opentelemetry.api.trace.Tracer;
3143
import io.opentelemetry.sdk.testing.junit5.OpenTelemetryExtension;
@@ -44,6 +56,7 @@
4456
import org.junit.jupiter.api.extension.RegisterExtension;
4557
import org.junit.jupiter.params.ParameterizedTest;
4658
import org.junit.jupiter.params.provider.CsvSource;
59+
import org.mockito.MockedStatic;
4760

4861
public class BigQueryConnectionTest extends BigQueryJdbcLoggingBaseTest {
4962

@@ -522,4 +535,133 @@ public void testConnectionPropertiesLoggingAndMasking() throws IOException, SQLE
522535
rootLogger.setLevel(originalLevel);
523536
}
524537
}
538+
539+
@ParameterizedTest(
540+
name =
541+
"Case {index}: custom={0}, global={1}, trace={2}, log={3} -> expectTrace={4}, expectLog={5}")
542+
@CsvSource({
543+
// hasCustom, useGlobal, enableTrace, enableLog, expectTrace, expectLog
544+
"true, true, true, true, CUSTOM, CUSTOM",
545+
"true, false, true, true, CUSTOM, CUSTOM",
546+
"false, true, true, true, GLOBAL, GLOBAL",
547+
"false, true, false, false, GLOBAL, GLOBAL",
548+
"false, false, true, false, DRIVER_MANAGED, NONE",
549+
"false, false, false, true, NONE, DRIVER_MANAGED",
550+
"false, false, true, true, DRIVER_MANAGED, DRIVER_MANAGED",
551+
"false, false, false, false, NONE, NONE"
552+
})
553+
public void testOpenTelemetryPrecedenceHierarchy(
554+
boolean hasCustom,
555+
boolean useGlobal,
556+
boolean enableTrace,
557+
boolean enableLog,
558+
String expectTrace,
559+
String expectLog)
560+
throws Exception {
561+
562+
DataSource ds = DataSource.fromUrl(BASE_URL);
563+
ds.setUseGlobalOpenTelemetry(useGlobal);
564+
ds.setEnableGcpTraceExporter(enableTrace);
565+
ds.setEnableGcpLogExporter(enableLog);
566+
567+
OpenTelemetry mockCustomOtel = mock(OpenTelemetry.class);
568+
OpenTelemetry mockGlobalOtel = mock(OpenTelemetry.class);
569+
OpenTelemetry mockDriverManagedOtel = mock(OpenTelemetry.class);
570+
Logging mockLogging = mock(Logging.class);
571+
when(mockCustomOtel.getTracer(anyString())).thenReturn(mock(Tracer.class));
572+
when(mockGlobalOtel.getTracer(anyString())).thenReturn(mock(Tracer.class));
573+
when(mockDriverManagedOtel.getTracer(anyString())).thenReturn(mock(Tracer.class));
574+
575+
if (hasCustom) {
576+
ds.setCustomOpenTelemetry(mockCustomOtel);
577+
}
578+
579+
try (MockedStatic<BigQueryJdbcOpenTelemetry> mockedOtel =
580+
mockStatic(BigQueryJdbcOpenTelemetry.class);
581+
MockedStatic<BigQueryJdbcOAuthUtility> mockedAuth =
582+
mockStatic(BigQueryJdbcOAuthUtility.class);
583+
MockedStatic<GoogleCredentials> mockedCreds = mockStatic(GoogleCredentials.class)) {
584+
585+
mockedCreds
586+
.when(GoogleCredentials::getApplicationDefault)
587+
.thenReturn(mock(GoogleCredentials.class));
588+
589+
// Mock parseOAuthProperties to always return ADC type to bypass validation
590+
mockedAuth
591+
.when(() -> BigQueryJdbcOAuthUtility.parseOAuthProperties(any(), anyString()))
592+
.thenAnswer(
593+
invocation -> {
594+
java.util.Map<String, String> props = new java.util.HashMap<>();
595+
props.put(
596+
BigQueryJdbcUrlUtility.OAUTH_TYPE_PROPERTY_NAME,
597+
"APPLICATION_DEFAULT_CREDENTIALS");
598+
return props;
599+
});
600+
601+
mockedAuth
602+
.when(() -> BigQueryJdbcOAuthUtility.getCredentials(any(), any(), any(), any()))
603+
.thenReturn(mock(GoogleCredentials.class));
604+
605+
mockedOtel
606+
.when(
607+
() ->
608+
BigQueryJdbcOpenTelemetry.createLoggingClient(
609+
anyBoolean(), any(), any(), any(), any()))
610+
.thenReturn(mockLogging);
611+
612+
// Stub getOpenTelemetry to return the expected mock based on inputs
613+
mockedOtel
614+
.when(
615+
() ->
616+
BigQueryJdbcOpenTelemetry.getOpenTelemetry(
617+
eq(useGlobal),
618+
eq(enableTrace),
619+
eq(enableLog),
620+
hasCustom ? eq(mockCustomOtel) : isNull(),
621+
any(),
622+
any()))
623+
.thenAnswer(
624+
invocation -> {
625+
if (hasCustom) return mockCustomOtel;
626+
if (useGlobal) return mockGlobalOtel;
627+
if (enableTrace || enableLog) return mockDriverManagedOtel;
628+
return OpenTelemetry.noop();
629+
});
630+
631+
try (BigQueryConnection connection = new BigQueryConnection(BASE_URL, ds)) {
632+
633+
boolean shouldBeRegistered = enableLog || hasCustom || useGlobal;
634+
635+
if (!shouldBeRegistered) {
636+
mockedOtel.verify(
637+
() ->
638+
BigQueryJdbcOpenTelemetry.registerConnection(
639+
anyString(), any(), any(), anyBoolean()),
640+
never());
641+
} else {
642+
final OpenTelemetry expectedOtelInstance;
643+
if ("CUSTOM".equals(expectTrace) || "CUSTOM".equals(expectLog)) {
644+
expectedOtelInstance = mockCustomOtel;
645+
} else if ("GLOBAL".equals(expectTrace) || "GLOBAL".equals(expectLog)) {
646+
expectedOtelInstance = mockGlobalOtel;
647+
} else if ("DRIVER_MANAGED".equals(expectTrace) || "DRIVER_MANAGED".equals(expectLog)) {
648+
expectedOtelInstance = mockDriverManagedOtel;
649+
} else {
650+
expectedOtelInstance = OpenTelemetry.noop();
651+
}
652+
653+
boolean expectUseDirectGcp = "DRIVER_MANAGED".equals(expectLog);
654+
Logging expectedLogClient = expectUseDirectGcp ? mockLogging : null;
655+
656+
mockedOtel.verify(
657+
() ->
658+
BigQueryJdbcOpenTelemetry.registerConnection(
659+
anyString(),
660+
eq(expectedOtelInstance),
661+
eq(expectedLogClient),
662+
eq(expectUseDirectGcp)));
663+
}
664+
}
665+
}
666+
}
525667
}

0 commit comments

Comments
 (0)