diff --git a/sdk-platform-java/gax-java/gax/src/main/java/com/google/api/gax/tracing/ErrorTypeUtil.java b/sdk-platform-java/gax-java/gax/src/main/java/com/google/api/gax/tracing/ErrorTypeUtil.java index 0548ee3f5b50..4a345f361496 100644 --- a/sdk-platform-java/gax-java/gax/src/main/java/com/google/api/gax/tracing/ErrorTypeUtil.java +++ b/sdk-platform-java/gax-java/gax/src/main/java/com/google/api/gax/tracing/ErrorTypeUtil.java @@ -43,6 +43,7 @@ import java.nio.channels.UnresolvedAddressException; import java.security.GeneralSecurityException; import java.util.Set; +import javax.annotation.Nonnull; import javax.annotation.Nullable; import javax.net.ssl.SSLHandshakeException; @@ -112,15 +113,10 @@ enum ErrorType { * * * @param error the Throwable from which to extract the error type string. - * @return a low-cardinality string representing the specific error type, or {@code null} if the - * provided error is {@code null} or non-determined. + * @return a low-cardinality string representing the specific error type */ // Requirement source: go/clo:product-requirements-v1 - public static String extractErrorType(@Nullable Throwable error) { - if (error == null) { - // No information about the error; return null so the attribute is not recorded. - return null; - } + public static String extractErrorType(@Nonnull Throwable error) { // 1. Unwrap standard wrapper exceptions if present Throwable realError = getRealCause(error); 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 d270259087ed..552ad2659bc4 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 @@ -35,6 +35,7 @@ import com.google.api.gax.logging.LoggerProvider; import com.google.api.gax.logging.LoggingUtils; import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Strings; import com.google.rpc.ErrorInfo; import java.util.HashMap; import java.util.Map; @@ -77,17 +78,16 @@ void recordActionableError(Throwable error) { } Map logContext = new HashMap<>(apiTracerContext.getAttemptAttributes()); + logContext.putAll( + ObservabilityUtils.getResponseAttributes(error, apiTracerContext.transport())); - logContext.put( - ObservabilityAttributes.RPC_RESPONSE_STATUS_ATTRIBUTE, - ObservabilityUtils.extractStatus(error).toString()); + if (!Strings.isNullOrEmpty(error.getMessage())) { + logContext.put(ObservabilityAttributes.EXCEPTION_MESSAGE_ATTRIBUTE, error.getMessage()); + } ErrorInfo errorInfo = ObservabilityUtils.extractErrorInfo(error); if (errorInfo != null) { - if (errorInfo.getReason() != null && !errorInfo.getReason().isEmpty()) { - logContext.put(ObservabilityAttributes.ERROR_TYPE_ATTRIBUTE, errorInfo.getReason()); - } - if (errorInfo.getDomain() != null && !errorInfo.getDomain().isEmpty()) { + if (!Strings.isNullOrEmpty(errorInfo.getDomain())) { logContext.put(ObservabilityAttributes.ERROR_DOMAIN_ATTRIBUTE, errorInfo.getDomain()); } if (errorInfo.getMetadataMap() != null) { 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 dd963ad96640..99981bd469fc 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 @@ -85,6 +85,9 @@ public class ObservabilityAttributes { /** If the error was caused by an exception, the exception class name. */ public static final String EXCEPTION_TYPE_ATTRIBUTE = "exception.type"; + /** If the error was caused by an exception, the exception message. */ + public static final String EXCEPTION_MESSAGE_ATTRIBUTE = "exception.message"; + /** Size of the response body in bytes. */ public static final String HTTP_RESPONSE_BODY_SIZE = "http.response.body.size"; 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 bd44e1f0a362..dfdfc4ccb459 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 @@ -44,15 +44,6 @@ final class ObservabilityUtils { - /** - * Extracts a low-cardinality string representing the specific classification of the error to be - * used in the {@link ObservabilityAttributes#ERROR_TYPE_ATTRIBUTE} attribute. See {@link - * ErrorTypeUtil#extractErrorType} for extended documentation. - */ - static String extractErrorType(@Nullable Throwable error) { - return ErrorTypeUtil.extractErrorType(error); - } - /** Function to extract the status of the error as a canonical code. */ static StatusCode.Code extractStatus(@Nullable Throwable error) { if (error == null) { @@ -182,7 +173,7 @@ static Map getResponseAttributes( } if (error != null) { attributes.put( - ObservabilityAttributes.ERROR_TYPE_ATTRIBUTE, ObservabilityUtils.extractErrorType(error)); + ObservabilityAttributes.ERROR_TYPE_ATTRIBUTE, ErrorTypeUtil.extractErrorType(error)); attributes.put(ObservabilityAttributes.EXCEPTION_TYPE_ATTRIBUTE, error.getClass().getName()); } return attributes; diff --git a/sdk-platform-java/gax-java/gax/src/test/java/com/google/api/gax/tracing/ErrorTypeUtilTest.java b/sdk-platform-java/gax-java/gax/src/test/java/com/google/api/gax/tracing/ErrorTypeUtilTest.java index 84d08792ec2a..e59c7f5e51cf 100644 --- a/sdk-platform-java/gax-java/gax/src/test/java/com/google/api/gax/tracing/ErrorTypeUtilTest.java +++ b/sdk-platform-java/gax-java/gax/src/test/java/com/google/api/gax/tracing/ErrorTypeUtilTest.java @@ -51,11 +51,6 @@ class ErrorTypeUtilTest { - @Test - void testExtractErrorType_null() { - assertThat(ErrorTypeUtil.extractErrorType(null)).isNull(); - } - @Test void testExtractErrorType_apiException_noReason() { ApiException exception = diff --git a/sdk-platform-java/gax-java/gax/src/test/java/com/google/api/gax/tracing/LoggingTracerTest.java b/sdk-platform-java/gax-java/gax/src/test/java/com/google/api/gax/tracing/LoggingTracerTest.java index 62e4506a5247..d1ba025d3867 100644 --- a/sdk-platform-java/gax-java/gax/src/test/java/com/google/api/gax/tracing/LoggingTracerTest.java +++ b/sdk-platform-java/gax-java/gax/src/test/java/com/google/api/gax/tracing/LoggingTracerTest.java @@ -31,7 +31,6 @@ package com.google.api.gax.tracing; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; import com.google.api.gax.logging.TestLogger; import com.google.api.gax.rpc.ApiExceptionFactory; @@ -121,8 +120,6 @@ void testRecordActionableError_logsStatus() { tracer.recordActionableError(error); Map attributesMap = getAttributesMap(); - - assertTrue(attributesMap != null && !attributesMap.isEmpty()); assertEquals( "INVALID_ARGUMENT", attributesMap.get(ObservabilityAttributes.RPC_RESPONSE_STATUS_ATTRIBUTE)); @@ -138,8 +135,6 @@ void testRecordActionableError_logsAttributes() { tracer.recordActionableError(error); Map attributesMap = getAttributesMap(); - - assertTrue(attributesMap != null && !attributesMap.isEmpty()); assertEquals( "test-service", attributesMap.get(ObservabilityAttributes.GCP_CLIENT_SERVICE_ATTRIBUTE)); } @@ -172,8 +167,6 @@ void testRecordActionableError_logsErrorInfo() { tracer.recordActionableError(error); Map attributesMap = getAttributesMap(); - - assertTrue(attributesMap != null && !attributesMap.isEmpty()); assertEquals("TEST_REASON", attributesMap.get(ObservabilityAttributes.ERROR_TYPE_ATTRIBUTE)); assertEquals( "test.domain.com", attributesMap.get(ObservabilityAttributes.ERROR_DOMAIN_ATTRIBUTE)); @@ -182,6 +175,45 @@ void testRecordActionableError_logsErrorInfo() { attributesMap.get(ObservabilityAttributes.ERROR_METADATA_ATTRIBUTE_PREFIX + "test_key")); } + @Test + void testRecordActionableError_logsExceptionDetails() { + ApiTracerContext context = ApiTracerContext.empty(); + LoggingTracer tracer = new LoggingTracer(context); + + Exception error = new RuntimeException("test error message"); + tracer.recordActionableError(error); + + Map attributesMap = getAttributesMap(); + assertEquals( + "java.lang.RuntimeException", + attributesMap.get(ObservabilityAttributes.EXCEPTION_TYPE_ATTRIBUTE)); + assertEquals( + "test error message", + attributesMap.get(ObservabilityAttributes.EXCEPTION_MESSAGE_ATTRIBUTE)); + } + + @Test + void testRecordActionableError_logsHttpStatus() { + ApiTracerContext context = + ApiTracerContext.empty().toBuilder().setTransport(ApiTracerContext.Transport.HTTP).build(); + LoggingTracer tracer = new LoggingTracer(context); + + Exception error = + ApiExceptionFactory.createException( + "test error message", + new RuntimeException("cause"), + FakeStatusCode.of(StatusCode.Code.INVALID_ARGUMENT), + false); + + tracer.recordActionableError(error); + + Map attributesMap = getAttributesMap(); + assertEquals( + "INVALID_ARGUMENT", + attributesMap.get(ObservabilityAttributes.RPC_RESPONSE_STATUS_ATTRIBUTE)); + assertEquals(400L, attributesMap.get(ObservabilityAttributes.HTTP_RESPONSE_STATUS_ATTRIBUTE)); + } + private Map getAttributesMap() { if (!testLogger.getMDCMap().isEmpty()) { return testLogger.getMDCMap();