@@ -53,6 +53,9 @@ class OpenTelemetryTracingTracer implements ApiTracer {
5353 private final Map <String , Object > attemptAttributes ;
5454 private final String attemptSpanName ;
5555 private final ApiTracerContext apiTracerContext ;
56+ private final io .opentelemetry .context .Context parentContext ;
57+ private final java .util .concurrent .locks .ReentrantLock lock =
58+ new java .util .concurrent .locks .ReentrantLock ();
5659 private volatile @ Nullable Span attemptSpan ;
5760 private final AtomicReference <io .opentelemetry .context .@ Nullable Scope > scope =
5861 new AtomicReference <>();
@@ -85,6 +88,7 @@ public void injectTraceContext(java.util.Map<String, String> carrier) {
8588 this .apiTracerContext = apiTracerContext ;
8689 this .attemptSpanName = resolveAttemptSpanName (apiTracerContext );
8790 this .attemptAttributes = new HashMap <>();
91+ this .parentContext = io .opentelemetry .context .Context .current ();
8892 buildAttributes ();
8993 }
9094
@@ -103,6 +107,7 @@ public void injectTraceContext(java.util.Map<String, String> carrier) {
103107 this .attemptSpanName = attemptSpanName ;
104108 this .apiTracerContext = apiTracerContext ;
105109 this .attemptAttributes = new HashMap <>();
110+ this .parentContext = io .opentelemetry .context .Context .current ();
106111 buildAttributes ();
107112 }
108113
@@ -128,30 +133,63 @@ private void buildAttributes() {
128133 @ Override
129134 @ SuppressWarnings ("MustBeClosedChecker" )
130135 public void attemptStarted (Object request , int attemptNumber ) {
131- Map <String , Object > currentAttemptAttributes = new HashMap <>(this .attemptAttributes );
132-
133- if (attemptNumber > 0 ) {
134- ApiTracerContext .Transport transport = apiTracerContext .transport ();
135- if (transport == ApiTracerContext .Transport .GRPC ) {
136- currentAttemptAttributes .put (
137- ObservabilityAttributes .GRPC_RESEND_COUNT_ATTRIBUTE , (long ) attemptNumber );
138- } else if (transport == ApiTracerContext .Transport .HTTP ) {
139- currentAttemptAttributes .put (
140- ObservabilityAttributes .HTTP_RESEND_COUNT_ATTRIBUTE , (long ) attemptNumber );
136+ Span oldSpan = null ;
137+ io .opentelemetry .context .Scope oldScope = null ;
138+ lock .lock ();
139+ try {
140+ if (attemptSpan != null ) {
141+ oldSpan = attemptSpan ;
142+ attemptSpan = null ;
143+ oldScope = this .scope .getAndSet (null );
144+ }
145+ Map <String , Object > currentAttemptAttributes = new HashMap <>(this .attemptAttributes );
146+
147+ if (attemptNumber > 0 ) {
148+ ApiTracerContext .Transport transport = apiTracerContext .transport ();
149+ if (transport == ApiTracerContext .Transport .GRPC ) {
150+ currentAttemptAttributes .put (
151+ ObservabilityAttributes .GRPC_RESEND_COUNT_ATTRIBUTE , (long ) attemptNumber );
152+ } else if (transport == ApiTracerContext .Transport .HTTP ) {
153+ currentAttemptAttributes .put (
154+ ObservabilityAttributes .HTTP_RESEND_COUNT_ATTRIBUTE , (long ) attemptNumber );
155+ }
141156 }
142- }
143157
144- SpanBuilder spanBuilder = tracer .spanBuilder (attemptSpanName );
158+ SpanBuilder spanBuilder = tracer .spanBuilder (attemptSpanName );
145159
146- // Attempt spans are of the CLIENT kind
147- spanBuilder .setSpanKind (SpanKind .CLIENT );
160+ // Attempt spans are of the CLIENT kind
161+ spanBuilder .setSpanKind (SpanKind .CLIENT );
162+
163+ // Link attempt span to parent context
164+ spanBuilder .setParent (parentContext );
165+
166+ // Pass the combined attributes to the new SpanBuilder method
167+ spanBuilder .setAllAttributes (ObservabilityUtils .toOtelAttributes (currentAttemptAttributes ));
168+
169+ this .attemptSpan = spanBuilder .startSpan ();
170+ // Make the span active on the current thread so logs can capture the trace ID.
171+ this .scope .set (attemptSpan .makeCurrent ());
172+ } finally {
173+ lock .unlock ();
174+ }
175+ if (oldSpan != null ) {
176+ endAttemptSpan (oldSpan , oldScope , null );
177+ }
178+ }
148179
149- // Pass the combined attributes to the new SpanBuilder method
150- spanBuilder .setAllAttributes (ObservabilityUtils .toOtelAttributes (currentAttemptAttributes ));
180+ @ Override
181+ public void operationSucceeded () {
182+ recordErrorAndEndAttempt (null );
183+ }
151184
152- this .attemptSpan = spanBuilder .startSpan ();
153- // Make the span active on the current thread so logs can capture the trace ID.
154- this .scope .set (attemptSpan .makeCurrent ());
185+ @ Override
186+ public void operationCancelled () {
187+ recordErrorAndEndAttempt (new CancellationException ());
188+ }
189+
190+ @ Override
191+ public void operationFailed (Throwable error ) {
192+ recordErrorAndEndAttempt (error );
155193 }
156194
157195 @ Override
@@ -161,12 +199,13 @@ public void attemptSucceeded() {
161199
162200 @ Override
163201 public void responseHeadersReceived (java .util .Map <String , Object > headers ) {
164- if (attemptSpan == null ) {
202+ Span currentSpan = attemptSpan ;
203+ if (currentSpan == null ) {
165204 return ;
166205 }
167206 long contentLength = extractContentLength (headers );
168207 if (contentLength >= 0 ) {
169- attemptSpan .setAttribute (ObservabilityAttributes .HTTP_RESPONSE_BODY_SIZE , contentLength );
208+ currentSpan .setAttribute (ObservabilityAttributes .HTTP_RESPONSE_BODY_SIZE , contentLength );
170209 }
171210 }
172211
@@ -222,49 +261,57 @@ public void attemptPermanentFailure(Throwable error) {
222261 }
223262
224263 private void recordErrorAndEndAttempt (@ Nullable Throwable error ) {
225- if (attemptSpan == null ) {
226- return ;
264+ Span localAttemptSpan ;
265+ io .opentelemetry .context .Scope localScope ;
266+ lock .lock ();
267+ try {
268+ localAttemptSpan = attemptSpan ;
269+ if (localAttemptSpan == null ) {
270+ return ;
271+ }
272+ attemptSpan = null ;
273+ localScope = this .scope .getAndSet (null );
274+ } finally {
275+ lock .unlock ();
227276 }
277+
278+ endAttemptSpan (localAttemptSpan , localScope , error );
279+ }
280+
281+ private void endAttemptSpan (
282+ Span localAttemptSpan ,
283+ io .opentelemetry .context .@ Nullable Scope localScope ,
284+ @ Nullable Throwable error ) {
228285 Map <String , Object > responseAttributes =
229286 ObservabilityUtils .getResponseAttributes (error , this .apiTracerContext .transport ());
230287 if (!responseAttributes .isEmpty ()) {
231- attemptSpan .setAllAttributes (ObservabilityUtils .toOtelAttributes (responseAttributes ));
288+ localAttemptSpan .setAllAttributes (ObservabilityUtils .toOtelAttributes (responseAttributes ));
232289 }
233290
234291 if (error != null && !Strings .isNullOrEmpty (error .getMessage ())) {
235- attemptSpan .setAttribute (
292+ localAttemptSpan .setAttribute (
236293 ObservabilityAttributes .STATUS_MESSAGE_ATTRIBUTE , error .getMessage ());
237294 }
238295
239- endAttempt ();
240- }
241-
242- private void endAttempt () {
243- Span currentSpan = this .attemptSpan ;
244- this .attemptSpan = null ;
245-
246- io .opentelemetry .context .Scope currentScope = this .scope .getAndSet (null );
247- // Remove the span from the current thread before closing the span.
248296 try {
249- if (currentScope != null ) {
250- currentScope .close ();
297+ if (localScope != null ) {
298+ localScope .close ();
251299 }
252300 } finally {
253- if (currentSpan != null ) {
254- currentSpan .end ();
255- }
301+ localAttemptSpan .end ();
256302 }
257303 }
258304
259305 @ Override
260306 public void requestUrlResolved (String url ) {
261- if (attemptSpan == null ) {
307+ Span currentSpan = attemptSpan ;
308+ if (currentSpan == null ) {
262309 return ;
263310 }
264311 String sanitizedUrlString = ObservabilityUtils .sanitizeUrlFull (url );
265312 if (sanitizedUrlString .isEmpty ()) {
266313 return ;
267314 }
268- attemptSpan .setAttribute (ObservabilityAttributes .HTTP_URL_FULL_ATTRIBUTE , sanitizedUrlString );
315+ currentSpan .setAttribute (ObservabilityAttributes .HTTP_URL_FULL_ATTRIBUTE , sanitizedUrlString );
269316 }
270317}
0 commit comments