Skip to content

Commit bdb53a9

Browse files
committed
Merge branch '1.4.x'
2 parents bab967c + 4d8208d commit bdb53a9

File tree

6 files changed

+100
-30
lines changed

6 files changed

+100
-30
lines changed

spring-graphql-docs/modules/ROOT/pages/observability.adoc

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,12 @@ By default, the following KeyValues are created:
2727
[cols="a,a"]
2828
|===
2929
|Name | Description
30-
|`graphql.operation` _(required)_|GraphQL Operation name.
30+
|`graphql.operation.type` _(required)_|GraphQL Operation type.
3131
|`graphql.outcome` _(required)_|Outcome of the GraphQL request.
3232
|===
3333

34-
The `graphql.operation` KeyValue will use the custom name of the provided query, or http://spec.graphql.org/draft/#sec-Language.Operations[the standard name for the operation] if none (`"query"`, `"mutation"` or `"subscription"`).
34+
The `graphql.operation.type` KeyValue will use the http://spec.graphql.org/draft/#sec-Language.Operations[the standard name for the operation] (`"query"`, `"mutation"` or `"subscription"`) or `"operation"` if the request document could not be parsed.
35+
3536
The `graphql.outcome` KeyValue will be:
3637

3738
* `"SUCCESS"` if a valid GraphQL response has been sent and it contains no errors
@@ -43,8 +44,11 @@ The `graphql.outcome` KeyValue will be:
4344
|===
4445
|Name | Description
4546
|`graphql.execution.id` _(required)_|`graphql.execution.ExecutionId` of the GraphQL request.
47+
|`graphql.operation.name` _(required)_|GraphQL Operation name.
4648
|===
4749

50+
The `graphql.operation.name` KeyValue will be similar to `graphql.operation.name` will use the operation name provided by the client.
51+
4852
Spring for GraphQL also contributes Events for Server Request Observations.
4953
https://docs.micrometer.io/micrometer/reference/observation/components.html#micrometer-observation-events[Micrometer Observation Events] are usually handled as span annotations in traces.
5054
This instrumentation records errors listed in the GraphQL response as events.

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

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

1717
package org.springframework.graphql.observation;
1818

19+
import java.util.Locale;
20+
21+
import graphql.language.OperationDefinition;
1922
import io.micrometer.common.KeyValue;
2023
import io.micrometer.common.KeyValues;
2124

@@ -42,7 +45,9 @@ public class DefaultExecutionRequestObservationConvention implements ExecutionRe
4245

4346
private static final KeyValue OUTCOME_INTERNAL_ERROR = KeyValue.of(ExecutionRequestLowCardinalityKeyNames.OUTCOME, "INTERNAL_ERROR");
4447

45-
private static final KeyValue OPERATION_QUERY = KeyValue.of(ExecutionRequestLowCardinalityKeyNames.OPERATION, "query");
48+
private static final KeyValue OPERATION_NAME_QUERY = KeyValue.of(ExecutionRequestHighCardinalityKeyNames.OPERATION_NAME, "query");
49+
50+
private static final KeyValue OPERATION_TYPE_DEFAULT = KeyValue.of(ExecutionRequestLowCardinalityKeyNames.OPERATION_TYPE, "operation");
4651

4752
private final String name;
4853

