Skip to content

Commit a9198ee

Browse files
authored
fix: Add error attributes to logging (#12685)
This PR - Add `exception.message` attribute to logging - Add `exception.type` attribute to logging - Update `error.type` to use [ObservabilityUtils#extractErrorType](https://github.com/googleapis/google-cloud-java/blob/914f97f38c0a531e8a0e8f5e18a4be265ca5981b/sdk-platform-java/gax-java/gax/src/main/java/com/google/api/gax/tracing/ObservabilityUtils.java#L52)
1 parent 1c53451 commit a9198ee

File tree

6 files changed

+53
-36
lines changed

6 files changed

+53
-36
lines changed

sdk-platform-java/gax-java/gax/src/main/java/com/google/api/gax/tracing/ErrorTypeUtil.java

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
import java.nio.channels.UnresolvedAddressException;
4444
import java.security.GeneralSecurityException;
4545
import java.util.Set;
46+
import javax.annotation.Nonnull;
4647
import javax.annotation.Nullable;
4748
import javax.net.ssl.SSLHandshakeException;
4849

@@ -112,15 +113,10 @@ enum ErrorType {
112113
* </ol>
113114
*
114115
* @param error the Throwable from which to extract the error type string.
115-
* @return a low-cardinality string representing the specific error type, or {@code null} if the
116-
* provided error is {@code null} or non-determined.
116+
* @return a low-cardinality string representing the specific error type
117117
*/
118118
// Requirement source: go/clo:product-requirements-v1
119-
public static String extractErrorType(@Nullable Throwable error) {
120-
if (error == null) {
121-
// No information about the error; return null so the attribute is not recorded.
122-
return null;
123-
}
119+
public static String extractErrorType(@Nonnull Throwable error) {
124120

125121
// 1. Unwrap standard wrapper exceptions if present
126122
Throwable realError = getRealCause(error);

sdk-platform-java/gax-java/gax/src/main/java/com/google/api/gax/tracing/LoggingTracer.java

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
import com.google.api.gax.logging.LoggerProvider;
3636
import com.google.api.gax.logging.LoggingUtils;
3737
import com.google.common.annotations.VisibleForTesting;
38+
import com.google.common.base.Strings;
3839
import com.google.rpc.ErrorInfo;
3940
import java.util.HashMap;
4041
import java.util.Map;
@@ -77,17 +78,16 @@ void recordActionableError(Throwable error) {
7778
}
7879

7980
Map<String, Object> logContext = new HashMap<>(apiTracerContext.getAttemptAttributes());
81+
logContext.putAll(
82+
ObservabilityUtils.getResponseAttributes(error, apiTracerContext.transport()));
8083

81-
logContext.put(
82-
ObservabilityAttributes.RPC_RESPONSE_STATUS_ATTRIBUTE,
83-
ObservabilityUtils.extractStatus(error).toString());
84+
if (!Strings.isNullOrEmpty(error.getMessage())) {
85+
logContext.put(ObservabilityAttributes.EXCEPTION_MESSAGE_ATTRIBUTE, error.getMessage());
86+
}
8487

8588
ErrorInfo errorInfo = ObservabilityUtils.extractErrorInfo(error);
8689
if (errorInfo != null) {
87-
if (errorInfo.getReason() != null && !errorInfo.getReason().isEmpty()) {
88-
logContext.put(ObservabilityAttributes.ERROR_TYPE_ATTRIBUTE, errorInfo.getReason());
89-
}
90-
if (errorInfo.getDomain() != null && !errorInfo.getDomain().isEmpty()) {
90+
if (!Strings.isNullOrEmpty(errorInfo.getDomain())) {
9191
logContext.put(ObservabilityAttributes.ERROR_DOMAIN_ATTRIBUTE, errorInfo.getDomain());
9292
}
9393
if (errorInfo.getMetadataMap() != null) {

sdk-platform-java/gax-java/gax/src/main/java/com/google/api/gax/tracing/ObservabilityAttributes.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,9 @@ public class ObservabilityAttributes {
8585
/** If the error was caused by an exception, the exception class name. */
8686
public static final String EXCEPTION_TYPE_ATTRIBUTE = "exception.type";
8787

88+
/** If the error was caused by an exception, the exception message. */
89+
public static final String EXCEPTION_MESSAGE_ATTRIBUTE = "exception.message";
90+
8891
/** Size of the response body in bytes. */
8992
public static final String HTTP_RESPONSE_BODY_SIZE = "http.response.body.size";
9093

sdk-platform-java/gax-java/gax/src/main/java/com/google/api/gax/tracing/ObservabilityUtils.java

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -44,15 +44,6 @@
4444

4545
final class ObservabilityUtils {
4646

47-
/**
48-
* Extracts a low-cardinality string representing the specific classification of the error to be
49-
* used in the {@link ObservabilityAttributes#ERROR_TYPE_ATTRIBUTE} attribute. See {@link
50-
* ErrorTypeUtil#extractErrorType} for extended documentation.
51-
*/
52-
static String extractErrorType(@Nullable Throwable error) {
53-
return ErrorTypeUtil.extractErrorType(error);
54-
}
55-
5647
/** Function to extract the status of the error as a canonical code. */
5748
static StatusCode.Code extractStatus(@Nullable Throwable error) {
5849
if (error == null) {
@@ -182,7 +173,7 @@ static Map<String, Object> getResponseAttributes(
182173
}
183174
if (error != null) {
184175
attributes.put(
185-
ObservabilityAttributes.ERROR_TYPE_ATTRIBUTE, ObservabilityUtils.extractErrorType(error));
176+
ObservabilityAttributes.ERROR_TYPE_ATTRIBUTE, ErrorTypeUtil.extractErrorType(error));
186177
attributes.put(ObservabilityAttributes.EXCEPTION_TYPE_ATTRIBUTE, error.getClass().getName());
187178
}
188179
return attributes;

sdk-platform-java/gax-java/gax/src/test/java/com/google/api/gax/tracing/ErrorTypeUtilTest.java

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -51,11 +51,6 @@
5151

5252
class ErrorTypeUtilTest {
5353

54-
@Test
55-
void testExtractErrorType_null() {
56-
assertThat(ErrorTypeUtil.extractErrorType(null)).isNull();
57-
}
58-
5954
@Test
6055
void testExtractErrorType_apiException_noReason() {
6156
ApiException exception =

sdk-platform-java/gax-java/gax/src/test/java/com/google/api/gax/tracing/LoggingTracerTest.java

Lines changed: 39 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,6 @@
3131
package com.google.api.gax.tracing;
3232

3333
import static org.junit.jupiter.api.Assertions.assertEquals;
34-
import static org.junit.jupiter.api.Assertions.assertTrue;
3534

3635
import com.google.api.gax.logging.TestLogger;
3736
import com.google.api.gax.rpc.ApiExceptionFactory;
@@ -121,8 +120,6 @@ void testRecordActionableError_logsStatus() {
121120
tracer.recordActionableError(error);
122121

123122
Map<String, ?> attributesMap = getAttributesMap();
124-
125-
assertTrue(attributesMap != null && !attributesMap.isEmpty());
126123
assertEquals(
127124
"INVALID_ARGUMENT",
128125
attributesMap.get(ObservabilityAttributes.RPC_RESPONSE_STATUS_ATTRIBUTE));
@@ -138,8 +135,6 @@ void testRecordActionableError_logsAttributes() {
138135
tracer.recordActionableError(error);
139136

140137
Map<String, ?> attributesMap = getAttributesMap();
141-
142-
assertTrue(attributesMap != null && !attributesMap.isEmpty());
143138
assertEquals(
144139
"test-service", attributesMap.get(ObservabilityAttributes.GCP_CLIENT_SERVICE_ATTRIBUTE));
145140
}
@@ -172,8 +167,6 @@ void testRecordActionableError_logsErrorInfo() {
172167
tracer.recordActionableError(error);
173168

174169
Map<String, ?> attributesMap = getAttributesMap();
175-
176-
assertTrue(attributesMap != null && !attributesMap.isEmpty());
177170
assertEquals("TEST_REASON", attributesMap.get(ObservabilityAttributes.ERROR_TYPE_ATTRIBUTE));
178171
assertEquals(
179172
"test.domain.com", attributesMap.get(ObservabilityAttributes.ERROR_DOMAIN_ATTRIBUTE));
@@ -182,6 +175,45 @@ void testRecordActionableError_logsErrorInfo() {
182175
attributesMap.get(ObservabilityAttributes.ERROR_METADATA_ATTRIBUTE_PREFIX + "test_key"));
183176
}
184177

178+
@Test
179+
void testRecordActionableError_logsExceptionDetails() {
180+
ApiTracerContext context = ApiTracerContext.empty();
181+
LoggingTracer tracer = new LoggingTracer(context);
182+
183+
Exception error = new RuntimeException("test error message");
184+
tracer.recordActionableError(error);
185+
186+
Map<String, ?> attributesMap = getAttributesMap();
187+
assertEquals(
188+
"java.lang.RuntimeException",
189+
attributesMap.get(ObservabilityAttributes.EXCEPTION_TYPE_ATTRIBUTE));
190+
assertEquals(
191+
"test error message",
192+
attributesMap.get(ObservabilityAttributes.EXCEPTION_MESSAGE_ATTRIBUTE));
193+
}
194+
195+
@Test
196+
void testRecordActionableError_logsHttpStatus() {
197+
ApiTracerContext context =
198+
ApiTracerContext.empty().toBuilder().setTransport(ApiTracerContext.Transport.HTTP).build();
199+
LoggingTracer tracer = new LoggingTracer(context);
200+
201+
Exception error =
202+
ApiExceptionFactory.createException(
203+
"test error message",
204+
new RuntimeException("cause"),
205+
FakeStatusCode.of(StatusCode.Code.INVALID_ARGUMENT),
206+
false);
207+
208+
tracer.recordActionableError(error);
209+
210+
Map<String, ?> attributesMap = getAttributesMap();
211+
assertEquals(
212+
"INVALID_ARGUMENT",
213+
attributesMap.get(ObservabilityAttributes.RPC_RESPONSE_STATUS_ATTRIBUTE));
214+
assertEquals(400L, attributesMap.get(ObservabilityAttributes.HTTP_RESPONSE_STATUS_ATTRIBUTE));
215+
}
216+
185217
private Map<String, ?> getAttributesMap() {
186218
if (!testLogger.getMDCMap().isEmpty()) {
187219
return testLogger.getMDCMap();

0 commit comments

Comments
 (0)