diff --git a/build.gradle b/build.gradle index f039d621..1e4dd5ca 100644 --- a/build.gradle +++ b/build.gradle @@ -2,7 +2,7 @@ description = "Spring for GraphQL" ext { moduleProjects = [project(":spring-graphql"), project(":spring-graphql-test")] - springFrameworkVersion = "6.0.3" + springFrameworkVersion = "6.0.4" graphQlJavaVersion = "20.0" springBootVersion = "3.0.0" } diff --git a/platform/build.gradle b/platform/build.gradle index 213371b6..02f27e52 100644 --- a/platform/build.gradle +++ b/platform/build.gradle @@ -8,11 +8,11 @@ javaPlatform { dependencies { api(platform("org.springframework:spring-framework-bom:${springFrameworkVersion}")) - api(platform("com.fasterxml.jackson:jackson-bom:2.14.1")) - api(platform("io.projectreactor:reactor-bom:2022.0.1")) - api(platform("io.micrometer:micrometer-bom:1.10.2")) - api(platform("io.micrometer:micrometer-tracing-bom:1.0.0")) - api(platform("org.springframework.data:spring-data-bom:2022.0.0")) + api(platform("com.fasterxml.jackson:jackson-bom:2.14.2")) + api(platform("io.projectreactor:reactor-bom:2022.0.2")) + api(platform("io.micrometer:micrometer-bom:1.10.4")) + api(platform("io.micrometer:micrometer-tracing-bom:1.0.2")) + api(platform("org.springframework.data:spring-data-bom:2022.0.1")) api(platform("org.springframework.security:spring-security-bom:6.0.1")) api(platform("com.querydsl:querydsl-bom:5.0.0")) api(platform("io.rsocket:rsocket-bom:1.1.3")) @@ -25,7 +25,7 @@ dependencies { constraints { api("com.graphql-java:graphql-java:${graphQlJavaVersion}") - api("io.micrometer:context-propagation:1.0.0") + api("io.micrometer:context-propagation:1.0.2") api("jakarta.annotation:jakarta.annotation-api:2.1.1") api("jakarta.servlet:jakarta.servlet-api:6.0.0") 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 6666a4de..2d231b39 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 @@ -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. @@ -19,6 +19,7 @@ package org.springframework.graphql.observation; import java.util.concurrent.CompletionStage; import graphql.ExecutionResult; +import graphql.GraphQLContext; import graphql.execution.instrumentation.InstrumentationContext; import graphql.execution.instrumentation.InstrumentationState; import graphql.execution.instrumentation.SimpleInstrumentationContext; @@ -100,20 +101,21 @@ public class GraphQlObservationInstrumentation extends SimplePerformantInstrumen InstrumentationState state) { if (state instanceof RequestObservationInstrumentationState instrumentationState) { ExecutionRequestObservationContext observationContext = new ExecutionRequestObservationContext(parameters.getExecutionInput()); + Observation parentObservation = parameters.getGraphQLContext().get(OBSERVATION_KEY); Observation requestObservation = instrumentationState.createRequestObservation(this.requestObservationConvention, observationContext, this.observationRegistry); + requestObservation.parentObservation(parentObservation); + parameters.getGraphQLContext().put(OBSERVATION_KEY, requestObservation); requestObservation.start(); return new SimpleInstrumentationContext<>() { @Override public void onCompleted(ExecutionResult result, Throwable exc) { observationContext.setResponse(result); if (exc != null) { - observationContext.setError(exc); requestObservation.error(exc); } - else { - requestObservation.stop(); - } + requestObservation.stop(); + instrumentationState.restoreParentObservation(parameters.getGraphQLContext(), parentObservation); } }; } @@ -126,58 +128,66 @@ public class GraphQlObservationInstrumentation extends SimplePerformantInstrumen if (!parameters.isTrivialDataFetcher() && state instanceof RequestObservationInstrumentationState instrumentationState) { return (environment) -> { + GraphQLContext graphQLContext = parameters.getExecutionContext().getGraphQLContext(); + Observation parentObservation = graphQLContext.get(OBSERVATION_KEY); DataFetcherObservationContext observationContext = new DataFetcherObservationContext(parameters.getEnvironment()); Observation dataFetcherObservation = instrumentationState.createDataFetcherObservation( this.dataFetcherObservationConvention, observationContext, this.observationRegistry); - parameters.getExecutionContext().getGraphQLContext().put(OBSERVATION_KEY, dataFetcherObservation); + dataFetcherObservation.parentObservation(parentObservation); + graphQLContext.put(OBSERVATION_KEY, dataFetcherObservation); dataFetcherObservation.start(); try { Object value = dataFetcher.get(environment); if (value instanceof CompletionStage completion) { return completion.whenComplete((result, error) -> { + observationContext.setValue(result); if (error != null) { dataFetcherObservation.error(error); } - observationContext.setValue(result); dataFetcherObservation.stop(); + instrumentationState.restoreParentObservation(graphQLContext, parentObservation); }); } else { observationContext.setValue(value); dataFetcherObservation.stop(); + instrumentationState.restoreParentObservation(graphQLContext, parentObservation); return value; } } catch (Throwable throwable) { dataFetcherObservation.error(throwable); dataFetcherObservation.stop(); + instrumentationState.restoreParentObservation(graphQLContext, parentObservation); throw throwable; } }; } - return super.instrumentDataFetcher(dataFetcher, parameters, state); + return dataFetcher; } static class RequestObservationInstrumentationState implements InstrumentationState { - private Observation requestObservation; - - Observation createRequestObservation(ExecutionRequestObservationConvention convention, ExecutionRequestObservationContext context, ObservationRegistry registry) { - Observation observation = GraphQlObservationDocumentation.EXECUTION_REQUEST.observation(convention, + return GraphQlObservationDocumentation.EXECUTION_REQUEST.observation(convention, DEFAULT_REQUEST_CONVENTION, () -> context, registry); - this.requestObservation = observation; - return observation; } Observation createDataFetcherObservation(DataFetcherObservationConvention convention, DataFetcherObservationContext context, ObservationRegistry registry) { - Observation dataFetcherObservation = GraphQlObservationDocumentation.DATA_FETCHER.observation(convention, + return GraphQlObservationDocumentation.DATA_FETCHER.observation(convention, DEFAULT_DATA_FETCHER_CONVENTION, () -> context, registry); - dataFetcherObservation.parentObservation(requestObservation); - return dataFetcherObservation; + } + + void restoreParentObservation(GraphQLContext context, Observation parentObservation) { + if (parentObservation != null) { + context.put(OBSERVATION_KEY, parentObservation); + } + else { + context.delete(OBSERVATION_KEY); + } } } 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 cf6e6cd4..b686907f 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 @@ -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. @@ -20,6 +20,8 @@ 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.tck.TestObservationRegistry; import io.micrometer.observation.tck.TestObservationRegistryAssert; import io.micrometer.observation.transport.ReceiverContext; @@ -199,6 +201,30 @@ class GraphQlObservationInstrumentationTests { .hasParentObservationContextMatching(context -> context instanceof ExecutionRequestObservationContext); } + @Test + void setIncomingObservationAsParent() { + String document = """ + { + bookById(id: 1) { + name + } + } + """; + 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()); + Mono responseMono = graphQlSetup + .queryFetcher("bookById", env -> BookSource.getBookWithoutAuthor(1L)) + .toGraphQlService() + .execute(graphQlRequest); + ResponseHelper response = ResponseHelper.forResponse(responseMono); + + TestObservationRegistryAssert.assertThat(this.observationRegistry).hasObservationWithNameEqualTo("graphql.request") + .that().hasParentObservationEqualTo(incoming); + incoming.stop(); + } + @Test void inboundTracingInformationIsPropagated() { SimpleTracer simpleTracer = new SimpleTracer();