Skip to content

Commit a56ff6e

Browse files
committed
Fix DataFetcher observations hierarchy
Prior to this commit, the `GraphQlObservationInstrumentation` would organize the parent/child relationship between data fetcher observations by setting the current observation under a well-known key in the global GraphQL context. In some cases, the order of execution and the scheduling of operations does not reflect the actual operation hierarchy as defined by the GraphQL `ExecutionStepInfo`. This would result in traces where data fetching operations are set with incorrect parent/child relationships. This commit ensures that data fetcher observations have their parent set with the expected one, by keeping track of active observations and using the `ExecutionStepInfo` path as a key. Fixes gh-676
1 parent 4e5aaed commit a56ff6e

File tree

1 file changed

+24
-30
lines changed

1 file changed

+24
-30
lines changed

spring-graphql/src/main/java/org/springframework/graphql/observation/GraphQlObservationInstrumentation.java

Lines changed: 24 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,9 @@
1616

1717
package org.springframework.graphql.observation;
1818

19-
import java.util.concurrent.CompletionStage;
20-
2119
import graphql.ExecutionResult;
22-
import graphql.GraphQLContext;
20+
import graphql.execution.ExecutionStepInfo;
21+
import graphql.execution.ResultPath;
2322
import graphql.execution.instrumentation.InstrumentationContext;
2423
import graphql.execution.instrumentation.InstrumentationState;
2524
import graphql.execution.instrumentation.SimpleInstrumentation;
@@ -30,6 +29,10 @@
3029
import graphql.schema.DataFetcher;
3130
import io.micrometer.observation.Observation;
3231
import io.micrometer.observation.ObservationRegistry;
32+
import io.micrometer.observation.contextpropagation.ObservationThreadLocalAccessor;
33+
34+
import java.util.concurrent.CompletionStage;
35+
import java.util.concurrent.ConcurrentHashMap;
3336

3437
/**
3538
* {@link SimpleInstrumentation} that creates {@link Observation observations}
@@ -50,8 +53,6 @@
5053
*/
5154
public class GraphQlObservationInstrumentation extends SimpleInstrumentation {
5255

53-
private static final String OBSERVATION_KEY = "micrometer.observation";
54-
5556
private static final ExecutionRequestObservationConvention DEFAULT_REQUEST_CONVENTION =
5657
new DefaultExecutionRequestObservationConvention();
5758

@@ -101,11 +102,10 @@ public InstrumentationContext<ExecutionResult> beginExecution(InstrumentationExe
101102
InstrumentationState state) {
102103
if (state instanceof RequestObservationInstrumentationState instrumentationState) {
103104
ExecutionRequestObservationContext observationContext = new ExecutionRequestObservationContext(parameters.getExecutionInput());
104-
Observation parentObservation = parameters.getGraphQLContext().get(OBSERVATION_KEY);
105+
Observation parentObservation = parameters.getGraphQLContext().get(ObservationThreadLocalAccessor.KEY);
105106
Observation requestObservation = instrumentationState.createRequestObservation(this.requestObservationConvention,
106107
observationContext, this.observationRegistry);
107108
requestObservation.parentObservation(parentObservation);
108-
parameters.getGraphQLContext().put(OBSERVATION_KEY, requestObservation);
109109
requestObservation.start();
110110
return new SimpleInstrumentationContext<>() {
111111
@Override
@@ -115,7 +115,6 @@ public void onCompleted(ExecutionResult result, Throwable exc) {
115115
requestObservation.error(exc);
116116
}
117117
requestObservation.stop();
118-
instrumentationState.restoreParentObservation(parameters.getGraphQLContext(), parentObservation);
119118
}
120119
};
121120
}
@@ -128,13 +127,8 @@ public DataFetcher<?> instrumentDataFetcher(DataFetcher<?> dataFetcher,
128127
if (!parameters.isTrivialDataFetcher()
129128
&& state instanceof RequestObservationInstrumentationState instrumentationState) {
130129
return (environment) -> {
131-
GraphQLContext graphQLContext = parameters.getExecutionContext().getGraphQLContext();
132-
Observation parentObservation = graphQLContext.get(OBSERVATION_KEY);
133130
DataFetcherObservationContext observationContext = new DataFetcherObservationContext(parameters.getEnvironment());
134-
Observation dataFetcherObservation = instrumentationState.createDataFetcherObservation(
135-
this.dataFetcherObservationConvention, observationContext, this.observationRegistry);
136-
dataFetcherObservation.parentObservation(parentObservation);
137-
graphQLContext.put(OBSERVATION_KEY, dataFetcherObservation);
131+
Observation dataFetcherObservation = instrumentationState.createDataFetcherObservation(this.dataFetcherObservationConvention, observationContext, this.observationRegistry);
138132
dataFetcherObservation.start();
139133
try {
140134
Object value = dataFetcher.get(environment);
@@ -145,20 +139,17 @@ public DataFetcher<?> instrumentDataFetcher(DataFetcher<?> dataFetcher,
145139
dataFetcherObservation.error(error);
146140
}
147141
dataFetcherObservation.stop();
148-
instrumentationState.restoreParentObservation(graphQLContext, parentObservation);
149142
});
150143
}
151144
else {
152145
observationContext.setValue(value);
153146
dataFetcherObservation.stop();
154-
instrumentationState.restoreParentObservation(graphQLContext, parentObservation);
155147
return value;
156148
}
157149
}
158150
catch (Throwable throwable) {
159151
dataFetcherObservation.error(throwable);
160152
dataFetcherObservation.stop();
161-
instrumentationState.restoreParentObservation(graphQLContext, parentObservation);
162153
throw throwable;
163154
}
164155
};
@@ -169,25 +160,28 @@ public DataFetcher<?> instrumentDataFetcher(DataFetcher<?> dataFetcher,
169160

170161
static class RequestObservationInstrumentationState implements InstrumentationState {
171162

163+
private Observation requestObservation;
164+
165+
private final ConcurrentHashMap<ResultPath, Observation> activeObservations = new ConcurrentHashMap<>(8);
166+
172167
Observation createRequestObservation(ExecutionRequestObservationConvention convention,
173-
ExecutionRequestObservationContext context, ObservationRegistry registry) {
174-
return GraphQlObservationDocumentation.EXECUTION_REQUEST.observation(convention,
168+
ExecutionRequestObservationContext context, ObservationRegistry registry) {
169+
Observation observation = GraphQlObservationDocumentation.EXECUTION_REQUEST.observation(convention,
175170
DEFAULT_REQUEST_CONVENTION, () -> context, registry);
171+
this.requestObservation = observation;
172+
return observation;
176173
}
177174

178175
Observation createDataFetcherObservation(DataFetcherObservationConvention convention,
179-
DataFetcherObservationContext context, ObservationRegistry registry) {
180-
return GraphQlObservationDocumentation.DATA_FETCHER.observation(convention,
176+
DataFetcherObservationContext context, ObservationRegistry registry) {
177+
ExecutionStepInfo executionStepInfo = context.getEnvironment().getExecutionStepInfo();
178+
Observation observation = GraphQlObservationDocumentation.DATA_FETCHER.observation(convention,
181179
DEFAULT_DATA_FETCHER_CONVENTION, () -> context, registry);
182-
}
183-
184-
void restoreParentObservation(GraphQLContext context, Observation parentObservation) {
185-
if (parentObservation != null) {
186-
context.put(OBSERVATION_KEY, parentObservation);
187-
}
188-
else {
189-
context.delete(OBSERVATION_KEY);
190-
}
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;
191185
}
192186

193187
}

0 commit comments

Comments
 (0)