diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/web/reactive/WebFluxMetricsAutoConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/web/reactive/WebFluxMetricsAutoConfiguration.java deleted file mode 100644 index 051b1f97a96..00000000000 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/web/reactive/WebFluxMetricsAutoConfiguration.java +++ /dev/null @@ -1,85 +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.web.reactive; - -import io.micrometer.core.instrument.MeterRegistry; -import io.micrometer.core.instrument.config.MeterFilter; - -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.MetricsProperties.Web.Server.ServerRequest; -import org.springframework.boot.actuate.autoconfigure.metrics.OnlyOnceLoggingDenyMeterFilter; -import org.springframework.boot.actuate.autoconfigure.metrics.PropertiesAutoTimer; -import org.springframework.boot.actuate.autoconfigure.metrics.export.simple.SimpleMetricsExportAutoConfiguration; -import org.springframework.boot.actuate.metrics.web.reactive.server.DefaultWebFluxTagsProvider; -import org.springframework.boot.actuate.metrics.web.reactive.server.MetricsWebFilter; -import org.springframework.boot.actuate.metrics.web.reactive.server.WebFluxTagsContributor; -import org.springframework.boot.actuate.metrics.web.reactive.server.WebFluxTagsProvider; -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.ConditionalOnMissingBean; -import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; -import org.springframework.context.annotation.Bean; -import org.springframework.core.annotation.Order; - -/** - * {@link EnableAutoConfiguration Auto-configuration} for instrumentation of Spring - * WebFlux applications. - * - * @author Jon Schneider - * @author Dmytro Nosan - * @since 2.0.0 - */ -@AutoConfiguration(after = { MetricsAutoConfiguration.class, CompositeMeterRegistryAutoConfiguration.class, - SimpleMetricsExportAutoConfiguration.class }) -@ConditionalOnBean(MeterRegistry.class) -@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.REACTIVE) -@SuppressWarnings("removal") -public class WebFluxMetricsAutoConfiguration { - - private final MetricsProperties properties; - - public WebFluxMetricsAutoConfiguration(MetricsProperties properties) { - this.properties = properties; - } - - @Bean - @ConditionalOnMissingBean(WebFluxTagsProvider.class) - public DefaultWebFluxTagsProvider webFluxTagsProvider(ObjectProvider contributors) { - return new DefaultWebFluxTagsProvider(true, contributors.orderedStream().toList()); - } - - @Bean - public MetricsWebFilter webfluxMetrics(MeterRegistry registry, WebFluxTagsProvider tagConfigurer) { - ServerRequest request = this.properties.getWeb().getServer().getRequest(); - return new MetricsWebFilter(registry, tagConfigurer, request.getMetricName(), new PropertiesAutoTimer(null)); - } - - @Bean - @Order(0) - public MeterFilter metricsHttpServerUriTagFilter() { - String metricName = this.properties.getWeb().getServer().getRequest().getMetricName(); - MeterFilter filter = new OnlyOnceLoggingDenyMeterFilter( - () -> String.format("Reached the maximum number of URI tags for '%s'.", metricName)); - return MeterFilter.maximumAllowableTags(metricName, "uri", this.properties.getWeb().getServer().getMaxUriTags(), - filter); - } - -} diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/observation/web/client/ClientHttpObservationConventionAdapter.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/observation/web/client/ClientHttpObservationConventionAdapter.java index d08b9bd1c56..9f9d5e70633 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/observation/web/client/ClientHttpObservationConventionAdapter.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/observation/web/client/ClientHttpObservationConventionAdapter.java @@ -18,7 +18,6 @@ package org.springframework.boot.actuate.autoconfigure.observation.web.client; import io.micrometer.common.KeyValues; import io.micrometer.core.instrument.Tag; -import io.micrometer.observation.Observation; import org.springframework.boot.actuate.metrics.web.client.RestTemplateExchangeTagsProvider; import org.springframework.http.client.observation.ClientRequestObservationContext; @@ -42,11 +41,6 @@ class ClientHttpObservationConventionAdapter implements ClientRequestObservation this.tagsProvider = tagsProvider; } - @Override - public boolean supportsContext(Observation.Context context) { - return context instanceof ClientRequestObservationContext; - } - @Override @SuppressWarnings("deprecation") public KeyValues getLowCardinalityKeyValues(ClientRequestObservationContext context) { diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/observation/web/reactive/ServerRequestObservationConventionAdapter.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/observation/web/reactive/ServerRequestObservationConventionAdapter.java new file mode 100644 index 00000000000..93df517940c --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/observation/web/reactive/ServerRequestObservationConventionAdapter.java @@ -0,0 +1,68 @@ +/* + * 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.web.reactive; + +import java.util.List; + +import io.micrometer.common.KeyValues; +import io.micrometer.core.instrument.Tag; + +import org.springframework.boot.actuate.metrics.web.reactive.server.DefaultWebFluxTagsProvider; +import org.springframework.boot.actuate.metrics.web.reactive.server.WebFluxTagsContributor; +import org.springframework.boot.actuate.metrics.web.reactive.server.WebFluxTagsProvider; +import org.springframework.http.observation.reactive.ServerRequestObservationContext; +import org.springframework.http.observation.reactive.ServerRequestObservationConvention; + +/** + * Adapter class that applies {@link WebFluxTagsProvider} tags as a + * {@link ServerRequestObservationConvention}. + * + * @author Brian Clozel + */ +@SuppressWarnings("removal") +class ServerRequestObservationConventionAdapter implements ServerRequestObservationConvention { + + private final String name; + + private final WebFluxTagsProvider tagsProvider; + + ServerRequestObservationConventionAdapter(String name, WebFluxTagsProvider tagsProvider) { + this.name = name; + this.tagsProvider = tagsProvider; + } + + ServerRequestObservationConventionAdapter(String name, List contributors) { + this.name = name; + this.tagsProvider = new DefaultWebFluxTagsProvider(contributors); + } + + @Override + public String getName() { + return this.name; + } + + @Override + public KeyValues getLowCardinalityKeyValues(ServerRequestObservationContext context) { + KeyValues keyValues = KeyValues.empty(); + Iterable tags = this.tagsProvider.httpRequestTags(context.getServerWebExchange(), context.getError()); + for (Tag tag : tags) { + keyValues = keyValues.and(tag.getKey(), tag.getValue()); + } + return keyValues; + } + +} diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/observation/web/reactive/WebFluxObservationAutoConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/observation/web/reactive/WebFluxObservationAutoConfiguration.java new file mode 100644 index 00000000000..b5343633b05 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/observation/web/reactive/WebFluxObservationAutoConfiguration.java @@ -0,0 +1,116 @@ +/* + * 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.web.reactive; + +import java.util.List; + +import io.micrometer.core.instrument.MeterRegistry; +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; +import org.springframework.boot.actuate.autoconfigure.observation.ObservationAutoConfiguration; +import org.springframework.boot.actuate.autoconfigure.observation.ObservationProperties; +import org.springframework.boot.actuate.metrics.web.reactive.server.WebFluxTagsContributor; +import org.springframework.boot.actuate.metrics.web.reactive.server.WebFluxTagsProvider; +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.annotation.Order; +import org.springframework.http.observation.reactive.DefaultServerRequestObservationConvention; +import org.springframework.http.observation.reactive.ServerRequestObservationConvention; +import org.springframework.web.filter.reactive.ServerHttpObservationFilter; + +/** + * {@link EnableAutoConfiguration Auto-configuration} for instrumentation of Spring + * WebFlux applications. + * + * @author Brian Clozel + * @author Jon Schneider + * @author Dmytro Nosan + * @since 3.0.0 + */ +@AutoConfiguration(after = { MetricsAutoConfiguration.class, CompositeMeterRegistryAutoConfiguration.class, + SimpleMetricsExportAutoConfiguration.class, ObservationAutoConfiguration.class }) +@ConditionalOnClass(Observation.class) +@ConditionalOnBean(ObservationRegistry.class) +@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.REACTIVE) +@EnableConfigurationProperties({ MetricsProperties.class, ObservationProperties.class }) +@SuppressWarnings("removal") +public class WebFluxObservationAutoConfiguration { + + private final MetricsProperties metricsProperties; + + private final ObservationProperties observationProperties; + + public WebFluxObservationAutoConfiguration(MetricsProperties metricsProperties, + ObservationProperties observationProperties) { + this.metricsProperties = metricsProperties; + this.observationProperties = observationProperties; + } + + @Bean + @ConditionalOnMissingBean + public ServerHttpObservationFilter webfluxObservationFilter(ObservationRegistry registry, + ObjectProvider tagConfigurer, + ObjectProvider contributorsProvider) { + + String observationName = this.observationProperties.getHttp().getServer().getRequests().getName(); + String metricName = this.metricsProperties.getWeb().getServer().getRequest().getMetricName(); + String name = (observationName != null) ? observationName : metricName; + WebFluxTagsProvider tagsProvider = tagConfigurer.getIfAvailable(); + List tagsContributors = contributorsProvider.orderedStream().toList(); + ServerRequestObservationConvention convention = new DefaultServerRequestObservationConvention(name); + if (tagsProvider != null) { + convention = new ServerRequestObservationConventionAdapter(name, tagsProvider); + } + else if (!tagsContributors.isEmpty()) { + convention = new ServerRequestObservationConventionAdapter(name, tagsContributors); + } + return new ServerHttpObservationFilter(registry, convention); + } + + @Configuration(proxyBeanMethods = false) + @ConditionalOnClass(MeterRegistry.class) + @ConditionalOnBean(MeterRegistry.class) + static class MeterFilterConfiguration { + + @Bean + @Order(0) + MeterFilter metricsHttpServerUriTagFilter(MetricsProperties properties) { + String metricName = properties.getWeb().getServer().getRequest().getMetricName(); + MeterFilter filter = new OnlyOnceLoggingDenyMeterFilter( + () -> String.format("Reached the maximum number of URI tags for '%s'.", metricName)); + return MeterFilter.maximumAllowableTags(metricName, "uri", properties.getWeb().getServer().getMaxUriTags(), + filter); + } + + } + +} diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/web/reactive/package-info.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/observation/web/reactive/package-info.java similarity index 74% rename from spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/web/reactive/package-info.java rename to spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/observation/web/reactive/package-info.java index 4849faffa23..e7770c5fba2 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/web/reactive/package-info.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/observation/web/reactive/package-info.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2019 the original author or authors. + * 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. @@ -15,6 +15,6 @@ */ /** - * Auto-configuration for WebFlux actuator metrics. + * Auto-configuration for WebFlux actuator observations. */ -package org.springframework.boot.actuate.autoconfigure.metrics.web.reactive; +package org.springframework.boot.actuate.autoconfigure.observation.web.reactive; diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports index 967582a86da..74ec09657fc 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -79,7 +79,7 @@ org.springframework.boot.actuate.autoconfigure.metrics.startup.StartupTimeMetric org.springframework.boot.actuate.autoconfigure.metrics.task.TaskExecutorMetricsAutoConfiguration org.springframework.boot.actuate.autoconfigure.observation.web.client.HttpClientObservationsAutoConfiguration org.springframework.boot.actuate.autoconfigure.metrics.web.jetty.JettyMetricsAutoConfiguration -org.springframework.boot.actuate.autoconfigure.metrics.web.reactive.WebFluxMetricsAutoConfiguration +org.springframework.boot.actuate.autoconfigure.observation.web.reactive.WebFluxObservationAutoConfiguration org.springframework.boot.actuate.autoconfigure.metrics.web.tomcat.TomcatMetricsAutoConfiguration org.springframework.boot.actuate.autoconfigure.data.mongo.MongoHealthContributorAutoConfiguration org.springframework.boot.actuate.autoconfigure.data.mongo.MongoReactiveHealthContributorAutoConfiguration diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/test/MetricsIntegrationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/test/MetricsIntegrationTests.java index 922a5e113e5..1062a3bafb6 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/test/MetricsIntegrationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/test/MetricsIntegrationTests.java @@ -41,9 +41,9 @@ import org.springframework.boot.actuate.autoconfigure.metrics.amqp.RabbitMetrics import org.springframework.boot.actuate.autoconfigure.metrics.cache.CacheMetricsAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.metrics.jdbc.DataSourcePoolMetricsAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.metrics.orm.jpa.HibernateMetricsAutoConfiguration; -import org.springframework.boot.actuate.autoconfigure.metrics.web.reactive.WebFluxMetricsAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.observation.ObservationAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.observation.web.client.HttpClientObservationsAutoConfiguration; +import org.springframework.boot.actuate.autoconfigure.observation.web.reactive.WebFluxObservationAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.observation.web.servlet.WebMvcObservationAutoConfiguration; import org.springframework.boot.autoconfigure.ImportAutoConfiguration; import org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration; @@ -141,7 +141,7 @@ class MetricsIntegrationTests { SystemMetricsAutoConfiguration.class, RabbitMetricsAutoConfiguration.class, CacheMetricsAutoConfiguration.class, DataSourcePoolMetricsAutoConfiguration.class, HibernateMetricsAutoConfiguration.class, HttpClientObservationsAutoConfiguration.class, - WebFluxMetricsAutoConfiguration.class, WebMvcObservationAutoConfiguration.class, + WebFluxObservationAutoConfiguration.class, WebMvcObservationAutoConfiguration.class, JacksonAutoConfiguration.class, HttpMessageConvertersAutoConfiguration.class, RestTemplateAutoConfiguration.class, WebMvcAutoConfiguration.class, DispatcherServletAutoConfiguration.class, ServletWebServerFactoryAutoConfiguration.class }) diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/web/reactive/WebFluxMetricsAutoConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/web/reactive/WebFluxMetricsAutoConfigurationTests.java deleted file mode 100644 index 14291de5107..00000000000 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/web/reactive/WebFluxMetricsAutoConfigurationTests.java +++ /dev/null @@ -1,159 +0,0 @@ -/* - * Copyright 2012-2020 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.web.reactive; - -import io.micrometer.core.instrument.MeterRegistry; -import org.junit.jupiter.api.Disabled; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; - -import org.springframework.boot.actuate.autoconfigure.metrics.test.MetricsRun; -import org.springframework.boot.actuate.autoconfigure.metrics.web.TestController; -import org.springframework.boot.actuate.metrics.web.reactive.server.DefaultWebFluxTagsProvider; -import org.springframework.boot.actuate.metrics.web.reactive.server.MetricsWebFilter; -import org.springframework.boot.actuate.metrics.web.reactive.server.WebFluxTagsContributor; -import org.springframework.boot.actuate.metrics.web.reactive.server.WebFluxTagsProvider; -import org.springframework.boot.autoconfigure.AutoConfigurations; -import org.springframework.boot.autoconfigure.web.reactive.WebFluxAutoConfiguration; -import org.springframework.boot.test.context.assertj.AssertableReactiveWebApplicationContext; -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.test.web.reactive.server.WebTestClient; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.mock; - -/** - * Tests for {@link WebFluxMetricsAutoConfiguration} - * - * @author Brian Clozel - * @author Dmytro Nosan - * @author Madhura Bhave - */ -@ExtendWith(OutputCaptureExtension.class) -@Disabled("until gh-32539 is fixed") -class WebFluxMetricsAutoConfigurationTests { - - private final ReactiveWebApplicationContextRunner contextRunner = new ReactiveWebApplicationContextRunner() - .with(MetricsRun.simple()).withConfiguration(AutoConfigurations.of(WebFluxMetricsAutoConfiguration.class)); - - @Test - void shouldProvideWebFluxMetricsBeans() { - this.contextRunner.run((context) -> { - assertThat(context).getBeans(MetricsWebFilter.class).hasSize(1); - assertThat(context).getBeans(DefaultWebFluxTagsProvider.class).hasSize(1); - assertThat(context.getBean(DefaultWebFluxTagsProvider.class)).extracting("ignoreTrailingSlash") - .isEqualTo(true); - }); - } - - @Test - void tagsProviderWhenIgnoreTrailingSlashIsFalse() { - this.contextRunner.withPropertyValues("management.metrics.web.server.request.ignore-trailing-slash=false") - .run((context) -> { - assertThat(context).hasSingleBean(DefaultWebFluxTagsProvider.class); - assertThat(context.getBean(DefaultWebFluxTagsProvider.class)).extracting("ignoreTrailingSlash") - .isEqualTo(false); - }); - } - - @Test - void shouldNotOverrideCustomTagsProvider() { - this.contextRunner.withUserConfiguration(CustomWebFluxTagsProviderConfig.class) - .run((context) -> assertThat(context).getBeans(WebFluxTagsProvider.class).hasSize(1) - .containsKey("customWebFluxTagsProvider")); - } - - @Test - void afterMaxUrisReachedFurtherUrisAreDenied(CapturedOutput output) { - this.contextRunner.withConfiguration(AutoConfigurations.of(WebFluxAutoConfiguration.class)) - .withUserConfiguration(TestController.class) - .withPropertyValues("management.metrics.web.server.max-uri-tags=2").run((context) -> { - MeterRegistry registry = getInitializedMeterRegistry(context); - assertThat(registry.get("http.server.requests").meters()).hasSize(2); - assertThat(output).contains("Reached the maximum number of URI tags for 'http.server.requests'"); - }); - } - - @Test - void shouldNotDenyNorLogIfMaxUrisIsNotReached(CapturedOutput output) { - this.contextRunner.withConfiguration(AutoConfigurations.of(WebFluxAutoConfiguration.class)) - .withUserConfiguration(TestController.class) - .withPropertyValues("management.metrics.web.server.max-uri-tags=5").run((context) -> { - MeterRegistry registry = getInitializedMeterRegistry(context); - assertThat(registry.get("http.server.requests").meters()).hasSize(3); - assertThat(output) - .doesNotContain("Reached the maximum number of URI tags for 'http.server.requests'"); - }); - } - - @Test - void metricsAreNotRecordedIfAutoTimeRequestsIsDisabled() { - this.contextRunner.withConfiguration(AutoConfigurations.of(WebFluxAutoConfiguration.class)) - .withUserConfiguration(TestController.class) - .withPropertyValues("management.metrics.web.server.request.autotime.enabled=false").run((context) -> { - MeterRegistry registry = getInitializedMeterRegistry(context); - assertThat(registry.find("http.server.requests").meter()).isNull(); - }); - } - - @Test - void whenTagContributorsAreDefinedThenTagsProviderUsesThem() { - this.contextRunner.withUserConfiguration(TagsContributorsConfiguration.class).run((context) -> { - assertThat(context).hasSingleBean(DefaultWebFluxTagsProvider.class); - assertThat(context.getBean(DefaultWebFluxTagsProvider.class)).extracting("contributors").asList() - .hasSize(2); - }); - } - - private MeterRegistry getInitializedMeterRegistry(AssertableReactiveWebApplicationContext context) { - WebTestClient webTestClient = WebTestClient.bindToApplicationContext(context).build(); - for (int i = 0; i < 3; i++) { - webTestClient.get().uri("/test" + i).exchange().expectStatus().isOk(); - } - return context.getBean(MeterRegistry.class); - } - - @Configuration(proxyBeanMethods = false) - static class CustomWebFluxTagsProviderConfig { - - @Bean - WebFluxTagsProvider customWebFluxTagsProvider() { - return mock(WebFluxTagsProvider.class); - } - - } - - @Configuration(proxyBeanMethods = false) - static class TagsContributorsConfiguration { - - @Bean - WebFluxTagsContributor tagContributorOne() { - return mock(WebFluxTagsContributor.class); - } - - @Bean - WebFluxTagsContributor tagContributorTwo() { - return mock(WebFluxTagsContributor.class); - } - - } - -} diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/observation/web/reactive/ServerRequestObservationConventionAdapterTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/observation/web/reactive/ServerRequestObservationConventionAdapterTests.java new file mode 100644 index 00000000000..8f1c79c89d2 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/observation/web/reactive/ServerRequestObservationConventionAdapterTests.java @@ -0,0 +1,64 @@ +/* + * 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.web.reactive; + +import io.micrometer.common.KeyValue; +import org.junit.jupiter.api.Test; + +import org.springframework.boot.actuate.metrics.web.reactive.server.DefaultWebFluxTagsProvider; +import org.springframework.http.observation.reactive.ServerRequestObservationContext; +import org.springframework.mock.http.server.reactive.MockServerHttpRequest; +import org.springframework.mock.web.server.MockServerWebExchange; +import org.springframework.web.reactive.HandlerMapping; +import org.springframework.web.util.pattern.PathPatternParser; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link ServerRequestObservationConventionAdapter}. + * + * @author Brian Clozel + */ +@SuppressWarnings("removal") +class ServerRequestObservationConventionAdapterTests { + + private static final String TEST_METRIC_NAME = "test.metric.name"; + + private final MockServerHttpRequest request = MockServerHttpRequest.get("/resource/test").build(); + + private final MockServerWebExchange serverWebExchange = MockServerWebExchange.builder(this.request).build(); + + private final ServerRequestObservationContext context = new ServerRequestObservationContext(this.serverWebExchange); + + private final ServerRequestObservationConventionAdapter convention = new ServerRequestObservationConventionAdapter( + TEST_METRIC_NAME, new DefaultWebFluxTagsProvider()); + + @Test + void shouldUseConfiguredName() { + assertThat(this.convention.getName()).isEqualTo(TEST_METRIC_NAME); + } + + @Test + void shouldPushTagsAsLowCardinalityKeyValues() { + this.serverWebExchange.getAttributes().put(HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE, + PathPatternParser.defaultInstance.parse("/resource/{name}")); + assertThat(this.convention.getLowCardinalityKeyValues(this.context)).contains(KeyValue.of("status", "200"), + KeyValue.of("outcome", "SUCCESS"), KeyValue.of("uri", "/resource/{name}"), + KeyValue.of("method", "GET")); + } + +} diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/observation/web/reactive/WebFluxObservationAutoConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/observation/web/reactive/WebFluxObservationAutoConfigurationTests.java new file mode 100644 index 00000000000..e5a7fadf17c --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/observation/web/reactive/WebFluxObservationAutoConfigurationTests.java @@ -0,0 +1,108 @@ +/* + * 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.web.reactive; + +import io.micrometer.core.instrument.Tag; +import io.micrometer.core.instrument.Tags; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; + +import org.springframework.boot.actuate.autoconfigure.metrics.test.MetricsRun; +import org.springframework.boot.actuate.autoconfigure.observation.ObservationAutoConfiguration; +import org.springframework.boot.actuate.metrics.web.reactive.server.DefaultWebFluxTagsProvider; +import org.springframework.boot.actuate.metrics.web.reactive.server.WebFluxTagsContributor; +import org.springframework.boot.actuate.metrics.web.reactive.server.WebFluxTagsProvider; +import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.test.context.runner.ReactiveWebApplicationContextRunner; +import org.springframework.boot.test.system.OutputCaptureExtension; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.filter.reactive.ServerHttpObservationFilter; +import org.springframework.web.server.ServerWebExchange; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link WebFluxObservationAutoConfiguration} + * + * @author Brian Clozel + * @author Dmytro Nosan + * @author Madhura Bhave + */ +@ExtendWith(OutputCaptureExtension.class) +@SuppressWarnings("removal") +class WebFluxObservationAutoConfigurationTests { + + private final ReactiveWebApplicationContextRunner contextRunner = new ReactiveWebApplicationContextRunner() + .with(MetricsRun.simple()).withConfiguration(AutoConfigurations.of(ObservationAutoConfiguration.class, + WebFluxObservationAutoConfiguration.class)); + + @Test + void shouldProvideWebFluxObservationFilter() { + this.contextRunner.run((context) -> assertThat(context).hasSingleBean(ServerHttpObservationFilter.class)); + } + + @Test + void shouldUseConventionAdapterWhenCustomTagsProvider() { + this.contextRunner.withUserConfiguration(CustomTagsProviderConfiguration.class).run((context) -> { + assertThat(context).hasSingleBean(ServerHttpObservationFilter.class); + assertThat(context).hasSingleBean(WebFluxTagsProvider.class); + assertThat(context).getBean(ServerHttpObservationFilter.class).extracting("observationConvention") + .isInstanceOf(ServerRequestObservationConventionAdapter.class); + }); + } + + @Test + void shouldUseConventionAdapterWhenCustomTagsContributor() { + this.contextRunner.withUserConfiguration(CustomTagsContributorConfiguration.class).run((context) -> { + assertThat(context).hasSingleBean(ServerHttpObservationFilter.class); + assertThat(context).hasSingleBean(WebFluxTagsContributor.class); + assertThat(context).getBean(ServerHttpObservationFilter.class).extracting("observationConvention") + .isInstanceOf(ServerRequestObservationConventionAdapter.class); + }); + } + + @Configuration(proxyBeanMethods = false) + static class CustomTagsProviderConfiguration { + + @Bean + WebFluxTagsProvider tagsProvider() { + return new DefaultWebFluxTagsProvider(); + } + + } + + @Configuration(proxyBeanMethods = false) + static class CustomTagsContributorConfiguration { + + @Bean + WebFluxTagsContributor tagsContributor() { + return new CustomTagsContributor(); + } + + } + + static class CustomTagsContributor implements WebFluxTagsContributor { + + @Override + public Iterable httpRequestTags(ServerWebExchange exchange, Throwable ex) { + return Tags.of("custom", "testvalue"); + } + + } + +} diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/web/reactive/server/CancelledServerWebExchangeException.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/web/reactive/server/CancelledServerWebExchangeException.java deleted file mode 100644 index b0e6c40c7d5..00000000000 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/web/reactive/server/CancelledServerWebExchangeException.java +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright 2012-2021 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.actuate.metrics.web.reactive.server; - -/** - * Runtime exception that materializes a {@link reactor.core.publisher.SignalType#CANCEL - * cancel signal} for the WebFlux server metrics instrumentation. - * - * @author Brian Clozel - * @since 2.5.0 - * @see MetricsWebFilter - */ -public class CancelledServerWebExchangeException extends RuntimeException { - -} diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/web/reactive/server/DefaultWebFluxTagsProvider.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/web/reactive/server/DefaultWebFluxTagsProvider.java index c3407a3da73..74deb59e472 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/web/reactive/server/DefaultWebFluxTagsProvider.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/web/reactive/server/DefaultWebFluxTagsProvider.java @@ -30,7 +30,11 @@ import org.springframework.web.server.ServerWebExchange; * @author Jon Schneider * @author Andy Wilkinson * @since 2.0.0 + * @deprecated since 3.0.0 for removal in 3.2.0 in favor of + * {@link org.springframework.http.observation.reactive.ServerRequestObservationConvention} */ +@Deprecated(since = "3.0.0", forRemoval = true) +@SuppressWarnings("removal") public class DefaultWebFluxTagsProvider implements WebFluxTagsProvider { private final boolean ignoreTrailingSlash; diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/web/reactive/server/MetricsWebFilter.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/web/reactive/server/MetricsWebFilter.java deleted file mode 100644 index f3428763772..00000000000 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/web/reactive/server/MetricsWebFilter.java +++ /dev/null @@ -1,128 +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.web.reactive.server; - -import java.util.Collections; -import java.util.Set; -import java.util.concurrent.TimeUnit; - -import io.micrometer.core.annotation.Timed; -import io.micrometer.core.instrument.MeterRegistry; -import io.micrometer.core.instrument.Tag; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.reactivestreams.Publisher; -import reactor.core.publisher.Mono; - -import org.springframework.boot.actuate.metrics.AutoTimer; -import org.springframework.boot.actuate.metrics.annotation.TimedAnnotations; -import org.springframework.boot.web.reactive.error.ErrorAttributes; -import org.springframework.core.Ordered; -import org.springframework.core.annotation.Order; -import org.springframework.http.server.reactive.ServerHttpResponse; -import org.springframework.web.method.HandlerMethod; -import org.springframework.web.reactive.HandlerMapping; -import org.springframework.web.server.ServerWebExchange; -import org.springframework.web.server.WebFilter; -import org.springframework.web.server.WebFilterChain; - -/** - * Intercepts incoming HTTP requests handled by Spring WebFlux handlers and records - * metrics about execution time and results. - * - * @author Jon Schneider - * @author Brian Clozel - * @since 2.0.0 - */ -@Order(Ordered.HIGHEST_PRECEDENCE + 1) -public class MetricsWebFilter implements WebFilter { - - private static Log logger = LogFactory.getLog(MetricsWebFilter.class); - - private final MeterRegistry registry; - - private final WebFluxTagsProvider tagsProvider; - - private final String metricName; - - private final AutoTimer autoTimer; - - /** - * Create a new {@code MetricsWebFilter}. - * @param registry the registry to which metrics are recorded - * @param tagsProvider provider for metrics tags - * @param metricName name of the metric to record - * @param autoTimer the auto-timers to apply or {@code null} to disable auto-timing - * @since 2.2.0 - */ - public MetricsWebFilter(MeterRegistry registry, WebFluxTagsProvider tagsProvider, String metricName, - AutoTimer autoTimer) { - this.registry = registry; - this.tagsProvider = tagsProvider; - this.metricName = metricName; - this.autoTimer = (autoTimer != null) ? autoTimer : AutoTimer.DISABLED; - } - - @Override - public Mono filter(ServerWebExchange exchange, WebFilterChain chain) { - return chain.filter(exchange).transformDeferred((call) -> filter(exchange, call)); - } - - private Publisher filter(ServerWebExchange exchange, Mono call) { - long start = System.nanoTime(); - return call.doOnEach((signal) -> onTerminalSignal(exchange, signal.getThrowable(), start)) - .doOnCancel(() -> onTerminalSignal(exchange, new CancelledServerWebExchangeException(), start)); - } - - private void onTerminalSignal(ServerWebExchange exchange, Throwable cause, long start) { - ServerHttpResponse response = exchange.getResponse(); - if (response.isCommitted() || cause instanceof CancelledServerWebExchangeException) { - record(exchange, cause, start); - } - else { - response.beforeCommit(() -> { - record(exchange, cause, start); - return Mono.empty(); - }); - } - } - - private void record(ServerWebExchange exchange, Throwable cause, long start) { - try { - cause = (cause != null) ? cause : exchange.getAttribute(ErrorAttributes.ERROR_ATTRIBUTE); - Object handler = exchange.getAttribute(HandlerMapping.BEST_MATCHING_HANDLER_ATTRIBUTE); - Set annotations = getTimedAnnotations(handler); - Iterable tags = this.tagsProvider.httpRequestTags(exchange, cause); - long duration = System.nanoTime() - start; - AutoTimer.apply(this.autoTimer, this.metricName, annotations, - (builder) -> builder.description("Duration of HTTP server request handling").tags(tags) - .register(this.registry).record(duration, TimeUnit.NANOSECONDS)); - } - catch (Exception ex) { - logger.warn("Failed to record timer metrics", ex); - // Allow exchange to continue, unaffected by metrics problem - } - } - - private Set getTimedAnnotations(Object handler) { - if (handler instanceof HandlerMethod handlerMethod) { - return TimedAnnotations.get(handlerMethod.getMethod(), handlerMethod.getBeanType()); - } - return Collections.emptySet(); - } - -} diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/web/reactive/server/WebFluxTags.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/web/reactive/server/WebFluxTags.java index 7b55fff6b48..756dd7660ab 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/web/reactive/server/WebFluxTags.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/web/reactive/server/WebFluxTags.java @@ -40,7 +40,10 @@ import org.springframework.web.util.pattern.PathPattern; * @author Michael McFadyen * @author Brian Clozel * @since 2.0.0 + * @deprecated since 3.0.0 for removal in 3.2.0 in favor of + * {@link org.springframework.http.observation.reactive.ServerRequestObservationConvention} */ +@Deprecated(since = "3.0.0", forRemoval = true) public final class WebFluxTags { private static final Tag URI_NOT_FOUND = Tag.of("uri", "NOT_FOUND"); @@ -176,8 +179,7 @@ public final class WebFluxTags { */ public static Tag outcome(ServerWebExchange exchange, Throwable exception) { if (exception != null) { - if (exception instanceof CancelledServerWebExchangeException - || DISCONNECTED_CLIENT_EXCEPTIONS.contains(exception.getClass().getSimpleName())) { + if (DISCONNECTED_CLIENT_EXCEPTIONS.contains(exception.getClass().getSimpleName())) { return Outcome.UNKNOWN.asTag(); } } diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/web/reactive/server/WebFluxTagsContributor.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/web/reactive/server/WebFluxTagsContributor.java index a0ff28daa39..a5a745a328e 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/web/reactive/server/WebFluxTagsContributor.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/web/reactive/server/WebFluxTagsContributor.java @@ -26,8 +26,11 @@ import org.springframework.web.server.ServerWebExchange; * * @author Andy Wilkinson * @since 2.3.0 + * @deprecated since 3.0.0 for removal in 3.2.0 in favor of + * {@link org.springframework.http.observation.reactive.ServerRequestObservationConvention} */ @FunctionalInterface +@Deprecated(since = "3.0.0", forRemoval = true) public interface WebFluxTagsContributor { /** diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/web/reactive/server/WebFluxTagsProvider.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/web/reactive/server/WebFluxTagsProvider.java index 7ccd79e36e4..b6b2963c513 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/web/reactive/server/WebFluxTagsProvider.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/web/reactive/server/WebFluxTagsProvider.java @@ -26,8 +26,11 @@ import org.springframework.web.server.ServerWebExchange; * @author Jon Schneider * @author Andy Wilkinson * @since 2.0.0 + * @deprecated since 3.0.0 for removal in 3.2.0 in favor of + * {@link org.springframework.http.observation.reactive.ServerRequestObservationConvention} */ @FunctionalInterface +@Deprecated(since = "3.0.0", forRemoval = true) public interface WebFluxTagsProvider { /** diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/web/reactive/server/DefaultWebFluxTagsProviderTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/web/reactive/server/DefaultWebFluxTagsProviderTests.java index c1541223ea1..4298a989777 100644 --- a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/web/reactive/server/DefaultWebFluxTagsProviderTests.java +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/web/reactive/server/DefaultWebFluxTagsProviderTests.java @@ -37,6 +37,7 @@ import static org.assertj.core.api.Assertions.assertThat; * * @author Andy Wilkinson */ +@SuppressWarnings("removal") class DefaultWebFluxTagsProviderTests { @Test diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/web/reactive/server/MetricsWebFilterTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/web/reactive/server/MetricsWebFilterTests.java deleted file mode 100644 index 267279dcec9..00000000000 --- a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/web/reactive/server/MetricsWebFilterTests.java +++ /dev/null @@ -1,283 +0,0 @@ -/* - * Copyright 2012-2021 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.actuate.metrics.web.reactive.server; - -import java.io.EOFException; -import java.time.Duration; -import java.util.concurrent.atomic.AtomicBoolean; - -import io.micrometer.core.annotation.Timed; -import io.micrometer.core.instrument.MockClock; -import io.micrometer.core.instrument.Tag; -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 reactor.core.publisher.Mono; -import reactor.test.StepVerifier; - -import org.springframework.boot.actuate.metrics.AutoTimer; -import org.springframework.boot.web.reactive.error.ErrorAttributes; -import org.springframework.mock.http.server.reactive.MockServerHttpRequest; -import org.springframework.mock.web.server.MockServerWebExchange; -import org.springframework.util.ReflectionUtils; -import org.springframework.web.method.HandlerMethod; -import org.springframework.web.reactive.HandlerMapping; -import org.springframework.web.server.ServerWebExchange; -import org.springframework.web.util.pattern.PathPatternParser; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * Tests for {@link MetricsWebFilter} - * - * @author Brian Clozel - * @author Madhura Bhave - */ -class MetricsWebFilterTests { - - private static final String REQUEST_METRICS_NAME = "http.server.requests"; - - private static final String REQUEST_METRICS_NAME_PERCENTILE = REQUEST_METRICS_NAME + ".percentile"; - - private final FaultyWebFluxTagsProvider tagsProvider = new FaultyWebFluxTagsProvider(); - - private SimpleMeterRegistry registry; - - private MetricsWebFilter webFilter; - - @BeforeEach - void setup() { - MockClock clock = new MockClock(); - this.registry = new SimpleMeterRegistry(SimpleConfig.DEFAULT, clock); - this.webFilter = new MetricsWebFilter(this.registry, this.tagsProvider, REQUEST_METRICS_NAME, - AutoTimer.ENABLED); - } - - @Test - void filterAddsTagsToRegistry() { - MockServerWebExchange exchange = createExchange("/projects/spring-boot", "/projects/{project}"); - this.webFilter.filter(exchange, (serverWebExchange) -> exchange.getResponse().setComplete()) - .block(Duration.ofSeconds(30)); - assertMetricsContainsTag("uri", "/projects/{project}"); - assertMetricsContainsTag("status", "200"); - } - - @Test - void filterAddsTagsToRegistryForExceptions() { - MockServerWebExchange exchange = createExchange("/projects/spring-boot", "/projects/{project}"); - this.webFilter.filter(exchange, (serverWebExchange) -> Mono.error(new IllegalStateException("test error"))) - .onErrorResume((t) -> { - exchange.getResponse().setRawStatusCode(500); - return exchange.getResponse().setComplete(); - }).block(Duration.ofSeconds(30)); - assertMetricsContainsTag("uri", "/projects/{project}"); - assertMetricsContainsTag("status", "500"); - assertMetricsContainsTag("exception", "IllegalStateException"); - } - - @Test - void filterAddsNonEmptyTagsToRegistryForAnonymousExceptions() { - final Exception anonymous = new Exception("test error") { - }; - - MockServerWebExchange exchange = createExchange("/projects/spring-boot", "/projects/{project}"); - this.webFilter.filter(exchange, (serverWebExchange) -> Mono.error(anonymous)).onErrorResume((t) -> { - exchange.getResponse().setRawStatusCode(500); - return exchange.getResponse().setComplete(); - }).block(Duration.ofSeconds(30)); - assertMetricsContainsTag("uri", "/projects/{project}"); - assertMetricsContainsTag("status", "500"); - assertMetricsContainsTag("exception", anonymous.getClass().getName()); - } - - @Test - void filterAddsTagsToRegistryForHandledExceptions() { - MockServerWebExchange exchange = createExchange("/projects/spring-boot", "/projects/{project}"); - this.webFilter.filter(exchange, (serverWebExchange) -> { - exchange.getAttributes().put(ErrorAttributes.ERROR_ATTRIBUTE, new IllegalStateException("test error")); - return exchange.getResponse().setComplete(); - }).block(Duration.ofSeconds(30)); - assertMetricsContainsTag("uri", "/projects/{project}"); - assertMetricsContainsTag("status", "200"); - assertMetricsContainsTag("exception", "IllegalStateException"); - } - - @Test - void filterAddsTagsToRegistryForExceptionsAndCommittedResponse() { - MockServerWebExchange exchange = createExchange("/projects/spring-boot", "/projects/{project}"); - this.webFilter.filter(exchange, (serverWebExchange) -> { - exchange.getResponse().setRawStatusCode(500); - return exchange.getResponse().setComplete().then(Mono.error(new IllegalStateException("test error"))); - }).onErrorResume((t) -> Mono.empty()).block(Duration.ofSeconds(30)); - assertMetricsContainsTag("uri", "/projects/{project}"); - assertMetricsContainsTag("status", "500"); - } - - @Test - void trailingSlashShouldNotRecordDuplicateMetrics() { - MockServerWebExchange exchange1 = createExchange("/projects/spring-boot", "/projects/{project}"); - MockServerWebExchange exchange2 = createExchange("/projects/spring-boot", "/projects/{project}/"); - this.webFilter.filter(exchange1, (serverWebExchange) -> exchange1.getResponse().setComplete()) - .block(Duration.ofSeconds(30)); - this.webFilter.filter(exchange2, (serverWebExchange) -> exchange2.getResponse().setComplete()) - .block(Duration.ofSeconds(30)); - assertThat(this.registry.get(REQUEST_METRICS_NAME).tag("uri", "/projects/{project}").timer().count()) - .isEqualTo(2); - assertThat(this.registry.get(REQUEST_METRICS_NAME).tag("status", "200").timer().count()).isEqualTo(2); - } - - @Test - void cancelledConnectionsShouldProduceMetrics() { - MockServerWebExchange exchange = createExchange("/projects/spring-boot", "/projects/{project}"); - Mono processing = this.webFilter.filter(exchange, - (serverWebExchange) -> exchange.getResponse().setComplete()); - StepVerifier.create(processing).thenCancel().verify(Duration.ofSeconds(5)); - assertMetricsContainsTag("uri", "/projects/{project}"); - assertMetricsContainsTag("status", "200"); - assertMetricsContainsTag("outcome", "UNKNOWN"); - } - - @Test - void disconnectedExceptionShouldProduceMetrics() { - MockServerWebExchange exchange = createExchange("/projects/spring-boot", "/projects/{project}"); - Mono processing = this.webFilter - .filter(exchange, (serverWebExchange) -> Mono.error(new EOFException("Disconnected"))) - .onErrorResume((t) -> { - exchange.getResponse().setRawStatusCode(500); - return exchange.getResponse().setComplete(); - }); - StepVerifier.create(processing).expectComplete().verify(Duration.ofSeconds(5)); - assertMetricsContainsTag("uri", "/projects/{project}"); - assertMetricsContainsTag("status", "500"); - assertMetricsContainsTag("outcome", "UNKNOWN"); - } - - @Test - void filterAddsStandardTags() { - MockServerWebExchange exchange = createTimedHandlerMethodExchange("timed"); - this.webFilter.filter(exchange, (serverWebExchange) -> exchange.getResponse().setComplete()) - .block(Duration.ofSeconds(30)); - assertMetricsContainsTag("uri", "/projects/{project}"); - assertMetricsContainsTag("status", "200"); - } - - @Test - void filterAddsExtraTags() { - MockServerWebExchange exchange = createTimedHandlerMethodExchange("timedExtraTags"); - this.webFilter.filter(exchange, (serverWebExchange) -> exchange.getResponse().setComplete()) - .block(Duration.ofSeconds(30)); - assertMetricsContainsTag("uri", "/projects/{project}"); - assertMetricsContainsTag("status", "200"); - assertMetricsContainsTag("tag1", "value1"); - assertMetricsContainsTag("tag2", "value2"); - } - - @Test - void filterAddsExtraTagsAndException() { - MockServerWebExchange exchange = createTimedHandlerMethodExchange("timedExtraTags"); - this.webFilter.filter(exchange, (serverWebExchange) -> Mono.error(new IllegalStateException("test error"))) - .onErrorResume((ex) -> { - exchange.getResponse().setRawStatusCode(500); - return exchange.getResponse().setComplete(); - }).block(Duration.ofSeconds(30)); - assertMetricsContainsTag("uri", "/projects/{project}"); - assertMetricsContainsTag("status", "500"); - assertMetricsContainsTag("exception", "IllegalStateException"); - assertMetricsContainsTag("tag1", "value1"); - assertMetricsContainsTag("tag2", "value2"); - } - - @Test - void filterAddsPercentileMeters() { - MockServerWebExchange exchange = createTimedHandlerMethodExchange("timedPercentiles"); - this.webFilter.filter(exchange, (serverWebExchange) -> exchange.getResponse().setComplete()) - .block(Duration.ofSeconds(30)); - assertMetricsContainsTag("uri", "/projects/{project}"); - assertMetricsContainsTag("status", "200"); - assertThat(this.registry.get(REQUEST_METRICS_NAME_PERCENTILE).tag("phi", "0.95").gauge().value()).isNotZero(); - assertThat(this.registry.get(REQUEST_METRICS_NAME_PERCENTILE).tag("phi", "0.5").gauge().value()).isNotZero(); - } - - @Test - void whenMetricsRecordingFailsThenExchangeFilteringSucceeds() { - MockServerWebExchange exchange = createExchange("/projects/spring-boot", "/projects/{project}"); - this.tagsProvider.failOnce(); - this.webFilter.filter(exchange, (serverWebExchange) -> exchange.getResponse().setComplete()) - .block(Duration.ofSeconds(30)); - } - - private MockServerWebExchange createTimedHandlerMethodExchange(String methodName) { - MockServerWebExchange exchange = createExchange("/projects/spring-boot", "/projects/{project}"); - exchange.getAttributes().put(HandlerMapping.BEST_MATCHING_HANDLER_ATTRIBUTE, - new HandlerMethod(this, ReflectionUtils.findMethod(Handlers.class, methodName))); - return exchange; - } - - private MockServerWebExchange createExchange(String path, String pathPattern) { - PathPatternParser parser = new PathPatternParser(); - MockServerWebExchange exchange = MockServerWebExchange.from(MockServerHttpRequest.get(path).build()); - exchange.getAttributes().put(HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE, parser.parse(pathPattern)); - return exchange; - } - - private void assertMetricsContainsTag(String tagKey, String tagValue) { - assertThat(this.registry.get(REQUEST_METRICS_NAME).tag(tagKey, tagValue).timer().count()).isEqualTo(1); - } - - static class Handlers { - - @Timed - Mono timed() { - return Mono.just("test"); - } - - @Timed(extraTags = { "tag1", "value1", "tag2", "value2" }) - Mono timedExtraTags() { - return Mono.just("test"); - } - - @Timed(percentiles = { 0.5, 0.95 }) - Mono timedPercentiles() { - return Mono.just("test"); - } - - } - - class FaultyWebFluxTagsProvider extends DefaultWebFluxTagsProvider { - - private final AtomicBoolean fail = new AtomicBoolean(); - - FaultyWebFluxTagsProvider() { - super(true); - } - - @Override - public Iterable httpRequestTags(ServerWebExchange exchange, Throwable exception) { - if (this.fail.compareAndSet(true, false)) { - throw new RuntimeException(); - } - return super.httpRequestTags(exchange, exception); - } - - void failOnce() { - this.fail.set(true); - } - - } - -} diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/web/reactive/server/WebFluxTagsTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/web/reactive/server/WebFluxTagsTests.java index c82b1a22e3d..ff741118080 100644 --- a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/web/reactive/server/WebFluxTagsTests.java +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/web/reactive/server/WebFluxTagsTests.java @@ -44,6 +44,7 @@ import static org.mockito.Mockito.mock; * @author Madhura Bhave * @author Stephane Nicoll */ +@SuppressWarnings("removal") class WebFluxTagsTests { private MockServerWebExchange exchange; @@ -215,10 +216,4 @@ class WebFluxTagsTests { assertThat(tag.getValue()).isEqualTo("UNKNOWN"); } - @Test - void outcomeTagIsUnknownWhenExceptionIsCancelledExchange() { - Tag tag = WebFluxTags.outcome(this.exchange, new CancelledServerWebExchangeException()); - assertThat(tag.getValue()).isEqualTo("UNKNOWN"); - } - }