17
17
package org .springframework .graphql .observation ;
18
18
19
19
import graphql .ExecutionResult ;
20
- import graphql .execution . ExecutionStepInfo ;
21
- import graphql .execution .ResultPath ;
20
+ import graphql .GraphQLContext ;
21
+ import graphql .execution .DataFetcherResult ;
22
22
import graphql .execution .instrumentation .InstrumentationContext ;
23
23
import graphql .execution .instrumentation .InstrumentationState ;
24
24
import graphql .execution .instrumentation .SimpleInstrumentation ;
27
27
import graphql .execution .instrumentation .parameters .InstrumentationExecutionParameters ;
28
28
import graphql .execution .instrumentation .parameters .InstrumentationFieldFetchParameters ;
29
29
import graphql .schema .DataFetcher ;
30
+ import graphql .schema .DataFetchingEnvironment ;
30
31
import io .micrometer .observation .Observation ;
31
32
import io .micrometer .observation .ObservationRegistry ;
32
33
import io .micrometer .observation .contextpropagation .ObservationThreadLocalAccessor ;
34
+ import org .apache .commons .logging .Log ;
35
+ import org .apache .commons .logging .LogFactory ;
36
+ import org .springframework .lang .Nullable ;
33
37
38
+ import java .util .concurrent .CompletableFuture ;
34
39
import java .util .concurrent .CompletionStage ;
35
- import java .util .concurrent .ConcurrentHashMap ;
36
40
37
41
/**
38
42
* {@link SimpleInstrumentation} that creates {@link Observation observations}
53
57
*/
54
58
public class GraphQlObservationInstrumentation extends SimpleInstrumentation {
55
59
60
+ private static final Log logger = LogFactory .getLog (GraphQlObservationInstrumentation .class );
61
+
56
62
private static final ExecutionRequestObservationConvention DEFAULT_REQUEST_CONVENTION =
57
63
new DefaultExecutionRequestObservationConvention ();
58
64
@@ -94,18 +100,19 @@ public GraphQlObservationInstrumentation(ObservationRegistry observationRegistry
94
100
95
101
@ Override
96
102
public InstrumentationState createState (InstrumentationCreateStateParameters parameters ) {
97
- return new RequestObservationInstrumentationState () ;
103
+ return RequestObservationInstrumentationState . INSTANCE ;
98
104
}
99
105
100
106
@ Override
101
107
public InstrumentationContext <ExecutionResult > beginExecution (InstrumentationExecutionParameters parameters ,
102
108
InstrumentationState state ) {
103
109
if (state instanceof RequestObservationInstrumentationState instrumentationState ) {
104
110
ExecutionRequestObservationContext observationContext = new ExecutionRequestObservationContext (parameters .getExecutionInput ());
105
- Observation parentObservation = parameters .getGraphQLContext ().get (ObservationThreadLocalAccessor .KEY );
106
- Observation requestObservation = instrumentationState .createRequestObservation (this .requestObservationConvention ,
107
- observationContext , this .observationRegistry );
108
- requestObservation .parentObservation (parentObservation );
111
+ Observation requestObservation = GraphQlObservationDocumentation .EXECUTION_REQUEST .observation (this .requestObservationConvention ,
112
+ DEFAULT_REQUEST_CONVENTION , () -> observationContext , this .observationRegistry );
113
+ requestObservation .parentObservation (getCurrentObservation (parameters .getGraphQLContext ()));
114
+ GraphQLContext graphQLContext = parameters .getGraphQLContext ();
115
+ graphQLContext .put (ObservationThreadLocalAccessor .KEY , requestObservation );
109
116
requestObservation .start ();
110
117
return new SimpleInstrumentationContext <>() {
111
118
@ Override
@@ -121,30 +128,41 @@ public void onCompleted(ExecutionResult result, Throwable exc) {
121
128
return super .beginExecution (parameters , state );
122
129
}
123
130
131
+ @ Nullable
132
+ private static Observation getCurrentObservation (GraphQLContext graphQLContext ) {
133
+ return graphQLContext .get (ObservationThreadLocalAccessor .KEY );
134
+ }
135
+
124
136
@ Override
125
137
public DataFetcher <?> instrumentDataFetcher (DataFetcher <?> dataFetcher ,
126
138
InstrumentationFieldFetchParameters parameters , InstrumentationState state ) {
127
139
if (!parameters .isTrivialDataFetcher ()
128
140
&& state instanceof RequestObservationInstrumentationState instrumentationState ) {
129
141
return (environment ) -> {
130
142
DataFetcherObservationContext observationContext = new DataFetcherObservationContext (parameters .getEnvironment ());
131
- Observation dataFetcherObservation = instrumentationState .createDataFetcherObservation (this .dataFetcherObservationConvention , observationContext , this .observationRegistry );
143
+ Observation dataFetcherObservation = GraphQlObservationDocumentation .DATA_FETCHER .observation (this .dataFetcherObservationConvention ,
144
+ DEFAULT_DATA_FETCHER_CONVENTION , () -> observationContext , this .observationRegistry );
145
+ dataFetcherObservation .parentObservation (getCurrentObservation (environment ));
132
146
dataFetcherObservation .start ();
133
147
try {
134
148
Object value = dataFetcher .get (environment );
135
149
if (value instanceof CompletionStage <?> completion ) {
136
- return completion .whenComplete ((result , error ) -> {
150
+ return completion .handle ((result , error ) -> {
137
151
observationContext .setValue (result );
138
152
if (error != null ) {
139
153
dataFetcherObservation .error (error );
154
+ dataFetcherObservation .stop ();
155
+ return CompletableFuture .failedStage (error );
140
156
}
141
157
dataFetcherObservation .stop ();
158
+ return CompletableFuture .completedStage (wrapAsDataFetcherResult (result , dataFetcherObservation ));
159
+
142
160
});
143
161
}
144
162
else {
145
163
observationContext .setValue (value );
146
164
dataFetcherObservation .stop ();
147
- return value ;
165
+ return wrapAsDataFetcherResult ( value , dataFetcherObservation ) ;
148
166
}
149
167
}
150
168
catch (Throwable throwable ) {
@@ -157,32 +175,45 @@ public DataFetcher<?> instrumentDataFetcher(DataFetcher<?> dataFetcher,
157
175
return dataFetcher ;
158
176
}
159
177
178
+ @ Nullable
179
+ private static Observation getCurrentObservation (DataFetchingEnvironment environment ) {
180
+ Observation currentObservation = null ;
181
+ if (environment .getLocalContext () != null && environment .getLocalContext () instanceof GraphQLContext ) {
182
+ GraphQLContext localContext = environment .getLocalContext ();
183
+ currentObservation = localContext .get (ObservationThreadLocalAccessor .KEY );
184
+ }
185
+ if (currentObservation == null ) {
186
+ return environment .getGraphQlContext ().get (ObservationThreadLocalAccessor .KEY );
187
+ }
188
+ return currentObservation ;
189
+ }
160
190
161
- static class RequestObservationInstrumentationState implements InstrumentationState {
191
+ private static DataFetcherResult <?> wrapAsDataFetcherResult (Object value , Observation dataFetcherObservation ) {
192
+ if (value instanceof DataFetcherResult <?> result ) {
193
+ if (result .getLocalContext () == null ) {
194
+ return result .transform (builder -> builder .localContext (GraphQLContext .newContext ().of (ObservationThreadLocalAccessor .KEY , dataFetcherObservation ).build ()));
195
+ }
196
+ else if (result .getLocalContext () instanceof GraphQLContext ) {
197
+ ((GraphQLContext ) result .getLocalContext ()).put (ObservationThreadLocalAccessor .KEY , dataFetcherObservation );
198
+ } else {
199
+ logger .debug ("Cannot add observation to localContext as it is not a GraphQLContext but a "
200
+ + result .getLocalContext ().getClass ().toString ());
201
+ }
202
+ return result ;
203
+ }
204
+ else {
205
+ return DataFetcherResult .newResult ()
206
+ .data (value )
207
+ .localContext (GraphQLContext .newContext ().of (ObservationThreadLocalAccessor .KEY , dataFetcherObservation ).build ())
208
+ .build ();
209
+ }
162
210
163
- private Observation requestObservation ;
211
+ }
164
212
165
- private final ConcurrentHashMap <ResultPath , Observation > activeObservations = new ConcurrentHashMap <>(8 );
166
213
167
- Observation createRequestObservation (ExecutionRequestObservationConvention convention ,
168
- ExecutionRequestObservationContext context , ObservationRegistry registry ) {
169
- Observation observation = GraphQlObservationDocumentation .EXECUTION_REQUEST .observation (convention ,
170
- DEFAULT_REQUEST_CONVENTION , () -> context , registry );
171
- this .requestObservation = observation ;
172
- return observation ;
173
- }
214
+ static class RequestObservationInstrumentationState implements InstrumentationState {
174
215
175
- Observation createDataFetcherObservation (DataFetcherObservationConvention convention ,
176
- DataFetcherObservationContext context , ObservationRegistry registry ) {
177
- ExecutionStepInfo executionStepInfo = context .getEnvironment ().getExecutionStepInfo ();
178
- Observation observation = GraphQlObservationDocumentation .DATA_FETCHER .observation (convention ,
179
- DEFAULT_DATA_FETCHER_CONVENTION , () -> context , registry );
180
- Observation parentObservation = (executionStepInfo .hasParent ()) ? this .activeObservations .getOrDefault (executionStepInfo .getParent ().getPath (), this .requestObservation )
181
- : this .requestObservation ;
182
- observation .parentObservation (parentObservation );
183
- this .activeObservations .put (executionStepInfo .getPath (), observation );
184
- return observation ;
185
- }
216
+ static final RequestObservationInstrumentationState INSTANCE = new RequestObservationInstrumentationState ();
186
217
187
218
}
188
219
0 commit comments