From 81a6701914dfaaf2b8fbc376789a78d7afc76c2e Mon Sep 17 00:00:00 2001 From: Dmytro Nosan Date: Wed, 22 Aug 2018 20:22:42 +0300 Subject: [PATCH] Limit metrics collection of incoming requests See gh-14173 --- .../metrics/MetricsProperties.java | 15 +++ .../OnlyOnceLoggingDenyMeterFilter.java | 59 +++++++++ .../RestTemplateMetricsAutoConfiguration.java | 46 +------ .../WebFluxMetricsAutoConfiguration.java | 14 ++ .../WebMvcMetricsAutoConfiguration.java | 14 ++ ...TemplateMetricsAutoConfigurationTests.java | 44 +++--- .../WebFluxMetricsAutoConfigurationTests.java | 125 ++++++++++++++++++ .../WebMvcMetricsAutoConfigurationTests.java | 66 +++++++++ .../appendix-application-properties.adoc | 1 + 9 files changed, 323 insertions(+), 61 deletions(-) create mode 100644 spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/OnlyOnceLoggingDenyMeterFilter.java create mode 100644 spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/web/reactive/WebFluxMetricsAutoConfigurationTests.java 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 281be71459e..73db193f222 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 @@ -134,6 +134,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; } @@ -150,6 +157,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..66af546097f --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/OnlyOnceLoggingDenyMeterFilter.java @@ -0,0 +1,59 @@ +/* + * 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 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 5999245e298..e2205838e25 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; @@ -76,43 +71,12 @@ public class RestTemplateMetricsAutoConfiguration { @Order(0) public MeterFilter metricsWebClientUriTagFilter(MetricsProperties properties) { String metricName = properties.getWeb().getClient().getRequestsMetricName(); - MeterFilter denyFilter = new MaximumUriTagsReachedMeterFilter(metricName); + 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 RestTemplate calls?"); - } - } - - } - } 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 6f619440d97..6b29ef8173b 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 MVC annotation-based programming model request mappings. * * @author Jon Schneider + * @author Dmytro Nosan * @since 2.0.0 */ @Configuration @@ -59,4 +63,14 @@ public class WebFluxMetricsAutoConfiguration { properties.getWeb().getServer().getRequestsMetricName()); } + @Bean + @Order(0) + public MeterFilter metricsHttpServerUriTagFilter(MetricsProperties properties) { + String metricName = 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", + 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..1a52fedcc0c 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 @@ -78,4 +82,14 @@ public class WebMvcMetricsAutoConfiguration { return registration; } + @Bean + @Order(0) + public MeterFilter metricsHttpServerUriTagFilter(MetricsProperties properties) { + String metricName = 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", + 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/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 19a90db871e..c2501d2caf5 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 @@ -75,26 +75,30 @@ public class RestTemplateMetricsAutoConfigurationTests { @Test public void afterMaxUrisReachedFurtherUrisAreDenied() { - this.contextRunner.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'"); - }); + this.contextRunner + .withPropertyValues("management.metrics.web.client.max-uri-tags=10") + .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'. Are you using uriVariables on RestTemplate calls?"); + }); } private void validateRestTemplate(RestTemplate restTemplate, MeterRegistry registry) { 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 new file mode 100644 index 00000000000..5aede3b8362 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/web/reactive/WebFluxMetricsAutoConfigurationTests.java @@ -0,0 +1,125 @@ +/* + * 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.reactive; + +import io.micrometer.core.instrument.MeterRegistry; +import org.junit.Rule; +import org.junit.Test; +import reactor.core.publisher.Mono; + +import org.springframework.boot.actuate.autoconfigure.metrics.MetricsAutoConfiguration; +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.WebFluxTagsProvider; +import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.autoconfigure.web.reactive.WebFluxAutoConfiguration; +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 org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; + +/** + * Tests for {@link WebFluxMetricsAutoConfiguration} + * + * @author Brian Clozel + * @author Dmytro Nosan + */ +public class WebFluxMetricsAutoConfigurationTests { + + private ReactiveWebApplicationContextRunner contextRunner = new ReactiveWebApplicationContextRunner() + .withConfiguration(AutoConfigurations.of(MetricsAutoConfiguration.class, + SimpleMetricsExportAutoConfiguration.class, + WebFluxMetricsAutoConfiguration.class)); + + @Rule + public OutputCapture output = new OutputCapture(); + + @Test + public void shouldProvideWebFluxMetricsBeans() { + this.contextRunner.run((context) -> { + assertThat(context).getBeans(MetricsWebFilter.class).hasSize(1); + assertThat(context).getBeans(DefaultWebFluxTagsProvider.class).hasSize(1); + }); + } + + @Test + public void shouldNotOverrideCustomTagsProvider() { + this.contextRunner.withUserConfiguration(CustomWebFluxTagsProviderConfig.class) + .run((context) -> assertThat(context).getBeans(WebFluxTagsProvider.class) + .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) -> { + WebTestClient webTestClient = WebTestClient + .bindToApplicationContext(context).build(); + + for (int i = 0; i < 3; i++) { + webTestClient.get().uri("/test" + i).exchange().expectStatus() + .isOk(); + } + MeterRegistry registry = context.getBean(MeterRegistry.class); + 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'"); + }); + } + + @Configuration + protected static class CustomWebFluxTagsProviderConfig { + + @Bean + public WebFluxTagsProvider customWebFluxTagsProvider() { + return mock(WebFluxTagsProvider.class); + } + + } + + @RestController + static class TestController { + + @GetMapping("test0") + public Mono test0() { + return Mono.just("test0"); + } + + @GetMapping("test1") + public Mono test1() { + return Mono.just("test1"); + } + + @GetMapping("test2") + public Mono test2() { + return Mono.just("test2"); + } + + } + +} 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/web/servlet/WebMvcMetricsAutoConfigurationTests.java index 671ddbf51bb..978745c2fff 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/web/servlet/WebMvcMetricsAutoConfigurationTests.java @@ -20,31 +20,43 @@ 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.MetricsAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.metrics.web.servlet.WebMvcMetricsAutoConfiguration; 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.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 org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; 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 +64,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 +107,37 @@ 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) -> { + + 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()); + } + + MeterRegistry registry = context.getBean(MeterRegistry.class); + 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'"); + }); + } + @Configuration static class MeterRegistryConfiguration { @@ -128,4 +174,24 @@ public class WebMvcMetricsAutoConfigurationTests { } + @RestController + static 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-docs/src/main/asciidoc/appendix-application-properties.adoc b/spring-boot-project/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc index 0f1329786ac..89d497b4064 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 @@ -1424,6 +1424,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.