From ee51f26755b2ef429217dd6e630cce8691e1fe80 Mon Sep 17 00:00:00 2001 From: Brian Clozel Date: Mon, 10 May 2021 10:41:57 +0200 Subject: [PATCH] Revisit GraphQL metrics Prior to this commit, we introduced a basic implementation for GraphQL metrics with Spring GraphQL. The main problem was that we were tracking the GraphQL queries themselves and those are good candidates for tags cardinality explosion. This commit revisits the metrics arrangement and introduces: * `graphql.request` as the main metric; this tracks the execution of a single request (i.e. multiple queries) * `graphql.datafetcher` for tracking data fetching operations. * `graphql.error` for counting errors. Closes gh-19 --- README.md | 29 +++++- graphql-spring-boot-starter/build.gradle | 2 + .../metrics/DefaultGraphQLTagsProvider.java | 48 ++++++---- .../GraphQLMetricsAutoConfiguration.java | 9 +- .../GraphQLMetricsInstrumentation.java | 89 ++++++++++++++---- .../boot/actuate/metrics/GraphQLTags.java | 94 +++++++++++++++++++ .../metrics/GraphQLTagsContributor.java | 33 +++++++ .../actuate/metrics/GraphQLTagsProvider.java | 12 ++- .../actuate/metrics/GraphQLTagsTests.java | 92 ++++++++++++++++++ .../support/ReactorDataFetcherAdapter.java | 2 - 10 files changed, 366 insertions(+), 44 deletions(-) create mode 100644 graphql-spring-boot-starter/src/main/java/org/springframework/graphql/boot/actuate/metrics/GraphQLTags.java create mode 100644 graphql-spring-boot-starter/src/main/java/org/springframework/graphql/boot/actuate/metrics/GraphQLTagsContributor.java create mode 100644 graphql-spring-boot-starter/src/test/java/org/springframework/graphql/boot/actuate/metrics/GraphQLTagsTests.java diff --git a/README.md b/README.md index daa13e89..7a9624aa 100644 --- a/README.md +++ b/README.md @@ -153,14 +153,39 @@ A custom `WebInterceptor` can, for example, change the HTTP request/response hea ### Metrics -If the `spring-boot-starter-actuator` dependency is on the classpath, metrics will be collected for GraphQL queries. +If the `spring-boot-starter-actuator` dependency is on the classpath, metrics will be collected for GraphQL requests. You can see those metrics by exposing the metrics endpoint with `application.properties`: ```properties management.endpoints.web.exposure.include=health,metrics,info ``` + +#### GraphQL Request (timer) -You can then check those metrics at `http://localhost:8080/actuator/metrics/graphql.query`. +A Request metric timer is available at `/actuator/metrics/graphql.request`. +| Tag | Description | Sample values | +|---------|-----------------|--------------------| +| outcome | Request outcome | "SUCCESS", "ERROR" | + + +#### GraphQL Data Fetcher (timer) + +A Data Fetcher metric timer is available at `/actuator/metrics/graphql.datafetcher`. + +| Tag | Description | Sample values | +|---------|-----------------------|--------------------| +| path | data fetcher path | "Query.project" | +| outcome | data fetching outcome | "SUCCESS", "ERROR" | + + +#### GraphQL Error (counter) + +A counter metric counter is available at `/actuator/metrics/graphql.error`. + +| Tag | Description | Sample values | +|-----------|-----------------|-------------------------| +| errorType | error type | "DataFetchingException" | +| errorPath | error JSON Path | "$.project" | ## Sample applications diff --git a/graphql-spring-boot-starter/build.gradle b/graphql-spring-boot-starter/build.gradle index f0c72f2c..48620129 100644 --- a/graphql-spring-boot-starter/build.gradle +++ b/graphql-spring-boot-starter/build.gradle @@ -43,6 +43,7 @@ dependencies { annotationProcessor 'org.springframework.boot:spring-boot-configuration-processor' annotationProcessor 'org.springframework.boot:spring-boot-autoconfigure-processor' + testImplementation project(':spring-graphql-test') testImplementation 'com.fasterxml.jackson.core:jackson-databind' testImplementation 'org.springframework:spring-webflux' testImplementation 'org.springframework:spring-webmvc' @@ -52,6 +53,7 @@ dependencies { testImplementation 'org.apache.tomcat.embed:tomcat-embed-core' testImplementation 'org.apache.tomcat.embed:tomcat-embed-websocket' testImplementation 'org.springframework.boot:spring-boot-actuator-autoconfigure' + testImplementation 'io.micrometer:micrometer-core' testImplementation 'org.springframework.boot:spring-boot-starter-test' } diff --git a/graphql-spring-boot-starter/src/main/java/org/springframework/graphql/boot/actuate/metrics/DefaultGraphQLTagsProvider.java b/graphql-spring-boot-starter/src/main/java/org/springframework/graphql/boot/actuate/metrics/DefaultGraphQLTagsProvider.java index a488c1e9..8ad8282a 100644 --- a/graphql-spring-boot-starter/src/main/java/org/springframework/graphql/boot/actuate/metrics/DefaultGraphQLTagsProvider.java +++ b/graphql-spring-boot-starter/src/main/java/org/springframework/graphql/boot/actuate/metrics/DefaultGraphQLTagsProvider.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2021 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,34 +16,48 @@ package org.springframework.graphql.boot.actuate.metrics; -import graphql.ErrorClassification; -import graphql.ErrorType; +import java.util.List; + import graphql.ExecutionResult; +import graphql.GraphQLError; import graphql.execution.instrumentation.parameters.InstrumentationExecutionParameters; +import graphql.execution.instrumentation.parameters.InstrumentationFieldFetchParameters; +import graphql.schema.DataFetcher; import io.micrometer.core.instrument.Tag; import io.micrometer.core.instrument.Tags; public class DefaultGraphQLTagsProvider implements GraphQLTagsProvider { - private static final Tag OUTCOME_SUCCESS = Tag.of("outcome", "SUCCESS"); + private final List contributors; - private static final Tag OUTCOME_ERROR = Tag.of("outcome", "ERROR"); + public DefaultGraphQLTagsProvider(List contributors) { + this.contributors = contributors; + } @Override - public Iterable getTags(InstrumentationExecutionParameters parameters, ExecutionResult result, Throwable exception) { - Tags tags = Tags.of(Tag.of("query", parameters.getQuery())); - if (result.isDataPresent()) { - tags = tags.and(OUTCOME_SUCCESS); + public Iterable getExecutionTags(InstrumentationExecutionParameters parameters, ExecutionResult result, Throwable exception) { + Tags tags = Tags.of(GraphQLTags.executionOutcome(result, exception)); + for (GraphQLTagsContributor contributor : this.contributors) { + tags = tags.and(contributor.getExecutionTags(parameters, result, exception)); } - else { - tags = tags.and(OUTCOME_ERROR); - if (!result.getErrors().isEmpty()) { - ErrorClassification errorClassification = result.getErrors().get(0).getErrorType(); - if (errorClassification instanceof ErrorType) { - tags = tags.and(Tag.of("errorType", ((ErrorType) errorClassification).name())); - } - } + return tags; + } + + @Override + public Iterable getErrorTags(InstrumentationExecutionParameters parameters, GraphQLError error) { + Tags tags = Tags.of(GraphQLTags.errorType(error), GraphQLTags.errorPath(error)); + for (GraphQLTagsContributor contributor : this.contributors) { + tags = tags.and(contributor.getErrorTags(parameters, error)); + } + return tags; + } + + @Override + public Iterable getDataFetchingTags(DataFetcher dataFetcher, InstrumentationFieldFetchParameters parameters, Throwable exception) { + Tags tags = Tags.of(GraphQLTags.dataFetchingOutcome(exception), GraphQLTags.dataFetchingPath(parameters)); + for (GraphQLTagsContributor contributor : this.contributors) { + tags = tags.and(contributor.getDataFetchingTags(dataFetcher, parameters, exception)); } return tags; } diff --git a/graphql-spring-boot-starter/src/main/java/org/springframework/graphql/boot/actuate/metrics/GraphQLMetricsAutoConfiguration.java b/graphql-spring-boot-starter/src/main/java/org/springframework/graphql/boot/actuate/metrics/GraphQLMetricsAutoConfiguration.java index 580b0929..2d5a91fd 100644 --- a/graphql-spring-boot-starter/src/main/java/org/springframework/graphql/boot/actuate/metrics/GraphQLMetricsAutoConfiguration.java +++ b/graphql-spring-boot-starter/src/main/java/org/springframework/graphql/boot/actuate/metrics/GraphQLMetricsAutoConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2021 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,8 +16,11 @@ package org.springframework.graphql.boot.actuate.metrics; +import java.util.stream.Collectors; + import io.micrometer.core.instrument.MeterRegistry; +import org.springframework.beans.factory.ObjectProvider; import org.springframework.boot.actuate.autoconfigure.metrics.CompositeMeterRegistryAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.metrics.MetricsAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.metrics.export.simple.SimpleMetricsExportAutoConfiguration; @@ -42,8 +45,8 @@ public class GraphQLMetricsAutoConfiguration { @Bean @ConditionalOnMissingBean(GraphQLTagsProvider.class) - public DefaultGraphQLTagsProvider graphQLTagsProvider() { - return new DefaultGraphQLTagsProvider(); + public DefaultGraphQLTagsProvider graphQLTagsProvider(ObjectProvider contributors) { + return new DefaultGraphQLTagsProvider(contributors.orderedStream().collect(Collectors.toList())); } @Bean diff --git a/graphql-spring-boot-starter/src/main/java/org/springframework/graphql/boot/actuate/metrics/GraphQLMetricsInstrumentation.java b/graphql-spring-boot-starter/src/main/java/org/springframework/graphql/boot/actuate/metrics/GraphQLMetricsInstrumentation.java index 91729cef..87079185 100644 --- a/graphql-spring-boot-starter/src/main/java/org/springframework/graphql/boot/actuate/metrics/GraphQLMetricsInstrumentation.java +++ b/graphql-spring-boot-starter/src/main/java/org/springframework/graphql/boot/actuate/metrics/GraphQLMetricsInstrumentation.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2021 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,12 +16,16 @@ package org.springframework.graphql.boot.actuate.metrics; +import java.util.concurrent.CompletionStage; + import graphql.ExecutionResult; import graphql.execution.instrumentation.InstrumentationContext; import graphql.execution.instrumentation.InstrumentationState; import graphql.execution.instrumentation.SimpleInstrumentation; import graphql.execution.instrumentation.SimpleInstrumentationContext; import graphql.execution.instrumentation.parameters.InstrumentationExecutionParameters; +import graphql.execution.instrumentation.parameters.InstrumentationFieldFetchParameters; +import graphql.schema.DataFetcher; import io.micrometer.core.instrument.MeterRegistry; import io.micrometer.core.instrument.Tag; import io.micrometer.core.instrument.Timer; @@ -44,40 +48,89 @@ public class GraphQLMetricsInstrumentation extends SimpleInstrumentation { @Override public InstrumentationState createState() { - return new MetricsInstrumentationState(this.registry); + return new RequestMetricsInstrumentationState(this.autoTimer, this.registry); } @Override public InstrumentationContext beginExecution(InstrumentationExecutionParameters parameters) { - MetricsInstrumentationState state = parameters.getInstrumentationState(); - state.startTimer(); + if (this.autoTimer.isEnabled()) { + RequestMetricsInstrumentationState state = parameters.getInstrumentationState(); + state.startTimer(); + return new SimpleInstrumentationContext() { + @Override + public void onCompleted(ExecutionResult result, Throwable exc) { + Iterable tags = tagsProvider.getExecutionTags(parameters, result, exc); + state.tags(tags).stopTimer(); + if (!result.getErrors().isEmpty()) { + result.getErrors().forEach(error -> { + registry.counter("graphql.error", tagsProvider.getErrorTags(parameters, error)).increment(); + }); + } + } + }; + } + return super.beginExecution(parameters); + } - return new SimpleInstrumentationContext() { - @Override - public void onCompleted(ExecutionResult result, Throwable exc) { - Iterable tags = tagsProvider.getTags(parameters, result, exc); - state.stopTimer(autoTimer.builder("graphql.query").tags(tags)); - } - }; + @Override + public DataFetcher instrumentDataFetcher(DataFetcher dataFetcher, InstrumentationFieldFetchParameters parameters) { + if (this.autoTimer.isEnabled() && !parameters.isTrivialDataFetcher()) { + return (environment) -> { + Timer.Sample sample = Timer.start(this.registry); + try { + Object value = dataFetcher.get(environment); + if (value instanceof CompletionStage) { + CompletionStage completion = (CompletionStage) value; + return completion.whenComplete((result, error) -> { + recordDataFetcherMetric(sample, dataFetcher, parameters, error); + }); + } + else { + recordDataFetcherMetric(sample, dataFetcher, parameters, null); + return value; + } + + } + catch (Throwable throwable) { + recordDataFetcherMetric(sample, dataFetcher, parameters, throwable); + throw throwable; + } + }; + } + return super.instrumentDataFetcher(dataFetcher, parameters); + } + + private void recordDataFetcherMetric(Timer.Sample sample, DataFetcher dataFetcher, InstrumentationFieldFetchParameters parameters, Throwable throwable) { + Timer.Builder timer = this.autoTimer.builder("graphql.datafetcher"); + timer.tags(this.tagsProvider.getDataFetchingTags(dataFetcher, parameters, throwable)); + sample.stop(timer.register(this.registry)); } - static class MetricsInstrumentationState implements InstrumentationState { + static class RequestMetricsInstrumentationState implements InstrumentationState { private final MeterRegistry registry; - private Timer.Sample timerSample; + private final Timer.Builder timer; - MetricsInstrumentationState(MeterRegistry registry) { + private Timer.Sample sample; + + RequestMetricsInstrumentationState(AutoTimer autoTimer, MeterRegistry registry) { + this.timer = autoTimer.builder("graphql.request"); this.registry = registry; } - public void startTimer() { - this.timerSample = Timer.start(this.registry); + public RequestMetricsInstrumentationState tags(Iterable tags) { + this.timer.tags(tags); + return this; } - public void stopTimer(Timer.Builder timer) { - this.timerSample.stop(timer.register(this.registry)); + public void startTimer() { + this.sample = Timer.start(this.registry); + } + + public void stopTimer() { + this.sample.stop(this.timer.register(this.registry)); } } diff --git a/graphql-spring-boot-starter/src/main/java/org/springframework/graphql/boot/actuate/metrics/GraphQLTags.java b/graphql-spring-boot-starter/src/main/java/org/springframework/graphql/boot/actuate/metrics/GraphQLTags.java new file mode 100644 index 00000000..8e916b66 --- /dev/null +++ b/graphql-spring-boot-starter/src/main/java/org/springframework/graphql/boot/actuate/metrics/GraphQLTags.java @@ -0,0 +1,94 @@ +/* + * Copyright 2020-2021 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.boot.actuate.metrics; + +import java.util.List; + +import graphql.ErrorClassification; +import graphql.ErrorType; +import graphql.ExecutionResult; +import graphql.GraphQLError; +import graphql.execution.ExecutionStepInfo; +import graphql.execution.instrumentation.parameters.InstrumentationFieldFetchParameters; +import graphql.schema.GraphQLObjectType; +import io.micrometer.core.instrument.Tag; + +/** + * Factory methods for Tags associated with a GraphQL requests. + * + * @author Brian Clozel + */ +public final class GraphQLTags { + + private static final Tag OUTCOME_SUCCESS = Tag.of("outcome", "SUCCESS"); + + private static final Tag OUTCOME_ERROR = Tag.of("outcome", "ERROR"); + + private static final Tag UNKNOWN_ERRORTYPE = Tag.of("errorType", "UNKNOWN"); + + public static Tag executionOutcome(ExecutionResult result, Throwable exception) { + if (exception == null && result.getErrors().isEmpty()) { + return OUTCOME_SUCCESS; + } + else { + return OUTCOME_ERROR; + } + } + + public static Tag errorType(GraphQLError error) { + ErrorClassification errorType = error.getErrorType(); + if (errorType instanceof ErrorType) { + return Tag.of("errorType", ((ErrorType) errorType).name()); + } + return UNKNOWN_ERRORTYPE; + } + + public static Tag errorPath(GraphQLError error) { + StringBuilder builder = new StringBuilder(); + List pathSegments = error.getPath(); + if (!pathSegments.isEmpty()) { + builder.append('$'); + for (Object segment : pathSegments) { + try { + int index = Integer.parseUnsignedInt(segment.toString()); + builder.append("[*]"); + } + catch (NumberFormatException exc) { + builder.append('.'); + builder.append(segment); + } + } + } + return Tag.of("errorPath", builder.toString()); + } + + public static Tag dataFetchingOutcome(Throwable exception) { + return (exception == null) ? OUTCOME_SUCCESS : OUTCOME_ERROR; + } + + public static Tag dataFetchingPath(InstrumentationFieldFetchParameters parameters) { + ExecutionStepInfo executionStepInfo = parameters.getExecutionStepInfo(); + StringBuilder dataFetchingType = new StringBuilder(); + if (executionStepInfo.hasParent() && + executionStepInfo.getParent().getType() instanceof GraphQLObjectType) { + dataFetchingType.append(((GraphQLObjectType) executionStepInfo.getParent().getType()).getName()); + dataFetchingType.append('.'); + } + dataFetchingType.append(executionStepInfo.getPath().getSegmentName()); + return Tag.of("path", dataFetchingType.toString()); + } +} diff --git a/graphql-spring-boot-starter/src/main/java/org/springframework/graphql/boot/actuate/metrics/GraphQLTagsContributor.java b/graphql-spring-boot-starter/src/main/java/org/springframework/graphql/boot/actuate/metrics/GraphQLTagsContributor.java new file mode 100644 index 00000000..8da71ed9 --- /dev/null +++ b/graphql-spring-boot-starter/src/main/java/org/springframework/graphql/boot/actuate/metrics/GraphQLTagsContributor.java @@ -0,0 +1,33 @@ +/* + * Copyright 2002-2021 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.boot.actuate.metrics; + +import graphql.ExecutionResult; +import graphql.GraphQLError; +import graphql.execution.instrumentation.parameters.InstrumentationExecutionParameters; +import graphql.execution.instrumentation.parameters.InstrumentationFieldFetchParameters; +import graphql.schema.DataFetcher; +import io.micrometer.core.instrument.Tag; + +public interface GraphQLTagsContributor { + + Iterable getExecutionTags(InstrumentationExecutionParameters parameters, ExecutionResult result, Throwable exception); + + Iterable getErrorTags(InstrumentationExecutionParameters parameters, GraphQLError error); + + Iterable getDataFetchingTags(DataFetcher dataFetcher, InstrumentationFieldFetchParameters parameters, Throwable exception); +} diff --git a/graphql-spring-boot-starter/src/main/java/org/springframework/graphql/boot/actuate/metrics/GraphQLTagsProvider.java b/graphql-spring-boot-starter/src/main/java/org/springframework/graphql/boot/actuate/metrics/GraphQLTagsProvider.java index 0e410a80..79e30d6c 100644 --- a/graphql-spring-boot-starter/src/main/java/org/springframework/graphql/boot/actuate/metrics/GraphQLTagsProvider.java +++ b/graphql-spring-boot-starter/src/main/java/org/springframework/graphql/boot/actuate/metrics/GraphQLTagsProvider.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2021 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. @@ -17,10 +17,18 @@ package org.springframework.graphql.boot.actuate.metrics; import graphql.ExecutionResult; +import graphql.GraphQLError; import graphql.execution.instrumentation.parameters.InstrumentationExecutionParameters; +import graphql.execution.instrumentation.parameters.InstrumentationFieldFetchParameters; +import graphql.schema.DataFetcher; import io.micrometer.core.instrument.Tag; public interface GraphQLTagsProvider { - Iterable getTags(InstrumentationExecutionParameters parameters, ExecutionResult result, Throwable exception); + Iterable getExecutionTags(InstrumentationExecutionParameters parameters, ExecutionResult result, Throwable exception); + + Iterable getErrorTags(InstrumentationExecutionParameters parameters, GraphQLError error); + + Iterable getDataFetchingTags(DataFetcher dataFetcher, InstrumentationFieldFetchParameters parameters, Throwable exception); + } diff --git a/graphql-spring-boot-starter/src/test/java/org/springframework/graphql/boot/actuate/metrics/GraphQLTagsTests.java b/graphql-spring-boot-starter/src/test/java/org/springframework/graphql/boot/actuate/metrics/GraphQLTagsTests.java new file mode 100644 index 00000000..29823bef --- /dev/null +++ b/graphql-spring-boot-starter/src/test/java/org/springframework/graphql/boot/actuate/metrics/GraphQLTagsTests.java @@ -0,0 +1,92 @@ +/* + * Copyright 2020-2021 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.boot.actuate.metrics; + +import java.util.Arrays; + +import graphql.ErrorType; +import graphql.ExecutionResultImpl; +import graphql.GraphQLError; +import graphql.GraphqlErrorBuilder; +import io.micrometer.core.instrument.Tag; +import org.junit.jupiter.api.Test; + +import org.springframework.graphql.test.tester.TestExecutionResult; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link GraphQLTags} + * + * @author Brian Clozel + */ +class GraphQLTagsTests { + + @Test + void executionOutcomeShouldSucceed() { + Tag outcomeTag = GraphQLTags.executionOutcome(new TestExecutionResult(), null); + assertThat(outcomeTag.getValue()).isEqualTo("SUCCESS"); + } + + @Test + void executionOutcomeShouldErrorWhenExceptionThrown() { + Tag outcomeTag = GraphQLTags.executionOutcome(new TestExecutionResult(), new IllegalArgumentException("test error")); + assertThat(outcomeTag.getValue()).isEqualTo("ERROR"); + } + + @Test + void executionOutcomeShouldErrorWhenResponseErrors() { + ExecutionResultImpl.Builder builder = new ExecutionResultImpl.Builder(); + builder.addError(GraphqlErrorBuilder.newError().message("Invalid query").build()); + Tag outcomeTag = GraphQLTags.executionOutcome(builder.build(), null); + assertThat(outcomeTag.getValue()).isEqualTo("ERROR"); + } + + @Test + void errorTypeShouldBeDefinedIfPresent() { + GraphQLError error = GraphqlErrorBuilder.newError().errorType(ErrorType.DataFetchingException).message("test error").build(); + Tag errorTypeTag = GraphQLTags.errorType(error); + assertThat(errorTypeTag.getValue()).isEqualTo("DataFetchingException"); + } + + @Test + void errorPathShouldUseJsonPathFormat() { + GraphQLError error = GraphqlErrorBuilder.newError().path(Arrays.asList("project", "name")).message("test error").build(); + Tag errorPathTag = GraphQLTags.errorPath(error); + assertThat(errorPathTag.getValue()).isEqualTo("$.project.name"); + } + + @Test + void errorPathShouldUseJsonPathFormatForIndices() { + GraphQLError error = GraphqlErrorBuilder.newError().path(Arrays.asList("issues", "42", "title")).message("test error").build(); + Tag errorPathTag = GraphQLTags.errorPath(error); + assertThat(errorPathTag.getValue()).isEqualTo("$.issues[*].title"); + } + + @Test + void dataFetchingOutcomeShouldBeSuccessfulIfNoException() { + Tag fetchingOutcomeTag = GraphQLTags.dataFetchingOutcome(null); + assertThat(fetchingOutcomeTag.getValue()).isEqualTo("SUCCESS"); + } + + @Test + void dataFetchingOutcomeShouldBeErrorIfException() { + Tag fetchingOutcomeTag = GraphQLTags.dataFetchingOutcome(new IllegalStateException("error state")); + assertThat(fetchingOutcomeTag.getValue()).isEqualTo("ERROR"); + } + +} \ No newline at end of file diff --git a/spring-graphql/src/main/java/org/springframework/graphql/support/ReactorDataFetcherAdapter.java b/spring-graphql/src/main/java/org/springframework/graphql/support/ReactorDataFetcherAdapter.java index 5da3a79e..42f2e739 100644 --- a/spring-graphql/src/main/java/org/springframework/graphql/support/ReactorDataFetcherAdapter.java +++ b/spring-graphql/src/main/java/org/springframework/graphql/support/ReactorDataFetcherAdapter.java @@ -123,8 +123,6 @@ class ReactorDataFetcherAdapter implements DataFetcher { Method method = ClassUtils.getMethod(dataFetcher.getClass(), "get", DataFetchingEnvironment.class); method = ClassUtils.getMostSpecificMethod(method, dataFetcher.getClass()); - Class returnType = method.getReturnType(); - System.out.println(returnType.getName()); dataFetcher = new ReactorDataFetcherAdapter(dataFetcher, parent.getName().equals("Subscription")); codeRegistry.dataFetcher(parent, fieldDefinition, dataFetcher);