@@ -61,13 +66,12 @@ public String getName() {
6166

6267
@Override
6368
public String getContextualName(ExecutionRequestObservationContext context) {
64-
String operationName = (context.getExecutionInput().getOperationName() != null) ? context.getExecutionInput().getOperationName() : "query";
65-
return BASE_CONTEXTUAL_NAME + operationName;
69+
return BASE_CONTEXTUAL_NAME + operationType(context).getValue();
6670
}
6771

6872
@Override
6973
public KeyValues getLowCardinalityKeyValues(ExecutionRequestObservationContext context) {
70-
return KeyValues.of(outcome(context), operation(context));
74+
return KeyValues.of(outcome(context), operationType(context));
7175
}
7276

7377
protected KeyValue outcome(ExecutionRequestObservationContext context) {
@@ -83,21 +87,29 @@ else if (!context.getExecutionResult().getErrors().isEmpty()) {
8387
return OUTCOME_SUCCESS;
8488
}
8589

86-
protected KeyValue operation(ExecutionRequestObservationContext context) {
87-
String operationName = context.getExecutionInput().getOperationName();
88-
if (operationName != null) {
89-
return KeyValue.of(ExecutionRequestLowCardinalityKeyNames.OPERATION, operationName);
90+
protected KeyValue operationType(ExecutionRequestObservationContext context) {
91+
if (context.getExecutionContext() != null) {
92+
OperationDefinition.Operation operation = context.getExecutionContext().getOperationDefinition().getOperation();
93+
return KeyValue.of(ExecutionRequestLowCardinalityKeyNames.OPERATION_TYPE, operation.name().toLowerCase(Locale.ROOT));
9094
}
91-
return OPERATION_QUERY;
95+
return OPERATION_TYPE_DEFAULT;
9296
}
9397

9498
@Override
9599
public KeyValues getHighCardinalityKeyValues(ExecutionRequestObservationContext context) {
96-
return KeyValues.of(executionId(context));
100+
return KeyValues.of(executionId(context), operationName(context));
97101
}
98102

99103
protected KeyValue executionId(ExecutionRequestObservationContext context) {
100104
return KeyValue.of(ExecutionRequestHighCardinalityKeyNames.EXECUTION_ID, context.getExecutionInput().getExecutionIdNonNull().toString());
101105
}
102106

107+
protected KeyValue operationName(ExecutionRequestObservationContext context) {
108+
String operationName = context.getExecutionInput().getOperationName();
109+
if (operationName != null) {
110+
return KeyValue.of(ExecutionRequestHighCardinalityKeyNames.OPERATION_NAME, operationName);
111+
}
112+
return OPERATION_NAME_QUERY;
113+
}
114+
103115
}

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

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
import graphql.ExecutionInput;
2020
import graphql.ExecutionResult;
21+
import graphql.execution.ExecutionContext;
2122
import io.micrometer.observation.Observation;
2223
import org.jspecify.annotations.Nullable;
2324

@@ -32,6 +33,8 @@ public class ExecutionRequestObservationContext extends Observation.Context {
3233

3334
private final ExecutionInput executionInput;
3435

36+
private @Nullable ExecutionContext executionContext;
37+
3538
private @Nullable ExecutionResult executionResult;
3639

3740
public ExecutionRequestObservationContext(ExecutionInput executionInput) {
@@ -46,6 +49,24 @@ public ExecutionInput getExecutionInput() {
4649
return this.executionInput;
4750
}
4851

52+
/**
53+
* Return the {@link ExecutionContext context} for the request execution.
54+
* @since 1.3.7
55+
*/
56+
57+
public @Nullable ExecutionContext getExecutionContext() {
58+
return this.executionContext;
59+
}
60+
61+
/**
62+
* Set the {@link ExecutionContext context} for the request execution.
63+
* @param executionContext the execution context
64+
* @since 1.3.7
65+
*/
66+
public void setExecutionContext(ExecutionContext executionContext) {
67+
this.executionContext = executionContext;
68+
}
69+
4970
/**
5071
* Return the {@link ExecutionResult result} for the request execution.
5172
* @since 1.1.4

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

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -122,18 +122,28 @@ public String asString() {
122122
},
123123

124124
/**
125-
* GraphQL Operation name.
125+
* GraphQL {@link graphql.language.OperationDefinition.Operation Operation type}.
126126
*/
127-
OPERATION {
127+
OPERATION_TYPE {
128128
@Override
129129
public String asString() {
130-
return "graphql.operation";
130+
return "graphql.operation.type";
131131
}
132132
}
133133
}
134134

135135
public enum ExecutionRequestHighCardinalityKeyNames implements KeyName {
136136

137+
/**
138+
* GraphQL Operation name.
139+
*/
140+
OPERATION_NAME {
141+
@Override
142+
public String asString() {
143+
return "graphql.operation.name";
144+
}
145+
},
146+
137147
/**
138148
* {@link graphql.execution.ExecutionId} of the GraphQL request.
139149
*/

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

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
import graphql.execution.instrumentation.SimpleInstrumentationContext;
3030
import graphql.execution.instrumentation.SimplePerformantInstrumentation;
3131
import graphql.execution.instrumentation.parameters.InstrumentationCreateStateParameters;
32+
import graphql.execution.instrumentation.parameters.InstrumentationExecuteOperationParameters;
3233
import graphql.execution.instrumentation.parameters.InstrumentationExecutionParameters;
3334
import graphql.execution.instrumentation.parameters.InstrumentationFieldFetchParameters;
3435
import graphql.schema.DataFetcher;
@@ -141,14 +142,15 @@ public GraphQlObservationInstrumentation(ObservationRegistry observationRegistry
141142

142143
@Override
143144
public CompletableFuture<InstrumentationState> createStateAsync(InstrumentationCreateStateParameters parameters) {
144-
return CompletableFuture.completedFuture(RequestObservationInstrumentationState.INSTANCE);
145+
ExecutionRequestObservationContext requestObservationContext = new ExecutionRequestObservationContext(parameters.getExecutionInput());
146+
return CompletableFuture.completedFuture(new RequestObservationInstrumentationState(requestObservationContext));
145147
}
146148

147149
@Override
148150
public @Nullable InstrumentationContext<ExecutionResult> beginExecution(InstrumentationExecutionParameters parameters,
149151
InstrumentationState state) {
150-
if (state == RequestObservationInstrumentationState.INSTANCE) {
151-
ExecutionRequestObservationContext observationContext = new ExecutionRequestObservationContext(parameters.getExecutionInput());
152+
if (state instanceof RequestObservationInstrumentationState observationState) {
153+
ExecutionRequestObservationContext observationContext = observationState.requestObservationContext;
152154
Observation requestObservation = GraphQlObservationDocumentation.EXECUTION_REQUEST.observation(this.requestObservationConvention,
153155
DEFAULT_REQUEST_CONVENTION, () -> observationContext, this.observationRegistry);
154156
setCurrentObservation(requestObservation, parameters.getGraphQLContext());
@@ -177,11 +179,19 @@ private static void setCurrentObservation(Observation currentObservation, GraphQ
177179
graphQlContext.put(ObservationThreadLocalAccessor.KEY, currentObservation);
178180
}
179181

182+
@Override
183+
public @Nullable InstrumentationContext<ExecutionResult> beginExecuteOperation(InstrumentationExecuteOperationParameters parameters, InstrumentationState state) {
184+
if (state instanceof RequestObservationInstrumentationState observationState) {
185+
observationState.requestObservationContext.setExecutionContext(parameters.getExecutionContext());
186+
}
187+
return super.beginExecuteOperation(parameters, state);
188+
}
189+
180190
@Override
181191
public DataFetcher<?> instrumentDataFetcher(DataFetcher<?> dataFetcher,
182192
InstrumentationFieldFetchParameters parameters, InstrumentationState state) {
183193
if (!parameters.isTrivialDataFetcher()
184-
&& state == RequestObservationInstrumentationState.INSTANCE) {
194+
&& state instanceof RequestObservationInstrumentationState) {
185195
// skip batch loading operations, already instrumented at the dataloader level
186196
if (dataFetcher instanceof SelfDescribingDataFetcher<?> selfDescribingDataFetcher
187197
&& selfDescribingDataFetcher.usesDataLoader()) {
@@ -262,7 +272,11 @@ private static DataFetchingEnvironment wrapDataFetchingEnvironment(DataFetchingE
262272

263273
static class RequestObservationInstrumentationState implements InstrumentationState {
264274

265-
static final RequestObservationInstrumentationState INSTANCE = new RequestObservationInstrumentationState();
275+
final ExecutionRequestObservationContext requestObservationContext;
276+
277+
RequestObservationInstrumentationState(ExecutionRequestObservationContext requestObservationContext) {
278+
this.requestObservationContext = requestObservationContext;
279+
}
266280

267281
}
268282

spring-graphql/src/test/java/org/springframework/graphql/observation/DefaultExecutionRequestObservationConventionTests.java

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,10 @@
2222
import graphql.ExecutionResult;
2323
import graphql.ExecutionResultImpl;
2424
import graphql.GraphQLError;
25+
import graphql.execution.ExecutionContext;
26+
import graphql.execution.ExecutionContextBuilder;
2527
import graphql.execution.ExecutionId;
28+
import graphql.language.OperationDefinition;
2629
import graphql.schema.idl.errors.QueryOperationMissingError;
2730
import io.micrometer.common.KeyValue;
2831
import org.junit.jupiter.api.Test;
@@ -61,21 +64,19 @@ void nameCanBeCustomized() {
6164
void hasContextualName() {
6265
ExecutionInput input = ExecutionInput.newExecutionInput().query("{ greeting }")
6366
.operationName("mutation").build();
64-
ExecutionRequestObservationContext context = createObservationContext(input, builder -> { });
67+
ExecutionRequestObservationContext context = createObservationContext(input);
6568
assertThat(this.convention.getContextualName(context)).isEqualTo("graphql mutation");
6669
}
6770

6871
@Test
6972
void hasOperationKeyValueWhenSuccessfulOutput() {
70-
ExecutionRequestObservationContext context = createObservationContext(this.input, builder -> {
71-
});
72-
assertThat(this.convention.getLowCardinalityKeyValues(context)).contains(KeyValue.of("graphql.operation", "query"));
73+
ExecutionRequestObservationContext context = createObservationContext(this.input);
74+
assertThat(this.convention.getHighCardinalityKeyValues(context)).contains(KeyValue.of("graphql.operation.name", "query"));
7375
}
7476

7577
@Test
7678
void hasOutcomeKeyValueWhenSuccessfulOutput() {
77-
ExecutionRequestObservationContext context = createObservationContext(this.input, builder -> {
78-
});
79+
ExecutionRequestObservationContext context = createObservationContext(this.input);
7980
assertThat(this.convention.getLowCardinalityKeyValues(context)).contains(KeyValue.of("graphql.outcome", "SUCCESS"));
8081
}
8182

@@ -88,26 +89,34 @@ void hasOutcomeKeyValueWhenErrorOutput() {
8889

8990
@Test
9091
void hasOutcomeKeyValueWhenInternalError() {
91-
ExecutionRequestObservationContext context = createObservationContext(this.input, builder -> {
92-
});
92+
ExecutionRequestObservationContext context = createObservationContext(this.input);
9393
GraphQLError graphQLError = GraphQLError.newError().errorType(ErrorType.INTERNAL_ERROR).message(ErrorType.INTERNAL_ERROR + " for [executionId]").build();
9494
context.setExecutionResult(ExecutionResult.newExecutionResult().addError(graphQLError).build());
9595
assertThat(this.convention.getLowCardinalityKeyValues(context)).contains(KeyValue.of("graphql.outcome", "INTERNAL_ERROR"));
9696
}
9797

9898
@Test
9999
void hasExecutionIdKeyValue() {
100-
ExecutionRequestObservationContext context = createObservationContext(this.input, builder -> {
101-
});
100+
ExecutionRequestObservationContext context = createObservationContext(this.input);
102101
assertThat(this.convention.getHighCardinalityKeyValues(context)).contains(KeyValue.of("graphql.execution.id", "42"));
103102
}
104103

104+
private ExecutionRequestObservationContext createObservationContext(ExecutionInput executionInput) {
105+
return createObservationContext(executionInput, builder -> { });
106+
}
105107

106108
private ExecutionRequestObservationContext createObservationContext(ExecutionInput executionInput, Consumer<ExecutionResultImpl.Builder> resultConsumer) {
107109
ExecutionRequestObservationContext context = new ExecutionRequestObservationContext(executionInput);
108110
ExecutionResultImpl.Builder builder = ExecutionResultImpl.newExecutionResult();
109111
resultConsumer.accept(builder);
110112
context.setExecutionResult(builder.build());
113+
114+
ExecutionContext executionContext = ExecutionContextBuilder.newExecutionContextBuilder()
115+
.executionId(ExecutionId.from("123_456_789"))
116+
.executionInput(executionInput)
117+
.operationDefinition(OperationDefinition.newOperationDefinition().operation(OperationDefinition.Operation.MUTATION).build())
118+
.build();
119+
context.setExecutionContext(executionContext);
111120
return context;
112121
}
113122

0 commit comments

Comments
 (0)