Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions java-bigquery/google-cloud-bigquery/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,10 @@
<groupId>io.opentelemetry</groupId>
<artifactId>opentelemetry-context</artifactId>
</dependency>
<dependency>
<groupId>com.google.api-client</groupId>
<artifactId>google-api-client</artifactId>
</dependency>

<dependency>
<groupId>com.google.api</groupId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import static java.net.HttpURLConnection.HTTP_OK;
import static java.net.HttpURLConnection.HTTP_UNAUTHORIZED;

import com.google.api.client.googleapis.json.GoogleJsonResponseException;
import com.google.api.client.http.ByteArrayContent;
import com.google.api.client.http.GenericUrl;
import com.google.api.client.http.HttpRequest;
Expand Down Expand Up @@ -65,6 +66,7 @@
import com.google.cloud.Tuple;
import com.google.cloud.bigquery.BigQueryException;
import com.google.cloud.bigquery.BigQueryOptions;
import com.google.cloud.bigquery.telemetry.BigQueryTelemetryTracer;
import com.google.cloud.bigquery.telemetry.HttpTracingRequestInitializer;
import com.google.cloud.http.HttpTransportOptions;
import com.google.common.base.Function;
Expand Down Expand Up @@ -1769,6 +1771,16 @@ private <T> T executeWithSpan(Span span, SpanOperation<T> operation) throws IOEx
}
try (Scope scope = span.makeCurrent()) {
return operation.execute(span);
} catch (Exception e) {
if (isHttpTracingEnabled()) {
if (e instanceof GoogleJsonResponseException) {
BigQueryTelemetryTracer.addServerErrorResponseToSpan(
((GoogleJsonResponseException) e), span);
} else {
BigQueryTelemetryTracer.addExceptionToSpan(e, span);
}
}
throw e;
} finally {
span.end();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,13 @@

package com.google.cloud.bigquery.telemetry;

import com.google.api.client.googleapis.json.GoogleJsonError;
import com.google.api.client.googleapis.json.GoogleJsonResponseException;
import com.google.api.core.BetaApi;
import com.google.api.core.InternalApi;
import io.opentelemetry.api.common.AttributeKey;
import io.opentelemetry.api.trace.Span;
import io.opentelemetry.api.trace.StatusCode;

/** BigQuery Telemetry class that stores generic telemetry attributes and values */
@BetaApi
Expand Down Expand Up @@ -70,4 +73,55 @@ public static void addCommonAttributeToSpan(Span span) {
.setAttribute(GCP_CLIENT_LANGUAGE, BQ_GCP_CLIENT_LANGUAGE);
// TODO: add version
}

/**
* Adds the following error attributes to trace span: STATUS_MESSAGE: the name of the exception +
* message if available EXCEPTION_TYPE: full name of exception ex: java.net.UnknownHostException
* ERROR_TYPE: mapped string based on {@link ErrorTypeUtil#getClientErrorType(Exception)}
*/
public static void addExceptionToSpan(Exception e, Span span) {
span.recordException(e);
String message = e.getMessage();
String simpleName = e.getClass().getSimpleName();
String statusMessage = simpleName + (message != null ? ": " + message : "");
span.setAttribute(BigQueryTelemetryTracer.EXCEPTION_TYPE, e.getClass().getName());
span.setAttribute(
BigQueryTelemetryTracer.ERROR_TYPE, ErrorTypeUtil.getClientErrorType(e).toString());
span.setAttribute(BigQueryTelemetryTracer.STATUS_MESSAGE, statusMessage);
span.setStatus(StatusCode.ERROR, statusMessage);
}

/**
* Adds the following error attributes to trace span from GoogleJsonResponseException:
* STATUS_MESSAGE: user readable error message ERROR_TYPE: reason if available otherwise the
* status code
*/
public static void addServerErrorResponseToSpan(
GoogleJsonResponseException errorResponse, Span span) {
span.setStatus(StatusCode.ERROR);
// set default values in case details aren't available below
if (errorResponse.getDetails() != null) {
span.setAttribute(
BigQueryTelemetryTracer.STATUS_MESSAGE, errorResponse.getDetails().getMessage());
Comment on lines +104 to +105
Copy link
Contributor

Choose a reason for hiding this comment

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

Per @westarle's comment in googleapis/java-bigquery#4126 (comment), I'm thinking we would need to document this in the enablement site, as this attribute is not part of OpenTelemetry semconv. What do you think?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

yes, thanks for pointing that out, let me look into how we do that and will update comment

} else {
span.setAttribute(BigQueryTelemetryTracer.STATUS_MESSAGE, errorResponse.getStatusMessage());
}
span.setAttribute(
BigQueryTelemetryTracer.ERROR_TYPE, Integer.toString(errorResponse.getStatusCode()));

// reads error details from GoogleJsonResponseException and override any available error details
if (errorResponse.getDetails() != null
&& errorResponse.getDetails().getErrors() != null
&& !errorResponse.getDetails().getErrors().isEmpty()) {
GoogleJsonError.ErrorInfo errorInfo = errorResponse.getDetails().getErrors().get(0);
String message = errorInfo.getMessage();
if (message != null) {
span.setAttribute(BigQueryTelemetryTracer.STATUS_MESSAGE, message);
}
String reason = errorInfo.getReason();
if (reason != null) {
span.setAttribute(BigQueryTelemetryTracer.ERROR_TYPE, reason);
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
/*
* 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.telemetry;

import com.google.api.core.BetaApi;

/**
* Utility class for identifying exception types for telemetry tracking. TODO: this class should get
* replaced with gax version when ready work tracked in
* https://github.com/googleapis/google-cloud-java/issues/12105
*/
@BetaApi
class ErrorTypeUtil {

enum ErrorType {
CLIENT_TIMEOUT,
CLIENT_CONNECTION_ERROR,
CLIENT_REQUEST_ERROR,
CLIENT_RESPONSE_DECODE_ERROR,
CLIENT_UNKNOWN_ERROR;

@Override
public String toString() {
return name();
}
}

static boolean isClientTimeout(Exception e) {
return e instanceof java.net.SocketTimeoutException;
}

static boolean isClientConnectionError(Exception e) {
return e instanceof java.net.ConnectException
|| e instanceof java.net.UnknownHostException
|| e instanceof javax.net.ssl.SSLHandshakeException
|| e instanceof java.nio.channels.UnresolvedAddressException;
}

static boolean isClientResponseDecodeError(Exception e) {
return e instanceof com.google.gson.JsonParseException;
}

static boolean isClientRequestError(Exception e) {
return e instanceof java.lang.IllegalArgumentException;
}

static ErrorType getClientErrorType(Exception e) {
if (isClientTimeout(e)) {
return ErrorType.CLIENT_TIMEOUT;
} else if (isClientConnectionError(e)) {
return ErrorType.CLIENT_CONNECTION_ERROR;
} else if (isClientResponseDecodeError(e)) {
return ErrorType.CLIENT_RESPONSE_DECODE_ERROR;
} else if (isClientRequestError(e)) {
return ErrorType.CLIENT_REQUEST_ERROR;
} else {
return ErrorType.CLIENT_UNKNOWN_ERROR;
}
}
}
Loading
Loading