Skip to content

Commit b6d8f63

Browse files
keshavdandevacloud-java-botlogachev
authored
test(bigquery-jdbc): add e2e otel test and code refinements (#13226)
b/499079838 ### Changes #### 1. BigQueryJdbcOpenTelemetry.java * **Feature**: Added safe, generous default attribute value length limits of **`60KB (61,440 characters)`** to the autoconfigured OpenTelemetry instance properties. * **Why**: Prevents GCP Cloud Trace from silently rejecting and dropping span batches when we log massive `BigQueryException` stack traces or Arrow schema payloads exceeding the hard 64KB Cloud Trace backend limits. * **Design**: If the user explicitly configures their own limits the driver automatically skips the defaults and respects their overrides. #### 2. OpenTelemetryJulHandler.java * **Fix**: Configured the handler level to `Level.ALL` in the constructor. * **Why**: Bypasses a standard Java Logging (JUL) constraint where handlers default to `Level.INFO` and silently drop `FINE`/`DEBUG` queries. Delegates log filtering exclusively to the Connection loggers. #### 3. BigQueryConnection.java * **Visibility**: Exposed the visibility of the connection session identifier by changing `getConnectionId()` from package-private to `public`. * **Why**: Allows automated E2E tests to retrieve the UUID and harvest specific logs/traces accurately. #### 4. ITOpenTelemetryTest.java * **Feature**: Implemented a new standalone E2E integration test suite verifying the live GCP OTel egress. * **Test 1 (`testExecute_withOpenTelemetryGcpExporter`)**: Natively resolves target project via `ServiceOptions.getDefaultProjectId()`. Runs an optimized in-memory array query and iterates results to trigger small-page JSON pagination. Queries Cloud Trace E2E to strictly assert that async pagination child spans are parented perfectly under the root JDBC span. * **Test 2 (`testExecute_withErrorCorrelation`)**: Triggers database failures, captures `SQLException`, harvests Trace IDs from standard logs, and verifies failed span ingestion in Cloud Trace. #### 5. BigQueryConnectionTest.java * **Feature**: Added a new unit test (`testConnect_withCustomOpenTelemetry_usesCustomInstance`) verifying the custom OTel injection pipeline. * **What it does**: Leverages `OpenTelemetryExtension` to mock an OTel provider locally. Injecting the custom SDK via properties, it validates that `BigQueryConnection` resolves the instance and routes spans exclusively to the custom provider #### 6. pom.xml * **Dependencies**: Added `google-cloud-trace` test-scoped dependency to query Cloud Trace v1 API programmatically during E2E validation. --------- Co-authored-by: cloud-java-bot <cloud-java-bot@google.com> Co-authored-by: Kirill Logachev <kirl@google.com>
1 parent a0312c7 commit b6d8f63

11 files changed

Lines changed: 549 additions & 47 deletions

File tree

java-bigquery-jdbc/pom.xml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -424,6 +424,12 @@
424424
<artifactId>opentelemetry-sdk-testing</artifactId>
425425
<scope>test</scope>
426426
</dependency>
427+
<dependency>
428+
<groupId>com.google.cloud</groupId>
429+
<artifactId>google-cloud-trace</artifactId>
430+
<version>2.92.0</version><!-- {x-version-update:google-cloud-trace:current} -->
431+
<scope>test</scope>
432+
</dependency>
427433
</dependencies>
428434

429435
<profiles>

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

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
import com.google.cloud.bigquery.storage.v1.BigQueryWriteSettings;
4343
import com.google.cloud.http.HttpTransportOptions;
4444
import com.google.cloud.logging.Logging;
45+
import com.google.common.annotations.VisibleForTesting;
4546
import com.google.common.collect.ImmutableSortedSet;
4647
import io.opentelemetry.api.OpenTelemetry;
4748
import io.opentelemetry.api.baggage.Baggage;
@@ -215,6 +216,7 @@ public class BigQueryConnection extends BigQueryNoOpsConnection {
215216
boolean enableGcpTraceExporter;
216217
boolean enableGcpLogExporter;
217218
OpenTelemetry customOpenTelemetry;
219+
boolean useGlobalOpenTelemetry;
218220
private OpenTelemetry openTelemetry;
219221
private Context otelContext;
220222
Tracer tracer =
@@ -366,6 +368,7 @@ public class BigQueryConnection extends BigQueryNoOpsConnection {
366368
this.enableGcpTraceExporter = ds.getEnableGcpTraceExporter();
367369
this.enableGcpLogExporter = ds.getEnableGcpLogExporter();
368370
this.customOpenTelemetry = ds.getCustomOpenTelemetry();
371+
this.useGlobalOpenTelemetry = ds.getUseGlobalOpenTelemetry();
369372
this.openTelemetry = getOpenTelemetryInstance();
370373
this.bigQuery = getBigQueryConnection();
371374
}
@@ -438,7 +441,8 @@ String getConnectionUrl() {
438441
return connectionUrl;
439442
}
440443

441-
String getConnectionId() {
444+
@VisibleForTesting
445+
public String getConnectionId() {
442446
return this.connectionId;
443447
}
444448

@@ -1036,7 +1040,6 @@ void removeStatement(Statement statement) {
10361040
}
10371041

10381042
private OpenTelemetry getOpenTelemetryInstance() {
1039-
boolean hasCustomOtel = this.customOpenTelemetry != null;
10401043

10411044
String effectiveProjectId =
10421045
(this.gcpTelemetryProjectId != null) ? this.gcpTelemetryProjectId : this.catalog;
@@ -1046,22 +1049,27 @@ private OpenTelemetry getOpenTelemetryInstance() {
10461049

10471050
OpenTelemetry openTelemetry =
10481051
BigQueryJdbcOpenTelemetry.getOpenTelemetry(
1052+
this.useGlobalOpenTelemetry,
10491053
this.enableGcpTraceExporter,
10501054
this.enableGcpLogExporter,
10511055
this.customOpenTelemetry,
10521056
effectiveCredentials,
10531057
effectiveProjectId);
10541058

1059+
boolean hasExternalOtel = this.customOpenTelemetry != null || this.useGlobalOpenTelemetry;
10551060
Logging localLoggingClient = null;
1056-
if (this.enableGcpLogExporter && !hasCustomOtel) {
1061+
if (this.enableGcpLogExporter && !hasExternalOtel) {
10571062
localLoggingClient =
10581063
BigQueryJdbcOpenTelemetry.createLoggingClient(
10591064
true, null, effectiveCredentials, effectiveProjectId, this.credentials);
10601065
}
10611066

1062-
if (this.enableGcpLogExporter || hasCustomOtel) {
1067+
if (this.enableGcpLogExporter || hasExternalOtel) {
10631068
BigQueryJdbcOpenTelemetry.registerConnection(
1064-
this.connectionId, openTelemetry, localLoggingClient, this.enableGcpLogExporter);
1069+
this.connectionId,
1070+
openTelemetry,
1071+
localLoggingClient,
1072+
this.enableGcpLogExporter && !hasExternalOtel);
10651073
}
10661074

10671075
return openTelemetry;
@@ -1126,7 +1134,9 @@ private BigQuery getBigQueryConnection() {
11261134
if (this.httpTransportOptions != null) {
11271135
bigQueryOptions.setTransportOptions(this.httpTransportOptions);
11281136
}
1129-
if (this.enableGcpTraceExporter || this.customOpenTelemetry != null) {
1137+
if (this.enableGcpTraceExporter
1138+
|| this.customOpenTelemetry != null
1139+
|| this.useGlobalOpenTelemetry) {
11301140
Tracer sdkTracer = this.openTelemetry.getTracer(BigQueryJdbcOpenTelemetry.BIGQUERY_NAMESPACE);
11311141
bigQueryOptions.setOpenTelemetryTracer(sdkTracer);
11321142
this.tracer =

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

Lines changed: 22 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;
@@ -65,6 +66,11 @@ public class BigQueryJdbcOpenTelemetry {
6566
private static final String OTLP_ENDPOINT_VALUE = "https://telemetry.googleapis.com:443";
6667
private static final String EXPORTER_NONE = "none";
6768
private static final String EXPORTER_OTLP = "otlp";
69+
private static final String OTEL_SPAN_ATTRIBUTE_VALUE_LENGTH_LIMIT =
70+
"otel.span.attribute.value.length.limit";
71+
private static final String OTEL_ATTRIBUTE_VALUE_LENGTH_LIMIT =
72+
"otel.attribute.value.length.limit";
73+
private static final String DEFAULT_ATTRIBUTE_LENGTH_LIMIT = "32768";
6874
private static final BigQueryJdbcCustomLogger LOG =
6975
new BigQueryJdbcCustomLogger("BigQueryJdbcOpenTelemetry");
7076

@@ -240,6 +246,7 @@ private static String getCredentialsIdentifier(String credentials) {
240246
* customOpenTelemetry if provided; fallback to an auto-configured GCP exporter if requested.
241247
*/
242248
public static OpenTelemetry getOpenTelemetry(
249+
boolean useGlobalOpenTelemetry,
243250
boolean enableGcpTraceExporter,
244251
boolean enableGcpLogExporter,
245252
OpenTelemetry customOpenTelemetry,
@@ -250,6 +257,10 @@ public static OpenTelemetry getOpenTelemetry(
250257
return customOpenTelemetry;
251258
}
252259

260+
if (useGlobalOpenTelemetry) {
261+
return GlobalOpenTelemetry.get();
262+
}
263+
253264
// NOTE: Currently, tracing only fully supports Application Default Credentials (ADC).
254265
// Once b/503721589 is completed, Service Account (SA) will work as well.
255266
if (!enableGcpTraceExporter && !enableGcpLogExporter) {
@@ -290,6 +301,17 @@ public static OpenTelemetry getOpenTelemetry(
290301
props.put(GOOGLE_CLOUD_PROJECT, gcpTelemetryProjectId);
291302
}
292303

304+
// Set safe, generous default limits on attribute value lengths (32KB) to protect
305+
// customers from GCP Cloud Trace 64KB span ingestion failures when logging massive
306+
// exception stack traces or database schema metadata.
307+
// Respect any existing user configuration overrides.
308+
if (!props.containsKey(OTEL_SPAN_ATTRIBUTE_VALUE_LENGTH_LIMIT)) {
309+
props.put(OTEL_SPAN_ATTRIBUTE_VALUE_LENGTH_LIMIT, DEFAULT_ATTRIBUTE_LENGTH_LIMIT);
310+
}
311+
if (!props.containsKey(OTEL_ATTRIBUTE_VALUE_LENGTH_LIMIT)) {
312+
props.put(OTEL_ATTRIBUTE_VALUE_LENGTH_LIMIT, DEFAULT_ATTRIBUTE_LENGTH_LIMIT);
313+
}
314+
293315
AutoConfiguredOpenTelemetrySdk autoConfigured =
294316
AutoConfiguredOpenTelemetrySdk.builder().addPropertiesSupplier(() -> props).build();
295317

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/BigQueryStatement.java

Lines changed: 10 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -912,20 +912,11 @@ private void processArrowStream(
912912
enqueueError(arrowBatchWrapperBlockingQueue, e);
913913
Thread.currentThread().interrupt();
914914
} catch (Exception e) {
915-
if (e.getCause() instanceof InterruptedException || Thread.currentThread().isInterrupted()) {
916-
LOG.log(
917-
Level.WARNING,
918-
"\n" + Thread.currentThread().getName() + " Interrupted @ arrowStreamProcessor",
919-
e);
920-
enqueueError(arrowBatchWrapperBlockingQueue, e);
921-
Thread.currentThread().interrupt();
922-
} else {
923-
LOG.log(
924-
Level.WARNING,
925-
"\n" + Thread.currentThread().getName() + " Error @ arrowStreamProcessor",
926-
e);
927-
enqueueError(arrowBatchWrapperBlockingQueue, e);
928-
}
915+
LOG.log(
916+
Level.WARNING,
917+
"\n" + Thread.currentThread().getName() + " Error @ arrowStreamProcessor",
918+
e);
919+
enqueueError(arrowBatchWrapperBlockingQueue, e);
929920
} finally { // logic needed for graceful shutdown
930921
enqueueEndOfStream(arrowBatchWrapperBlockingQueue);
931922
}
@@ -1683,20 +1674,11 @@ private void parseAndPopulateRpcData(
16831674
}
16841675

16851676
} catch (Exception ex) {
1686-
if (ex.getCause() instanceof InterruptedException || Thread.currentThread().isInterrupted()) {
1687-
LOG.log(
1688-
Level.WARNING,
1689-
"\n" + Thread.currentThread().getName() + " Interrupted @ populateBufferAsync",
1690-
ex);
1691-
enqueueBufferError(bigQueryFieldValueListWrapperBlockingQueue, ex);
1692-
Thread.currentThread().interrupt();
1693-
} else {
1694-
LOG.log(
1695-
Level.WARNING,
1696-
"\n" + Thread.currentThread().getName() + " Error @ populateBufferAsync",
1697-
ex);
1698-
enqueueBufferError(bigQueryFieldValueListWrapperBlockingQueue, ex);
1699-
}
1677+
LOG.log(
1678+
Level.WARNING,
1679+
"\n" + Thread.currentThread().getName() + " Error @ populateBufferAsync",
1680+
ex);
1681+
enqueueBufferError(bigQueryFieldValueListWrapperBlockingQueue, ex);
17001682
} finally {
17011683
enqueueBufferEndOfStream(bigQueryFieldValueListWrapperBlockingQueue);
17021684
}

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/main/java/com/google/cloud/bigquery/jdbc/OpenTelemetryJulHandler.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,9 @@
4242
public class OpenTelemetryJulHandler extends Handler {
4343
private static final Pattern UNSAFE_LOG_CHARACTERS = Pattern.compile("[^a-zA-Z0-9./_-]");
4444

45-
public OpenTelemetryJulHandler() {}
45+
public OpenTelemetryJulHandler() {
46+
setLevel(Level.ALL);
47+
}
4648

4749
@Override
4850
public void publish(LogRecord record) {

0 commit comments

Comments
 (0)