Skip to content

Commit 9313dc1

Browse files
committed
feat: Add status.message and exception.type attributes to OpenTelemetry spans
Added recursive logic to trace the final underlying exception message and updated relevant telemetry tests to test iteration logic as requested by client.
1 parent 0f33445 commit 9313dc1

4 files changed

Lines changed: 79 additions & 0 deletions

File tree

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,4 +79,10 @@ public class ObservabilityAttributes {
7979
* Code, Client-Side Network/Operational Error (e.g., CLIENT_TIMEOUT) or internal fallback.
8080
*/
8181
public static final String ERROR_TYPE_ATTRIBUTE = "error.type";
82+
83+
/** A human-readable error message, which may include details from the exception or response. */
84+
public static final String STATUS_MESSAGE_ATTRIBUTE = "status.message";
85+
86+
/** If the error was caused by an exception, the exception class name. */
87+
public static final String EXCEPTION_TYPE_ATTRIBUTE = "exception.type";
8288
}

gax-java/gax/src/main/java/com/google/api/gax/tracing/SpanTracer.java

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,10 +109,33 @@ private void recordErrorAndEndAttempt(Throwable error) {
109109
if (attemptHandle != null) {
110110
attemptHandle.addAttribute(
111111
ObservabilityAttributes.ERROR_TYPE_ATTRIBUTE, ObservabilityUtils.extractErrorType(error));
112+
113+
if (error != null) {
114+
attemptHandle.addAttribute(
115+
ObservabilityAttributes.EXCEPTION_TYPE_ATTRIBUTE, error.getClass().getName());
116+
117+
String errorMessage = extractErrorMessage(error);
118+
if (errorMessage != null) {
119+
attemptHandle.addAttribute(
120+
ObservabilityAttributes.STATUS_MESSAGE_ATTRIBUTE, errorMessage);
121+
}
122+
}
123+
112124
endAttempt();
113125
}
114126
}
115127

128+
private String extractErrorMessage(Throwable error) {
129+
Throwable cause = error;
130+
while (cause != null) {
131+
if (cause.getMessage() != null && !cause.getMessage().isEmpty()) {
132+
return cause.getMessage();
133+
}
134+
cause = cause.getCause();
135+
}
136+
return null;
137+
}
138+
116139
private void endAttempt() {
117140
if (attemptHandle != null) {
118141
attemptHandle.end();

gax-java/gax/src/test/java/com/google/api/gax/tracing/SpanTracerTest.java

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -297,6 +297,39 @@ void testAttemptFailed_internalFallback_nullError() {
297297
verify(attemptHandle).end();
298298
}
299299

300+
@Test
301+
void testAttemptFailed_populatesExceptionTypeAndMessage() {
302+
when(recorder.createSpan(eq(ATTEMPT_SPAN_NAME), anyMap())).thenReturn(attemptHandle);
303+
tracer.attemptStarted(new Object(), 1);
304+
305+
tracer.attemptFailedRetriesExhausted(new IllegalStateException("custom error message"));
306+
307+
verify(attemptHandle)
308+
.addAttribute(
309+
ObservabilityAttributes.EXCEPTION_TYPE_ATTRIBUTE, "java.lang.IllegalStateException");
310+
verify(attemptHandle)
311+
.addAttribute(ObservabilityAttributes.STATUS_MESSAGE_ATTRIBUTE, "custom error message");
312+
verify(attemptHandle).end();
313+
}
314+
315+
@Test
316+
void testAttemptFailed_recursiveMessageSearch() {
317+
when(recorder.createSpan(eq(ATTEMPT_SPAN_NAME), anyMap())).thenReturn(attemptHandle);
318+
tracer.attemptStarted(new Object(), 1);
319+
320+
Throwable cause = new IllegalArgumentException("root cause message");
321+
Throwable wrapper = new IllegalStateException("", cause);
322+
323+
tracer.attemptFailedRetriesExhausted(wrapper);
324+
325+
verify(attemptHandle)
326+
.addAttribute(
327+
ObservabilityAttributes.EXCEPTION_TYPE_ATTRIBUTE, "java.lang.IllegalStateException");
328+
verify(attemptHandle)
329+
.addAttribute(ObservabilityAttributes.STATUS_MESSAGE_ATTRIBUTE, "root cause message");
330+
verify(attemptHandle).end();
331+
}
332+
300333
private static class RedirectException extends RuntimeException {
301334
public RedirectException(String message) {
302335
super(message);

java-showcase/gapic-showcase/src/test/java/com/google/showcase/v1beta1/it/ITOtelErrorType.java

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -395,6 +395,23 @@ void testTracing_clientRequestError_IllegalArgumentException_grpc() throws Excep
395395
try (EchoClient client = createInterceptorClient(new IllegalArgumentException("Mock request error"))) {
396396
assertThrows(IllegalArgumentException.class, () -> client.echo(EchoRequest.newBuilder().setContent("test").build()));
397397
verifyErrorTypeAttribute("CLIENT_REQUEST_ERROR");
398+
399+
SpanData errorSpan =
400+
spanExporter.getFinishedSpanItems().stream()
401+
.filter(span -> span.getAttributes().get(AttributeKey.stringKey(ObservabilityAttributes.ERROR_TYPE_ATTRIBUTE)) != null)
402+
.findFirst()
403+
.orElseThrow(() -> new AssertionError("Span with error.type not found"));
404+
405+
assertThat(
406+
errorSpan
407+
.getAttributes()
408+
.get(AttributeKey.stringKey(ObservabilityAttributes.EXCEPTION_TYPE_ATTRIBUTE)))
409+
.isEqualTo("java.lang.IllegalArgumentException");
410+
assertThat(
411+
errorSpan
412+
.getAttributes()
413+
.get(AttributeKey.stringKey(ObservabilityAttributes.STATUS_MESSAGE_ATTRIBUTE)))
414+
.isEqualTo("Mock request error");
398415
}
399416
}
400417

0 commit comments

Comments
 (0)