2525import co .elastic .apm .agent .impl .transaction .Outcome ;
2626import co .elastic .apm .agent .impl .transaction .Span ;
2727import co .elastic .apm .agent .impl .transaction .TraceContext ;
28+ import co .elastic .apm .agent .sdk .state .CallDepth ;
29+ import co .elastic .apm .agent .sdk .state .GlobalState ;
2830import co .elastic .apm .agent .sdk .weakconcurrent .WeakMap ;
2931import net .bytebuddy .asm .Advice ;
3032import net .bytebuddy .description .NamedElement ;
4547import static net .bytebuddy .matcher .ElementMatchers .not ;
4648import static net .bytebuddy .matcher .ElementMatchers .takesArguments ;
4749
50+ @ GlobalState
4851public abstract class HttpUrlConnectionInstrumentation extends TracerAwareInstrumentation {
4952
50- private static final WeakMap <HttpURLConnection , Span > inFlightSpans = WeakConcurrentProviderImpl .createWeakSpanMap ();
53+ public static final WeakMap <HttpURLConnection , Span > inFlightSpans = WeakConcurrentProviderImpl .createWeakSpanMap ();
54+ public static final CallDepth callDepth = CallDepth .get (HttpUrlConnectionInstrumentation .class );
5155
5256 @ Override
5357 public Collection <String > getInstrumentationGroupNames () {
@@ -73,6 +77,7 @@ public static Object enter(@Advice.This HttpURLConnection thiz,
7377 @ Advice .FieldValue ("connected" ) boolean connected ,
7478 @ Advice .Origin String signature ) {
7579
80+ boolean isNestedCall = callDepth .isNestedCallAndIncrement ();
7681 AbstractSpan <?> parent = tracer .getActive ();
7782 if (parent == null ) {
7883 return null ;
@@ -89,10 +94,11 @@ public static Object enter(@Advice.This HttpURLConnection thiz,
8994 }
9095 }
9196 }
92- if (span != null ) {
97+ if (! isNestedCall && span != null ) {
9398 span .activate ();
99+ return span ;
94100 }
95- return span ;
101+ return null ;
96102 }
97103
98104 @ Advice .OnMethodExit (suppress = Throwable .class , onThrowable = Throwable .class , inline = false )
@@ -102,29 +108,39 @@ public static void exit(@Advice.This HttpURLConnection thiz,
102108 @ Advice .Enter @ Nullable Object spanObject ,
103109 @ Advice .Origin String signature ) {
104110
111+ callDepth .decrement ();
105112 Span span = (Span ) spanObject ;
106113 if (span == null ) {
107114 return ;
108115 }
109- span .deactivate ();
110- if (responseCode != -1 ) {
111- inFlightSpans .remove (thiz );
112- // if the response code is set, the connection has been established via getOutputStream
113- // if the response code is unset even after getOutputStream has been called, there will be an exception
114- span .getContext ().getHttp ().withStatusCode (responseCode );
115- span .captureException (t ).end ();
116- } else if (t != null ) {
117- inFlightSpans .remove (thiz );
118-
119- // an exception here is synonym of failure, for example with circular redirects
120- span .captureException (t )
121- .withOutcome (Outcome .FAILURE )
122- .end ();
123- } else {
124- // if connect or getOutputStream has been called we can't end the span right away
125- // we have to store associate it with thiz HttpURLConnection instance and end once getInputStream has been called
126- // note that this could happen on another thread
127- inFlightSpans .put (thiz , span );
116+ try {
117+ if (responseCode != -1 ) {
118+ inFlightSpans .remove (thiz );
119+ // if the response code is set, the connection has been established via getOutputStream
120+ // if the response code is unset even after getOutputStream has been called, there will be an exception
121+ // checking if "finished" to avoid multiple endings on nested calls
122+ if (!span .isFinished ()) {
123+ span .getContext ().getHttp ().withStatusCode (responseCode );
124+ span .captureException (t ).end ();
125+ }
126+ } else if (t != null ) {
127+ inFlightSpans .remove (thiz );
128+
129+ // an exception here is synonym of failure, for example with circular redirects
130+ // checking if "finished" to avoid multiple endings on nested calls
131+ if (!span .isFinished ()) {
132+ span .captureException (t )
133+ .withOutcome (Outcome .FAILURE )
134+ .end ();
135+ }
136+ } else {
137+ // if connect or getOutputStream has been called we can't end the span right away
138+ // we have to store associate it with thiz HttpURLConnection instance and end once getInputStream has been called
139+ // note that this could happen on another thread
140+ inFlightSpans .put (thiz , span );
141+ }
142+ } finally {
143+ span .deactivate ();
128144 }
129145 }
130146 }
0 commit comments