diff --git a/spring-graphql-docs/src/docs/asciidoc/includes/observability.adoc b/spring-graphql-docs/src/docs/asciidoc/includes/observability.adoc
index e2919efb9..09a0c625b 100644
--- a/spring-graphql-docs/src/docs/asciidoc/includes/observability.adoc
+++ b/spring-graphql-docs/src/docs/asciidoc/includes/observability.adoc
@@ -13,7 +13,11 @@ If your application is using Spring Boot, contributing the custom convention as
[[observability.server.request]]
== Server Requests instrumentation
-GraphQL Server Requests observations are created with the name `"graphql.request"` for Servlet and Reactive applications and above all supported transports.
+GraphQL Server Requests observations are created with the name `"graphql.request"` for traditional and Reactive applications and above all supported transports.
+This instrumentation assumes that any parent observation must be set as the current one on the GraphQL context with the well-known `"micrometer.observation"` key.
+For trace propagation across network boundaries, a separate instrumentation at the transport level must be in charge.
+In the case of HTTP, Spring Framework {spring-framework-ref-docs}/integration.html#integration.observability.http-server[has dedicated instrumentation that takes care of trace propagation].
+
Applications need to configure the `org.springframework.graphql.observation.GraphQlObservationInstrumentation` instrumentation in their application.
It is using the `org.springframework.graphql.observation.DefaultExecutionRequestObservationConvention` by default, backed by the `ExecutionRequestObservationContext`.
diff --git a/spring-graphql/src/main/java/org/springframework/graphql/observation/DefaultExecutionRequestObservationConvention.java b/spring-graphql/src/main/java/org/springframework/graphql/observation/DefaultExecutionRequestObservationConvention.java
index 49912994e..9cd4e62d4 100644
--- a/spring-graphql/src/main/java/org/springframework/graphql/observation/DefaultExecutionRequestObservationConvention.java
+++ b/spring-graphql/src/main/java/org/springframework/graphql/observation/DefaultExecutionRequestObservationConvention.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2020-2022 the original author or authors.
+ * Copyright 2020-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -60,7 +60,7 @@ public String getName() {
@Override
public String getContextualName(ExecutionRequestObservationContext context) {
- String operationName = (context.getCarrier().getOperationName() != null) ? context.getCarrier().getOperationName() : "query";
+ String operationName = (context.getExecutionInput().getOperationName() != null) ? context.getExecutionInput().getOperationName() : "query";
return BASE_CONTEXTUAL_NAME + operationName;
}
@@ -70,17 +70,17 @@ public KeyValues getLowCardinalityKeyValues(ExecutionRequestObservationContext c
}
protected KeyValue outcome(ExecutionRequestObservationContext context) {
- if (context.getError() != null || context.getResponse() == null) {
+ if (context.getError() != null || context.getExecutionResult() == null) {
return OUTCOME_INTERNAL_ERROR;
}
- else if (context.getResponse().getErrors().size() > 0) {
+ else if (context.getExecutionResult().getErrors().size() > 0) {
return OUTCOME_REQUEST_ERROR;
}
return OUTCOME_SUCCESS;
}
protected KeyValue operation(ExecutionRequestObservationContext context) {
- String operationName = context.getCarrier().getOperationName();
+ String operationName = context.getExecutionInput().getOperationName();
if (operationName != null) {
return KeyValue.of(ExecutionRequestLowCardinalityKeyNames.OPERATION, operationName);
}
@@ -93,7 +93,7 @@ public KeyValues getHighCardinalityKeyValues(ExecutionRequestObservationContext
}
protected KeyValue executionId(ExecutionRequestObservationContext context) {
- return KeyValue.of(ExecutionRequestHighCardinalityKeyNames.EXECUTION_ID, context.getCarrier().getExecutionId().toString());
+ return KeyValue.of(ExecutionRequestHighCardinalityKeyNames.EXECUTION_ID, context.getExecutionInput().getExecutionId().toString());
}
}
diff --git a/spring-graphql/src/main/java/org/springframework/graphql/observation/ExecutionRequestObservationContext.java b/spring-graphql/src/main/java/org/springframework/graphql/observation/ExecutionRequestObservationContext.java
index fff84209e..044468cd0 100644
--- a/spring-graphql/src/main/java/org/springframework/graphql/observation/ExecutionRequestObservationContext.java
+++ b/spring-graphql/src/main/java/org/springframework/graphql/observation/ExecutionRequestObservationContext.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2020-2022 the original author or authors.
+ * Copyright 2020-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,42 +16,72 @@
package org.springframework.graphql.observation;
-import java.util.Map;
-
import graphql.ExecutionInput;
import graphql.ExecutionResult;
-import io.micrometer.observation.transport.RequestReplyReceiverContext;
+import io.micrometer.observation.Observation;
+import org.springframework.lang.Nullable;
/**
* Context that holds information for metadata collection during observations
* for {@link GraphQlObservationDocumentation#EXECUTION_REQUEST GraphQL requests}.
- *
This context also extends {@link RequestReplyReceiverContext} for propagating
- * tracing information from the {@link graphql.GraphQLContext}
- * or the {@link ExecutionInput#getExtensions() input extensions}.
*
* @author Brian Clozel
* @since 1.1.0
*/
-public class ExecutionRequestObservationContext extends RequestReplyReceiverContext {
-
- public ExecutionRequestObservationContext(ExecutionInput executionInput) {
- super(ExecutionRequestObservationContext::getContextValue);
- setCarrier(executionInput);
- }
-
- /**
- * Read propagation field from the {@link graphql.GraphQLContext},
- * or the {@link ExecutionInput#getExtensions() input extensions} as a fallback.
- */
- private static String getContextValue(ExecutionInput executionInput, String key) {
- String value = executionInput.getGraphQLContext().get(key);
- if (value == null) {
- Map extensions = executionInput.getExtensions();
- if (extensions != null) {
- value = (String) extensions.get(key);
- }
- }
- return value;
- }
+public class ExecutionRequestObservationContext extends Observation.Context {
+
+ private final ExecutionInput executionInput;
+
+ @Nullable
+ private ExecutionResult executionResult;
+
+ public ExecutionRequestObservationContext(ExecutionInput executionInput) {
+ this.executionInput = executionInput;
+ }
+
+ /**
+ * Return the {@link ExecutionInput input} for the request execution.
+ * @since 1.1.4
+ */
+ public ExecutionInput getExecutionInput() {
+ return this.executionInput;
+ }
+
+ /**
+ * Return the {@link ExecutionInput input} for the request execution.
+ * @deprecated since 1.1.4 in favor of {@link #getExecutionInput()}
+ */
+ @Deprecated(since = "1.1.4", forRemoval = true)
+ public ExecutionInput getCarrier() {
+ return this.executionInput;
+ }
+
+ /**
+ * Return the {@link ExecutionResult result} for the request execution.
+ * @since 1.1.4
+ */
+ @Nullable
+ public ExecutionResult getExecutionResult() {
+ return this.executionResult;
+ }
+
+ /**
+ * Set the {@link ExecutionResult result} for the request execution.
+ * @param executionResult the execution result
+ * @since 1.1.4
+ */
+ public void setExecutionResult(ExecutionResult executionResult) {
+ this.executionResult = executionResult;
+ }
+
+ /**
+ * Return the {@link ExecutionResult result} for the request execution.
+ * @deprecated since 1.1.4 in favor of {@link #getExecutionResult()}
+ */
+ @Nullable
+ @Deprecated(since = "1.1.4", forRemoval = true)
+ public ExecutionResult getResponse() {
+ return this.executionResult;
+ }
}
diff --git a/spring-graphql/src/main/java/org/springframework/graphql/observation/GraphQlObservationInstrumentation.java b/spring-graphql/src/main/java/org/springframework/graphql/observation/GraphQlObservationInstrumentation.java
index c26507b91..696799894 100644
--- a/spring-graphql/src/main/java/org/springframework/graphql/observation/GraphQlObservationInstrumentation.java
+++ b/spring-graphql/src/main/java/org/springframework/graphql/observation/GraphQlObservationInstrumentation.java
@@ -110,7 +110,7 @@ public InstrumentationContext beginExecution(InstrumentationExe
return new SimpleInstrumentationContext<>() {
@Override
public void onCompleted(ExecutionResult result, Throwable exc) {
- observationContext.setResponse(result);
+ observationContext.setExecutionResult(result);
if (exc != null) {
requestObservation.error(exc);
}
diff --git a/spring-graphql/src/main/java/org/springframework/graphql/observation/PropagationWebGraphQlInterceptor.java b/spring-graphql/src/main/java/org/springframework/graphql/observation/PropagationWebGraphQlInterceptor.java
index c883d0d78..d7d9845fc 100644
--- a/spring-graphql/src/main/java/org/springframework/graphql/observation/PropagationWebGraphQlInterceptor.java
+++ b/spring-graphql/src/main/java/org/springframework/graphql/observation/PropagationWebGraphQlInterceptor.java
@@ -32,7 +32,9 @@
*
* @author Brian Clozel
* @since 1.1.1
+ * @deprecated since 1.1.4 with no replacement.
*/
+@Deprecated(since = "1.1.4", forRemoval = true)
public class PropagationWebGraphQlInterceptor implements WebGraphQlInterceptor {
private final Propagator propagator;
diff --git a/spring-graphql/src/test/java/org/springframework/graphql/observation/DefaultExecutionRequestObservationConventionTests.java b/spring-graphql/src/test/java/org/springframework/graphql/observation/DefaultExecutionRequestObservationConventionTests.java
index 88340b003..d4eca661f 100644
--- a/spring-graphql/src/test/java/org/springframework/graphql/observation/DefaultExecutionRequestObservationConventionTests.java
+++ b/spring-graphql/src/test/java/org/springframework/graphql/observation/DefaultExecutionRequestObservationConventionTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2020-2022 the original author or authors.
+ * Copyright 2020-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -102,7 +102,7 @@ private ExecutionRequestObservationContext createObservationContext(ExecutionInp
ExecutionRequestObservationContext context = new ExecutionRequestObservationContext(executionInput);
ExecutionResultImpl.Builder builder = ExecutionResultImpl.newExecutionResult();
resultConsumer.accept(builder);
- context.setResponse(builder.build());
+ context.setExecutionResult(builder.build());
return context;
}
diff --git a/spring-graphql/src/test/java/org/springframework/graphql/observation/ExecutionRequestObservationContextTests.java b/spring-graphql/src/test/java/org/springframework/graphql/observation/ExecutionRequestObservationContextTests.java
deleted file mode 100644
index 6e5df6452..000000000
--- a/spring-graphql/src/test/java/org/springframework/graphql/observation/ExecutionRequestObservationContextTests.java
+++ /dev/null
@@ -1,62 +0,0 @@
-/*
- * Copyright 2020-2022 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * https://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.springframework.graphql.observation;
-
-
-import java.util.Map;
-
-import graphql.ExecutionInput;
-import org.junit.jupiter.api.Test;
-
-import static org.assertj.core.api.Assertions.assertThat;
-
-/**
- * Tests for {@link ExecutionRequestObservationContext}.
- *
- * @author Brian Clozel
- */
-class ExecutionRequestObservationContextTests {
-
- @Test
- void readPropagationFieldFromGraphQlContext() {
- ExecutionInput executionInput = ExecutionInput
- .newExecutionInput("{ notUsed }")
- .graphQLContext(builder -> builder.of("X-Tracing-Test", "traceId"))
- .build();
- ExecutionRequestObservationContext context = new ExecutionRequestObservationContext(executionInput);
- assertThat(context.getGetter().get(executionInput, "X-Tracing-Test")).isEqualTo("traceId");
- }
-
- @Test
- void readPropagationFieldFromExtensions() {
- ExecutionInput executionInput = ExecutionInput
- .newExecutionInput("{ notUsed }")
- .extensions(Map.of("X-Tracing-Test", "traceId"))
- .build();
- ExecutionRequestObservationContext context = new ExecutionRequestObservationContext(executionInput);
- assertThat(context.getGetter().get(executionInput, "X-Tracing-Test")).isEqualTo("traceId");
- }
-
- @Test
- void doesNotFailIsMissingPropagationField() {
- ExecutionInput executionInput = ExecutionInput
- .newExecutionInput("{ notUsed }")
- .build();
- ExecutionRequestObservationContext context = new ExecutionRequestObservationContext(executionInput);
- assertThat(context.getGetter().get(executionInput, "X-Tracing-Test")).isNull();
- }
-}
\ No newline at end of file
diff --git a/spring-graphql/src/test/java/org/springframework/graphql/observation/GraphQlObservationInstrumentationTests.java b/spring-graphql/src/test/java/org/springframework/graphql/observation/GraphQlObservationInstrumentationTests.java
index b686907f3..c06fa9b57 100644
--- a/spring-graphql/src/test/java/org/springframework/graphql/observation/GraphQlObservationInstrumentationTests.java
+++ b/spring-graphql/src/test/java/org/springframework/graphql/observation/GraphQlObservationInstrumentationTests.java
@@ -16,23 +16,12 @@
package org.springframework.graphql.observation;
-import java.util.List;
-import java.util.concurrent.CompletableFuture;
-
import graphql.GraphqlErrorBuilder;
import io.micrometer.observation.Observation;
import io.micrometer.observation.ObservationRegistry;
+import io.micrometer.observation.contextpropagation.ObservationThreadLocalAccessor;
import io.micrometer.observation.tck.TestObservationRegistry;
import io.micrometer.observation.tck.TestObservationRegistryAssert;
-import io.micrometer.observation.transport.ReceiverContext;
-import io.micrometer.tracing.Span;
-import io.micrometer.tracing.TraceContext;
-import io.micrometer.tracing.handler.PropagatingReceiverTracingObservationHandler;
-import io.micrometer.tracing.handler.TracingObservationHandler;
-import io.micrometer.tracing.propagation.Propagator;
-import io.micrometer.tracing.test.simple.SimpleSpanBuilder;
-import io.micrometer.tracing.test.simple.SimpleTracer;
-import io.micrometer.tracing.test.simple.TracerAssert;
import org.junit.jupiter.api.Test;
import reactor.core.publisher.Mono;
@@ -45,7 +34,7 @@
import org.springframework.graphql.execution.DataFetcherExceptionResolver;
import org.springframework.graphql.execution.ErrorType;
-import static org.assertj.core.api.Assertions.assertThat;
+import java.util.concurrent.CompletableFuture;
/**
* Tests for {@link GraphQlObservationInstrumentation}.
@@ -134,7 +123,8 @@ void instrumentMultipleDataFetcherOperations() {
.that()
.hasLowCardinalityKeyValue("graphql.outcome", "SUCCESS")
.hasLowCardinalityKeyValue("graphql.field.name", "bookById")
- .hasHighCardinalityKeyValue("graphql.field.path", "/bookById");
+ .hasHighCardinalityKeyValue("graphql.field.path", "/bookById")
+ .hasParentObservationContextMatching(context -> context instanceof ExecutionRequestObservationContext);
TestObservationRegistryAssert.assertThat(this.observationRegistry)
.hasAnObservationWithAKeyValue("graphql.field.name", "author")
@@ -213,7 +203,7 @@ void setIncomingObservationAsParent() {
ExecutionGraphQlRequest graphQlRequest = TestExecutionRequest.forDocument(document);
Observation incoming = Observation.start("incoming", ObservationRegistry.create());
graphQlRequest.configureExecutionInput((input, builder) ->
- builder.graphQLContext(contextBuilder -> contextBuilder.of("micrometer.observation", incoming)).build());
+ builder.graphQLContext(contextBuilder -> contextBuilder.of(ObservationThreadLocalAccessor.KEY, incoming)).build());
Mono responseMono = graphQlSetup
.queryFetcher("bookById", env -> BookSource.getBookWithoutAuthor(1L))
.toGraphQlService()
@@ -225,65 +215,4 @@ void setIncomingObservationAsParent() {
incoming.stop();
}
- @Test
- void inboundTracingInformationIsPropagated() {
- SimpleTracer simpleTracer = new SimpleTracer();
- String traceId = "traceId";
- TracingObservationHandler tracingHandler = new PropagatingReceiverTracingObservationHandler<>(simpleTracer, new TestPropagator(simpleTracer, traceId));
- this.observationRegistry.observationConfig().observationHandler(tracingHandler);
- String document = """
- {
- bookById(id: 1) {
- name
- }
- }
- """;
- ExecutionGraphQlRequest executionRequest = TestExecutionRequest.forDocument(document);
- executionRequest.configureExecutionInput((input, builder) ->
- builder.graphQLContext(context -> context.of(TestPropagator.TRACING_HEADER_NAME, traceId)).build());
- Mono responseMono = graphQlSetup
- .queryFetcher("bookById", env -> BookSource.getBookWithoutAuthor(1L))
- .toGraphQlService()
- .execute(executionRequest);
- ResponseHelper response = ResponseHelper.forResponse(responseMono);
-
- TracerAssert.assertThat(simpleTracer)
- .onlySpan()
- .hasNameEqualTo("graphql query")
- .hasKindEqualTo(Span.Kind.SERVER)
- .hasTag("graphql.operation", "query")
- .hasTag("graphql.outcome", "SUCCESS")
- .hasTagWithKey("graphql.execution.id");
- }
-
- static class TestPropagator implements Propagator {
-
- public static String TRACING_HEADER_NAME = "X-Test-Tracing";
-
- private final SimpleTracer tracer;
-
- private final String traceId;
-
- TestPropagator(SimpleTracer tracer, String traceId) {
- this.tracer = tracer;
- this.traceId = traceId;
- }
-
- @Override
- public List fields() {
- return List.of(TRACING_HEADER_NAME);
- }
-
- @Override
- public void inject(TraceContext context, C carrier, Setter setter) {
- setter.set(carrier, TRACING_HEADER_NAME, "traceId");
- }
-
- @Override
- public Span.Builder extract(C carrier, Getter getter) {
- String foo = getter.get(carrier, TRACING_HEADER_NAME);
- assertThat(foo).isEqualTo(this.traceId);
- return new SimpleSpanBuilder(this.tracer);
- }
- }
}
diff --git a/spring-graphql/src/test/java/org/springframework/graphql/observation/PropagationWebGraphQlInterceptorTests.java b/spring-graphql/src/test/java/org/springframework/graphql/observation/PropagationWebGraphQlInterceptorTests.java
index fafe47a26..1c6186a9d 100644
--- a/spring-graphql/src/test/java/org/springframework/graphql/observation/PropagationWebGraphQlInterceptorTests.java
+++ b/spring-graphql/src/test/java/org/springframework/graphql/observation/PropagationWebGraphQlInterceptorTests.java
@@ -47,6 +47,7 @@
*
* @author Brian Clozel
*/
+@SuppressWarnings("removal")
class PropagationWebGraphQlInterceptorTests {
PropagationWebGraphQlInterceptor interceptor = new PropagationWebGraphQlInterceptor(new TestPropagator());