5
5
6
6
package io .opentelemetry .javaagent .instrumentation .reactornetty .v1_0 ;
7
7
8
- import static io .opentelemetry .javaagent .instrumentation .reactornetty .v1_0 .ReactorContextKeys .CLIENT_CONTEXT_KEY ;
9
- import static io .opentelemetry .javaagent .instrumentation .reactornetty .v1_0 .ReactorContextKeys .CLIENT_PARENT_CONTEXT_KEY ;
10
- import static io .opentelemetry .javaagent .instrumentation .reactornetty .v1_0 .ReactorNettySingletons .instrumenter ;
8
+ import static io .opentelemetry .javaagent .instrumentation .reactornetty .v1_0 .ReactorContextKeys .CONTEXTS_HOLDER_KEY ;
11
9
12
- import io .opentelemetry .api .GlobalOpenTelemetry ;
13
10
import io .opentelemetry .context .Context ;
11
+ import io .opentelemetry .instrumentation .api .instrumenter .http .HttpClientResend ;
14
12
import io .opentelemetry .instrumentation .netty .v4_1 .NettyClientTelemetry ;
15
- import io .opentelemetry .instrumentation .reactor .v3_1 .ContextPropagationOperator ;
16
- import java .util .concurrent .atomic .AtomicReferenceFieldUpdater ;
17
13
import java .util .function .BiConsumer ;
18
14
import java .util .function .Function ;
19
15
import javax .annotation .Nullable ;
20
16
import reactor .core .publisher .Mono ;
21
17
import reactor .netty .Connection ;
22
18
import reactor .netty .http .client .HttpClient ;
23
- import reactor .netty .http .client .HttpClientConfig ;
24
19
import reactor .netty .http .client .HttpClientRequest ;
25
20
import reactor .netty .http .client .HttpClientResponse ;
26
21
@@ -29,25 +24,26 @@ public final class HttpResponseReceiverInstrumenter {
29
24
// this method adds several stateful listeners that execute the instrumenter lifecycle during HTTP
30
25
// request processing
31
26
// it should be used just before one of the response*() methods is called - after this point the
32
- // HTTP
33
- // request is no longer modifiable by the user
27
+ // HTTP request is no longer modifiable by the user
34
28
@ Nullable
35
29
public static HttpClient .ResponseReceiver <?> instrument (HttpClient .ResponseReceiver <?> receiver ) {
36
30
// receiver should always be an HttpClientFinalizer, which both extends HttpClient and
37
31
// implements ResponseReceiver
38
32
if (receiver instanceof HttpClient ) {
39
33
HttpClient client = (HttpClient ) receiver ;
40
- HttpClientConfig config = client .configuration ();
41
34
42
- ContextHolder contextHolder = new ContextHolder ();
35
+ InstrumentationContexts instrumentationContexts = new InstrumentationContexts ();
43
36
44
37
HttpClient modified =
45
38
client
46
- .mapConnect (new StartOperation (contextHolder , config ))
47
- .doOnRequest (new PropagateContext (contextHolder ))
48
- .doOnRequestError (new EndOperationWithRequestError (contextHolder , config ))
49
- .doOnResponseError (new EndOperationWithResponseError (contextHolder , config ))
50
- .doAfterResponseSuccess (new EndOperationWithSuccess (contextHolder , config ));
39
+ .mapConnect (new CaptureParentContext (instrumentationContexts ))
40
+ .doOnRequestError (new EndOperationWithRequestError (instrumentationContexts ))
41
+ .doOnRequest (new StartOperation (instrumentationContexts ))
42
+ .doOnResponseError (new EndOperationWithResponseError (instrumentationContexts ))
43
+ .doAfterResponseSuccess (new EndOperationWithSuccess (instrumentationContexts ))
44
+ // end the current span on redirects; StartOperation will start another one for the
45
+ // next resend
46
+ .doOnRedirect (new EndOperationWithSuccess (instrumentationContexts ));
51
47
52
48
// modified should always be an HttpClientFinalizer too
53
49
if (modified instanceof HttpClient .ResponseReceiver ) {
@@ -58,151 +54,106 @@ public static HttpClient.ResponseReceiver<?> instrument(HttpClient.ResponseRecei
58
54
return null ;
59
55
}
60
56
61
- static final class ContextHolder {
62
-
63
- private static final AtomicReferenceFieldUpdater <ContextHolder , Context > contextUpdater =
64
- AtomicReferenceFieldUpdater .newUpdater (ContextHolder .class , Context .class , "context" );
65
-
66
- volatile Context parentContext ;
67
- volatile Context context ;
68
-
69
- void setContext (Context context ) {
70
- contextUpdater .set (this , context );
71
- }
72
-
73
- Context getAndRemoveContext () {
74
- return contextUpdater .getAndSet (this , null );
75
- }
76
- }
77
-
78
- static final class StartOperation
57
+ private static final class CaptureParentContext
79
58
implements Function <Mono <? extends Connection >, Mono <? extends Connection >> {
80
59
81
- private final ContextHolder contextHolder ;
82
- private final HttpClientConfig config ;
60
+ private final InstrumentationContexts instrumentationContexts ;
83
61
84
- StartOperation (ContextHolder contextHolder , HttpClientConfig config ) {
85
- this .contextHolder = contextHolder ;
86
- this .config = config ;
62
+ CaptureParentContext (InstrumentationContexts instrumentationContexts ) {
63
+ this .instrumentationContexts = instrumentationContexts ;
87
64
}
88
65
89
66
@ Override
90
67
public Mono <? extends Connection > apply (Mono <? extends Connection > mono ) {
91
68
return Mono .defer (
92
69
() -> {
93
70
Context parentContext = Context .current ();
94
- contextHolder .parentContext = parentContext ;
95
- if (!instrumenter ().shouldStart (parentContext , config )) {
96
- // make context accessible via the reactor ContextView - the doOn* callbacks
97
- // instrumentation uses this to set the proper context for callbacks
98
- return mono .contextWrite (
99
- ctx -> ctx .put (CLIENT_PARENT_CONTEXT_KEY , parentContext ));
100
- }
101
-
102
- Context context = instrumenter ().start (parentContext , config );
103
- contextHolder .setContext (context );
104
- return ContextPropagationOperator .runWithContext (mono , context )
105
- // make contexts accessible via the reactor ContextView - the doOn* callbacks
106
- // instrumentation uses the parent context to set the proper context for
107
- // callbacks
108
- .contextWrite (ctx -> ctx .put (CLIENT_PARENT_CONTEXT_KEY , parentContext ))
109
- .contextWrite (ctx -> ctx .put (CLIENT_CONTEXT_KEY , context ));
71
+ instrumentationContexts .initialize (parentContext );
72
+ // make contexts accessible via the reactor ContextView - the doOn* callbacks
73
+ // instrumentation uses this to set the proper context for callbacks
74
+ return mono .contextWrite (
75
+ ctx -> ctx .put (CONTEXTS_HOLDER_KEY , instrumentationContexts ));
110
76
})
111
- .doOnCancel (
112
- () -> {
113
- Context context = contextHolder .getAndRemoveContext ();
114
- if (context == null ) {
115
- return ;
116
- }
117
- instrumenter ().end (context , config , null , null );
118
- });
77
+ // if there's still any span in flight, end it
78
+ .doOnCancel (() -> instrumentationContexts .endClientSpan (null , null ));
119
79
}
120
80
}
121
81
122
- static final class PropagateContext implements BiConsumer <HttpClientRequest , Connection > {
82
+ private static final class StartOperation implements BiConsumer <HttpClientRequest , Connection > {
123
83
124
- private final ContextHolder contextHolder ;
84
+ private final InstrumentationContexts instrumentationContexts ;
125
85
126
- PropagateContext ( ContextHolder contextHolder ) {
127
- this .contextHolder = contextHolder ;
86
+ StartOperation ( InstrumentationContexts instrumentationContexts ) {
87
+ this .instrumentationContexts = instrumentationContexts ;
128
88
}
129
89
130
90
@ Override
131
- public void accept (HttpClientRequest httpClientRequest , Connection connection ) {
132
- Context context = contextHolder .context ;
133
- if (context != null ) {
134
- GlobalOpenTelemetry .getPropagators ()
135
- .getTextMapPropagator ()
136
- .inject (context , httpClientRequest , HttpClientRequestHeadersSetter .INSTANCE );
137
- }
91
+ public void accept (HttpClientRequest request , Connection connection ) {
92
+ Context context = instrumentationContexts .startClientSpan (request );
138
93
139
94
// also propagate the context to the underlying netty instrumentation
140
95
// if this span was suppressed and context is null, propagate parentContext - this will allow
141
96
// netty spans to be suppressed too
142
- Context nettyParentContext = context == null ? contextHolder .parentContext : context ;
97
+ Context nettyParentContext =
98
+ context == null ? instrumentationContexts .getParentContext () : context ;
143
99
NettyClientTelemetry .setChannelContext (connection .channel (), nettyParentContext );
144
100
}
145
101
}
146
102
147
- static final class EndOperationWithRequestError
103
+ private static final class EndOperationWithRequestError
148
104
implements BiConsumer <HttpClientRequest , Throwable > {
149
105
150
- private final ContextHolder contextHolder ;
151
- private final HttpClientConfig config ;
106
+ private final InstrumentationContexts instrumentationContexts ;
152
107
153
- EndOperationWithRequestError (ContextHolder contextHolder , HttpClientConfig config ) {
154
- this .contextHolder = contextHolder ;
155
- this .config = config ;
108
+ EndOperationWithRequestError (InstrumentationContexts instrumentationContexts ) {
109
+ this .instrumentationContexts = instrumentationContexts ;
156
110
}
157
111
158
112
@ Override
159
- public void accept (HttpClientRequest httpClientRequest , Throwable error ) {
160
- Context context = contextHolder .getAndRemoveContext ();
161
- if (context == null ) {
162
- return ;
113
+ public void accept (HttpClientRequest request , Throwable error ) {
114
+ instrumentationContexts .endClientSpan (null , error );
115
+
116
+ if (HttpClientResend .get (instrumentationContexts .getParentContext ()) == 0 ) {
117
+ // TODO: emit connection error span
118
+
119
+ // FIXME: this branch requires lots of changes around the NettyConnectionInstrumenter
120
+ // currently it also creates that connection error span (when the connection telemetry is
121
+ // turned off), but without HTTP semantics - it does not have access to any HTTP information
122
+ // after all
123
+ // it should be possible to completely disable it, and just start and end the span here
124
+ // this requires lots of refactoring and pretty uninteresting changes in the netty code, so
125
+ // I'll do that in a separate PR - for better readability
163
126
}
164
- instrumenter ().end (context , config , null , error );
165
127
}
166
128
}
167
129
168
- static final class EndOperationWithResponseError
130
+ private static final class EndOperationWithResponseError
169
131
implements BiConsumer <HttpClientResponse , Throwable > {
170
132
171
- private final ContextHolder contextHolder ;
172
- private final HttpClientConfig config ;
133
+ private final InstrumentationContexts instrumentationContexts ;
173
134
174
- EndOperationWithResponseError (ContextHolder contextHolder , HttpClientConfig config ) {
175
- this .contextHolder = contextHolder ;
176
- this .config = config ;
135
+ EndOperationWithResponseError (InstrumentationContexts instrumentationContexts ) {
136
+ this .instrumentationContexts = instrumentationContexts ;
177
137
}
178
138
179
139
@ Override
180
140
public void accept (HttpClientResponse response , Throwable error ) {
181
- Context context = contextHolder .getAndRemoveContext ();
182
- if (context == null ) {
183
- return ;
184
- }
185
- instrumenter ().end (context , config , response , error );
141
+ instrumentationContexts .endClientSpan (response , error );
186
142
}
187
143
}
188
144
189
- static final class EndOperationWithSuccess implements BiConsumer <HttpClientResponse , Connection > {
145
+ private static final class EndOperationWithSuccess
146
+ implements BiConsumer <HttpClientResponse , Connection > {
190
147
191
- private final ContextHolder contextHolder ;
192
- private final HttpClientConfig config ;
148
+ private final InstrumentationContexts instrumentationContexts ;
193
149
194
- EndOperationWithSuccess (ContextHolder contextHolder , HttpClientConfig config ) {
195
- this .contextHolder = contextHolder ;
196
- this .config = config ;
150
+ EndOperationWithSuccess (InstrumentationContexts instrumentationContexts ) {
151
+ this .instrumentationContexts = instrumentationContexts ;
197
152
}
198
153
199
154
@ Override
200
155
public void accept (HttpClientResponse response , Connection connection ) {
201
- Context context = contextHolder .getAndRemoveContext ();
202
- if (context == null ) {
203
- return ;
204
- }
205
- instrumenter ().end (context , config , response , null );
156
+ instrumentationContexts .endClientSpan (response , null );
206
157
}
207
158
}
208
159
0 commit comments