Skip to content

Commit 863d23b

Browse files
authored
feat(bigquery): added error attributes to span tracing (#12115)
This adds capture of error attributes to BigQuery span tracing. [Example with GoogleJsonResponseException](https://screenshot.googleplex.com/54scDAtJYwhijST.png) [Example with exception](https://screenshot.googleplex.com/7X787su6MSP8v3T.png) see note [here](https://github.com/GoogleCloudPlatform/opentelemetry-operations-java/blob/85b304b4916f2775a6f06b582fc866b24b8cea44/exporters/trace/README.md?plain=1#L125) about exception.type mapping to error/name
1 parent 015d9a1 commit 863d23b

File tree

7 files changed

+613
-78
lines changed

7 files changed

+613
-78
lines changed

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,10 @@
133133
<groupId>io.opentelemetry</groupId>
134134
<artifactId>opentelemetry-context</artifactId>
135135
</dependency>
136+
<dependency>
137+
<groupId>com.google.api-client</groupId>
138+
<artifactId>google-api-client</artifactId>
139+
</dependency>
136140

137141
<dependency>
138142
<groupId>com.google.api</groupId>

java-bigquery/google-cloud-bigquery/src/main/java/com/google/cloud/bigquery/spi/v2/HttpBigQueryRpc.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import static java.net.HttpURLConnection.HTTP_OK;
2222
import static java.net.HttpURLConnection.HTTP_UNAUTHORIZED;
2323

24+
import com.google.api.client.googleapis.json.GoogleJsonResponseException;
2425
import com.google.api.client.http.ByteArrayContent;
2526
import com.google.api.client.http.GenericUrl;
2627
import com.google.api.client.http.HttpRequest;
@@ -65,6 +66,7 @@
6566
import com.google.cloud.Tuple;
6667
import com.google.cloud.bigquery.BigQueryException;
6768
import com.google.cloud.bigquery.BigQueryOptions;
69+
import com.google.cloud.bigquery.telemetry.BigQueryTelemetryTracer;
6870
import com.google.cloud.bigquery.telemetry.HttpTracingRequestInitializer;
6971
import com.google.cloud.http.HttpTransportOptions;
7072
import com.google.common.base.Function;
@@ -1769,6 +1771,16 @@ private <T> T executeWithSpan(Span span, SpanOperation<T> operation) throws IOEx
17691771
}
17701772
try (Scope scope = span.makeCurrent()) {
17711773
return operation.execute(span);
1774+
} catch (Exception e) {
1775+
if (isHttpTracingEnabled()) {
1776+
if (e instanceof GoogleJsonResponseException) {
1777+
BigQueryTelemetryTracer.addServerErrorResponseToSpan(
1778+
((GoogleJsonResponseException) e), span);
1779+
} else {
1780+
BigQueryTelemetryTracer.addExceptionToSpan(e, span);
1781+
}
1782+
}
1783+
throw e;
17721784
} finally {
17731785
span.end();
17741786
}

java-bigquery/google-cloud-bigquery/src/main/java/com/google/cloud/bigquery/telemetry/BigQueryTelemetryTracer.java

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,13 @@
1616

1717
package com.google.cloud.bigquery.telemetry;
1818

19+
import com.google.api.client.googleapis.json.GoogleJsonError;
20+
import com.google.api.client.googleapis.json.GoogleJsonResponseException;
1921
import com.google.api.core.BetaApi;
2022
import com.google.api.core.InternalApi;
2123
import io.opentelemetry.api.common.AttributeKey;
2224
import io.opentelemetry.api.trace.Span;
25+
import io.opentelemetry.api.trace.StatusCode;
2326

2427
/** BigQuery Telemetry class that stores generic telemetry attributes and values */
2528
@BetaApi
@@ -70,4 +73,55 @@ public static void addCommonAttributeToSpan(Span span) {
7073
.setAttribute(GCP_CLIENT_LANGUAGE, BQ_GCP_CLIENT_LANGUAGE);
7174
// TODO: add version
7275
}
76+
77+
/**
78+
* Adds the following error attributes to trace span: STATUS_MESSAGE: the name of the exception +
79+
* message if available EXCEPTION_TYPE: full name of exception ex: java.net.UnknownHostException
80+
* ERROR_TYPE: mapped string based on {@link ErrorTypeUtil#getClientErrorType(Exception)}
81+
*/
82+
public static void addExceptionToSpan(Exception e, Span span) {
83+
span.recordException(e);
84+
String message = e.getMessage();
85+
String simpleName = e.getClass().getSimpleName();
86+
String statusMessage = simpleName + (message != null ? ": " + message : "");
87+
span.setAttribute(BigQueryTelemetryTracer.EXCEPTION_TYPE, e.getClass().getName());
88+
span.setAttribute(
89+
BigQueryTelemetryTracer.ERROR_TYPE, ErrorTypeUtil.getClientErrorType(e).toString());
90+
span.setAttribute(BigQueryTelemetryTracer.STATUS_MESSAGE, statusMessage);
91+
span.setStatus(StatusCode.ERROR, statusMessage);
92+
}
93+
94+
/**
95+
* Adds the following error attributes to trace span from GoogleJsonResponseException:
96+
* STATUS_MESSAGE: user readable error message ERROR_TYPE: reason if available otherwise the
97+
* status code
98+
*/
99+
public static void addServerErrorResponseToSpan(
100+
GoogleJsonResponseException errorResponse, Span span) {
101+
span.setStatus(StatusCode.ERROR);
102+
// set default values in case details aren't available below
103+
if (errorResponse.getDetails() != null) {
104+
span.setAttribute(
105+
BigQueryTelemetryTracer.STATUS_MESSAGE, errorResponse.getDetails().getMessage());
106+
} else {
107+
span.setAttribute(BigQueryTelemetryTracer.STATUS_MESSAGE, errorResponse.getStatusMessage());
108+
}
109+
span.setAttribute(
110+
BigQueryTelemetryTracer.ERROR_TYPE, Integer.toString(errorResponse.getStatusCode()));
111+
112+
// reads error details from GoogleJsonResponseException and override any available error details
113+
if (errorResponse.getDetails() != null
114+
&& errorResponse.getDetails().getErrors() != null
115+
&& !errorResponse.getDetails().getErrors().isEmpty()) {
116+
GoogleJsonError.ErrorInfo errorInfo = errorResponse.getDetails().getErrors().get(0);
117+
String message = errorInfo.getMessage();
118+
if (message != null) {
119+
span.setAttribute(BigQueryTelemetryTracer.STATUS_MESSAGE, message);
120+
}
121+
String reason = errorInfo.getReason();
122+
if (reason != null) {
123+
span.setAttribute(BigQueryTelemetryTracer.ERROR_TYPE, reason);
124+
}
125+
}
126+
}
73127
}
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
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+
package com.google.cloud.bigquery.telemetry;
17+
18+
import com.google.api.core.BetaApi;
19+
20+
/**
21+
* Utility class for identifying exception types for telemetry tracking. TODO: this class should get
22+
* replaced with gax version when ready work tracked in
23+
* https://github.com/googleapis/google-cloud-java/issues/12105
24+
*/
25+
@BetaApi
26+
class ErrorTypeUtil {
27+
28+
enum ErrorType {
29+
CLIENT_TIMEOUT,
30+
CLIENT_CONNECTION_ERROR,
31+
CLIENT_REQUEST_ERROR,
32+
CLIENT_RESPONSE_DECODE_ERROR,
33+
CLIENT_UNKNOWN_ERROR;
34+
35+
@Override
36+
public String toString() {
37+
return name();
38+
}
39+
}
40+
41+
static boolean isClientTimeout(Exception e) {
42+
return e instanceof java.net.SocketTimeoutException;
43+
}
44+
45+
static boolean isClientConnectionError(Exception e) {
46+
return e instanceof java.net.ConnectException
47+
|| e instanceof java.net.UnknownHostException
48+
|| e instanceof javax.net.ssl.SSLHandshakeException
49+
|| e instanceof java.nio.channels.UnresolvedAddressException;
50+
}
51+
52+
static boolean isClientResponseDecodeError(Exception e) {
53+
return e instanceof com.google.gson.JsonParseException;
54+
}
55+
56+
static boolean isClientRequestError(Exception e) {
57+
return e instanceof java.lang.IllegalArgumentException;
58+
}
59+
60+
static ErrorType getClientErrorType(Exception e) {
61+
if (isClientTimeout(e)) {
62+
return ErrorType.CLIENT_TIMEOUT;
63+
} else if (isClientConnectionError(e)) {
64+
return ErrorType.CLIENT_CONNECTION_ERROR;
65+
} else if (isClientResponseDecodeError(e)) {
66+
return ErrorType.CLIENT_RESPONSE_DECODE_ERROR;
67+
} else if (isClientRequestError(e)) {
68+
return ErrorType.CLIENT_REQUEST_ERROR;
69+
} else {
70+
return ErrorType.CLIENT_UNKNOWN_ERROR;
71+
}
72+
}
73+
}

0 commit comments

Comments
 (0)