Apply user-provided ObservationConventions in auto-configurations
Prior to this commit, we would advise developers, as migration path from Spring Boot 2.0-x metrics, to create `GlobalObservationConvention` beans for the observations they want to customize (observation name or key values). `GlobalObservationConvention` are currently applied **in addition** to the chosen convention in some cases, so this does not work well with this migration path. Instead, instrumentations always provide a default convention but also a way to configure a custom convention for their observations. Spring Boot should inject custom convention beans in the relevant auto-configurations. Fixes gh-33285
This commit is contained in:
parent
f6ac891cc1
commit
07766c436c
|
@ -20,6 +20,7 @@ import graphql.GraphQL;
|
||||||
import io.micrometer.observation.Observation;
|
import io.micrometer.observation.Observation;
|
||||||
import io.micrometer.observation.ObservationRegistry;
|
import io.micrometer.observation.ObservationRegistry;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.ObjectProvider;
|
||||||
import org.springframework.boot.actuate.autoconfigure.observation.ObservationAutoConfiguration;
|
import org.springframework.boot.actuate.autoconfigure.observation.ObservationAutoConfiguration;
|
||||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||||
|
@ -28,6 +29,8 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.graphql.execution.GraphQlSource;
|
import org.springframework.graphql.execution.GraphQlSource;
|
||||||
|
import org.springframework.graphql.observation.DataFetcherObservationConvention;
|
||||||
|
import org.springframework.graphql.observation.ExecutionRequestObservationConvention;
|
||||||
import org.springframework.graphql.observation.GraphQlObservationInstrumentation;
|
import org.springframework.graphql.observation.GraphQlObservationInstrumentation;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -45,9 +48,11 @@ public class GraphQlObservationAutoConfiguration {
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
@ConditionalOnMissingBean
|
@ConditionalOnMissingBean
|
||||||
public GraphQlObservationInstrumentation graphQlObservationInstrumentation(
|
public GraphQlObservationInstrumentation graphQlObservationInstrumentation(ObservationRegistry observationRegistry,
|
||||||
ObservationRegistry observationRegistry) {
|
ObjectProvider<ExecutionRequestObservationConvention> executionConvention,
|
||||||
return new GraphQlObservationInstrumentation(observationRegistry);
|
ObjectProvider<DataFetcherObservationConvention> dataFetcherConvention) {
|
||||||
|
return new GraphQlObservationInstrumentation(observationRegistry, executionConvention.getIfAvailable(),
|
||||||
|
dataFetcherConvention.getIfAvailable());
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -45,16 +45,34 @@ class RestTemplateObservationConfiguration {
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
ObservationRestTemplateCustomizer observationRestTemplateCustomizer(ObservationRegistry observationRegistry,
|
ObservationRestTemplateCustomizer observationRestTemplateCustomizer(ObservationRegistry observationRegistry,
|
||||||
|
ObjectProvider<ClientRequestObservationConvention> customConvention,
|
||||||
ObservationProperties observationProperties, MetricsProperties metricsProperties,
|
ObservationProperties observationProperties, MetricsProperties metricsProperties,
|
||||||
ObjectProvider<RestTemplateExchangeTagsProvider> optionalTagsProvider) {
|
ObjectProvider<RestTemplateExchangeTagsProvider> optionalTagsProvider) {
|
||||||
String metricName = metricsProperties.getWeb().getClient().getRequest().getMetricName();
|
String name = observationName(observationProperties, metricsProperties);
|
||||||
String observationName = observationProperties.getHttp().getClient().getRequests().getName();
|
ClientRequestObservationConvention observationConvention = createConvention(customConvention.getIfAvailable(),
|
||||||
String name = (observationName != null) ? observationName : metricName;
|
name, optionalTagsProvider.getIfAvailable());
|
||||||
RestTemplateExchangeTagsProvider tagsProvider = optionalTagsProvider.getIfAvailable();
|
|
||||||
ClientRequestObservationConvention observationConvention = (tagsProvider != null)
|
|
||||||
? new ClientHttpObservationConventionAdapter(name, tagsProvider)
|
|
||||||
: new DefaultClientRequestObservationConvention(name);
|
|
||||||
return new ObservationRestTemplateCustomizer(observationRegistry, observationConvention);
|
return new ObservationRestTemplateCustomizer(observationRegistry, observationConvention);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static String observationName(ObservationProperties observationProperties,
|
||||||
|
MetricsProperties metricsProperties) {
|
||||||
|
String metricName = metricsProperties.getWeb().getClient().getRequest().getMetricName();
|
||||||
|
String observationName = observationProperties.getHttp().getClient().getRequests().getName();
|
||||||
|
return (observationName != null) ? observationName : metricName;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ClientRequestObservationConvention createConvention(
|
||||||
|
ClientRequestObservationConvention customConvention, String name,
|
||||||
|
RestTemplateExchangeTagsProvider tagsProvider) {
|
||||||
|
if (customConvention != null) {
|
||||||
|
return customConvention;
|
||||||
|
}
|
||||||
|
else if (tagsProvider != null) {
|
||||||
|
return new ClientHttpObservationConventionAdapter(name, tagsProvider);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return new DefaultClientRequestObservationConvention(name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -42,16 +42,34 @@ class WebClientObservationConfiguration {
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
ObservationWebClientCustomizer observationWebClientCustomizer(ObservationRegistry observationRegistry,
|
ObservationWebClientCustomizer observationWebClientCustomizer(ObservationRegistry observationRegistry,
|
||||||
ObservationProperties observationProperties,
|
ObjectProvider<ClientRequestObservationConvention> customConvention,
|
||||||
ObjectProvider<WebClientExchangeTagsProvider> optionalTagsProvider, MetricsProperties metricsProperties) {
|
ObservationProperties observationProperties, ObjectProvider<WebClientExchangeTagsProvider> tagsProvider,
|
||||||
String metricName = metricsProperties.getWeb().getClient().getRequest().getMetricName();
|
MetricsProperties metricsProperties) {
|
||||||
String observationName = observationProperties.getHttp().getClient().getRequests().getName();
|
String name = observationName(observationProperties, metricsProperties);
|
||||||
String name = (observationName != null) ? observationName : metricName;
|
ClientRequestObservationConvention observationConvention = createConvention(customConvention.getIfAvailable(),
|
||||||
WebClientExchangeTagsProvider tagsProvider = optionalTagsProvider.getIfAvailable();
|
tagsProvider.getIfAvailable(), name);
|
||||||
ClientRequestObservationConvention observationConvention = (tagsProvider != null)
|
|
||||||
? new ClientObservationConventionAdapter(name, tagsProvider)
|
|
||||||
: new DefaultClientRequestObservationConvention(name);
|
|
||||||
return new ObservationWebClientCustomizer(observationRegistry, observationConvention);
|
return new ObservationWebClientCustomizer(observationRegistry, observationConvention);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static ClientRequestObservationConvention createConvention(
|
||||||
|
ClientRequestObservationConvention customConvention, WebClientExchangeTagsProvider tagsProvider,
|
||||||
|
String name) {
|
||||||
|
if (customConvention != null) {
|
||||||
|
return customConvention;
|
||||||
|
}
|
||||||
|
else if (tagsProvider != null) {
|
||||||
|
return new ClientObservationConventionAdapter(name, tagsProvider);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return new DefaultClientRequestObservationConvention(name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String observationName(ObservationProperties observationProperties,
|
||||||
|
MetricsProperties metricsProperties) {
|
||||||
|
String metricName = metricsProperties.getWeb().getClient().getRequest().getMetricName();
|
||||||
|
String observationName = observationProperties.getHttp().getClient().getRequests().getName();
|
||||||
|
return (observationName != null) ? observationName : metricName;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -78,6 +78,7 @@ public class WebFluxObservationAutoConfiguration {
|
||||||
@Bean
|
@Bean
|
||||||
@ConditionalOnMissingBean
|
@ConditionalOnMissingBean
|
||||||
public ServerHttpObservationFilter webfluxObservationFilter(ObservationRegistry registry,
|
public ServerHttpObservationFilter webfluxObservationFilter(ObservationRegistry registry,
|
||||||
|
ObjectProvider<ServerRequestObservationConvention> customConvention,
|
||||||
ObjectProvider<WebFluxTagsProvider> tagConfigurer,
|
ObjectProvider<WebFluxTagsProvider> tagConfigurer,
|
||||||
ObjectProvider<WebFluxTagsContributor> contributorsProvider) {
|
ObjectProvider<WebFluxTagsContributor> contributorsProvider) {
|
||||||
String observationName = this.observationProperties.getHttp().getServer().getRequests().getName();
|
String observationName = this.observationProperties.getHttp().getServer().getRequests().getName();
|
||||||
|
@ -85,12 +86,17 @@ public class WebFluxObservationAutoConfiguration {
|
||||||
String name = (observationName != null) ? observationName : metricName;
|
String name = (observationName != null) ? observationName : metricName;
|
||||||
WebFluxTagsProvider tagsProvider = tagConfigurer.getIfAvailable();
|
WebFluxTagsProvider tagsProvider = tagConfigurer.getIfAvailable();
|
||||||
List<WebFluxTagsContributor> tagsContributors = contributorsProvider.orderedStream().toList();
|
List<WebFluxTagsContributor> tagsContributors = contributorsProvider.orderedStream().toList();
|
||||||
ServerRequestObservationConvention convention = createConvention(name, tagsProvider, tagsContributors);
|
ServerRequestObservationConvention convention = createConvention(customConvention.getIfAvailable(), name,
|
||||||
|
tagsProvider, tagsContributors);
|
||||||
return new ServerHttpObservationFilter(registry, convention);
|
return new ServerHttpObservationFilter(registry, convention);
|
||||||
}
|
}
|
||||||
|
|
||||||
private ServerRequestObservationConvention createConvention(String name, WebFluxTagsProvider tagsProvider,
|
private static ServerRequestObservationConvention createConvention(
|
||||||
|
ServerRequestObservationConvention customConvention, String name, WebFluxTagsProvider tagsProvider,
|
||||||
List<WebFluxTagsContributor> tagsContributors) {
|
List<WebFluxTagsContributor> tagsContributors) {
|
||||||
|
if (customConvention != null) {
|
||||||
|
return customConvention;
|
||||||
|
}
|
||||||
if (tagsProvider != null) {
|
if (tagsProvider != null) {
|
||||||
return new ServerRequestObservationConventionAdapter(name, tagsProvider);
|
return new ServerRequestObservationConventionAdapter(name, tagsProvider);
|
||||||
}
|
}
|
||||||
|
|
|
@ -82,15 +82,12 @@ public class WebMvcObservationAutoConfiguration {
|
||||||
@Bean
|
@Bean
|
||||||
@ConditionalOnMissingFilterBean
|
@ConditionalOnMissingFilterBean
|
||||||
public FilterRegistrationBean<ServerHttpObservationFilter> webMvcObservationFilter(ObservationRegistry registry,
|
public FilterRegistrationBean<ServerHttpObservationFilter> webMvcObservationFilter(ObservationRegistry registry,
|
||||||
|
ObjectProvider<ServerRequestObservationConvention> customConvention,
|
||||||
ObjectProvider<WebMvcTagsProvider> customTagsProvider,
|
ObjectProvider<WebMvcTagsProvider> customTagsProvider,
|
||||||
ObjectProvider<WebMvcTagsContributor> contributorsProvider) {
|
ObjectProvider<WebMvcTagsContributor> contributorsProvider) {
|
||||||
String name = httpRequestsMetricName(this.observationProperties, this.metricsProperties);
|
String name = httpRequestsMetricName(this.observationProperties, this.metricsProperties);
|
||||||
ServerRequestObservationConvention convention = new DefaultServerRequestObservationConvention(name);
|
ServerRequestObservationConvention convention = createConvention(customConvention.getIfAvailable(), name,
|
||||||
WebMvcTagsProvider tagsProvider = customTagsProvider.getIfAvailable();
|
customTagsProvider.getIfAvailable(), contributorsProvider.orderedStream().toList());
|
||||||
List<WebMvcTagsContributor> contributors = contributorsProvider.orderedStream().toList();
|
|
||||||
if (tagsProvider != null || contributors.size() > 0) {
|
|
||||||
convention = new ServerRequestObservationConventionAdapter(name, tagsProvider, contributors);
|
|
||||||
}
|
|
||||||
ServerHttpObservationFilter filter = new ServerHttpObservationFilter(registry, convention);
|
ServerHttpObservationFilter filter = new ServerHttpObservationFilter(registry, convention);
|
||||||
FilterRegistrationBean<ServerHttpObservationFilter> registration = new FilterRegistrationBean<>(filter);
|
FilterRegistrationBean<ServerHttpObservationFilter> registration = new FilterRegistrationBean<>(filter);
|
||||||
registration.setOrder(Ordered.HIGHEST_PRECEDENCE + 1);
|
registration.setOrder(Ordered.HIGHEST_PRECEDENCE + 1);
|
||||||
|
@ -98,6 +95,20 @@ public class WebMvcObservationAutoConfiguration {
|
||||||
return registration;
|
return registration;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static ServerRequestObservationConvention createConvention(
|
||||||
|
ServerRequestObservationConvention customConvention, String name, WebMvcTagsProvider tagsProvider,
|
||||||
|
List<WebMvcTagsContributor> contributors) {
|
||||||
|
if (customConvention != null) {
|
||||||
|
return customConvention;
|
||||||
|
}
|
||||||
|
else if (tagsProvider != null || contributors.size() > 0) {
|
||||||
|
return new ServerRequestObservationConventionAdapter(name, tagsProvider, contributors);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return new DefaultServerRequestObservationConvention(name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static String httpRequestsMetricName(ObservationProperties observationProperties,
|
private static String httpRequestsMetricName(ObservationProperties observationProperties,
|
||||||
MetricsProperties metricsProperties) {
|
MetricsProperties metricsProperties) {
|
||||||
String observationName = observationProperties.getHttp().getServer().getRequests().getName();
|
String observationName = observationProperties.getHttp().getServer().getRequests().getName();
|
||||||
|
|
|
@ -24,6 +24,8 @@ import org.springframework.boot.autoconfigure.AutoConfigurations;
|
||||||
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
|
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.graphql.observation.DefaultDataFetcherObservationConvention;
|
||||||
|
import org.springframework.graphql.observation.DefaultExecutionRequestObservationConvention;
|
||||||
import org.springframework.graphql.observation.GraphQlObservationInstrumentation;
|
import org.springframework.graphql.observation.GraphQlObservationInstrumentation;
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
@ -58,6 +60,18 @@ class GraphQlObservationAutoConfigurationTests {
|
||||||
.hasBean("customInstrumentation"));
|
.hasBean("customInstrumentation"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void instrumentationUsesCustomConventionsIfAvailable() {
|
||||||
|
this.contextRunner.withUserConfiguration(CustomConventionsConfiguration.class).run((context) -> {
|
||||||
|
GraphQlObservationInstrumentation instrumentation = context
|
||||||
|
.getBean(GraphQlObservationInstrumentation.class);
|
||||||
|
assertThat(instrumentation).extracting("requestObservationConvention")
|
||||||
|
.isInstanceOf(CustomExecutionRequestObservationConvention.class);
|
||||||
|
assertThat(instrumentation).extracting("dataFetcherObservationConvention")
|
||||||
|
.isInstanceOf(CustomDataFetcherObservationConvention.class);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
@Configuration(proxyBeanMethods = false)
|
@Configuration(proxyBeanMethods = false)
|
||||||
static class InstrumentationConfiguration {
|
static class InstrumentationConfiguration {
|
||||||
|
|
||||||
|
@ -68,4 +82,27 @@ class GraphQlObservationAutoConfigurationTests {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Configuration(proxyBeanMethods = false)
|
||||||
|
static class CustomConventionsConfiguration {
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
CustomExecutionRequestObservationConvention customExecutionConvention() {
|
||||||
|
return new CustomExecutionRequestObservationConvention();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
CustomDataFetcherObservationConvention customDataFetcherConvention() {
|
||||||
|
return new CustomDataFetcherObservationConvention();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
static class CustomExecutionRequestObservationConvention extends DefaultExecutionRequestObservationConvention {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
static class CustomDataFetcherObservationConvention extends DefaultDataFetcherObservationConvention {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
|
|
||||||
package org.springframework.boot.actuate.autoconfigure.observation.web.client;
|
package org.springframework.boot.actuate.autoconfigure.observation.web.client;
|
||||||
|
|
||||||
|
import io.micrometer.common.KeyValues;
|
||||||
import io.micrometer.core.instrument.Tag;
|
import io.micrometer.core.instrument.Tag;
|
||||||
import io.micrometer.core.instrument.Tags;
|
import io.micrometer.core.instrument.Tags;
|
||||||
import io.micrometer.observation.ObservationRegistry;
|
import io.micrometer.observation.ObservationRegistry;
|
||||||
|
@ -41,6 +42,8 @@ import org.springframework.context.annotation.Configuration;
|
||||||
import org.springframework.http.HttpRequest;
|
import org.springframework.http.HttpRequest;
|
||||||
import org.springframework.http.HttpStatus;
|
import org.springframework.http.HttpStatus;
|
||||||
import org.springframework.http.client.ClientHttpResponse;
|
import org.springframework.http.client.ClientHttpResponse;
|
||||||
|
import org.springframework.http.client.observation.ClientRequestObservationContext;
|
||||||
|
import org.springframework.http.client.observation.DefaultClientRequestObservationConvention;
|
||||||
import org.springframework.test.web.client.MockRestServiceServer;
|
import org.springframework.test.web.client.MockRestServiceServer;
|
||||||
import org.springframework.web.client.RestTemplate;
|
import org.springframework.web.client.RestTemplate;
|
||||||
|
|
||||||
|
@ -116,6 +119,17 @@ class RestTemplateObservationConfigurationTests {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void restTemplateCreatedWithBuilderUsesCustomConvention() {
|
||||||
|
this.contextRunner.withUserConfiguration(CustomConvention.class).run((context) -> {
|
||||||
|
RestTemplate restTemplate = buildRestTemplate(context);
|
||||||
|
restTemplate.getForEntity("/projects/{project}", Void.class, "spring-boot");
|
||||||
|
TestObservationRegistry registry = context.getBean(TestObservationRegistry.class);
|
||||||
|
TestObservationRegistryAssert.assertThat(registry).hasObservationWithNameEqualTo("http.client.requests")
|
||||||
|
.that().hasLowCardinalityKeyValue("project", "spring-boot");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void afterMaxUrisReachedFurtherUrisAreDenied(CapturedOutput output) {
|
void afterMaxUrisReachedFurtherUrisAreDenied(CapturedOutput output) {
|
||||||
this.contextRunner.with(MetricsRun.simple()).withPropertyValues("management.metrics.web.client.max-uri-tags=2")
|
this.contextRunner.with(MetricsRun.simple()).withPropertyValues("management.metrics.web.client.max-uri-tags=2")
|
||||||
|
@ -153,7 +167,7 @@ class RestTemplateObservationConfigurationTests {
|
||||||
return restTemplate;
|
return restTemplate;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Configuration
|
@Configuration(proxyBeanMethods = false)
|
||||||
static class CustomTagsConfiguration {
|
static class CustomTagsConfiguration {
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
|
@ -173,4 +187,23 @@ class RestTemplateObservationConfigurationTests {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Configuration(proxyBeanMethods = false)
|
||||||
|
static class CustomConventionConfiguration {
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
CustomConvention customConvention() {
|
||||||
|
return new CustomConvention();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
static class CustomConvention extends DefaultClientRequestObservationConvention {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public KeyValues getLowCardinalityKeyValues(ClientRequestObservationContext context) {
|
||||||
|
return super.getLowCardinalityKeyValues(context).and("project", "spring-boot");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,6 +18,7 @@ package org.springframework.boot.actuate.autoconfigure.observation.web.client;
|
||||||
|
|
||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
|
|
||||||
|
import io.micrometer.common.KeyValues;
|
||||||
import io.micrometer.observation.ObservationRegistry;
|
import io.micrometer.observation.ObservationRegistry;
|
||||||
import io.micrometer.observation.tck.TestObservationRegistry;
|
import io.micrometer.observation.tck.TestObservationRegistry;
|
||||||
import io.micrometer.observation.tck.TestObservationRegistryAssert;
|
import io.micrometer.observation.tck.TestObservationRegistryAssert;
|
||||||
|
@ -41,6 +42,8 @@ import org.springframework.context.annotation.Configuration;
|
||||||
import org.springframework.http.HttpStatus;
|
import org.springframework.http.HttpStatus;
|
||||||
import org.springframework.http.client.reactive.ClientHttpConnector;
|
import org.springframework.http.client.reactive.ClientHttpConnector;
|
||||||
import org.springframework.mock.http.client.reactive.MockClientHttpResponse;
|
import org.springframework.mock.http.client.reactive.MockClientHttpResponse;
|
||||||
|
import org.springframework.web.reactive.function.client.ClientRequestObservationContext;
|
||||||
|
import org.springframework.web.reactive.function.client.DefaultClientRequestObservationConvention;
|
||||||
import org.springframework.web.reactive.function.client.WebClient;
|
import org.springframework.web.reactive.function.client.WebClient;
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
@ -84,6 +87,20 @@ class WebClientObservationConfigurationTests {
|
||||||
.getBeans(WebClientExchangeTagsProvider.class).hasSize(1).containsKey("customTagsProvider"));
|
.getBeans(WebClientExchangeTagsProvider.class).hasSize(1).containsKey("customTagsProvider"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldUseCustomConventionIfAvailable() {
|
||||||
|
this.contextRunner.withUserConfiguration(CustomConvention.class).run((context) -> {
|
||||||
|
TestObservationRegistry registry = context.getBean(TestObservationRegistry.class);
|
||||||
|
WebClient.Builder builder = context.getBean(WebClient.Builder.class);
|
||||||
|
WebClient webClient = mockWebClient(builder);
|
||||||
|
TestObservationRegistryAssert.assertThat(registry).doesNotHaveAnyObservation();
|
||||||
|
webClient.get().uri("https://example.org/projects/{project}", "spring-boot").retrieve().toBodilessEntity()
|
||||||
|
.block(Duration.ofSeconds(30));
|
||||||
|
TestObservationRegistryAssert.assertThat(registry).hasObservationWithNameEqualTo("http.client.requests")
|
||||||
|
.that().hasLowCardinalityKeyValue("project", "spring-boot");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void afterMaxUrisReachedFurtherUrisAreDenied(CapturedOutput output) {
|
void afterMaxUrisReachedFurtherUrisAreDenied(CapturedOutput output) {
|
||||||
this.contextRunner.withPropertyValues("management.metrics.web.client.max-uri-tags=2").run((context) -> {
|
this.contextRunner.withPropertyValues("management.metrics.web.client.max-uri-tags=2").run((context) -> {
|
||||||
|
@ -141,4 +158,23 @@ class WebClientObservationConfigurationTests {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Configuration(proxyBeanMethods = false)
|
||||||
|
static class CustomConventionConfig {
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
CustomConvention customConvention() {
|
||||||
|
return new CustomConvention();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
static class CustomConvention extends DefaultClientRequestObservationConvention {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public KeyValues getLowCardinalityKeyValues(ClientRequestObservationContext context) {
|
||||||
|
return super.getLowCardinalityKeyValues(context).and("project", "spring-boot");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,6 +37,7 @@ import org.springframework.boot.test.system.CapturedOutput;
|
||||||
import org.springframework.boot.test.system.OutputCaptureExtension;
|
import org.springframework.boot.test.system.OutputCaptureExtension;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.http.server.reactive.observation.DefaultServerRequestObservationConvention;
|
||||||
import org.springframework.test.web.reactive.server.WebTestClient;
|
import org.springframework.test.web.reactive.server.WebTestClient;
|
||||||
import org.springframework.web.filter.reactive.ServerHttpObservationFilter;
|
import org.springframework.web.filter.reactive.ServerHttpObservationFilter;
|
||||||
import org.springframework.web.server.ServerWebExchange;
|
import org.springframework.web.server.ServerWebExchange;
|
||||||
|
@ -83,6 +84,15 @@ class WebFluxObservationAutoConfigurationTests {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldUseCustomConventionWhenAvailable() {
|
||||||
|
this.contextRunner.withUserConfiguration(CustomConventionConfiguration.class).run((context) -> {
|
||||||
|
assertThat(context).hasSingleBean(ServerHttpObservationFilter.class);
|
||||||
|
assertThat(context).getBean(ServerHttpObservationFilter.class).extracting("observationConvention")
|
||||||
|
.isInstanceOf(CustomConvention.class);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void afterMaxUrisReachedFurtherUrisAreDenied(CapturedOutput output) {
|
void afterMaxUrisReachedFurtherUrisAreDenied(CapturedOutput output) {
|
||||||
this.contextRunner.withUserConfiguration(TestController.class)
|
this.contextRunner.withUserConfiguration(TestController.class)
|
||||||
|
@ -183,4 +193,18 @@ class WebFluxObservationAutoConfigurationTests {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Configuration(proxyBeanMethods = false)
|
||||||
|
static class CustomConventionConfiguration {
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
CustomConvention customConvention() {
|
||||||
|
return new CustomConvention();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
static class CustomConvention extends DefaultServerRequestObservationConvention {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -46,6 +46,7 @@ import org.springframework.boot.web.servlet.FilterRegistrationBean;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
import org.springframework.core.Ordered;
|
import org.springframework.core.Ordered;
|
||||||
|
import org.springframework.http.server.observation.DefaultServerRequestObservationConvention;
|
||||||
import org.springframework.test.web.servlet.MockMvc;
|
import org.springframework.test.web.servlet.MockMvc;
|
||||||
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
|
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
|
||||||
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
|
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
|
||||||
|
@ -97,6 +98,13 @@ class WebMvcObservationAutoConfigurationTests {
|
||||||
.isInstanceOf(ServerRequestObservationConventionAdapter.class));
|
.isInstanceOf(ServerRequestObservationConventionAdapter.class));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void customConventionWhenPresent() {
|
||||||
|
this.contextRunner.withUserConfiguration(CustomConventionConfiguration.class)
|
||||||
|
.run((context) -> assertThat(context.getBean(FilterRegistrationBean.class).getFilter())
|
||||||
|
.extracting("observationConvention").isInstanceOf(CustomConvention.class));
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void filterRegistrationHasExpectedDispatcherTypesAndOrder() {
|
void filterRegistrationHasExpectedDispatcherTypesAndOrder() {
|
||||||
this.contextRunner.run((context) -> {
|
this.contextRunner.run((context) -> {
|
||||||
|
@ -297,4 +305,18 @@ class WebMvcObservationAutoConfigurationTests {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Configuration(proxyBeanMethods = false)
|
||||||
|
static class CustomConventionConfiguration {
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
CustomConvention customConvention() {
|
||||||
|
return new CustomConvention();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
static class CustomConvention extends DefaultServerRequestObservationConvention {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -769,7 +769,7 @@ By default, Spring MVC related metrics are tagged with the following information
|
||||||
|===
|
|===
|
||||||
|
|
||||||
To add to the default tags, provide a `@Bean` that extends `DefaultServerRequestObservationConvention` from the `org.springframework.http.observation` package.
|
To add to the default tags, provide a `@Bean` that extends `DefaultServerRequestObservationConvention` from the `org.springframework.http.observation` package.
|
||||||
To replace the default tags, provide a `@Bean` that implements `GlobalObservationConvention<ServerRequestObservationContext>`.
|
To replace the default tags, provide a `@Bean` that implements `ServerRequestObservationConvention`.
|
||||||
|
|
||||||
|
|
||||||
TIP: In some cases, exceptions handled in web controllers are not recorded as request metrics tags.
|
TIP: In some cases, exceptions handled in web controllers are not recorded as request metrics tags.
|
||||||
|
@ -809,7 +809,7 @@ By default, WebFlux related metrics are tagged with the following information:
|
||||||
|===
|
|===
|
||||||
|
|
||||||
To add to the default tags, provide a `@Bean` that extends `DefaultServerRequestObservationConvention` from the `org.springframework.http.observation.reactive` package.
|
To add to the default tags, provide a `@Bean` that extends `DefaultServerRequestObservationConvention` from the `org.springframework.http.observation.reactive` package.
|
||||||
To replace the default tags, provide a `@Bean` that implements `GlobalObservationConvention<ServerRequestObservationContext>`.
|
To replace the default tags, provide a `@Bean` that implements `ServerRequestObservationConvention`.
|
||||||
|
|
||||||
TIP: In some cases, exceptions handled in controllers and handler functions are not recorded as request metrics tags.
|
TIP: In some cases, exceptions handled in controllers and handler functions are not recorded as request metrics tags.
|
||||||
Applications can opt in and record exceptions by <<web#web.reactive.webflux.error-handling, setting handled exceptions as request attributes>>.
|
Applications can opt in and record exceptions by <<web#web.reactive.webflux.error-handling, setting handled exceptions as request attributes>>.
|
||||||
|
|
Loading…
Reference in New Issue