Use GraphQL Observation instrumentation
This commit migrates the existing metrics support (added in #29140) to the new `Observation` instrumentation contributed in spring-projects/spring-graphql#501. We cannot have a smoother migration path here as the instrumentation does not use the same context information for metadata extraction. Closes gh-32794pull/32812/head
parent
09f3d459c6
commit
9948fc3e39
@ -1,68 +0,0 @@
|
||||
/*
|
||||
* Copyright 2012-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.boot.actuate.autoconfigure.metrics.graphql;
|
||||
|
||||
import graphql.GraphQL;
|
||||
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.MetricsProperties;
|
||||
import org.springframework.boot.actuate.autoconfigure.metrics.PropertiesAutoTimer;
|
||||
import org.springframework.boot.actuate.autoconfigure.metrics.export.simple.SimpleMetricsExportAutoConfiguration;
|
||||
import org.springframework.boot.actuate.metrics.graphql.DefaultGraphQlTagsProvider;
|
||||
import org.springframework.boot.actuate.metrics.graphql.GraphQlMetricsInstrumentation;
|
||||
import org.springframework.boot.actuate.metrics.graphql.GraphQlTagsContributor;
|
||||
import org.springframework.boot.actuate.metrics.graphql.GraphQlTagsProvider;
|
||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.graphql.execution.GraphQlSource;
|
||||
|
||||
/**
|
||||
* {@link EnableAutoConfiguration Auto-configuration} for instrumentation of Spring
|
||||
* GraphQL endpoints.
|
||||
*
|
||||
* @author Brian Clozel
|
||||
* @since 2.7.0
|
||||
*/
|
||||
@AutoConfiguration(after = { MetricsAutoConfiguration.class, CompositeMeterRegistryAutoConfiguration.class,
|
||||
SimpleMetricsExportAutoConfiguration.class })
|
||||
@ConditionalOnBean(MeterRegistry.class)
|
||||
@ConditionalOnClass({ GraphQL.class, GraphQlSource.class })
|
||||
@EnableConfigurationProperties(MetricsProperties.class)
|
||||
public class GraphQlMetricsAutoConfiguration {
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(GraphQlTagsProvider.class)
|
||||
public DefaultGraphQlTagsProvider graphQlTagsProvider(ObjectProvider<GraphQlTagsContributor> contributors) {
|
||||
return new DefaultGraphQlTagsProvider(contributors.orderedStream().toList());
|
||||
}
|
||||
|
||||
@Bean
|
||||
public GraphQlMetricsInstrumentation graphQlMetricsInstrumentation(MeterRegistry meterRegistry,
|
||||
GraphQlTagsProvider tagsProvider, MetricsProperties properties) {
|
||||
return new GraphQlMetricsInstrumentation(meterRegistry, tagsProvider,
|
||||
new PropertiesAutoTimer(properties.getGraphql().getAutotime()));
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,53 @@
|
||||
/*
|
||||
* Copyright 2012-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.boot.actuate.autoconfigure.observation.graphql;
|
||||
|
||||
import graphql.GraphQL;
|
||||
import io.micrometer.observation.Observation;
|
||||
import io.micrometer.observation.ObservationRegistry;
|
||||
|
||||
import org.springframework.boot.actuate.autoconfigure.observation.ObservationAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.graphql.execution.GraphQlSource;
|
||||
import org.springframework.graphql.observation.GraphQlObservationInstrumentation;
|
||||
|
||||
/**
|
||||
* {@link EnableAutoConfiguration Auto-configuration} for instrumentation of Spring
|
||||
* GraphQL endpoints.
|
||||
*
|
||||
* @author Brian Clozel
|
||||
* @since 3.0.0
|
||||
*/
|
||||
@AutoConfiguration(after = ObservationAutoConfiguration.class)
|
||||
@ConditionalOnBean(ObservationRegistry.class)
|
||||
@ConditionalOnClass({ GraphQL.class, GraphQlSource.class, Observation.class })
|
||||
@SuppressWarnings("removal")
|
||||
public class GraphQlObservationAutoConfiguration {
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public GraphQlObservationInstrumentation graphQlObservationInstrumentation(
|
||||
ObservationRegistry observationRegistry) {
|
||||
return new GraphQlObservationInstrumentation(observationRegistry);
|
||||
}
|
||||
|
||||
}
|
4
spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/graphql/package-info.java → spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/observation/graphql/package-info.java
4
spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/graphql/package-info.java → spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/observation/graphql/package-info.java
@ -1,98 +0,0 @@
|
||||
/*
|
||||
* Copyright 2012-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.boot.actuate.autoconfigure.metrics.graphql;
|
||||
|
||||
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 org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.boot.actuate.autoconfigure.metrics.test.MetricsRun;
|
||||
import org.springframework.boot.actuate.metrics.graphql.DefaultGraphQlTagsProvider;
|
||||
import org.springframework.boot.actuate.metrics.graphql.GraphQlMetricsInstrumentation;
|
||||
import org.springframework.boot.actuate.metrics.graphql.GraphQlTagsProvider;
|
||||
import org.springframework.boot.autoconfigure.AutoConfigurations;
|
||||
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Tests for {@link GraphQlMetricsAutoConfiguration}.
|
||||
*
|
||||
* @author Brian Clozel
|
||||
*/
|
||||
class GraphQlMetricsAutoConfigurationTests {
|
||||
|
||||
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner().with(MetricsRun.simple())
|
||||
.withConfiguration(AutoConfigurations.of(GraphQlMetricsAutoConfiguration.class));
|
||||
|
||||
@Test
|
||||
void backsOffWhenMeterRegistryIsMissing() {
|
||||
new ApplicationContextRunner().withConfiguration(AutoConfigurations.of(GraphQlMetricsAutoConfiguration.class))
|
||||
.run((context) -> assertThat(context).doesNotHaveBean(DefaultGraphQlTagsProvider.class)
|
||||
.doesNotHaveBean(GraphQlMetricsInstrumentation.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void definesTagsProviderAndInstrumentationWhenMeterRegistryIsPresent() {
|
||||
this.contextRunner.run((context) -> assertThat(context).hasSingleBean(DefaultGraphQlTagsProvider.class)
|
||||
.hasSingleBean(GraphQlMetricsInstrumentation.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void tagsProviderBacksOffIfAlreadyPresent() {
|
||||
this.contextRunner.withUserConfiguration(TagsProviderConfiguration.class).run((context) -> assertThat(context)
|
||||
.doesNotHaveBean(DefaultGraphQlTagsProvider.class).hasSingleBean(TestGraphQlTagsProvider.class));
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
static class TagsProviderConfiguration {
|
||||
|
||||
@Bean
|
||||
TestGraphQlTagsProvider tagsProvider() {
|
||||
return new TestGraphQlTagsProvider();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static class TestGraphQlTagsProvider implements GraphQlTagsProvider {
|
||||
|
||||
@Override
|
||||
public Iterable<Tag> getExecutionTags(InstrumentationExecutionParameters parameters, ExecutionResult result,
|
||||
Throwable exception) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterable<Tag> getErrorTags(InstrumentationExecutionParameters parameters, GraphQLError error) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterable<Tag> getDataFetchingTags(DataFetcher<?> dataFetcher,
|
||||
InstrumentationFieldFetchParameters parameters, Throwable exception) {
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,71 @@
|
||||
/*
|
||||
* Copyright 2012-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.boot.actuate.autoconfigure.observation.graphql;
|
||||
|
||||
import io.micrometer.observation.ObservationRegistry;
|
||||
import io.micrometer.observation.tck.TestObservationRegistry;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.boot.autoconfigure.AutoConfigurations;
|
||||
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.graphql.observation.GraphQlObservationInstrumentation;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Tests for {@link GraphQlObservationAutoConfiguration}.
|
||||
*
|
||||
* @author Brian Clozel
|
||||
*/
|
||||
class GraphQlObservationAutoConfigurationTests {
|
||||
|
||||
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
|
||||
.withBean(TestObservationRegistry.class, TestObservationRegistry::create)
|
||||
.withConfiguration(AutoConfigurations.of(GraphQlObservationAutoConfiguration.class));
|
||||
|
||||
@Test
|
||||
void backsOffWhenObservationRegistryIsMissing() {
|
||||
new ApplicationContextRunner()
|
||||
.withConfiguration(AutoConfigurations.of(GraphQlObservationAutoConfiguration.class))
|
||||
.run((context) -> assertThat(context).doesNotHaveBean(GraphQlObservationInstrumentation.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void definesInstrumentationWhenObservationRegistryIsPresent() {
|
||||
this.contextRunner.run((context) -> assertThat(context).hasSingleBean(GraphQlObservationInstrumentation.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void instrumentationBacksOffIfAlreadyPresent() {
|
||||
this.contextRunner.withUserConfiguration(InstrumentationConfiguration.class)
|
||||
.run((context) -> assertThat(context).hasSingleBean(GraphQlObservationInstrumentation.class)
|
||||
.hasBean("customInstrumentation"));
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
static class InstrumentationConfiguration {
|
||||
|
||||
@Bean
|
||||
GraphQlObservationInstrumentation customInstrumentation(ObservationRegistry registry) {
|
||||
return new GraphQlObservationInstrumentation(registry);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -1,77 +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.boot.actuate.metrics.graphql;
|
||||
|
||||
import java.util.Collections;
|
||||
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;
|
||||
|
||||
/**
|
||||
* Default implementation for {@link GraphQlTagsProvider}.
|
||||
*
|
||||
* @author Brian Clozel
|
||||
* @since 2.7.0
|
||||
*/
|
||||
public class DefaultGraphQlTagsProvider implements GraphQlTagsProvider {
|
||||
|
||||
private final List<GraphQlTagsContributor> contributors;
|
||||
|
||||
public DefaultGraphQlTagsProvider(List<GraphQlTagsContributor> contributors) {
|
||||
this.contributors = contributors;
|
||||
}
|
||||
|
||||
public DefaultGraphQlTagsProvider() {
|
||||
this(Collections.emptyList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterable<Tag> 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));
|
||||
}
|
||||
return tags;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterable<Tag> 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<Tag> 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;
|
||||
}
|
||||
|
||||
}
|
@ -1,166 +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.boot.actuate.metrics.graphql;
|
||||
|
||||
import java.util.concurrent.CompletionStage;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
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.InstrumentationCreateStateParameters;
|
||||
import graphql.execution.instrumentation.parameters.InstrumentationExecutionParameters;
|
||||
import graphql.execution.instrumentation.parameters.InstrumentationFieldFetchParameters;
|
||||
import graphql.schema.DataFetcher;
|
||||
import io.micrometer.core.instrument.DistributionSummary;
|
||||
import io.micrometer.core.instrument.MeterRegistry;
|
||||
import io.micrometer.core.instrument.Tag;
|
||||
import io.micrometer.core.instrument.Timer;
|
||||
|
||||
import org.springframework.boot.actuate.metrics.AutoTimer;
|
||||
|
||||
/**
|
||||
* Micrometer-based {@link SimpleInstrumentation}.
|
||||
*
|
||||
* @author Brian Clozel
|
||||
* @since 2.7.0
|
||||
*/
|
||||
public class GraphQlMetricsInstrumentation extends SimpleInstrumentation {
|
||||
|
||||
private final MeterRegistry registry;
|
||||
|
||||
private final GraphQlTagsProvider tagsProvider;
|
||||
|
||||
private final AutoTimer autoTimer;
|
||||
|
||||
private final DistributionSummary dataFetchingSummary;
|
||||
|
||||
public GraphQlMetricsInstrumentation(MeterRegistry registry, GraphQlTagsProvider tagsProvider,
|
||||
AutoTimer autoTimer) {
|
||||
this.registry = registry;
|
||||
this.tagsProvider = tagsProvider;
|
||||
this.autoTimer = autoTimer;
|
||||
this.dataFetchingSummary = DistributionSummary.builder("graphql.request.datafetch.count").baseUnit("calls")
|
||||
.description("Count of DataFetcher calls per request.").register(this.registry);
|
||||
}
|
||||
|
||||
@Override
|
||||
public InstrumentationState createState(InstrumentationCreateStateParameters parameters) {
|
||||
return new RequestMetricsInstrumentationState(this.autoTimer, this.registry);
|
||||
}
|
||||
|
||||
@Override
|
||||
public InstrumentationContext<ExecutionResult> beginExecution(InstrumentationExecutionParameters parameters,
|
||||
InstrumentationState state) {
|
||||
if (this.autoTimer.isEnabled() && state instanceof RequestMetricsInstrumentationState instrumentationState) {
|
||||
instrumentationState.startTimer();
|
||||
return new SimpleInstrumentationContext<ExecutionResult>() {
|
||||
@Override
|
||||
public void onCompleted(ExecutionResult result, Throwable exc) {
|
||||
Iterable<Tag> tags = GraphQlMetricsInstrumentation.this.tagsProvider.getExecutionTags(parameters,
|
||||
result, exc);
|
||||
instrumentationState.tags(tags).stopTimer();
|
||||
if (!result.getErrors().isEmpty()) {
|
||||
result.getErrors()
|
||||
.forEach((error) -> GraphQlMetricsInstrumentation.this.registry.counter("graphql.error",
|
||||
GraphQlMetricsInstrumentation.this.tagsProvider.getErrorTags(parameters, error))
|
||||
.increment());
|
||||
}
|
||||
GraphQlMetricsInstrumentation.this.dataFetchingSummary
|
||||
.record(instrumentationState.getDataFetchingCount());
|
||||
}
|
||||
};
|
||||
}
|
||||
return super.beginExecution(parameters);
|
||||
}
|
||||
|
||||
@Override
|
||||
public DataFetcher<?> instrumentDataFetcher(DataFetcher<?> dataFetcher,
|
||||
InstrumentationFieldFetchParameters parameters, InstrumentationState state) {
|
||||
if (this.autoTimer.isEnabled() && !parameters.isTrivialDataFetcher()
|
||||
&& state instanceof RequestMetricsInstrumentationState instrumentationState) {
|
||||
return (environment) -> {
|
||||
Timer.Sample sample = Timer.start(this.registry);
|
||||
try {
|
||||
Object value = dataFetcher.get(environment);
|
||||
if (value instanceof CompletionStage<?> completion) {
|
||||
return completion.whenComplete((result, error) -> recordDataFetcherMetric(sample,
|
||||
instrumentationState, dataFetcher, parameters, error));
|
||||
}
|
||||
else {
|
||||
recordDataFetcherMetric(sample, instrumentationState, dataFetcher, parameters, null);
|
||||
return value;
|
||||
}
|
||||
}
|
||||
catch (Throwable throwable) {
|
||||
recordDataFetcherMetric(sample, instrumentationState, dataFetcher, parameters, throwable);
|
||||
throw throwable;
|
||||
}
|
||||
};
|
||||
}
|
||||
return super.instrumentDataFetcher(dataFetcher, parameters, state);
|
||||
}
|
||||
|
||||
private void recordDataFetcherMetric(Timer.Sample sample, RequestMetricsInstrumentationState instrumentationState,
|
||||
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));
|
||||
instrumentationState.incrementDataFetchingCount();
|
||||
}
|
||||
|
||||
static class RequestMetricsInstrumentationState implements InstrumentationState {
|
||||
|
||||
private final MeterRegistry registry;
|
||||
|
||||
private final Timer.Builder timer;
|
||||
|
||||
private Timer.Sample sample;
|
||||
|
||||
private final AtomicLong dataFetchingCount = new AtomicLong();
|
||||
|
||||
RequestMetricsInstrumentationState(AutoTimer autoTimer, MeterRegistry registry) {
|
||||
this.timer = autoTimer.builder("graphql.request");
|
||||
this.registry = registry;
|
||||
}
|
||||
|
||||
RequestMetricsInstrumentationState tags(Iterable<Tag> tags) {
|
||||
this.timer.tags(tags);
|
||||
return this;
|
||||
}
|
||||
|
||||
void startTimer() {
|
||||
this.sample = Timer.start(this.registry);
|
||||
}
|
||||
|
||||
void stopTimer() {
|
||||
this.sample.stop(this.timer.register(this.registry));
|
||||
}
|
||||
|
||||
void incrementDataFetchingCount() {
|
||||
this.dataFetchingCount.incrementAndGet();
|
||||
}
|
||||
|
||||
long getDataFetchingCount() {
|
||||
return this.dataFetchingCount.get();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -1,101 +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.boot.actuate.metrics.graphql;
|
||||
|
||||
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;
|
||||
|
||||
import org.springframework.util.CollectionUtils;
|
||||
|
||||
/**
|
||||
* Factory methods for Tags associated with a GraphQL request.
|
||||
*
|
||||
* @author Brian Clozel
|
||||
* @since 2.7.0
|
||||
*/
|
||||
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_ERROR_TYPE = Tag.of("error.type", "UNKNOWN");
|
||||
|
||||
private GraphQlTags() {
|
||||
|
||||
}
|
||||
|
||||
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("error.type", ((ErrorType) errorType).name());
|
||||
}
|
||||
return UNKNOWN_ERROR_TYPE;
|
||||
}
|
||||
|
||||
public static Tag errorPath(GraphQLError error) {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
List<Object> pathSegments = error.getPath();
|
||||
if (!CollectionUtils.isEmpty(pathSegments)) {
|
||||
builder.append('$');
|
||||
for (Object segment : pathSegments) {
|
||||
try {
|
||||
Integer.parseUnsignedInt(segment.toString());
|
||||
builder.append("[*]");
|
||||
}
|
||||
catch (NumberFormatException exc) {
|
||||
builder.append('.');
|
||||
builder.append(segment);
|
||||
}
|
||||
}
|
||||
}
|
||||
return Tag.of("error.path", builder.toString());
|
||||
}
|
||||
|
||||
public static Tag dataFetchingOutcome(Throwable exception) {
|
||||
return (exception != null) ? OUTCOME_ERROR : OUTCOME_SUCCESS;
|
||||
}
|
||||
|
||||
public static Tag dataFetchingPath(InstrumentationFieldFetchParameters parameters) {
|
||||
ExecutionStepInfo executionStepInfo = parameters.getExecutionStepInfo();
|
||||
StringBuilder dataFetchingPath = new StringBuilder();
|
||||
if (executionStepInfo.hasParent() && executionStepInfo.getParent().getType() instanceof GraphQLObjectType) {
|
||||
dataFetchingPath.append(((GraphQLObjectType) executionStepInfo.getParent().getType()).getName());
|
||||
dataFetchingPath.append('.');
|
||||
}
|
||||
dataFetchingPath.append(executionStepInfo.getPath().getSegmentName());
|
||||
return Tag.of("path", dataFetchingPath.toString());
|
||||
}
|
||||
|
||||
}
|
@ -1,43 +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.boot.actuate.metrics.graphql;
|
||||
|
||||
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;
|
||||
|
||||
/**
|
||||
* A contributor of {@link Tag Tags} for Spring GraphQL-based request handling. Typically,
|
||||
* used by a {@link GraphQlTagsProvider} to provide tags in addition to its defaults.
|
||||
*
|
||||
* @author Brian Clozel
|
||||
* @since 2.7.0
|
||||
*/
|
||||
public interface GraphQlTagsContributor {
|
||||
|
||||
Iterable<Tag> getExecutionTags(InstrumentationExecutionParameters parameters, ExecutionResult result,
|
||||
Throwable exception);
|
||||
|
||||
Iterable<Tag> getErrorTags(InstrumentationExecutionParameters parameters, GraphQLError error);
|
||||
|
||||
Iterable<Tag> getDataFetchingTags(DataFetcher<?> dataFetcher, InstrumentationFieldFetchParameters parameters,
|
||||
Throwable exception);
|
||||
|
||||
}
|
@ -1,42 +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.boot.actuate.metrics.graphql;
|
||||
|
||||
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;
|
||||
|
||||
/**
|
||||
* Provides {@link Tag Tags} for Spring GraphQL-based request handling.
|
||||
*
|
||||
* @author Brian Clozel
|
||||
* @since 2.7.0
|
||||
*/
|
||||
public interface GraphQlTagsProvider {
|
||||
|
||||
Iterable<Tag> getExecutionTags(InstrumentationExecutionParameters parameters, ExecutionResult result,
|
||||
Throwable exception);
|
||||
|
||||
Iterable<Tag> getErrorTags(InstrumentationExecutionParameters parameters, GraphQLError error);
|
||||
|
||||
Iterable<Tag> getDataFetchingTags(DataFetcher<?> dataFetcher, InstrumentationFieldFetchParameters parameters,
|
||||
Throwable exception);
|
||||
|
||||
}
|
@ -1,20 +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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Provides instrumentation support for Spring GraphQL.
|
||||
*/
|
||||
package org.springframework.boot.actuate.metrics.graphql;
|
@ -1,185 +0,0 @@
|
||||
/*
|
||||
* Copyright 2012-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.boot.actuate.metrics.graphql;
|
||||
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.CompletionStage;
|
||||
|
||||
import graphql.ExecutionInput;
|
||||
import graphql.ExecutionResult;
|
||||
import graphql.ExecutionResultImpl;
|
||||
import graphql.execution.instrumentation.InstrumentationContext;
|
||||
import graphql.execution.instrumentation.InstrumentationState;
|
||||
import graphql.execution.instrumentation.parameters.InstrumentationCreateStateParameters;
|
||||
import graphql.execution.instrumentation.parameters.InstrumentationExecutionParameters;
|
||||
import graphql.execution.instrumentation.parameters.InstrumentationFieldFetchParameters;
|
||||
import graphql.schema.DataFetcher;
|
||||
import graphql.schema.DataFetchingEnvironment;
|
||||
import graphql.schema.DataFetchingEnvironmentImpl;
|
||||
import graphql.schema.GraphQLSchema;
|
||||
import graphql.schema.idl.SchemaGenerator;
|
||||
import io.micrometer.core.instrument.DistributionSummary;
|
||||
import io.micrometer.core.instrument.MeterRegistry;
|
||||
import io.micrometer.core.instrument.MockClock;
|
||||
import io.micrometer.core.instrument.Timer;
|
||||
import io.micrometer.core.instrument.simple.SimpleConfig;
|
||||
import io.micrometer.core.instrument.simple.SimpleMeterRegistry;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.boot.actuate.metrics.AutoTimer;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
/**
|
||||
* Tests for {@link GraphQlMetricsInstrumentation}.
|
||||
*
|
||||
* @author Brian Clozel
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
class GraphQlMetricsInstrumentationTests {
|
||||
|
||||
private final ExecutionInput input = ExecutionInput.newExecutionInput("{greeting}").build();
|
||||
|
||||
private final GraphQLSchema schema = SchemaGenerator.createdMockedSchema("type Query { greeting: String }");
|
||||
|
||||
private MeterRegistry registry;
|
||||
|
||||
private GraphQlMetricsInstrumentation instrumentation;
|
||||
|
||||
private InstrumentationState state;
|
||||
|
||||
private InstrumentationExecutionParameters parameters;
|
||||
|
||||
@BeforeEach
|
||||
void setup() {
|
||||
this.registry = new SimpleMeterRegistry(SimpleConfig.DEFAULT, new MockClock());
|
||||
this.instrumentation = new GraphQlMetricsInstrumentation(this.registry, mock(GraphQlTagsProvider.class),
|
||||
AutoTimer.ENABLED);
|
||||
this.state = this.instrumentation
|
||||
.createState(new InstrumentationCreateStateParameters(this.schema, this.input));
|
||||
this.parameters = new InstrumentationExecutionParameters(this.input, this.schema, this.state);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldRecordTimerWhenResult() {
|
||||
InstrumentationContext<ExecutionResult> execution = this.instrumentation.beginExecution(this.parameters,
|
||||
this.state);
|
||||
ExecutionResult result = new ExecutionResultImpl("Hello", null);
|
||||
execution.onCompleted(result, null);
|
||||
|
||||
Timer timer = this.registry.find("graphql.request").timer();
|
||||
assertThat(timer).isNotNull();
|
||||
assertThat(timer.count()).isEqualTo(1);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldRecordDataFetchingCount() throws Exception {
|
||||
InstrumentationContext<ExecutionResult> execution = this.instrumentation.beginExecution(this.parameters,
|
||||
this.state);
|
||||
ExecutionResult result = new ExecutionResultImpl("Hello", null);
|
||||
|
||||
DataFetcher<String> dataFetcher = mock(DataFetcher.class);
|
||||
given(dataFetcher.get(any())).willReturn("Hello");
|
||||
InstrumentationFieldFetchParameters fieldFetchParameters = mockFieldFetchParameters(false);
|
||||
|
||||
DataFetcher<?> instrumented = this.instrumentation.instrumentDataFetcher(dataFetcher, fieldFetchParameters,
|
||||
this.state);
|
||||
DataFetchingEnvironment environment = DataFetchingEnvironmentImpl.newDataFetchingEnvironment().build();
|
||||
instrumented.get(environment);
|
||||
|
||||
execution.onCompleted(result, null);
|
||||
|
||||
DistributionSummary summary = this.registry.find("graphql.request.datafetch.count").summary();
|
||||
assertThat(summary).isNotNull();
|
||||
assertThat(summary.count()).isEqualTo(1);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldRecordDataFetchingMetricWhenSuccess() throws Exception {
|
||||
DataFetcher<String> dataFetcher = mock(DataFetcher.class);
|
||||
given(dataFetcher.get(any())).willReturn("Hello");
|
||||
InstrumentationFieldFetchParameters fieldFetchParameters = mockFieldFetchParameters(false);
|
||||
|
||||
DataFetcher<?> instrumented = this.instrumentation.instrumentDataFetcher(dataFetcher, fieldFetchParameters,
|
||||
this.state);
|
||||
DataFetchingEnvironment environment = DataFetchingEnvironmentImpl.newDataFetchingEnvironment().build();
|
||||
instrumented.get(environment);
|
||||
|
||||
Timer timer = this.registry.find("graphql.datafetcher").timer();
|
||||
assertThat(timer).isNotNull();
|
||||
assertThat(timer.count()).isEqualTo(1);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldRecordDataFetchingMetricWhenSuccessCompletionStage() throws Exception {
|
||||
DataFetcher<CompletionStage<String>> dataFetcher = mock(DataFetcher.class);
|
||||
given(dataFetcher.get(any())).willReturn(CompletableFuture.completedFuture("Hello"));
|
||||
InstrumentationFieldFetchParameters fieldFetchParameters = mockFieldFetchParameters(false);
|
||||
|
||||
DataFetcher<?> instrumented = this.instrumentation.instrumentDataFetcher(dataFetcher, fieldFetchParameters,
|
||||
this.state);
|
||||
DataFetchingEnvironment environment = DataFetchingEnvironmentImpl.newDataFetchingEnvironment().build();
|
||||
instrumented.get(environment);
|
||||
|
||||
Timer timer = this.registry.find("graphql.datafetcher").timer();
|
||||
assertThat(timer).isNotNull();
|
||||
assertThat(timer.count()).isEqualTo(1);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldRecordDataFetchingMetricWhenError() throws Exception {
|
||||
DataFetcher<CompletionStage<String>> dataFetcher = mock(DataFetcher.class);
|
||||
given(dataFetcher.get(any())).willThrow(new IllegalStateException("test"));
|
||||
InstrumentationFieldFetchParameters fieldFetchParameters = mockFieldFetchParameters(false);
|
||||
|
||||
DataFetcher<?> instrumented = this.instrumentation.instrumentDataFetcher(dataFetcher, fieldFetchParameters,
|
||||
this.state);
|
||||
DataFetchingEnvironment environment = DataFetchingEnvironmentImpl.newDataFetchingEnvironment().build();
|
||||
assertThatThrownBy(() -> instrumented.get(environment)).isInstanceOf(IllegalStateException.class);
|
||||
|
||||
Timer timer = this.registry.find("graphql.datafetcher").timer();
|
||||
assertThat(timer).isNotNull();
|
||||
assertThat(timer.count()).isEqualTo(1);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldNotRecordDataFetchingMetricWhenTrivial() throws Exception {
|
||||
DataFetcher<String> dataFetcher = mock(DataFetcher.class);
|
||||
given(dataFetcher.get(any())).willReturn("Hello");
|
||||
InstrumentationFieldFetchParameters fieldFetchParameters = mockFieldFetchParameters(true);
|
||||
|
||||
DataFetcher<?> instrumented = this.instrumentation.instrumentDataFetcher(dataFetcher, fieldFetchParameters,
|
||||
this.state);
|
||||
DataFetchingEnvironment environment = DataFetchingEnvironmentImpl.newDataFetchingEnvironment().build();
|
||||
instrumented.get(environment);
|
||||
|
||||
Timer timer = this.registry.find("graphql.datafetcher").timer();
|
||||
assertThat(timer).isNull();
|
||||
}
|
||||
|
||||
private InstrumentationFieldFetchParameters mockFieldFetchParameters(boolean isTrivial) {
|
||||
InstrumentationFieldFetchParameters fieldFetchParameters = mock(InstrumentationFieldFetchParameters.class);
|
||||
given(fieldFetchParameters.isTrivialDataFetcher()).willReturn(isTrivial);
|
||||
return fieldFetchParameters;
|
||||
}
|
||||
|
||||
}
|
@ -1,95 +0,0 @@
|
||||
/*
|
||||
* Copyright 2012-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.boot.actuate.metrics.graphql;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import graphql.ErrorType;
|
||||
import graphql.ExecutionResult;
|
||||
import graphql.ExecutionResultImpl;
|
||||
import graphql.GraphQLError;
|
||||
import graphql.GraphqlErrorBuilder;
|
||||
import io.micrometer.core.instrument.Tag;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Tests for {@link GraphQlTags}.
|
||||
*
|
||||
* @author Brian Clozel
|
||||
*/
|
||||
class GraphQlTagsTests {
|
||||
|
||||
@Test
|
||||
void executionOutcomeShouldSucceed() {
|
||||
ExecutionResult result = ExecutionResultImpl.newExecutionResult().build();
|
||||
Tag outcomeTag = GraphQlTags.executionOutcome(result, null);
|
||||
assertThat(outcomeTag).isEqualTo(Tag.of("outcome", "SUCCESS"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void executionOutcomeShouldErrorWhenExceptionThrown() {
|
||||
ExecutionResult result = ExecutionResultImpl.newExecutionResult().build();
|
||||
Tag tag = GraphQlTags.executionOutcome(result, new IllegalArgumentException("test error"));
|
||||
assertThat(tag).isEqualTo(Tag.of("outcome", "ERROR"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void executionOutcomeShouldErrorWhenResponseErrors() {
|
||||
GraphQLError error = GraphqlErrorBuilder.newError().message("Invalid query").build();
|
||||
Tag tag = GraphQlTags.executionOutcome(ExecutionResultImpl.newExecutionResult().addError(error).build(), null);
|
||||
assertThat(tag).isEqualTo(Tag.of("outcome", "ERROR"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void errorTypeShouldBeDefinedIfPresent() {
|
||||
GraphQLError error = GraphqlErrorBuilder.newError().errorType(ErrorType.DataFetchingException)
|
||||
.message("test error").build();
|
||||
Tag errorTypeTag = GraphQlTags.errorType(error);
|
||||
assertThat(errorTypeTag).isEqualTo(Tag.of("error.type", "DataFetchingException"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void errorPathShouldUseJsonPathFormat() {
|
||||
GraphQLError error = GraphqlErrorBuilder.newError().path(Arrays.asList("project", "name")).message("test error")
|
||||
.build();
|
||||
Tag errorPathTag = GraphQlTags.errorPath(error);
|
||||
assertThat(errorPathTag).isEqualTo(Tag.of("error.path", "$.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).isEqualTo(Tag.of("error.path", "$.issues[*].title"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void dataFetchingOutcomeShouldBeSuccessfulIfNoException() {
|
||||
Tag fetchingOutcomeTag = GraphQlTags.dataFetchingOutcome(null);
|
||||
assertThat(fetchingOutcomeTag).isEqualTo(Tag.of("outcome", "SUCCESS"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void dataFetchingOutcomeShouldBeErrorIfException() {
|
||||
Tag fetchingOutcomeTag = GraphQlTags.dataFetchingOutcome(new IllegalStateException("error state"));
|
||||
assertThat(fetchingOutcomeTag).isEqualTo(Tag.of("outcome", "ERROR"));
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue