@@ -257,4 +257,99 @@ void testAttemptStarted_afterOperationCompleted_doesNotEmitNewSpan() {
257257 assertThat (finishedSpans ).hasSize (1 );
258258 assertThat (finishedSpans .get (0 ).getKind ()).isEqualTo (SpanKind .INTERNAL );
259259 }
260+
261+ @ Test
262+ void testRetrySucceeds_operationAggregatesSuccessAttributes () {
263+ ApiTracer apiTracer = tracingFactory .newTracer (BaseApiTracer .getInstance (), TRACER_CONTEXT );
264+
265+ // Attempt 0 fails with transient error
266+ apiTracer .attemptStarted (new Object (), 0 );
267+ apiTracer .attemptFailedDuration (
268+ new RuntimeException ("transient 503" ), java .time .Duration .ofMillis (10 ));
269+
270+ // Attempt 1 succeeds
271+ apiTracer .attemptStarted (new Object (), 1 );
272+ apiTracer .attemptSucceeded ();
273+ apiTracer .operationSucceeded ();
274+
275+ List <SpanData > finishedSpans = spanExporter .getFinishedSpanItems ();
276+ assertThat (finishedSpans ).hasSize (3 ); // attempt 0, attempt 1, operation
277+
278+ SpanData operationSpan =
279+ finishedSpans .stream ()
280+ .filter (s -> s .getKind () == SpanKind .INTERNAL )
281+ .findFirst ()
282+ .orElseThrow (() -> new AssertionError ("Operation span not found" ));
283+
284+ List <SpanData > attemptSpans =
285+ finishedSpans .stream ()
286+ .filter (s -> s .getKind () == SpanKind .CLIENT )
287+ .collect (java .util .stream .Collectors .toList ());
288+ assertThat (attemptSpans ).hasSize (2 );
289+
290+ for (SpanData attempt : attemptSpans ) {
291+ assertThat (attempt .getParentSpanId ()).isEqualTo (operationSpan .getSpanContext ().getSpanId ());
292+ }
293+
294+ assertThat (operationSpan .getStatus ().getStatusCode ())
295+ .isEqualTo (io .opentelemetry .api .trace .StatusCode .UNSET );
296+ assertThat (
297+ operationSpan
298+ .getAttributes ()
299+ .get (AttributeKey .stringKey (ObservabilityAttributes .RPC_RESPONSE_STATUS_ATTRIBUTE )))
300+ .isEqualTo ("OK" );
301+ assertThat (
302+ operationSpan
303+ .getAttributes ()
304+ .get (AttributeKey .stringKey (ObservabilityAttributes .ERROR_TYPE_ATTRIBUTE )))
305+ .isNull ();
306+ }
307+
308+ @ Test
309+ void testRetriesExhausted_operationAggregatesFailureAttributes () {
310+ ApiTracer apiTracer = tracingFactory .newTracer (BaseApiTracer .getInstance (), TRACER_CONTEXT );
311+
312+ // Attempt 0 fails with transient error
313+ apiTracer .attemptStarted (new Object (), 0 );
314+ apiTracer .attemptFailedDuration (
315+ new RuntimeException ("transient 503" ), java .time .Duration .ofMillis (10 ));
316+
317+ // Attempt 1 fails and exhausts retries
318+ apiTracer .attemptStarted (new Object (), 1 );
319+ RuntimeException finalError = new RuntimeException ("unavailable: retries exhausted" );
320+ apiTracer .attemptFailedRetriesExhausted (finalError );
321+ apiTracer .operationFailed (finalError );
322+
323+ List <SpanData > finishedSpans = spanExporter .getFinishedSpanItems ();
324+ assertThat (finishedSpans ).hasSize (3 ); // attempt 0, attempt 1, operation
325+
326+ SpanData operationSpan =
327+ finishedSpans .stream ()
328+ .filter (s -> s .getKind () == SpanKind .INTERNAL )
329+ .findFirst ()
330+ .orElseThrow (() -> new AssertionError ("Operation span not found" ));
331+
332+ List <SpanData > attemptSpans =
333+ finishedSpans .stream ()
334+ .filter (s -> s .getKind () == SpanKind .CLIENT )
335+ .collect (java .util .stream .Collectors .toList ());
336+ assertThat (attemptSpans ).hasSize (2 );
337+
338+ for (SpanData attempt : attemptSpans ) {
339+ assertThat (attempt .getParentSpanId ()).isEqualTo (operationSpan .getSpanContext ().getSpanId ());
340+ }
341+
342+ assertThat (operationSpan .getStatus ().getStatusCode ())
343+ .isEqualTo (io .opentelemetry .api .trace .StatusCode .ERROR );
344+ assertThat (
345+ operationSpan
346+ .getAttributes ()
347+ .get (AttributeKey .stringKey (ObservabilityAttributes .STATUS_MESSAGE_ATTRIBUTE )))
348+ .isEqualTo ("unavailable: retries exhausted" );
349+ assertThat (
350+ operationSpan
351+ .getAttributes ()
352+ .get (AttributeKey .stringKey (ObservabilityAttributes .ERROR_TYPE_ATTRIBUTE )))
353+ .isNotNull ();
354+ }
260355}
0 commit comments