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