@@ -52,6 +52,10 @@ class OpenTelemetryTracingTracer implements ApiTracer {
5252 private final Map <String , Object > attemptAttributes ;
5353 private final String attemptSpanName ;
5454 private final ApiTracerContext apiTracerContext ;
55+ // Captures the active trace context from the calling thread at RPC initiation.
56+ // This allows attempt spans—including retries dispatched on background threads—to
57+ // link back to the original parent trace.
58+ private final io .opentelemetry .context .Context parentContext ;
5559 private @ Nullable Span attemptSpan ;
5660
5761 @ Override
@@ -82,6 +86,7 @@ public void injectTraceContext(java.util.Map<String, String> carrier) {
8286 this .apiTracerContext = apiTracerContext ;
8387 this .attemptSpanName = resolveAttemptSpanName (apiTracerContext );
8488 this .attemptAttributes = new HashMap <>();
89+ this .parentContext = io .opentelemetry .context .Context .current ();
8590 buildAttributes ();
8691 }
8792
@@ -100,6 +105,7 @@ public void injectTraceContext(java.util.Map<String, String> carrier) {
100105 this .attemptSpanName = attemptSpanName ;
101106 this .apiTracerContext = apiTracerContext ;
102107 this .attemptAttributes = new HashMap <>();
108+ this .parentContext = io .opentelemetry .context .Context .current ();
103109 buildAttributes ();
104110 }
105111
@@ -142,12 +148,47 @@ public void attemptStarted(Object request, int attemptNumber) {
142148 // Attempt spans are of the CLIENT kind
143149 spanBuilder .setSpanKind (SpanKind .CLIENT );
144150
151+ // Link attempt span to parent context
152+ spanBuilder .setParent (parentContext );
153+
145154 // Pass the combined attributes to the new SpanBuilder method
146155 spanBuilder .setAllAttributes (ObservabilityUtils .toOtelAttributes (currentAttemptAttributes ));
147156
148157 this .attemptSpan = spanBuilder .startSpan ();
149158 }
150159
160+ /**
161+ * Signals that the overall logical operation succeeded.
162+ *
163+ * <p>Closes any remaining in-flight attempt span.
164+ */
165+ @ Override
166+ public void operationSucceeded () {
167+ recordErrorAndEndAttempt (null );
168+ }
169+
170+ /**
171+ * Signals that the overall logical operation was cancelled.
172+ *
173+ * <p>Closes any remaining in-flight attempt span with a {@link CancellationException}.
174+ */
175+ @ Override
176+ public void operationCancelled () {
177+ recordErrorAndEndAttempt (new CancellationException ());
178+ }
179+
180+ /**
181+ * Signals that the overall logical operation failed permanently.
182+ *
183+ * <p>Closes any remaining in-flight attempt span with the provided error details.
184+ *
185+ * @param error the cause of the operation failure
186+ */
187+ @ Override
188+ public void operationFailed (Throwable error ) {
189+ recordErrorAndEndAttempt (error );
190+ }
191+
151192 @ Override
152193 public void attemptSucceeded () {
153194 recordErrorAndEndAttempt (null );
@@ -215,10 +256,16 @@ public void attemptPermanentFailure(Throwable error) {
215256 recordErrorAndEndAttempt (error );
216257 }
217258
259+ /**
260+ * Records error details and ends the current attempt span.
261+ *
262+ * @param error the exception associated with the attempt failure, or {@code null} if successful
263+ */
218264 private void recordErrorAndEndAttempt (@ Nullable Throwable error ) {
219265 if (attemptSpan == null ) {
220266 return ;
221267 }
268+
222269 Map <String , Object > responseAttributes =
223270 ObservabilityUtils .getResponseAttributes (error , this .apiTracerContext .transport ());
224271 if (!responseAttributes .isEmpty ()) {
@@ -230,14 +277,6 @@ private void recordErrorAndEndAttempt(@Nullable Throwable error) {
230277 ObservabilityAttributes .STATUS_MESSAGE_ATTRIBUTE , error .getMessage ());
231278 }
232279
233- endAttempt ();
234- }
235-
236- private void endAttempt () {
237- if (attemptSpan == null ) {
238- return ;
239- }
240-
241280 attemptSpan .end ();
242281 attemptSpan = null ;
243282 }
0 commit comments