diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/MetricsProperties.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/MetricsProperties.java index 860a94d948f..2eeabbdf4d3 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/MetricsProperties.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/MetricsProperties.java @@ -137,6 +137,13 @@ public class MetricsProperties { */ private String requestsMetricName = "http.server.requests"; + /** + * Maximum number of unique URI tag values allowed. After the max number of + * tag values is reached, metrics with additional tag values are denied by + * filter. + */ + private int maxUriTags = 100; + public boolean isAutoTimeRequests() { return this.autoTimeRequests; } @@ -153,6 +160,14 @@ public class MetricsProperties { this.requestsMetricName = requestsMetricName; } + public int getMaxUriTags() { + return this.maxUriTags; + } + + public void setMaxUriTags(int maxUriTags) { + this.maxUriTags = maxUriTags; + } + } } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/OnlyOnceLoggingDenyMeterFilter.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/OnlyOnceLoggingDenyMeterFilter.java new file mode 100644 index 00000000000..3538a6f2a5e --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/OnlyOnceLoggingDenyMeterFilter.java @@ -0,0 +1,60 @@ +/* + * Copyright 2012-2018 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 + * + * http://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; + +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.function.Supplier; + +import io.micrometer.core.instrument.Meter; +import io.micrometer.core.instrument.config.MeterFilter; +import io.micrometer.core.instrument.config.MeterFilterReply; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import org.springframework.util.Assert; + +/** + * {@link MeterFilter} to log only once a warning message and deny {@link Meter.Id}. + * + * @author Jon Schneider + * @author Dmytro Nosan + * @since 2.0.5 + */ +public final class OnlyOnceLoggingDenyMeterFilter implements MeterFilter { + + private final Logger logger = LoggerFactory + .getLogger(OnlyOnceLoggingDenyMeterFilter.class); + + private final AtomicBoolean alreadyWarned = new AtomicBoolean(false); + + private final Supplier message; + + public OnlyOnceLoggingDenyMeterFilter(Supplier message) { + Assert.notNull(message, "Message must not be null"); + this.message = message; + } + + @Override + public MeterFilterReply accept(Meter.Id id) { + if (this.logger.isWarnEnabled() + && this.alreadyWarned.compareAndSet(false, true)) { + this.logger.warn(this.message.get()); + } + return MeterFilterReply.DENY; + } + +} diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/web/client/RestTemplateMetricsAutoConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/web/client/RestTemplateMetricsAutoConfiguration.java index 66ac2f7eb1c..694d65144c8 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/web/client/RestTemplateMetricsAutoConfiguration.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/web/client/RestTemplateMetricsAutoConfiguration.java @@ -16,17 +16,12 @@ package org.springframework.boot.actuate.autoconfigure.metrics.web.client; -import java.util.concurrent.atomic.AtomicBoolean; - -import io.micrometer.core.instrument.Meter.Id; import io.micrometer.core.instrument.MeterRegistry; import io.micrometer.core.instrument.config.MeterFilter; -import io.micrometer.core.instrument.config.MeterFilterReply; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; 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.metrics.web.client.DefaultRestTemplateExchangeTagsProvider; import org.springframework.boot.actuate.metrics.web.client.MetricsRestTemplateCustomizer; @@ -57,6 +52,12 @@ import org.springframework.web.client.RestTemplate; @ConditionalOnBean(MeterRegistry.class) public class RestTemplateMetricsAutoConfiguration { + private final MetricsProperties properties; + + public RestTemplateMetricsAutoConfiguration(MetricsProperties properties) { + this.properties = properties; + } + @Bean @ConditionalOnMissingBean(RestTemplateExchangeTagsProvider.class) public DefaultRestTemplateExchangeTagsProvider restTemplateTagConfigurer() { @@ -66,53 +67,20 @@ public class RestTemplateMetricsAutoConfiguration { @Bean public MetricsRestTemplateCustomizer metricsRestTemplateCustomizer( MeterRegistry meterRegistry, - RestTemplateExchangeTagsProvider restTemplateTagConfigurer, - MetricsProperties properties) { + RestTemplateExchangeTagsProvider restTemplateTagConfigurer) { return new MetricsRestTemplateCustomizer(meterRegistry, restTemplateTagConfigurer, - properties.getWeb().getClient().getRequestsMetricName()); + this.properties.getWeb().getClient().getRequestsMetricName()); } @Bean @Order(0) - public MeterFilter metricsHttpClientUriTagFilter(MetricsProperties properties) { - String metricName = properties.getWeb().getClient().getRequestsMetricName(); - MeterFilter denyFilter = new MaximumUriTagsReachedMeterFilter(metricName); + public MeterFilter metricsHttpClientUriTagFilter() { + String metricName = this.properties.getWeb().getClient().getRequestsMetricName(); + MeterFilter denyFilter = new OnlyOnceLoggingDenyMeterFilter(() -> String + .format("Reached the maximum number of URI tags for '%s'. Are you using " + + "'uriVariables' on RestTemplate calls?", metricName)); return MeterFilter.maximumAllowableTags(metricName, "uri", - properties.getWeb().getClient().getMaxUriTags(), denyFilter); - } - - /** - * {@link MeterFilter} to deny further URI tags and log a warning. - */ - private static class MaximumUriTagsReachedMeterFilter implements MeterFilter { - - private final Logger logger = LoggerFactory - .getLogger(MaximumUriTagsReachedMeterFilter.class); - - private final String metricName; - - private final AtomicBoolean alreadyWarned = new AtomicBoolean(false); - - MaximumUriTagsReachedMeterFilter(String metricName) { - this.metricName = metricName; - } - - @Override - public MeterFilterReply accept(Id id) { - if (this.alreadyWarned.compareAndSet(false, true)) { - logWarning(); - } - return MeterFilterReply.DENY; - } - - private void logWarning() { - if (this.logger.isWarnEnabled()) { - this.logger.warn( - "Reached the maximum number of URI tags for '" + this.metricName - + "'. Are you using uriVariables on HTTP client calls?"); - } - } - + this.properties.getWeb().getClient().getMaxUriTags(), denyFilter); } } 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 index 9828805672b..40d79038d6e 100644 --- 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 @@ -17,9 +17,11 @@ 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.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.metrics.web.reactive.server.DefaultWebFluxTagsProvider; import org.springframework.boot.actuate.metrics.web.reactive.server.MetricsWebFilter; @@ -31,12 +33,14 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +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 */ @Configuration @@ -46,6 +50,12 @@ import org.springframework.context.annotation.Configuration; @ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.REACTIVE) public class WebFluxMetricsAutoConfiguration { + private final MetricsProperties properties; + + public WebFluxMetricsAutoConfiguration(MetricsProperties properties) { + this.properties = properties; + } + @Bean @ConditionalOnMissingBean(WebFluxTagsProvider.class) public DefaultWebFluxTagsProvider webfluxTagConfigurer() { @@ -54,9 +64,19 @@ public class WebFluxMetricsAutoConfiguration { @Bean public MetricsWebFilter webfluxMetrics(MeterRegistry registry, - WebFluxTagsProvider tagConfigurer, MetricsProperties properties) { + WebFluxTagsProvider tagConfigurer) { return new MetricsWebFilter(registry, tagConfigurer, - properties.getWeb().getServer().getRequestsMetricName()); + this.properties.getWeb().getServer().getRequestsMetricName()); + } + + @Bean + @Order(0) + public MeterFilter metricsHttpServerUriTagFilter() { + String metricName = this.properties.getWeb().getServer().getRequestsMetricName(); + 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/metrics/web/servlet/WebMvcMetricsAutoConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/web/servlet/WebMvcMetricsAutoConfiguration.java index 566307f2632..a64036b673d 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/web/servlet/WebMvcMetricsAutoConfiguration.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/web/servlet/WebMvcMetricsAutoConfiguration.java @@ -19,10 +19,12 @@ package org.springframework.boot.actuate.autoconfigure.metrics.web.servlet; import javax.servlet.DispatcherType; import io.micrometer.core.instrument.MeterRegistry; +import io.micrometer.core.instrument.config.MeterFilter; 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; +import org.springframework.boot.actuate.autoconfigure.metrics.OnlyOnceLoggingDenyMeterFilter; import org.springframework.boot.actuate.autoconfigure.metrics.export.simple.SimpleMetricsExportAutoConfiguration; import org.springframework.boot.actuate.metrics.web.servlet.DefaultWebMvcTagsProvider; import org.springframework.boot.actuate.metrics.web.servlet.WebMvcMetricsFilter; @@ -38,6 +40,7 @@ import org.springframework.boot.web.servlet.FilterRegistrationBean; 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.web.context.WebApplicationContext; import org.springframework.web.servlet.DispatcherServlet; @@ -46,6 +49,7 @@ import org.springframework.web.servlet.DispatcherServlet; * MVC servlet-based request mappings. * * @author Jon Schneider + * @author Dmytro Nosan * @since 2.0.0 */ @Configuration @@ -57,6 +61,12 @@ import org.springframework.web.servlet.DispatcherServlet; @EnableConfigurationProperties(MetricsProperties.class) public class WebMvcMetricsAutoConfiguration { + private final MetricsProperties properties; + + public WebMvcMetricsAutoConfiguration(MetricsProperties properties) { + this.properties = properties; + } + @Bean @ConditionalOnMissingBean(WebMvcTagsProvider.class) public DefaultWebMvcTagsProvider webMvcTagsProvider() { @@ -65,9 +75,9 @@ public class WebMvcMetricsAutoConfiguration { @Bean public FilterRegistrationBean webMvcMetricsFilter( - MeterRegistry registry, MetricsProperties properties, - WebMvcTagsProvider tagsProvider, WebApplicationContext context) { - Server serverProperties = properties.getWeb().getServer(); + MeterRegistry registry, WebMvcTagsProvider tagsProvider, + WebApplicationContext context) { + Server serverProperties = this.properties.getWeb().getServer(); WebMvcMetricsFilter filter = new WebMvcMetricsFilter(context, registry, tagsProvider, serverProperties.getRequestsMetricName(), serverProperties.isAutoTimeRequests()); @@ -78,4 +88,14 @@ public class WebMvcMetricsAutoConfiguration { return registration; } + @Bean + @Order(0) + public MeterFilter metricsHttpServerUriTagFilter() { + String metricName = this.properties.getWeb().getServer().getRequestsMetricName(); + 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/test/java/org/springframework/boot/actuate/autoconfigure/metrics/web/TestController.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/web/TestController.java new file mode 100644 index 00000000000..3d5f6fcb8e6 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/web/TestController.java @@ -0,0 +1,46 @@ +/* + * Copyright 2012-2018 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 + * + * http://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; + +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + * Test controller used by metrics tests. + * + * @author Dmytro Nosan + * @author Stephane Nicoll + */ +@RestController +public class TestController { + + @GetMapping("test0") + public String test0() { + return "test0"; + } + + @GetMapping("test1") + public String test1() { + return "test1"; + } + + @GetMapping("test2") + public String test2() { + return "test2"; + } + +} diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/web/client/RestTemplateMetricsAutoConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/web/client/RestTemplateMetricsAutoConfigurationTests.java index 62abdf3b6ed..9b85a821d2d 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/web/client/RestTemplateMetricsAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/web/client/RestTemplateMetricsAutoConfigurationTests.java @@ -20,11 +20,11 @@ import io.micrometer.core.instrument.MeterRegistry; import org.junit.Rule; import org.junit.Test; -import org.springframework.boot.actuate.autoconfigure.metrics.MetricsProperties; import org.springframework.boot.actuate.autoconfigure.metrics.test.MetricsRun; import org.springframework.boot.actuate.metrics.web.client.MetricsRestTemplateCustomizer; import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.autoconfigure.web.client.RestTemplateAutoConfiguration; +import org.springframework.boot.test.context.assertj.AssertableApplicationContext; import org.springframework.boot.test.context.runner.ApplicationContextRunner; import org.springframework.boot.test.rule.OutputCapture; import org.springframework.boot.web.client.RestTemplateBuilder; @@ -76,31 +76,45 @@ public class RestTemplateMetricsAutoConfigurationTests { @Test public void afterMaxUrisReachedFurtherUrisAreDenied() { this.contextRunner - .withPropertyValues("management.metrics.web.client.max-uri-tags=10") + .withPropertyValues("management.metrics.web.client.max-uri-tags=2") .run((context) -> { - MetricsProperties properties = context - .getBean(MetricsProperties.class); - int maxUriTags = properties.getWeb().getClient().getMaxUriTags(); - MeterRegistry registry = context.getBean(MeterRegistry.class); - RestTemplate restTemplate = context.getBean(RestTemplateBuilder.class) - .build(); - MockRestServiceServer server = MockRestServiceServer - .createServer(restTemplate); - for (int i = 0; i < maxUriTags + 10; i++) { - server.expect(requestTo("/test/" + i)) - .andRespond(withStatus(HttpStatus.OK)); - } - for (int i = 0; i < maxUriTags + 10; i++) { - restTemplate.getForObject("/test/" + i, String.class); - } - assertThat(registry.get("http.client.requests").meters()) - .hasSize(maxUriTags); - assertThat(this.out.toString()) - .contains("Reached the maximum number of URI tags " - + "for 'http.client.requests'"); + MeterRegistry registry = getInitializedMeterRegistry(context); + assertThat(registry.get("http.client.requests").meters()).hasSize(2); + assertThat(this.out.toString()).contains( + "Reached the maximum number of URI tags for 'http.client.requests'."); + assertThat(this.out.toString()).contains( + "Are you using 'uriVariables' on RestTemplate calls?"); }); } + @Test + public void shouldNotDenyNorLogIfMaxUrisIsNotReached() { + this.contextRunner + .withPropertyValues("management.metrics.web.client.max-uri-tags=5") + .run((context) -> { + MeterRegistry registry = getInitializedMeterRegistry(context); + assertThat(registry.get("http.client.requests").meters()).hasSize(3); + assertThat(this.out.toString()).doesNotContain( + "Reached the maximum number of URI tags for 'http.client.requests'."); + assertThat(this.out.toString()).doesNotContain( + "Are you using 'uriVariables' on RestTemplate calls?"); + }); + } + + private MeterRegistry getInitializedMeterRegistry( + AssertableApplicationContext context) { + MeterRegistry registry = context.getBean(MeterRegistry.class); + RestTemplate restTemplate = context.getBean(RestTemplateBuilder.class).build(); + MockRestServiceServer server = MockRestServiceServer.createServer(restTemplate); + for (int i = 0; i < 3; i++) { + server.expect(requestTo("/test/" + i)).andRespond(withStatus(HttpStatus.OK)); + } + for (int i = 0; i < 3; i++) { + restTemplate.getForObject("/test/" + i, String.class); + } + return registry; + } + private void validateRestTemplate(RestTemplate restTemplate, MeterRegistry registry) { MockRestServiceServer server = MockRestServiceServer.createServer(restTemplate); server.expect(requestTo("/test")).andRespond(withStatus(HttpStatus.OK)); 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 index b1760fa64d8..ff970367fc4 100644 --- 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 @@ -16,17 +16,24 @@ package org.springframework.boot.actuate.autoconfigure.metrics.web.reactive; +import io.micrometer.core.instrument.MeterRegistry; +import org.junit.Rule; import org.junit.Test; import org.springframework.boot.actuate.autoconfigure.metrics.MetricsAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.metrics.export.simple.SimpleMetricsExportAutoConfiguration; +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.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.rule.OutputCapture; 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; @@ -35,6 +42,7 @@ import static org.mockito.Mockito.mock; * Tests for {@link WebFluxMetricsAutoConfiguration} * * @author Brian Clozel + * @author Dmytro Nosan */ public class WebFluxMetricsAutoConfigurationTests { @@ -43,6 +51,9 @@ public class WebFluxMetricsAutoConfigurationTests { SimpleMetricsExportAutoConfiguration.class, WebFluxMetricsAutoConfiguration.class)); + @Rule + public OutputCapture output = new OutputCapture(); + @Test public void shouldProvideWebFluxMetricsBeans() { this.contextRunner.run((context) -> { @@ -58,6 +69,45 @@ public class WebFluxMetricsAutoConfigurationTests { .hasSize(1).containsKey("customWebFluxTagsProvider")); } + @Test + public void afterMaxUrisReachedFurtherUrisAreDenied() { + 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(this.output.toString()) + .contains("Reached the maximum number of URI tags " + + "for 'http.server.requests'"); + }); + } + + @Test + public void shouldNotDenyNorLogIfMaxUrisIsNotReached() { + 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(this.output.toString()).doesNotContain( + "Reached the maximum number of URI tags for 'http.server.requests'"); + }); + } + + 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 protected static class CustomWebFluxTagsProviderConfig { diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/web/servlet/WebMvcMetricsAutoConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/web/servlet/WebMvcMetricsAutoConfigurationTests.java similarity index 60% rename from spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/web/servlet/WebMvcMetricsAutoConfigurationTests.java rename to spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/web/servlet/WebMvcMetricsAutoConfigurationTests.java index 671ddbf51bb..354f6671cda 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/web/servlet/WebMvcMetricsAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/web/servlet/WebMvcMetricsAutoConfigurationTests.java @@ -14,37 +14,48 @@ * limitations under the License. */ -package org.springframework.boot.actuate.autoconfigure.web.servlet; +package org.springframework.boot.actuate.autoconfigure.metrics.web.servlet; import java.util.Collections; import java.util.EnumSet; import javax.servlet.DispatcherType; +import javax.servlet.Filter; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import io.micrometer.core.instrument.MeterRegistry; import io.micrometer.core.instrument.Tag; import io.micrometer.core.instrument.simple.SimpleMeterRegistry; +import org.junit.Rule; import org.junit.Test; -import org.springframework.boot.actuate.autoconfigure.metrics.web.servlet.WebMvcMetricsAutoConfiguration; +import org.springframework.boot.actuate.autoconfigure.metrics.MetricsAutoConfiguration; +import org.springframework.boot.actuate.autoconfigure.metrics.web.TestController; import org.springframework.boot.actuate.metrics.web.servlet.DefaultWebMvcTagsProvider; import org.springframework.boot.actuate.metrics.web.servlet.WebMvcMetricsFilter; import org.springframework.boot.actuate.metrics.web.servlet.WebMvcTagsProvider; import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration; +import org.springframework.boot.test.context.assertj.AssertableWebApplicationContext; import org.springframework.boot.test.context.runner.WebApplicationContextRunner; +import org.springframework.boot.test.rule.OutputCapture; import org.springframework.boot.web.servlet.FilterRegistrationBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.Ordered; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; import static org.assertj.core.api.Assertions.assertThat; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; /** * Tests for {@link WebMvcMetricsAutoConfiguration}. * * @author Andy Wilkinson + * @author Dmytro Nosan */ public class WebMvcMetricsAutoConfigurationTests { @@ -52,6 +63,9 @@ public class WebMvcMetricsAutoConfigurationTests { .withConfiguration( AutoConfigurations.of(WebMvcMetricsAutoConfiguration.class)); + @Rule + public OutputCapture output = new OutputCapture(); + @Test public void backsOffWhenMeterRegistryIsMissing() { this.contextRunner.run((context) -> assertThat(context) @@ -92,6 +106,54 @@ public class WebMvcMetricsAutoConfigurationTests { }); } + @Test + public void afterMaxUrisReachedFurtherUrisAreDenied() { + this.contextRunner + .withUserConfiguration(TestController.class, + MeterRegistryConfiguration.class) + .withConfiguration(AutoConfigurations.of(MetricsAutoConfiguration.class, + WebMvcAutoConfiguration.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(this.output.toString()) + .contains("Reached the maximum number of URI tags " + + "for 'http.server.requests'"); + }); + } + + @Test + public void shouldNotDenyNorLogIfMaxUrisIsNotReached() { + this.contextRunner + .withUserConfiguration(TestController.class, + MeterRegistryConfiguration.class) + .withConfiguration(AutoConfigurations.of(MetricsAutoConfiguration.class, + WebMvcAutoConfiguration.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(this.output.toString()) + .doesNotContain("Reached the maximum number of URI tags " + + "for 'http.server.requests'"); + }); + } + + private MeterRegistry getInitializedMeterRegistry( + AssertableWebApplicationContext context) throws Exception { + assertThat(context).hasSingleBean(FilterRegistrationBean.class); + Filter filter = context.getBean(FilterRegistrationBean.class).getFilter(); + assertThat(filter).isInstanceOf(WebMvcMetricsFilter.class); + MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(context).addFilters(filter) + .build(); + for (int i = 0; i < 3; i++) { + mockMvc.perform(MockMvcRequestBuilders.get("/test" + i)) + .andExpect(status().isOk()); + } + return context.getBean(MeterRegistry.class); + } + @Configuration static class MeterRegistryConfiguration { diff --git a/spring-boot-project/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc b/spring-boot-project/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc index 8db9dd7edec..328ed881e6f 100644 --- a/spring-boot-project/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc +++ b/spring-boot-project/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc @@ -1489,6 +1489,7 @@ content into your application. Rather, pick only the properties that you need. management.metrics.web.client.max-uri-tags=100 # Maximum number of unique URI tag values allowed. After the max number of tag values is reached, metrics with additional tag values are denied by filter. management.metrics.web.client.requests-metric-name=http.client.requests # Name of the metric for sent requests. management.metrics.web.server.auto-time-requests=true # Whether requests handled by Spring MVC or WebFlux should be automatically timed. + management.metrics.web.server.max-uri-tags=100 # Maximum number of unique URI tag values allowed. After the max number of tag values is reached, metrics with additional tag values are denied by filter. management.metrics.web.server.requests-metric-name=http.server.requests # Name of the metric for received requests.