From ef9960c69fb63fc12793de2c83ed2577ce07a464 Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Thu, 12 Mar 2020 11:48:28 +0000 Subject: [PATCH] Allow additional tags to be contributed to WebMvc and WebFlux defaults Closes gh-20175 --- .../WebFluxMetricsAutoConfiguration.java | 10 +- .../WebMvcMetricsAutoConfiguration.java | 9 +- .../WebFluxMetricsAutoConfigurationTests.java | 25 +++++ .../WebMvcMetricsAutoConfigurationTests.java | 25 +++++ .../server/DefaultWebFluxTagsProvider.java | 35 ++++++- .../server/WebFluxTagsContributor.java | 41 ++++++++ .../servlet/DefaultWebMvcTagsProvider.java | 40 +++++++- .../web/servlet/WebMvcTagsContributor.java | 56 +++++++++++ .../DefaultWebMvcTagsProviderTests.java | 99 +++++++++++++++++++ .../DefaultWebFluxTagsProviderTests.java | 79 +++++++++++++++ .../asciidoc/production-ready-features.adoc | 6 +- 11 files changed, 414 insertions(+), 11 deletions(-) create mode 100644 spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/web/reactive/server/WebFluxTagsContributor.java create mode 100644 spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/web/servlet/WebMvcTagsContributor.java create mode 100644 spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/servlet/DefaultWebMvcTagsProviderTests.java create mode 100644 spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/web/reactive/server/DefaultWebFluxTagsProviderTests.java 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 a08ede72f4..c14d538806 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 @@ -16,9 +16,12 @@ package org.springframework.boot.actuate.autoconfigure.metrics.web.reactive; +import java.util.stream.Collectors; + 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.MetricsAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.metrics.MetricsProperties; import org.springframework.boot.actuate.autoconfigure.metrics.MetricsProperties.Web.Server.ServerRequest; @@ -26,6 +29,7 @@ import org.springframework.boot.actuate.autoconfigure.metrics.OnlyOnceLoggingDen 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.AutoConfigureAfter; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; @@ -58,9 +62,9 @@ public class WebFluxMetricsAutoConfiguration { @Bean @ConditionalOnMissingBean(WebFluxTagsProvider.class) - public DefaultWebFluxTagsProvider webfluxTagConfigurer() { - return new DefaultWebFluxTagsProvider( - this.properties.getWeb().getServer().getRequest().isIgnoreTrailingSlash()); + public DefaultWebFluxTagsProvider webFluxTagsProvider(ObjectProvider contributors) { + return new DefaultWebFluxTagsProvider(this.properties.getWeb().getServer().getRequest().isIgnoreTrailingSlash(), + contributors.orderedStream().collect(Collectors.toList())); } @Bean 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 b514f00bab..2fe34a1d96 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 @@ -16,11 +16,14 @@ package org.springframework.boot.actuate.autoconfigure.metrics.web.servlet; +import java.util.stream.Collectors; + import javax.servlet.DispatcherType; 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.MetricsAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.metrics.MetricsProperties; import org.springframework.boot.actuate.autoconfigure.metrics.MetricsProperties.Web.Server.ServerRequest; @@ -29,6 +32,7 @@ import org.springframework.boot.actuate.autoconfigure.metrics.export.simple.Simp import org.springframework.boot.actuate.metrics.web.servlet.DefaultWebMvcTagsProvider; import org.springframework.boot.actuate.metrics.web.servlet.LongTaskTimingHandlerInterceptor; import org.springframework.boot.actuate.metrics.web.servlet.WebMvcMetricsFilter; +import org.springframework.boot.actuate.metrics.web.servlet.WebMvcTagsContributor; import org.springframework.boot.actuate.metrics.web.servlet.WebMvcTagsProvider; import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; @@ -70,8 +74,9 @@ public class WebMvcMetricsAutoConfiguration { @Bean @ConditionalOnMissingBean(WebMvcTagsProvider.class) - public DefaultWebMvcTagsProvider webMvcTagsProvider() { - return new DefaultWebMvcTagsProvider(this.properties.getWeb().getServer().getRequest().isIgnoreTrailingSlash()); + public DefaultWebMvcTagsProvider webMvcTagsProvider(ObjectProvider contributors) { + return new DefaultWebMvcTagsProvider(this.properties.getWeb().getServer().getRequest().isIgnoreTrailingSlash(), + contributors.orderedStream().collect(Collectors.toList())); } @Bean 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 9fab2e7f0c..843b965a23 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 @@ -24,6 +24,7 @@ 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; @@ -111,6 +112,15 @@ class WebFluxMetricsAutoConfigurationTests { }); } + @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++) { @@ -129,4 +139,19 @@ class WebFluxMetricsAutoConfigurationTests { } + @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/metrics/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 index df12e89bf3..0c07ec22fc 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/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 @@ -39,6 +39,7 @@ 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.LongTaskTimingHandlerInterceptor; import org.springframework.boot.actuate.metrics.web.servlet.WebMvcMetricsFilter; +import org.springframework.boot.actuate.metrics.web.servlet.WebMvcTagsContributor; import org.springframework.boot.actuate.metrics.web.servlet.WebMvcTagsProvider; import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration; @@ -56,6 +57,7 @@ import org.springframework.test.web.servlet.setup.MockMvcBuilders; import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping; import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; /** @@ -182,6 +184,14 @@ class WebMvcMetricsAutoConfigurationTests { .contains(LongTaskTimingHandlerInterceptor.class)); } + @Test + void whenTagContributorsAreDefinedThenTagsProviderUsesThem() { + this.contextRunner.withUserConfiguration(TagsContributorsConfiguration.class).run((context) -> { + assertThat(context).hasSingleBean(DefaultWebMvcTagsProvider.class); + assertThat(context.getBean(DefaultWebMvcTagsProvider.class)).extracting("contributors").asList().hasSize(2); + }); + } + private MeterRegistry getInitializedMeterRegistry(AssertableWebApplicationContext context) throws Exception { return getInitializedMeterRegistry(context, "/test0", "/test1", "/test2"); } @@ -208,6 +218,21 @@ class WebMvcMetricsAutoConfigurationTests { } + @Configuration(proxyBeanMethods = false) + static class TagsContributorsConfiguration { + + @Bean + WebMvcTagsContributor tagContributorOne() { + return mock(WebMvcTagsContributor.class); + } + + @Bean + WebMvcTagsContributor tagContributorTwo() { + return mock(WebMvcTagsContributor.class); + } + + } + private static final class TestWebMvcTagsProvider implements WebMvcTagsProvider { @Override 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 2a70bd2eac..1e151cfd42 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 @@ -16,9 +16,11 @@ package org.springframework.boot.actuate.metrics.web.reactive.server; -import java.util.Arrays; +import java.util.Collections; +import java.util.List; import io.micrometer.core.instrument.Tag; +import io.micrometer.core.instrument.Tags; import org.springframework.web.server.ServerWebExchange; @@ -33,18 +35,47 @@ public class DefaultWebFluxTagsProvider implements WebFluxTagsProvider { private final boolean ignoreTrailingSlash; + private final List contributors; + public DefaultWebFluxTagsProvider() { this(false); } + /** + * Creates a new {@link DefaultWebFluxTagsProvider} that will provide tags from the + * given {@code contributors} in addition to its own. + * @param contributors the contributors that will provide additional tags + * @since 2.3.0 + */ + public DefaultWebFluxTagsProvider(List contributors) { + this(false, contributors); + } + public DefaultWebFluxTagsProvider(boolean ignoreTrailingSlash) { + this(ignoreTrailingSlash, Collections.emptyList()); + } + + /** + * Creates a new {@link DefaultWebFluxTagsProvider} that will provide tags from the + * given {@code contributors} in addition to its own. + * @param ignoreTrailingSlash wither trailing slashes should be ignored when + * determining the {@code uri} tag. + * @param contributors the contributors that will provide additional tags + * @since 2.3.0 + */ + public DefaultWebFluxTagsProvider(boolean ignoreTrailingSlash, List contributors) { this.ignoreTrailingSlash = ignoreTrailingSlash; + this.contributors = contributors; } @Override public Iterable httpRequestTags(ServerWebExchange exchange, Throwable exception) { - return Arrays.asList(WebFluxTags.method(exchange), WebFluxTags.uri(exchange, this.ignoreTrailingSlash), + Tags tags = Tags.of(WebFluxTags.method(exchange), WebFluxTags.uri(exchange, this.ignoreTrailingSlash), WebFluxTags.exception(exception), WebFluxTags.status(exchange), WebFluxTags.outcome(exchange)); + for (WebFluxTagsContributor contributor : this.contributors) { + tags = tags.and(contributor.httpRequestTags(exchange, exception)); + } + return tags; } } 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 new file mode 100644 index 0000000000..a0ff28daa3 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/web/reactive/server/WebFluxTagsContributor.java @@ -0,0 +1,41 @@ +/* + * 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.metrics.web.reactive.server; + +import io.micrometer.core.instrument.Tag; + +import org.springframework.web.server.ServerWebExchange; + +/** + * A contributor of {@link Tag Tags} for WebFlux-based request handling. Typically used by + * a {@link WebFluxTagsProvider} to provide tags in addition to its defaults. + * + * @author Andy Wilkinson + * @since 2.3.0 + */ +@FunctionalInterface +public interface WebFluxTagsContributor { + + /** + * Provides tags to be associated with metrics for the given {@code exchange}. + * @param exchange the exchange + * @param ex the current exception (may be {@code null}) + * @return tags to associate with metrics for the request and response exchange + */ + Iterable httpRequestTags(ServerWebExchange exchange, Throwable ex); + +} diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/web/servlet/DefaultWebMvcTagsProvider.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/web/servlet/DefaultWebMvcTagsProvider.java index 71b5543ab4..3336e89d02 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/web/servlet/DefaultWebMvcTagsProvider.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/web/servlet/DefaultWebMvcTagsProvider.java @@ -16,6 +16,9 @@ package org.springframework.boot.actuate.metrics.web.servlet; +import java.util.Collections; +import java.util.List; + import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @@ -32,24 +35,57 @@ public class DefaultWebMvcTagsProvider implements WebMvcTagsProvider { private final boolean ignoreTrailingSlash; + private final List contributors; + public DefaultWebMvcTagsProvider() { this(false); } + /** + * Creates a new {@link DefaultWebMvcTagsProvider} that will provide tags from the + * given {@code contributors} in addition to its own. + * @param contributors the contributors that will provide additional tags + * @since 2.3.0 + */ + public DefaultWebMvcTagsProvider(List contributors) { + this(false, contributors); + } + public DefaultWebMvcTagsProvider(boolean ignoreTrailingSlash) { + this(ignoreTrailingSlash, Collections.emptyList()); + } + + /** + * Creates a new {@link DefaultWebMvcTagsProvider} that will provide tags from the + * given {@code contributors} in addition to its own. + * @param ignoreTrailingSlash whether trailing slashes should be ignored when + * determining the {@code uri} tag. + * @param contributors the contributors that will provide additional tags + * @since 2.3.0 + */ + public DefaultWebMvcTagsProvider(boolean ignoreTrailingSlash, List contributors) { this.ignoreTrailingSlash = ignoreTrailingSlash; + this.contributors = contributors; } @Override public Iterable getTags(HttpServletRequest request, HttpServletResponse response, Object handler, Throwable exception) { - return Tags.of(WebMvcTags.method(request), WebMvcTags.uri(request, response, this.ignoreTrailingSlash), + Tags tags = Tags.of(WebMvcTags.method(request), WebMvcTags.uri(request, response, this.ignoreTrailingSlash), WebMvcTags.exception(exception), WebMvcTags.status(response), WebMvcTags.outcome(response)); + for (WebMvcTagsContributor contributor : this.contributors) { + tags = tags.and(contributor.getTags(request, response, handler, exception)); + } + return tags; } @Override public Iterable getLongRequestTags(HttpServletRequest request, Object handler) { - return Tags.of(WebMvcTags.method(request), WebMvcTags.uri(request, null, this.ignoreTrailingSlash)); + Tags tags = Tags.of(WebMvcTags.method(request), WebMvcTags.uri(request, null, this.ignoreTrailingSlash)); + for (WebMvcTagsContributor contributor : this.contributors) { + tags = tags.and(contributor.getLongRequestTags(request, handler)); + } + return tags; } } diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/web/servlet/WebMvcTagsContributor.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/web/servlet/WebMvcTagsContributor.java new file mode 100644 index 0000000000..0b0d3d5ef7 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/web/servlet/WebMvcTagsContributor.java @@ -0,0 +1,56 @@ +/* + * 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.metrics.web.servlet; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import io.micrometer.core.instrument.LongTaskTimer; +import io.micrometer.core.instrument.Tag; + +/** + * A contributor of {@link Tag Tags} for Spring MVC-based request handling. Typically used + * by a {@link WebMvcTagsProvider} to provide tags in addition to its defaults. + * + * @author Andy Wilkinson + * @since 2.3.0 + */ +public interface WebMvcTagsContributor { + + /** + * Provides tags to be associated with metrics for the given {@code request} and + * {@code response} exchange. + * @param request the request + * @param response the response + * @param handler the handler for the request or {@code null} if the handler is + * unknown + * @param exception the current exception, if any + * @return tags to associate with metrics for the request and response exchange + */ + Iterable getTags(HttpServletRequest request, HttpServletResponse response, Object handler, + Throwable exception); + + /** + * Provides tags to be used by {@link LongTaskTimer long task timers}. + * @param request the HTTP request + * @param handler the handler for the request or {@code null} if the handler is + * unknown + * @return tags to associate with metrics recorded for the request + */ + Iterable getLongRequestTags(HttpServletRequest request, Object handler); + +} diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/servlet/DefaultWebMvcTagsProviderTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/servlet/DefaultWebMvcTagsProviderTests.java new file mode 100644 index 0000000000..0eeaad2f80 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/servlet/DefaultWebMvcTagsProviderTests.java @@ -0,0 +1,99 @@ +/* + * 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.endpoint.web.servlet; + +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; +import java.util.stream.StreamSupport; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import com.datastax.oss.driver.shaded.guava.common.base.Functions; +import io.micrometer.core.instrument.Tag; +import org.junit.jupiter.api.Test; + +import org.springframework.boot.actuate.metrics.web.servlet.DefaultWebMvcTagsProvider; +import org.springframework.boot.actuate.metrics.web.servlet.WebMvcTagsContributor; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link DefaultWebMvcTagsProvider}. + * + * @author Andy Wilkinson + */ +public class DefaultWebMvcTagsProviderTests { + + @Test + void whenTagsAreProvidedThenDefaultTagsArePresent() { + Map tags = asMap(new DefaultWebMvcTagsProvider().getTags(null, null, null, null)); + assertThat(tags).containsOnlyKeys("exception", "method", "outcome", "status", "uri"); + } + + @Test + void givenSomeContributorsWhenTagsAreProvidedThenDefaultTagsAndContributedTagsArePresent() { + Map tags = asMap( + new DefaultWebMvcTagsProvider(Arrays.asList(new TestWebMvcTagsContributor("alpha"), + new TestWebMvcTagsContributor("bravo", "charlie"))).getTags(null, null, null, null)); + assertThat(tags).containsOnlyKeys("exception", "method", "outcome", "status", "uri", "alpha", "bravo", + "charlie"); + } + + @Test + void whenLongRequestTagsAreProvidedThenDefaultTagsArePresent() { + Map tags = asMap(new DefaultWebMvcTagsProvider().getLongRequestTags(null, null)); + assertThat(tags).containsOnlyKeys("method", "uri"); + } + + @Test + void givenSomeContributorsWhenLongRequestTagsAreProvidedThenDefaultTagsAndContributedTagsArePresent() { + Map tags = asMap( + new DefaultWebMvcTagsProvider(Arrays.asList(new TestWebMvcTagsContributor("alpha"), + new TestWebMvcTagsContributor("bravo", "charlie"))).getLongRequestTags(null, null)); + assertThat(tags).containsOnlyKeys("method", "uri", "alpha", "bravo", "charlie"); + } + + private Map asMap(Iterable tags) { + return StreamSupport.stream(tags.spliterator(), false) + .collect(Collectors.toMap(Tag::getKey, Functions.identity())); + } + + private static final class TestWebMvcTagsContributor implements WebMvcTagsContributor { + + private final List tagNames; + + private TestWebMvcTagsContributor(String... tagNames) { + this.tagNames = Arrays.asList(tagNames); + } + + @Override + public Iterable getTags(HttpServletRequest request, HttpServletResponse response, Object handler, + Throwable exception) { + return this.tagNames.stream().map((name) -> Tag.of(name, "value")).collect(Collectors.toList()); + } + + @Override + public Iterable getLongRequestTags(HttpServletRequest request, Object handler) { + return this.tagNames.stream().map((name) -> Tag.of(name, "value")).collect(Collectors.toList()); + } + + } + +} 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 new file mode 100644 index 0000000000..948af61107 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/web/reactive/server/DefaultWebFluxTagsProviderTests.java @@ -0,0 +1,79 @@ +/* + * 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.metrics.web.reactive.server; + +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; +import java.util.stream.StreamSupport; + +import com.datastax.oss.driver.shaded.guava.common.base.Functions; +import io.micrometer.core.instrument.Tag; +import org.junit.jupiter.api.Test; + +import org.springframework.mock.http.server.reactive.MockServerHttpRequest; +import org.springframework.mock.web.server.MockServerWebExchange; +import org.springframework.web.server.ServerWebExchange; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link DefaultWebFluxTagsProvider}. + * + * @author Andy Wilkinson + */ +public class DefaultWebFluxTagsProviderTests { + + @Test + void whenTagsAreProvidedThenDefaultTagsArePresent() { + ServerWebExchange exchange = MockServerWebExchange.from(MockServerHttpRequest.get("/test")); + Map tags = asMap(new DefaultWebFluxTagsProvider().httpRequestTags(exchange, null)); + assertThat(tags).containsOnlyKeys("exception", "method", "outcome", "status", "uri"); + } + + @Test + void givenSomeContributorsWhenTagsAreProvidedThenDefaultTagsAndContributedTagsArePresent() { + ServerWebExchange exchange = MockServerWebExchange.from(MockServerHttpRequest.get("/test")); + Map tags = asMap( + new DefaultWebFluxTagsProvider(Arrays.asList(new TestWebFluxTagsContributor("alpha"), + new TestWebFluxTagsContributor("bravo", "charlie"))).httpRequestTags(exchange, null)); + assertThat(tags).containsOnlyKeys("exception", "method", "outcome", "status", "uri", "alpha", "bravo", + "charlie"); + } + + private Map asMap(Iterable tags) { + return StreamSupport.stream(tags.spliterator(), false) + .collect(Collectors.toMap(Tag::getKey, Functions.identity())); + } + + private static final class TestWebFluxTagsContributor implements WebFluxTagsContributor { + + private final List tagNames; + + private TestWebFluxTagsContributor(String... tagNames) { + this.tagNames = Arrays.asList(tagNames); + } + + @Override + public Iterable httpRequestTags(ServerWebExchange exchange, Throwable ex) { + return this.tagNames.stream().map((name) -> Tag.of(name, "value")).collect(Collectors.toList()); + } + + } + +} diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/production-ready-features.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/production-ready-features.adoc index d2e3f52df2..3103cd737d 100644 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/production-ready-features.adoc +++ b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/production-ready-features.adoc @@ -1727,7 +1727,8 @@ By default, Spring MVC-related metrics are tagged with the following information | Request's URI template prior to variable substitution, if possible (for example, `/api/person/\{id}`) |=== -To customize the tags, provide a `@Bean` that implements `WebMvcTagsProvider`. +To add to the default tags, provide one or more `@Bean`s that implement `WebMvcTagsContributor`. +To replace the default tags, provide a `@Bean` that implements `WebMvcTagsProvider`. @@ -1760,7 +1761,8 @@ By default, WebFlux-related metrics are tagged with the following information: | Request's URI template prior to variable substitution, if possible (for example, `/api/person/\{id}`) |=== -To customize the tags, provide a `@Bean` that implements `WebFluxTagsProvider`. +To add to the default tags, provide one or more `@Bean`s that implement `WebFluxTagsContributor`. +To replace the default tags, provide a `@Bean` that implements `WebFluxTagsProvider`.