From a49d06be886f1b081455fec708ca45032fec484c Mon Sep 17 00:00:00 2001 From: Diego Date: Mon, 30 Mar 2026 12:29:38 -0400 Subject: [PATCH 1/7] impl(o11y): introduce `http.response.status_code` --- .../gax/tracing/ObservabilityAttributes.java | 3 + .../api/gax/tracing/ObservabilityUtils.java | 48 ++++++- .../google/api/gax/tracing/SpanTracer.java | 23 +++- .../gax/tracing/ObservabilityUtilsTest.java | 100 ++++++++++++++ .../api/gax/tracing/SpanTracerTest.java | 126 ++++++++++++++++++ .../showcase/v1beta1/it/ITOtelTracing.java | 70 ++++++++++ 6 files changed, 361 insertions(+), 9 deletions(-) diff --git a/sdk-platform-java/gax-java/gax/src/main/java/com/google/api/gax/tracing/ObservabilityAttributes.java b/sdk-platform-java/gax-java/gax/src/main/java/com/google/api/gax/tracing/ObservabilityAttributes.java index c04c76362df1..e468e8083403 100644 --- a/sdk-platform-java/gax-java/gax/src/main/java/com/google/api/gax/tracing/ObservabilityAttributes.java +++ b/sdk-platform-java/gax-java/gax/src/main/java/com/google/api/gax/tracing/ObservabilityAttributes.java @@ -88,6 +88,9 @@ public class ObservabilityAttributes { /** Size of the response body in bytes. */ public static final String HTTP_RESPONSE_BODY_SIZE = "http.response.body.size"; + /** The HTTP status code of the request (e.g., 200, 404). */ + public static final String HTTP_RESPONSE_STATUS_ATTRIBUTE = "http.response.status_code"; + /** The resend count of the request. Only used in HTTP transport. */ public static final String HTTP_RESEND_COUNT_ATTRIBUTE = "http.request.resend_count"; diff --git a/sdk-platform-java/gax-java/gax/src/main/java/com/google/api/gax/tracing/ObservabilityUtils.java b/sdk-platform-java/gax-java/gax/src/main/java/com/google/api/gax/tracing/ObservabilityUtils.java index 2487964370ca..4d143d0594c4 100644 --- a/sdk-platform-java/gax-java/gax/src/main/java/com/google/api/gax/tracing/ObservabilityUtils.java +++ b/sdk-platform-java/gax-java/gax/src/main/java/com/google/api/gax/tracing/ObservabilityUtils.java @@ -39,10 +39,20 @@ class ObservabilityUtils { - /** Function to extract the status of the error as a string */ + /** Function to extract the status of the error as a string (defaults to gRPC canonical codes). */ static String extractStatus(@Nullable Throwable error) { - final String statusString; + return (String) extractStatus(error, ApiTracerContext.Transport.GRPC); + } + + static Object extractStatus(@Nullable Throwable error, ApiTracerContext.Transport transport) { + if (transport == ApiTracerContext.Transport.HTTP) { + return extractHttpStatus(error); + } + return extractGrpcStatus(error); + } + private static String extractGrpcStatus(@Nullable Throwable error) { + final String statusString; if (error == null) { return StatusCode.Code.OK.toString(); } else if (error instanceof CancellationException) { @@ -52,10 +62,40 @@ static String extractStatus(@Nullable Throwable error) { } else { statusString = StatusCode.Code.UNKNOWN.toString(); } - return statusString; } + private static Long extractHttpStatus(@Nullable Throwable error) { + if (error == null) { + return 200L; + } else if (error instanceof ApiException) { + Object transportCode = ((ApiException) error).getStatusCode().getTransportCode(); + if (transportCode instanceof Integer) { + return ((Integer) transportCode).longValue(); + } else { + return (long) ((ApiException) error).getStatusCode().getCode().getHttpStatusCode(); + } + } + StatusCode.Code code = StatusCode.Code.UNKNOWN; + if (error instanceof CancellationException) { + code = StatusCode.Code.CANCELLED; + } + return (long) code.getHttpStatusCode(); + } + + static void populateStatusAttributes( + Map attributes, + @Nullable Throwable error, + ApiTracerContext.Transport transport) { + if (transport == ApiTracerContext.Transport.GRPC) { + attributes.put( + ObservabilityAttributes.RPC_RESPONSE_STATUS_ATTRIBUTE, extractStatus(error, transport)); + } else if (transport == ApiTracerContext.Transport.HTTP) { + attributes.put( + ObservabilityAttributes.HTTP_RESPONSE_STATUS_ATTRIBUTE, extractStatus(error, transport)); + } + } + static Attributes toOtelAttributes(Map attributes) { AttributesBuilder attributesBuilder = Attributes.builder(); if (attributes == null) { @@ -69,6 +109,8 @@ static Attributes toOtelAttributes(Map attributes) { attributesBuilder.put(k, (Long) v); } else if (v instanceof Integer) { attributesBuilder.put(k, (long) (Integer) v); + } else if (v instanceof Long) { + attributesBuilder.put(k, (Long) v); } }); return attributesBuilder.build(); diff --git a/sdk-platform-java/gax-java/gax/src/main/java/com/google/api/gax/tracing/SpanTracer.java b/sdk-platform-java/gax-java/gax/src/main/java/com/google/api/gax/tracing/SpanTracer.java index bad8b40e5761..47dbb646a48d 100644 --- a/sdk-platform-java/gax-java/gax/src/main/java/com/google/api/gax/tracing/SpanTracer.java +++ b/sdk-platform-java/gax-java/gax/src/main/java/com/google/api/gax/tracing/SpanTracer.java @@ -36,8 +36,11 @@ import io.opentelemetry.api.trace.SpanBuilder; import io.opentelemetry.api.trace.SpanKind; import io.opentelemetry.api.trace.Tracer; + +import java.time.Duration; import java.util.HashMap; import java.util.Map; +import java.util.concurrent.CancellationException; /** An implementation of {@link ApiTracer} that uses OpenTelemetry to record traces. */ @BetaApi @@ -133,7 +136,7 @@ public void attemptStarted(Object request, int attemptNumber) { @Override public void attemptSucceeded() { - endAttempt(); + endAttempt(null); } @Override @@ -180,26 +183,34 @@ private long extractContentLength(java.util.Map headers) { @Override public void attemptCancelled() { - endAttempt(); + endAttempt(new CancellationException()); } @Override public void attemptFailedDuration(Throwable error, java.time.Duration delay) { - endAttempt(); + endAttempt(error); } @Override public void attemptFailedRetriesExhausted(Throwable error) { - endAttempt(); + endAttempt(error); } @Override public void attemptPermanentFailure(Throwable error) { - endAttempt(); + endAttempt(error); } - private void endAttempt() { + private void endAttempt(Throwable error) { if (attemptSpan != null) { + Map endAttributes = new HashMap<>(); + ObservabilityUtils.populateStatusAttributes( + endAttributes, error, this.apiTracerContext.transport()); + + if (!endAttributes.isEmpty()) { + attemptSpan.setAllAttributes(ObservabilityUtils.toOtelAttributes(endAttributes)); + } + attemptSpan.end(); attemptSpan = null; } diff --git a/sdk-platform-java/gax-java/gax/src/test/java/com/google/api/gax/tracing/ObservabilityUtilsTest.java b/sdk-platform-java/gax-java/gax/src/test/java/com/google/api/gax/tracing/ObservabilityUtilsTest.java index 0af3be474604..8fad41052e99 100644 --- a/sdk-platform-java/gax-java/gax/src/test/java/com/google/api/gax/tracing/ObservabilityUtilsTest.java +++ b/sdk-platform-java/gax-java/gax/src/test/java/com/google/api/gax/tracing/ObservabilityUtilsTest.java @@ -114,6 +114,106 @@ void testToOtelAttributes_shouldMapIntAttributes() { .isEqualTo((long) attribute2Value); } + @Test + void testPopulateStatusAttributes_grpc_success() { + Map attributes = new java.util.HashMap<>(); + ObservabilityUtils.populateStatusAttributes(attributes, null, ApiTracerContext.Transport.GRPC); + assertThat(attributes) + .containsEntry(ObservabilityAttributes.RPC_RESPONSE_STATUS_ATTRIBUTE, "OK"); + } + + @Test + void testPopulateStatusAttributes_grpc_apiException() { + Map attributes = new java.util.HashMap<>(); + ApiException error = + new ApiException("fake_error", null, new FakeStatusCode(StatusCode.Code.NOT_FOUND), false); + ObservabilityUtils.populateStatusAttributes(attributes, error, ApiTracerContext.Transport.GRPC); + assertThat(attributes) + .containsEntry(ObservabilityAttributes.RPC_RESPONSE_STATUS_ATTRIBUTE, "NOT_FOUND"); + } + + @Test + void testPopulateStatusAttributes_grpc_cancellationException() { + Map attributes = new java.util.HashMap<>(); + Throwable error = new java.util.concurrent.CancellationException(); + ObservabilityUtils.populateStatusAttributes(attributes, error, ApiTracerContext.Transport.GRPC); + assertThat(attributes) + .containsEntry(ObservabilityAttributes.RPC_RESPONSE_STATUS_ATTRIBUTE, "CANCELLED"); + } + + @Test + void testPopulateStatusAttributes_http_success() { + Map attributes = new java.util.HashMap<>(); + ObservabilityUtils.populateStatusAttributes(attributes, null, ApiTracerContext.Transport.HTTP); + assertThat(attributes) + .containsEntry( + ObservabilityAttributes.HTTP_RESPONSE_STATUS_ATTRIBUTE, + (long) StatusCode.Code.OK.getHttpStatusCode()); + } + + @Test + void testPopulateStatusAttributes_http_apiExceptionWithIntegerTransportCode() { + Map attributes = new java.util.HashMap<>(); + ApiException error = + new ApiException( + "fake_error", + null, + new com.google.api.gax.rpc.StatusCode() { + @Override + public Code getCode() { + return Code.NOT_FOUND; + } + + @Override + public Object getTransportCode() { + return StatusCode.Code.NOT_FOUND.getHttpStatusCode(); + } + }, + false); + ObservabilityUtils.populateStatusAttributes(attributes, error, ApiTracerContext.Transport.HTTP); + assertThat(attributes) + .containsEntry( + ObservabilityAttributes.HTTP_RESPONSE_STATUS_ATTRIBUTE, + (long) StatusCode.Code.NOT_FOUND.getHttpStatusCode()); + } + + @Test + void testPopulateStatusAttributes_http_apiExceptionWithNonIntegerTransportCode() { + Map attributes = new java.util.HashMap<>(); + ApiException error = + new ApiException( + "fake_error", + null, + new com.google.api.gax.rpc.StatusCode() { + @Override + public Code getCode() { + return Code.NOT_FOUND; + } + + @Override + public Object getTransportCode() { + return "Not Found"; + } + }, + false); + ObservabilityUtils.populateStatusAttributes(attributes, error, ApiTracerContext.Transport.HTTP); + assertThat(attributes) + .containsEntry( + ObservabilityAttributes.HTTP_RESPONSE_STATUS_ATTRIBUTE, + (long) StatusCode.Code.NOT_FOUND.getHttpStatusCode()); + } + + @Test + void testPopulateStatusAttributes_http_cancellationException() { + Map attributes = new java.util.HashMap<>(); + Throwable error = new java.util.concurrent.CancellationException(); + ObservabilityUtils.populateStatusAttributes(attributes, error, ApiTracerContext.Transport.HTTP); + assertThat(attributes) + .containsEntry( + ObservabilityAttributes.HTTP_RESPONSE_STATUS_ATTRIBUTE, + (long) StatusCode.Code.CANCELLED.getHttpStatusCode()); + } + @Test void testToOtelAttributes_shouldReturnEmptyAttributes_nullInput() { assertThat(ObservabilityUtils.toOtelAttributes(null)).isEqualTo(Attributes.empty()); diff --git a/sdk-platform-java/gax-java/gax/src/test/java/com/google/api/gax/tracing/SpanTracerTest.java b/sdk-platform-java/gax-java/gax/src/test/java/com/google/api/gax/tracing/SpanTracerTest.java index 2bed37c282b1..7dd70e0f6a87 100644 --- a/sdk-platform-java/gax-java/gax/src/test/java/com/google/api/gax/tracing/SpanTracerTest.java +++ b/sdk-platform-java/gax-java/gax/src/test/java/com/google/api/gax/tracing/SpanTracerTest.java @@ -87,6 +87,52 @@ void testAttemptStarted_includesLanguageAttribute() { SpanTracer.DEFAULT_LANGUAGE); } + @Test + void testAttemptSucceeded_grpc() { + ApiTracerContext context = + ApiTracerContext.newBuilder() + .setLibraryMetadata(com.google.api.gax.rpc.LibraryMetadata.empty()) + .setTransport(ApiTracerContext.Transport.GRPC) + .build(); + spanTracer = new SpanTracer(tracer, context, ATTEMPT_SPAN_NAME); + + spanTracer.attemptStarted(new Object(), 1); + spanTracer.attemptSucceeded(); + + ArgumentCaptor attrsCaptor = ArgumentCaptor.forClass(Attributes.class); + verify(span).setAllAttributes(attrsCaptor.capture()); + verify(span).end(); + + assertThat(attrsCaptor.getValue().asMap()) + .containsEntry( + io.opentelemetry.api.common.AttributeKey.stringKey( + ObservabilityAttributes.RPC_RESPONSE_STATUS_ATTRIBUTE), + "OK"); + } + + @Test + void testAttemptSucceeded_http() { + ApiTracerContext context = + ApiTracerContext.newBuilder() + .setLibraryMetadata(com.google.api.gax.rpc.LibraryMetadata.empty()) + .setTransport(ApiTracerContext.Transport.HTTP) + .build(); + spanTracer = new SpanTracer(tracer, context, ATTEMPT_SPAN_NAME); + + spanTracer.attemptStarted(new Object(), 1); + spanTracer.attemptSucceeded(); + + ArgumentCaptor attrsCaptor = ArgumentCaptor.forClass(Attributes.class); + verify(span).setAllAttributes(attrsCaptor.capture()); + verify(span).end(); + + assertThat(attrsCaptor.getValue().asMap()) + .containsEntry( + io.opentelemetry.api.common.AttributeKey.longKey( + ObservabilityAttributes.HTTP_RESPONSE_STATUS_ATTRIBUTE), + 200L); + } + @Test void testResponseHeadersReceived_setsContentLengthAttribute() { spanTracer.attemptStarted(new Object(), 1); @@ -170,6 +216,46 @@ void testAttemptStarted_noRetryAttributes_grpc() { ObservabilityAttributes.HTTP_RESEND_COUNT_ATTRIBUTE)); } + @Test + void testAttemptFailed_grpc() { + ApiTracerContext context = + ApiTracerContext.newBuilder() + .setLibraryMetadata(com.google.api.gax.rpc.LibraryMetadata.empty()) + .setTransport(ApiTracerContext.Transport.GRPC) + .build(); + spanTracer = new SpanTracer(tracer, context, ATTEMPT_SPAN_NAME); + + com.google.api.gax.rpc.ApiException exception = + new com.google.api.gax.rpc.ApiException( + "error", + null, + new com.google.api.gax.rpc.StatusCode() { + @Override + public Code getCode() { + return Code.NOT_FOUND; + } + + @Override + public Object getTransportCode() { + return null; + } + }, + false); + + spanTracer.attemptStarted(new Object(), 1); + spanTracer.attemptFailedRetriesExhausted(exception); + + ArgumentCaptor attrsCaptor = ArgumentCaptor.forClass(Attributes.class); + verify(span).setAllAttributes(attrsCaptor.capture()); + verify(span).end(); + + assertThat(attrsCaptor.getValue().asMap()) + .containsEntry( + io.opentelemetry.api.common.AttributeKey.stringKey( + ObservabilityAttributes.RPC_RESPONSE_STATUS_ATTRIBUTE), + "NOT_FOUND"); + } + @Test void testAttemptStarted_retryAttributes_grpc() { ApiTracerContext grpcContext = @@ -196,6 +282,46 @@ void testAttemptStarted_retryAttributes_grpc() { ObservabilityAttributes.HTTP_RESEND_COUNT_ATTRIBUTE)); } + @Test + void testAttemptFailed_http() { + ApiTracerContext context = + ApiTracerContext.newBuilder() + .setLibraryMetadata(com.google.api.gax.rpc.LibraryMetadata.empty()) + .setTransport(ApiTracerContext.Transport.HTTP) + .build(); + spanTracer = new SpanTracer(tracer, context, ATTEMPT_SPAN_NAME); + + com.google.api.gax.rpc.ApiException exception = + new com.google.api.gax.rpc.ApiException( + "error", + null, + new com.google.api.gax.rpc.StatusCode() { + @Override + public Code getCode() { + return Code.NOT_FOUND; + } + + @Override + public Object getTransportCode() { + return 404; + } + }, + false); + + spanTracer.attemptStarted(new Object(), 1); + spanTracer.attemptFailedRetriesExhausted(exception); + + ArgumentCaptor attrsCaptor = ArgumentCaptor.forClass(Attributes.class); + verify(span).setAllAttributes(attrsCaptor.capture()); + verify(span).end(); + + assertThat(attrsCaptor.getValue().asMap()) + .containsEntry( + io.opentelemetry.api.common.AttributeKey.longKey( + ObservabilityAttributes.HTTP_RESPONSE_STATUS_ATTRIBUTE), + 404L); + } + @Test void testAttemptStarted_noRetryAttributes_http() { ApiTracerContext httpContext = diff --git a/sdk-platform-java/java-showcase/gapic-showcase/src/test/java/com/google/showcase/v1beta1/it/ITOtelTracing.java b/sdk-platform-java/java-showcase/gapic-showcase/src/test/java/com/google/showcase/v1beta1/it/ITOtelTracing.java index ff4731c99d36..ad136ec321b7 100644 --- a/sdk-platform-java/java-showcase/gapic-showcase/src/test/java/com/google/showcase/v1beta1/it/ITOtelTracing.java +++ b/sdk-platform-java/java-showcase/gapic-showcase/src/test/java/com/google/showcase/v1beta1/it/ITOtelTracing.java @@ -365,4 +365,74 @@ void testTracing_retry_httpjson() throws Exception { .collect(java.util.stream.Collectors.toList()); assertThat(resendCounts).containsExactlyElementsIn(expectedCounts).inOrder(); } + + @Test + void testTracing_statusCodes_grpc() throws Exception { + SpanTracerFactory tracingFactory = new SpanTracerFactory(openTelemetrySdk); + EchoRequest errorRequest = + EchoRequest.newBuilder() + .setError(Status.newBuilder().setCode(StatusCode.Code.INVALID_ARGUMENT.ordinal()).build()) + .build(); + EchoRequest successRequest = EchoRequest.newBuilder().setContent("tracing-test").build(); + + try (EchoClient grpcClient = + TestClientInitializer.createGrpcEchoClientOpentelemetry(tracingFactory)) { + + grpcClient.echo(successRequest); + assertThrows( + com.google.api.gax.rpc.InvalidArgumentException.class, () -> grpcClient.echo(errorRequest)); + + List spans = spanExporter.getFinishedSpanItems(); + assertThat(spans).hasSize(2); + + SpanData grpcSuccessSpan = spans.get(0); + assertThat( + grpcSuccessSpan + .getAttributes() + .get(AttributeKey.stringKey(ObservabilityAttributes.RPC_RESPONSE_STATUS_ATTRIBUTE))) + .isEqualTo("OK"); + + SpanData grpcErrorSpan = spans.get(1); + assertThat( + grpcErrorSpan + .getAttributes() + .get(AttributeKey.stringKey(ObservabilityAttributes.RPC_RESPONSE_STATUS_ATTRIBUTE))) + .isEqualTo("INVALID_ARGUMENT"); + } + } + + @Test + void testTracing_statusCodes_httpjson() throws Exception { + SpanTracerFactory tracingFactory = new SpanTracerFactory(openTelemetrySdk); + EchoRequest errorRequest = + EchoRequest.newBuilder() + .setError(Status.newBuilder().setCode(StatusCode.Code.INVALID_ARGUMENT.ordinal()).build()) + .build(); + EchoRequest successRequest = EchoRequest.newBuilder().setContent("tracing-test").build(); + + try (EchoClient httpClient = + TestClientInitializer.createHttpJsonEchoClientOpentelemetry(tracingFactory)) { + + httpClient.echo(successRequest); + assertThrows( + com.google.api.gax.rpc.InvalidArgumentException.class, () -> httpClient.echo(errorRequest)); + + List spans = spanExporter.getFinishedSpanItems(); + assertThat(spans).hasSize(2); + + SpanData httpSuccessSpan = spans.get(0); + assertThat( + httpSuccessSpan + .getAttributes() + .get(AttributeKey.longKey(ObservabilityAttributes.HTTP_RESPONSE_STATUS_ATTRIBUTE))) + .isEqualTo(200L); + + SpanData httpErrorSpan = spans.get(1); + assertThat( + httpErrorSpan + .getAttributes() + .get(AttributeKey.longKey(ObservabilityAttributes.HTTP_RESPONSE_STATUS_ATTRIBUTE))) + .isEqualTo((long) StatusCode.Code.INVALID_ARGUMENT.getHttpStatusCode()); + } + } } From c3ed5e87276e17d48b1bcb609ae3f855558d79bd Mon Sep 17 00:00:00 2001 From: Diego Date: Mon, 30 Mar 2026 14:27:45 -0400 Subject: [PATCH 2/7] refactor: remove unnecessary overload --- .../tracing/GoldenSignalsMetricsTracer.java | 6 ++++- .../google/api/gax/tracing/MetricsTracer.java | 16 +++++++++--- .../api/gax/tracing/ObservabilityUtils.java | 11 ++++---- .../google/api/gax/tracing/SpanTracer.java | 26 +++++++++---------- .../gax/tracing/ObservabilityUtilsTest.java | 10 ++++--- 5 files changed, 41 insertions(+), 28 deletions(-) diff --git a/sdk-platform-java/gax-java/gax/src/main/java/com/google/api/gax/tracing/GoldenSignalsMetricsTracer.java b/sdk-platform-java/gax-java/gax/src/main/java/com/google/api/gax/tracing/GoldenSignalsMetricsTracer.java index 21d9d7093bdf..bec1f2d9c1f7 100644 --- a/sdk-platform-java/gax-java/gax/src/main/java/com/google/api/gax/tracing/GoldenSignalsMetricsTracer.java +++ b/sdk-platform-java/gax-java/gax/src/main/java/com/google/api/gax/tracing/GoldenSignalsMetricsTracer.java @@ -49,12 +49,14 @@ class GoldenSignalsMetricsTracer implements ApiTracer { private final Stopwatch clientRequestTimer; private final GoldenSignalsMetricsRecorder metricsRecorder; private final Map attributes; + private final ApiTracerContext.Transport transport; GoldenSignalsMetricsTracer( GoldenSignalsMetricsRecorder metricsRecorder, ApiTracerContext apiTracerContext) { this.clientRequestTimer = Stopwatch.createStarted(); this.metricsRecorder = metricsRecorder; this.attributes = apiTracerContext.getMetricsAttributes(); + this.transport = apiTracerContext.transport(); } @VisibleForTesting @@ -65,6 +67,7 @@ class GoldenSignalsMetricsTracer implements ApiTracer { this.clientRequestTimer = Stopwatch.createStarted(ticker); this.metricsRecorder = metricsRecorder; this.attributes = new HashMap<>(apiTracerContext.getMetricsAttributes()); + this.transport = apiTracerContext.transport(); } /** @@ -88,7 +91,8 @@ public void operationCancelled() { @Override public void operationFailed(Throwable error) { - attributes.put(RPC_RESPONSE_STATUS_ATTRIBUTE, ObservabilityUtils.extractStatus(error)); + attributes.put( + RPC_RESPONSE_STATUS_ATTRIBUTE, ObservabilityUtils.extractStatus(error, transport)); metricsRecorder.recordOperationLatency( clientRequestTimer.elapsed(TimeUnit.NANOSECONDS) / 1_000_000_000.0, attributes); } diff --git a/sdk-platform-java/gax-java/gax/src/main/java/com/google/api/gax/tracing/MetricsTracer.java b/sdk-platform-java/gax-java/gax/src/main/java/com/google/api/gax/tracing/MetricsTracer.java index e9ad908c2194..f41cb436c292 100644 --- a/sdk-platform-java/gax-java/gax/src/main/java/com/google/api/gax/tracing/MetricsTracer.java +++ b/sdk-platform-java/gax-java/gax/src/main/java/com/google/api/gax/tracing/MetricsTracer.java @@ -120,7 +120,9 @@ public void operationFailed(Throwable error) { if (operationFinished.getAndSet(true)) { throw new IllegalStateException(OPERATION_FINISHED_STATUS_MESSAGE); } - attributes.put(STATUS_ATTRIBUTE, ObservabilityUtils.extractStatus(error)); + attributes.put( + STATUS_ATTRIBUTE, + (String) ObservabilityUtils.extractStatus(error, ApiTracerContext.Transport.GRPC)); metricsRecorder.recordOperationLatency( operationTimer.elapsed(TimeUnit.MILLISECONDS), attributes); metricsRecorder.recordOperationCount(1, attributes); @@ -172,7 +174,9 @@ public void attemptCancelled() { */ @Override public void attemptFailedDuration(Throwable error, java.time.Duration delay) { - attributes.put(STATUS_ATTRIBUTE, ObservabilityUtils.extractStatus(error)); + attributes.put( + STATUS_ATTRIBUTE, + (String) ObservabilityUtils.extractStatus(error, ApiTracerContext.Transport.GRPC)); metricsRecorder.recordAttemptLatency(attemptTimer.elapsed(TimeUnit.MILLISECONDS), attributes); metricsRecorder.recordAttemptCount(1, attributes); } @@ -196,7 +200,9 @@ public void attemptFailed(Throwable error, org.threeten.bp.Duration delay) { */ @Override public void attemptFailedRetriesExhausted(Throwable error) { - attributes.put(STATUS_ATTRIBUTE, ObservabilityUtils.extractStatus(error)); + attributes.put( + STATUS_ATTRIBUTE, + (String) ObservabilityUtils.extractStatus(error, ApiTracerContext.Transport.GRPC)); metricsRecorder.recordAttemptLatency(attemptTimer.elapsed(TimeUnit.MILLISECONDS), attributes); metricsRecorder.recordAttemptCount(1, attributes); } @@ -210,7 +216,9 @@ public void attemptFailedRetriesExhausted(Throwable error) { */ @Override public void attemptPermanentFailure(Throwable error) { - attributes.put(STATUS_ATTRIBUTE, ObservabilityUtils.extractStatus(error)); + attributes.put( + STATUS_ATTRIBUTE, + (String) ObservabilityUtils.extractStatus(error, ApiTracerContext.Transport.GRPC)); metricsRecorder.recordAttemptLatency(attemptTimer.elapsed(TimeUnit.MILLISECONDS), attributes); metricsRecorder.recordAttemptCount(1, attributes); } diff --git a/sdk-platform-java/gax-java/gax/src/main/java/com/google/api/gax/tracing/ObservabilityUtils.java b/sdk-platform-java/gax-java/gax/src/main/java/com/google/api/gax/tracing/ObservabilityUtils.java index 4d143d0594c4..91d0447498a9 100644 --- a/sdk-platform-java/gax-java/gax/src/main/java/com/google/api/gax/tracing/ObservabilityUtils.java +++ b/sdk-platform-java/gax-java/gax/src/main/java/com/google/api/gax/tracing/ObservabilityUtils.java @@ -39,11 +39,6 @@ class ObservabilityUtils { - /** Function to extract the status of the error as a string (defaults to gRPC canonical codes). */ - static String extractStatus(@Nullable Throwable error) { - return (String) extractStatus(error, ApiTracerContext.Transport.GRPC); - } - static Object extractStatus(@Nullable Throwable error, ApiTracerContext.Transport transport) { if (transport == ApiTracerContext.Transport.HTTP) { return extractHttpStatus(error); @@ -70,12 +65,16 @@ private static Long extractHttpStatus(@Nullable Throwable error) { return 200L; } else if (error instanceof ApiException) { Object transportCode = ((ApiException) error).getStatusCode().getTransportCode(); + // HttpJsonStatusCode.getTransportCode() returns an Integer (HTTP status code). + // GrpcStatusCode returns a Status.Code enum, and FakeStatusCode (in tests) returns + // a StatusCode.Code enum. If it's not an Integer, we fall back to the mapped + // HTTP status code of the canonical code. if (transportCode instanceof Integer) { return ((Integer) transportCode).longValue(); } else { return (long) ((ApiException) error).getStatusCode().getCode().getHttpStatusCode(); } - } + } StatusCode.Code code = StatusCode.Code.UNKNOWN; if (error instanceof CancellationException) { code = StatusCode.Code.CANCELLED; diff --git a/sdk-platform-java/gax-java/gax/src/main/java/com/google/api/gax/tracing/SpanTracer.java b/sdk-platform-java/gax-java/gax/src/main/java/com/google/api/gax/tracing/SpanTracer.java index 47dbb646a48d..8eecfa5a60a4 100644 --- a/sdk-platform-java/gax-java/gax/src/main/java/com/google/api/gax/tracing/SpanTracer.java +++ b/sdk-platform-java/gax-java/gax/src/main/java/com/google/api/gax/tracing/SpanTracer.java @@ -36,8 +36,6 @@ import io.opentelemetry.api.trace.SpanBuilder; import io.opentelemetry.api.trace.SpanKind; import io.opentelemetry.api.trace.Tracer; - -import java.time.Duration; import java.util.HashMap; import java.util.Map; import java.util.concurrent.CancellationException; @@ -136,7 +134,7 @@ public void attemptStarted(Object request, int attemptNumber) { @Override public void attemptSucceeded() { - endAttempt(null); + endAttempt(null); } @Override @@ -183,33 +181,33 @@ private long extractContentLength(java.util.Map headers) { @Override public void attemptCancelled() { - endAttempt(new CancellationException()); + endAttempt(new CancellationException()); } @Override public void attemptFailedDuration(Throwable error, java.time.Duration delay) { - endAttempt(error); + endAttempt(error); } @Override public void attemptFailedRetriesExhausted(Throwable error) { - endAttempt(error); + endAttempt(error); } @Override public void attemptPermanentFailure(Throwable error) { - endAttempt(error); + endAttempt(error); } - private void endAttempt(Throwable error) { + private void endAttempt(Throwable error) { if (attemptSpan != null) { - Map endAttributes = new HashMap<>(); - ObservabilityUtils.populateStatusAttributes( - endAttributes, error, this.apiTracerContext.transport()); + Map endAttributes = new HashMap<>(); + ObservabilityUtils.populateStatusAttributes( + endAttributes, error, this.apiTracerContext.transport()); - if (!endAttributes.isEmpty()) { - attemptSpan.setAllAttributes(ObservabilityUtils.toOtelAttributes(endAttributes)); - } + if (!endAttributes.isEmpty()) { + attemptSpan.setAllAttributes(ObservabilityUtils.toOtelAttributes(endAttributes)); + } attemptSpan.end(); attemptSpan = null; diff --git a/sdk-platform-java/gax-java/gax/src/test/java/com/google/api/gax/tracing/ObservabilityUtilsTest.java b/sdk-platform-java/gax-java/gax/src/test/java/com/google/api/gax/tracing/ObservabilityUtilsTest.java index 8fad41052e99..c3398bd7d1ee 100644 --- a/sdk-platform-java/gax-java/gax/src/test/java/com/google/api/gax/tracing/ObservabilityUtilsTest.java +++ b/sdk-platform-java/gax-java/gax/src/test/java/com/google/api/gax/tracing/ObservabilityUtilsTest.java @@ -47,14 +47,16 @@ void testExtractStatus_errorConversion_apiExceptions() { ApiException error = new ApiException( "fake_error", null, new FakeStatusCode(StatusCode.Code.INVALID_ARGUMENT), false); - String errorCode = ObservabilityUtils.extractStatus(error); + String errorCode = + (String) ObservabilityUtils.extractStatus(error, ApiTracerContext.Transport.GRPC); assertThat(errorCode).isEqualTo(StatusCode.Code.INVALID_ARGUMENT.toString()); } @Test void testExtractStatus_errorConversion_noError() { // test "OK", which corresponds to a "null" error. - String successCode = ObservabilityUtils.extractStatus(null); + String successCode = + (String) ObservabilityUtils.extractStatus(null, ApiTracerContext.Transport.GRPC); assertThat(successCode).isEqualTo(StatusCode.Code.OK.toString()); } @@ -62,7 +64,9 @@ void testExtractStatus_errorConversion_noError() { void testExtractStatus_errorConversion_unknownException() { // test "UNKNOWN" Throwable unknownException = new RuntimeException(); - String errorCode2 = ObservabilityUtils.extractStatus(unknownException); + String errorCode2 = + (String) + ObservabilityUtils.extractStatus(unknownException, ApiTracerContext.Transport.GRPC); assertThat(errorCode2).isEqualTo(StatusCode.Code.UNKNOWN.toString()); } From 074ab6735b9a7f3a08d109995851ed917cab9357 Mon Sep 17 00:00:00 2001 From: Diego Date: Mon, 30 Mar 2026 14:44:06 -0400 Subject: [PATCH 3/7] fix: adjust method usage in LoggingTracer --- .../main/java/com/google/api/gax/tracing/LoggingTracer.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/sdk-platform-java/gax-java/gax/src/main/java/com/google/api/gax/tracing/LoggingTracer.java b/sdk-platform-java/gax-java/gax/src/main/java/com/google/api/gax/tracing/LoggingTracer.java index ec1a39c33337..01d54992dd92 100644 --- a/sdk-platform-java/gax-java/gax/src/main/java/com/google/api/gax/tracing/LoggingTracer.java +++ b/sdk-platform-java/gax-java/gax/src/main/java/com/google/api/gax/tracing/LoggingTracer.java @@ -34,6 +34,7 @@ import com.google.api.core.InternalApi; import com.google.api.gax.logging.LoggerProvider; import com.google.api.gax.logging.LoggingUtils; +import com.google.api.gax.tracing.ApiTracerContext.Transport; import com.google.common.annotations.VisibleForTesting; import com.google.rpc.ErrorInfo; import java.util.HashMap; @@ -80,7 +81,7 @@ void recordActionableError(Throwable error) { logContext.put( ObservabilityAttributes.RPC_RESPONSE_STATUS_ATTRIBUTE, - ObservabilityUtils.extractStatus(error)); + ObservabilityUtils.extractStatus(error, Transport.GRPC)); ErrorInfo errorInfo = ObservabilityUtils.extractErrorInfo(error); if (errorInfo != null) { From 12143fd128c8734f0c74447f302387f46504a420 Mon Sep 17 00:00:00 2001 From: Diego Date: Mon, 30 Mar 2026 15:56:05 -0400 Subject: [PATCH 4/7] fix: restore default overload --- .../gax/tracing/GoldenSignalsMetricsTracer.java | 11 +++++++++-- .../google/api/gax/tracing/LoggingTracer.java | 3 +-- .../google/api/gax/tracing/MetricsTracer.java | 16 ++++------------ .../api/gax/tracing/ObservabilityUtils.java | 5 +++++ 4 files changed, 19 insertions(+), 16 deletions(-) diff --git a/sdk-platform-java/gax-java/gax/src/main/java/com/google/api/gax/tracing/GoldenSignalsMetricsTracer.java b/sdk-platform-java/gax-java/gax/src/main/java/com/google/api/gax/tracing/GoldenSignalsMetricsTracer.java index bec1f2d9c1f7..4362d72c309e 100644 --- a/sdk-platform-java/gax-java/gax/src/main/java/com/google/api/gax/tracing/GoldenSignalsMetricsTracer.java +++ b/sdk-platform-java/gax-java/gax/src/main/java/com/google/api/gax/tracing/GoldenSignalsMetricsTracer.java @@ -29,9 +29,11 @@ */ package com.google.api.gax.tracing; +import static com.google.api.gax.tracing.ObservabilityAttributes.HTTP_RESPONSE_STATUS_ATTRIBUTE; import static com.google.api.gax.tracing.ObservabilityAttributes.RPC_RESPONSE_STATUS_ATTRIBUTE; import com.google.api.gax.rpc.StatusCode; +import com.google.api.gax.tracing.ApiTracerContext.Transport; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Stopwatch; import com.google.common.base.Ticker; @@ -91,8 +93,13 @@ public void operationCancelled() { @Override public void operationFailed(Throwable error) { - attributes.put( - RPC_RESPONSE_STATUS_ATTRIBUTE, ObservabilityUtils.extractStatus(error, transport)); + if (transport == Transport.HTTP) { + attributes.put( + HTTP_RESPONSE_STATUS_ATTRIBUTE, ObservabilityUtils.extractStatus(error, transport)); + } else { + attributes.put( + RPC_RESPONSE_STATUS_ATTRIBUTE, ObservabilityUtils.extractStatus(error, transport)); + } metricsRecorder.recordOperationLatency( clientRequestTimer.elapsed(TimeUnit.NANOSECONDS) / 1_000_000_000.0, attributes); } diff --git a/sdk-platform-java/gax-java/gax/src/main/java/com/google/api/gax/tracing/LoggingTracer.java b/sdk-platform-java/gax-java/gax/src/main/java/com/google/api/gax/tracing/LoggingTracer.java index 01d54992dd92..ec1a39c33337 100644 --- a/sdk-platform-java/gax-java/gax/src/main/java/com/google/api/gax/tracing/LoggingTracer.java +++ b/sdk-platform-java/gax-java/gax/src/main/java/com/google/api/gax/tracing/LoggingTracer.java @@ -34,7 +34,6 @@ import com.google.api.core.InternalApi; import com.google.api.gax.logging.LoggerProvider; import com.google.api.gax.logging.LoggingUtils; -import com.google.api.gax.tracing.ApiTracerContext.Transport; import com.google.common.annotations.VisibleForTesting; import com.google.rpc.ErrorInfo; import java.util.HashMap; @@ -81,7 +80,7 @@ void recordActionableError(Throwable error) { logContext.put( ObservabilityAttributes.RPC_RESPONSE_STATUS_ATTRIBUTE, - ObservabilityUtils.extractStatus(error, Transport.GRPC)); + ObservabilityUtils.extractStatus(error)); ErrorInfo errorInfo = ObservabilityUtils.extractErrorInfo(error); if (errorInfo != null) { diff --git a/sdk-platform-java/gax-java/gax/src/main/java/com/google/api/gax/tracing/MetricsTracer.java b/sdk-platform-java/gax-java/gax/src/main/java/com/google/api/gax/tracing/MetricsTracer.java index f41cb436c292..4b77d681fbf2 100644 --- a/sdk-platform-java/gax-java/gax/src/main/java/com/google/api/gax/tracing/MetricsTracer.java +++ b/sdk-platform-java/gax-java/gax/src/main/java/com/google/api/gax/tracing/MetricsTracer.java @@ -120,9 +120,7 @@ public void operationFailed(Throwable error) { if (operationFinished.getAndSet(true)) { throw new IllegalStateException(OPERATION_FINISHED_STATUS_MESSAGE); } - attributes.put( - STATUS_ATTRIBUTE, - (String) ObservabilityUtils.extractStatus(error, ApiTracerContext.Transport.GRPC)); + attributes.put(STATUS_ATTRIBUTE, (String) ObservabilityUtils.extractStatus(error)); metricsRecorder.recordOperationLatency( operationTimer.elapsed(TimeUnit.MILLISECONDS), attributes); metricsRecorder.recordOperationCount(1, attributes); @@ -174,9 +172,7 @@ public void attemptCancelled() { */ @Override public void attemptFailedDuration(Throwable error, java.time.Duration delay) { - attributes.put( - STATUS_ATTRIBUTE, - (String) ObservabilityUtils.extractStatus(error, ApiTracerContext.Transport.GRPC)); + attributes.put(STATUS_ATTRIBUTE, (String) ObservabilityUtils.extractStatus(error)); metricsRecorder.recordAttemptLatency(attemptTimer.elapsed(TimeUnit.MILLISECONDS), attributes); metricsRecorder.recordAttemptCount(1, attributes); } @@ -200,9 +196,7 @@ public void attemptFailed(Throwable error, org.threeten.bp.Duration delay) { */ @Override public void attemptFailedRetriesExhausted(Throwable error) { - attributes.put( - STATUS_ATTRIBUTE, - (String) ObservabilityUtils.extractStatus(error, ApiTracerContext.Transport.GRPC)); + attributes.put(STATUS_ATTRIBUTE, (String) ObservabilityUtils.extractStatus(error)); metricsRecorder.recordAttemptLatency(attemptTimer.elapsed(TimeUnit.MILLISECONDS), attributes); metricsRecorder.recordAttemptCount(1, attributes); } @@ -216,9 +210,7 @@ public void attemptFailedRetriesExhausted(Throwable error) { */ @Override public void attemptPermanentFailure(Throwable error) { - attributes.put( - STATUS_ATTRIBUTE, - (String) ObservabilityUtils.extractStatus(error, ApiTracerContext.Transport.GRPC)); + attributes.put(STATUS_ATTRIBUTE, (String) ObservabilityUtils.extractStatus(error)); metricsRecorder.recordAttemptLatency(attemptTimer.elapsed(TimeUnit.MILLISECONDS), attributes); metricsRecorder.recordAttemptCount(1, attributes); } diff --git a/sdk-platform-java/gax-java/gax/src/main/java/com/google/api/gax/tracing/ObservabilityUtils.java b/sdk-platform-java/gax-java/gax/src/main/java/com/google/api/gax/tracing/ObservabilityUtils.java index 569835d7ba18..bb58b116cdd3 100644 --- a/sdk-platform-java/gax-java/gax/src/main/java/com/google/api/gax/tracing/ObservabilityUtils.java +++ b/sdk-platform-java/gax-java/gax/src/main/java/com/google/api/gax/tracing/ObservabilityUtils.java @@ -43,6 +43,11 @@ final class ObservabilityUtils { + /** Function to extract the status of the error as a string (defaults to gRPC canonical codes). */ + static String extractStatus(@Nullable Throwable error) { + return (String) extractStatus(error, ApiTracerContext.Transport.GRPC); + } + static Object extractStatus(@Nullable Throwable error, ApiTracerContext.Transport transport) { if (transport == ApiTracerContext.Transport.HTTP) { return extractHttpStatus(error); From c2895afe7ed6381c7fa9e9eccad937cc844dea36 Mon Sep 17 00:00:00 2001 From: Diego Date: Mon, 30 Mar 2026 17:36:10 -0400 Subject: [PATCH 5/7] fix: remove redundant Long check --- .../java/com/google/api/gax/tracing/ObservabilityUtils.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/sdk-platform-java/gax-java/gax/src/main/java/com/google/api/gax/tracing/ObservabilityUtils.java b/sdk-platform-java/gax-java/gax/src/main/java/com/google/api/gax/tracing/ObservabilityUtils.java index bb58b116cdd3..156d4102cb5f 100644 --- a/sdk-platform-java/gax-java/gax/src/main/java/com/google/api/gax/tracing/ObservabilityUtils.java +++ b/sdk-platform-java/gax-java/gax/src/main/java/com/google/api/gax/tracing/ObservabilityUtils.java @@ -235,8 +235,6 @@ static Attributes toOtelAttributes(Map attributes) { attributesBuilder.put(k, (Long) v); } else if (v instanceof Integer) { attributesBuilder.put(k, (long) (Integer) v); - } else if (v instanceof Long) { - attributesBuilder.put(k, (Long) v); } }); return attributesBuilder.build(); From 8c8fdbd623ed83ed00c15d9ffd5365a8e35dbe3d Mon Sep 17 00:00:00 2001 From: Diego Date: Tue, 31 Mar 2026 16:16:04 -0400 Subject: [PATCH 6/7] fix: simplify status extration logic and return types --- .../tracing/GoldenSignalsMetricsTracer.java | 10 +-- .../google/api/gax/tracing/MetricsTracer.java | 9 +-- .../api/gax/tracing/ObservabilityUtils.java | 66 +++++-------------- .../gax/tracing/ObservabilityUtilsTest.java | 16 ++--- 4 files changed, 27 insertions(+), 74 deletions(-) diff --git a/sdk-platform-java/gax-java/gax/src/main/java/com/google/api/gax/tracing/GoldenSignalsMetricsTracer.java b/sdk-platform-java/gax-java/gax/src/main/java/com/google/api/gax/tracing/GoldenSignalsMetricsTracer.java index 4362d72c309e..875947661e8f 100644 --- a/sdk-platform-java/gax-java/gax/src/main/java/com/google/api/gax/tracing/GoldenSignalsMetricsTracer.java +++ b/sdk-platform-java/gax-java/gax/src/main/java/com/google/api/gax/tracing/GoldenSignalsMetricsTracer.java @@ -29,11 +29,9 @@ */ package com.google.api.gax.tracing; -import static com.google.api.gax.tracing.ObservabilityAttributes.HTTP_RESPONSE_STATUS_ATTRIBUTE; import static com.google.api.gax.tracing.ObservabilityAttributes.RPC_RESPONSE_STATUS_ATTRIBUTE; import com.google.api.gax.rpc.StatusCode; -import com.google.api.gax.tracing.ApiTracerContext.Transport; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Stopwatch; import com.google.common.base.Ticker; @@ -93,13 +91,7 @@ public void operationCancelled() { @Override public void operationFailed(Throwable error) { - if (transport == Transport.HTTP) { - attributes.put( - HTTP_RESPONSE_STATUS_ATTRIBUTE, ObservabilityUtils.extractStatus(error, transport)); - } else { - attributes.put( - RPC_RESPONSE_STATUS_ATTRIBUTE, ObservabilityUtils.extractStatus(error, transport)); - } + ObservabilityUtils.populateStatusAttributes(attributes, error, transport); metricsRecorder.recordOperationLatency( clientRequestTimer.elapsed(TimeUnit.NANOSECONDS) / 1_000_000_000.0, attributes); } diff --git a/sdk-platform-java/gax-java/gax/src/main/java/com/google/api/gax/tracing/MetricsTracer.java b/sdk-platform-java/gax-java/gax/src/main/java/com/google/api/gax/tracing/MetricsTracer.java index 4b77d681fbf2..13aac8986274 100644 --- a/sdk-platform-java/gax-java/gax/src/main/java/com/google/api/gax/tracing/MetricsTracer.java +++ b/sdk-platform-java/gax-java/gax/src/main/java/com/google/api/gax/tracing/MetricsTracer.java @@ -120,7 +120,8 @@ public void operationFailed(Throwable error) { if (operationFinished.getAndSet(true)) { throw new IllegalStateException(OPERATION_FINISHED_STATUS_MESSAGE); } - attributes.put(STATUS_ATTRIBUTE, (String) ObservabilityUtils.extractStatus(error)); + // Uses the GRPC status code representation. + attributes.put(STATUS_ATTRIBUTE, ObservabilityUtils.extractStatus(error).toString()); metricsRecorder.recordOperationLatency( operationTimer.elapsed(TimeUnit.MILLISECONDS), attributes); metricsRecorder.recordOperationCount(1, attributes); @@ -172,7 +173,7 @@ public void attemptCancelled() { */ @Override public void attemptFailedDuration(Throwable error, java.time.Duration delay) { - attributes.put(STATUS_ATTRIBUTE, (String) ObservabilityUtils.extractStatus(error)); + attributes.put(STATUS_ATTRIBUTE, ObservabilityUtils.extractStatus(error).toString()); metricsRecorder.recordAttemptLatency(attemptTimer.elapsed(TimeUnit.MILLISECONDS), attributes); metricsRecorder.recordAttemptCount(1, attributes); } @@ -196,7 +197,7 @@ public void attemptFailed(Throwable error, org.threeten.bp.Duration delay) { */ @Override public void attemptFailedRetriesExhausted(Throwable error) { - attributes.put(STATUS_ATTRIBUTE, (String) ObservabilityUtils.extractStatus(error)); + attributes.put(STATUS_ATTRIBUTE, ObservabilityUtils.extractStatus(error).toString()); metricsRecorder.recordAttemptLatency(attemptTimer.elapsed(TimeUnit.MILLISECONDS), attributes); metricsRecorder.recordAttemptCount(1, attributes); } @@ -210,7 +211,7 @@ public void attemptFailedRetriesExhausted(Throwable error) { */ @Override public void attemptPermanentFailure(Throwable error) { - attributes.put(STATUS_ATTRIBUTE, (String) ObservabilityUtils.extractStatus(error)); + attributes.put(STATUS_ATTRIBUTE, ObservabilityUtils.extractStatus(error).toString()); metricsRecorder.recordAttemptLatency(attemptTimer.elapsed(TimeUnit.MILLISECONDS), attributes); metricsRecorder.recordAttemptCount(1, attributes); } diff --git a/sdk-platform-java/gax-java/gax/src/main/java/com/google/api/gax/tracing/ObservabilityUtils.java b/sdk-platform-java/gax-java/gax/src/main/java/com/google/api/gax/tracing/ObservabilityUtils.java index 156d4102cb5f..a50643f3c780 100644 --- a/sdk-platform-java/gax-java/gax/src/main/java/com/google/api/gax/tracing/ObservabilityUtils.java +++ b/sdk-platform-java/gax-java/gax/src/main/java/com/google/api/gax/tracing/ObservabilityUtils.java @@ -43,16 +43,17 @@ final class ObservabilityUtils { - /** Function to extract the status of the error as a string (defaults to gRPC canonical codes). */ - static String extractStatus(@Nullable Throwable error) { - return (String) extractStatus(error, ApiTracerContext.Transport.GRPC); - } - - static Object extractStatus(@Nullable Throwable error, ApiTracerContext.Transport transport) { - if (transport == ApiTracerContext.Transport.HTTP) { - return extractHttpStatus(error); + /** Function to extract the status of the error as a canonical code. */ + static StatusCode.Code extractStatus(@Nullable Throwable error) { + if (error == null) { + return StatusCode.Code.OK; + } else if (error instanceof CancellationException) { + return StatusCode.Code.CANCELLED; + } else if (error instanceof ApiException) { + return ((ApiException) error).getStatusCode().getCode(); + } else { + return StatusCode.Code.UNKNOWN; } - return extractGrpcStatus(error); } /** Constant for redacted values. */ @@ -160,53 +161,16 @@ private static String redactSensitiveQueryValues(final String rawQuery) { return Joiner.on('&').join(redactedParams); } - private static String extractGrpcStatus(@Nullable Throwable error) { - final String statusString; - if (error == null) { - return StatusCode.Code.OK.toString(); - } else if (error instanceof CancellationException) { - statusString = StatusCode.Code.CANCELLED.toString(); - } else if (error instanceof ApiException) { - statusString = ((ApiException) error).getStatusCode().getCode().toString(); - } else { - statusString = StatusCode.Code.UNKNOWN.toString(); - } - return statusString; - } - - private static Long extractHttpStatus(@Nullable Throwable error) { - if (error == null) { - return 200L; - } else if (error instanceof ApiException) { - Object transportCode = ((ApiException) error).getStatusCode().getTransportCode(); - // HttpJsonStatusCode.getTransportCode() returns an Integer (HTTP status code). - // GrpcStatusCode returns a Status.Code enum, and FakeStatusCode (in tests) - // returns - // a StatusCode.Code enum. If it's not an Integer, we fall back to the mapped - // HTTP status code of the canonical code. - if (transportCode instanceof Integer) { - return ((Integer) transportCode).longValue(); - } else { - return (long) ((ApiException) error).getStatusCode().getCode().getHttpStatusCode(); - } - } - StatusCode.Code code = StatusCode.Code.UNKNOWN; - if (error instanceof CancellationException) { - code = StatusCode.Code.CANCELLED; - } - return (long) code.getHttpStatusCode(); - } - static void populateStatusAttributes( Map attributes, @Nullable Throwable error, ApiTracerContext.Transport transport) { - if (transport == ApiTracerContext.Transport.GRPC) { - attributes.put( - ObservabilityAttributes.RPC_RESPONSE_STATUS_ATTRIBUTE, extractStatus(error, transport)); - } else if (transport == ApiTracerContext.Transport.HTTP) { + StatusCode.Code code = extractStatus(error); + if (transport == ApiTracerContext.Transport.HTTP) { attributes.put( - ObservabilityAttributes.HTTP_RESPONSE_STATUS_ATTRIBUTE, extractStatus(error, transport)); + ObservabilityAttributes.HTTP_RESPONSE_STATUS_ATTRIBUTE, (long) code.getHttpStatusCode()); + } else { + attributes.put(ObservabilityAttributes.RPC_RESPONSE_STATUS_ATTRIBUTE, code.toString()); } } diff --git a/sdk-platform-java/gax-java/gax/src/test/java/com/google/api/gax/tracing/ObservabilityUtilsTest.java b/sdk-platform-java/gax-java/gax/src/test/java/com/google/api/gax/tracing/ObservabilityUtilsTest.java index 3159ab04c515..bebbc89dd16c 100644 --- a/sdk-platform-java/gax-java/gax/src/test/java/com/google/api/gax/tracing/ObservabilityUtilsTest.java +++ b/sdk-platform-java/gax-java/gax/src/test/java/com/google/api/gax/tracing/ObservabilityUtilsTest.java @@ -47,27 +47,23 @@ void testExtractStatus_errorConversion_apiExceptions() { ApiException error = new ApiException( "fake_error", null, new FakeStatusCode(StatusCode.Code.INVALID_ARGUMENT), false); - String errorCode = - (String) ObservabilityUtils.extractStatus(error, ApiTracerContext.Transport.GRPC); - assertThat(errorCode).isEqualTo(StatusCode.Code.INVALID_ARGUMENT.toString()); + StatusCode.Code errorCode = ObservabilityUtils.extractStatus(error); + assertThat(errorCode).isEqualTo(StatusCode.Code.INVALID_ARGUMENT); } @Test void testExtractStatus_errorConversion_noError() { // test "OK", which corresponds to a "null" error. - String successCode = - (String) ObservabilityUtils.extractStatus(null, ApiTracerContext.Transport.GRPC); - assertThat(successCode).isEqualTo(StatusCode.Code.OK.toString()); + StatusCode.Code successCode = ObservabilityUtils.extractStatus(null); + assertThat(successCode).isEqualTo(StatusCode.Code.OK); } @Test void testExtractStatus_errorConversion_unknownException() { // test "UNKNOWN" Throwable unknownException = new RuntimeException(); - String errorCode2 = - (String) - ObservabilityUtils.extractStatus(unknownException, ApiTracerContext.Transport.GRPC); - assertThat(errorCode2).isEqualTo(StatusCode.Code.UNKNOWN.toString()); + StatusCode.Code errorCode2 = ObservabilityUtils.extractStatus(unknownException); + assertThat(errorCode2).isEqualTo(StatusCode.Code.UNKNOWN); } @Test From 50352e68b35faad30cb62718ec296b8dcdfd8813 Mon Sep 17 00:00:00 2001 From: Diego Date: Tue, 31 Mar 2026 16:52:11 -0400 Subject: [PATCH 7/7] test: fix logging tracer test --- .../src/main/java/com/google/api/gax/tracing/LoggingTracer.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk-platform-java/gax-java/gax/src/main/java/com/google/api/gax/tracing/LoggingTracer.java b/sdk-platform-java/gax-java/gax/src/main/java/com/google/api/gax/tracing/LoggingTracer.java index ec1a39c33337..d270259087ed 100644 --- a/sdk-platform-java/gax-java/gax/src/main/java/com/google/api/gax/tracing/LoggingTracer.java +++ b/sdk-platform-java/gax-java/gax/src/main/java/com/google/api/gax/tracing/LoggingTracer.java @@ -80,7 +80,7 @@ void recordActionableError(Throwable error) { logContext.put( ObservabilityAttributes.RPC_RESPONSE_STATUS_ATTRIBUTE, - ObservabilityUtils.extractStatus(error)); + ObservabilityUtils.extractStatus(error).toString()); ErrorInfo errorInfo = ObservabilityUtils.extractErrorInfo(error); if (errorInfo != null) {