Remove ServerHttpObservationFilter from WebFlux

This commit removes the auto-configuration of the
`ServerHttpObservationFilter` bean for WebFlux applications as it's been
deprecated by Spring Framework.

The Observability instrumentation is now handled at the
`WebHttpHandlerBuilder` in Framework directly and doesn't need any
auto-configuration from Spring Boot.

Closes gh-37344
This commit is contained in:
Brian Clozel 2023-09-12 10:59:25 +02:00
parent 8a1f6d4f32
commit c8d036eaa8
3 changed files with 23 additions and 133 deletions

View File

@ -21,9 +21,6 @@ import io.micrometer.core.instrument.config.MeterFilter;
import io.micrometer.observation.Observation;
import io.micrometer.observation.ObservationRegistry;
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.OnlyOnceLoggingDenyMeterFilter;
import org.springframework.boot.actuate.autoconfigure.metrics.export.simple.SimpleMetricsExportAutoConfiguration;
@ -33,16 +30,10 @@ 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.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.http.server.reactive.observation.DefaultServerRequestObservationConvention;
import org.springframework.http.server.reactive.observation.ServerRequestObservationConvention;
import org.springframework.web.filter.reactive.ServerHttpObservationFilter;
/**
* {@link EnableAutoConfiguration Auto-configuration} for instrumentation of Spring
@ -53,48 +44,22 @@ import org.springframework.web.filter.reactive.ServerHttpObservationFilter;
* @author Dmytro Nosan
* @since 3.0.0
*/
@AutoConfiguration(after = { MetricsAutoConfiguration.class, CompositeMeterRegistryAutoConfiguration.class,
SimpleMetricsExportAutoConfiguration.class, ObservationAutoConfiguration.class })
@ConditionalOnClass(Observation.class)
@ConditionalOnBean(ObservationRegistry.class)
@AutoConfiguration(after = { SimpleMetricsExportAutoConfiguration.class, ObservationAutoConfiguration.class })
@ConditionalOnClass({ Observation.class, MeterRegistry.class })
@ConditionalOnBean({ ObservationRegistry.class, MeterRegistry.class })
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.REACTIVE)
@EnableConfigurationProperties({ MetricsProperties.class, ObservationProperties.class })
@SuppressWarnings("removal")
public class WebFluxObservationAutoConfiguration {
private final ObservationProperties observationProperties;
public WebFluxObservationAutoConfiguration(ObservationProperties observationProperties) {
this.observationProperties = observationProperties;
}
@Bean
@ConditionalOnMissingBean(ServerHttpObservationFilter.class)
@Order(Ordered.HIGHEST_PRECEDENCE + 1)
public ServerHttpObservationFilter webfluxObservationFilter(ObservationRegistry registry,
ObjectProvider<ServerRequestObservationConvention> customConvention) {
String name = this.observationProperties.getHttp().getServer().getRequests().getName();
ServerRequestObservationConvention convention = customConvention
.getIfAvailable(() -> new DefaultServerRequestObservationConvention(name));
return new ServerHttpObservationFilter(registry, convention);
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(MeterRegistry.class)
@ConditionalOnBean(MeterRegistry.class)
static class MeterFilterConfiguration {
@Bean
@Order(0)
MeterFilter metricsHttpServerUriTagFilter(MetricsProperties metricsProperties,
ObservationProperties observationProperties) {
String name = observationProperties.getHttp().getServer().getRequests().getName();
MeterFilter filter = new OnlyOnceLoggingDenyMeterFilter(
() -> "Reached the maximum number of URI tags for '%s'.".formatted(name));
return MeterFilter.maximumAllowableTags(name, "uri", metricsProperties.getWeb().getServer().getMaxUriTags(),
filter);
}
@Order(0)
MeterFilter metricsHttpServerUriTagFilter(MetricsProperties metricsProperties,
ObservationProperties observationProperties) {
String name = observationProperties.getHttp().getServer().getRequests().getName();
MeterFilter filter = new OnlyOnceLoggingDenyMeterFilter(
() -> "Reached the maximum number of URI tags for '%s'.".formatted(name));
return MeterFilter.maximumAllowableTags(name, "uri", metricsProperties.getWeb().getServer().getMaxUriTags(),
filter);
}
}

View File

@ -16,12 +16,12 @@
package org.springframework.boot.actuate.autoconfigure.observation.web.reactive;
import java.util.List;
import java.time.Duration;
import java.time.temporal.ChronoUnit;
import io.micrometer.core.instrument.MeterRegistry;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import reactor.core.publisher.Mono;
import org.springframework.boot.actuate.autoconfigure.metrics.MetricsAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.metrics.test.MetricsRun;
@ -33,16 +33,6 @@ import org.springframework.boot.test.context.assertj.AssertableReactiveWebApplic
import org.springframework.boot.test.context.runner.ReactiveWebApplicationContextRunner;
import org.springframework.boot.test.system.CapturedOutput;
import org.springframework.boot.test.system.OutputCaptureExtension;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.http.server.reactive.observation.DefaultServerRequestObservationConvention;
import org.springframework.test.web.reactive.server.WebTestClient;
import org.springframework.web.filter.reactive.ServerHttpObservationFilter;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebFilter;
import org.springframework.web.server.WebFilterChain;
import static org.assertj.core.api.Assertions.assertThat;
@ -54,7 +44,6 @@ import static org.assertj.core.api.Assertions.assertThat;
* @author Madhura Bhave
*/
@ExtendWith(OutputCaptureExtension.class)
@SuppressWarnings("removal")
class WebFluxObservationAutoConfigurationTests {
private final ReactiveWebApplicationContextRunner contextRunner = new ReactiveWebApplicationContextRunner()
@ -62,31 +51,6 @@ class WebFluxObservationAutoConfigurationTests {
.withConfiguration(
AutoConfigurations.of(ObservationAutoConfiguration.class, WebFluxObservationAutoConfiguration.class));
@Test
void shouldProvideWebFluxObservationFilter() {
this.contextRunner.run((context) -> assertThat(context).hasSingleBean(ServerHttpObservationFilter.class));
}
@Test
void shouldProvideWebFluxObservationFilterOrdered() {
this.contextRunner.withBean(FirstWebFilter.class).withBean(ThirdWebFilter.class).run((context) -> {
List<WebFilter> webFilters = context.getBeanProvider(WebFilter.class).orderedStream().toList();
assertThat(webFilters.get(0)).isInstanceOf(FirstWebFilter.class);
assertThat(webFilters.get(1)).isInstanceOf(ServerHttpObservationFilter.class);
assertThat(webFilters.get(2)).isInstanceOf(ThirdWebFilter.class);
});
}
@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
void afterMaxUrisReachedFurtherUrisAreDenied(CapturedOutput output) {
this.contextRunner.withUserConfiguration(TestController.class)
@ -108,7 +72,7 @@ class WebFluxObservationAutoConfigurationTests {
.withPropertyValues("management.metrics.web.server.max-uri-tags=2",
"management.observations.http.server.requests.name=my.http.server.requests")
.run((context) -> {
MeterRegistry registry = getInitializedMeterRegistry(context);
MeterRegistry registry = getInitializedMeterRegistry(context, "my.http.server.requests");
assertThat(registry.get("my.http.server.requests").meters()).hasSizeLessThanOrEqualTo(2);
assertThat(output).contains("Reached the maximum number of URI tags for 'my.http.server.requests'");
});
@ -127,53 +91,17 @@ class WebFluxObservationAutoConfigurationTests {
});
}
private MeterRegistry getInitializedMeterRegistry(AssertableReactiveWebApplicationContext context)
throws Exception {
return getInitializedMeterRegistry(context, "/test0", "/test1", "/test2");
private MeterRegistry getInitializedMeterRegistry(AssertableReactiveWebApplicationContext context) {
return getInitializedMeterRegistry(context, "http.server.requests");
}
private MeterRegistry getInitializedMeterRegistry(AssertableReactiveWebApplicationContext context, String... urls)
throws Exception {
assertThat(context).hasSingleBean(ServerHttpObservationFilter.class);
WebTestClient client = WebTestClient.bindToApplicationContext(context).build();
for (String url : urls) {
client.get().uri(url).exchange().expectStatus().isOk();
}
return context.getBean(MeterRegistry.class);
}
@Configuration(proxyBeanMethods = false)
static class CustomConventionConfiguration {
@Bean
CustomConvention customConvention() {
return new CustomConvention();
}
}
static class CustomConvention extends DefaultServerRequestObservationConvention {
}
@Order(Ordered.HIGHEST_PRECEDENCE)
static class FirstWebFilter implements WebFilter {
@Override
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
return chain.filter(exchange);
}
}
@Order(Ordered.HIGHEST_PRECEDENCE + 2)
static class ThirdWebFilter implements WebFilter {
@Override
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
return chain.filter(exchange);
}
private MeterRegistry getInitializedMeterRegistry(AssertableReactiveWebApplicationContext context,
String metricName) {
MeterRegistry meterRegistry = context.getBean(MeterRegistry.class);
meterRegistry.timer(metricName, "uri", "/test0").record(Duration.of(500, ChronoUnit.SECONDS));
meterRegistry.timer(metricName, "uri", "/test1").record(Duration.of(500, ChronoUnit.SECONDS));
meterRegistry.timer(metricName, "uri", "/test2").record(Duration.of(500, ChronoUnit.SECONDS));
return meterRegistry;
}
}

View File

@ -245,9 +245,6 @@ When it does so, the orders shown in the following table will be used:
|===
| Web Filter | Order
| `ServerHttpObservationFilter` (Micrometer Observability)
| `Ordered.HIGHEST_PRECEDENCE + 1`
| `WebFilterChainProxy` (Spring Security)
| `-100`