Skip to content

Commit f55065b

Browse files
nosannosandima
authored andcommitted
Add support MeterFilter#maximumAllowableTags for WebFlux and WebMvc
closes gh-13913.
1 parent 53f3982 commit f55065b

File tree

9 files changed

+258
-73
lines changed

9 files changed

+258
-73
lines changed

spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/MetricsProperties.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,13 @@ public static class Server {
137137
*/
138138
private String requestsMetricName = "http.server.requests";
139139

140+
/**
141+
* Maximum number of unique URI tag values allowed. After the max number of
142+
* tag values is reached, metrics with additional tag values are denied by
143+
* filter.
144+
*/
145+
private int maxUriTags = 100;
146+
140147
public boolean isAutoTimeRequests() {
141148
return this.autoTimeRequests;
142149
}
@@ -153,6 +160,14 @@ public void setRequestsMetricName(String requestsMetricName) {
153160
this.requestsMetricName = requestsMetricName;
154161
}
155162

163+
public int getMaxUriTags() {
164+
return this.maxUriTags;
165+
}
166+
167+
public void setMaxUriTags(int maxUriTags) {
168+
this.maxUriTags = maxUriTags;
169+
}
170+
156171
}
157172

158173
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
/*
2+
* Copyright 2012-2018 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.actuate.autoconfigure.metrics;
18+
19+
import java.util.concurrent.atomic.AtomicBoolean;
20+
import java.util.function.Supplier;
21+
22+
import io.micrometer.core.instrument.Meter;
23+
import io.micrometer.core.instrument.config.MeterFilter;
24+
import io.micrometer.core.instrument.config.MeterFilterReply;
25+
import org.slf4j.Logger;
26+
import org.slf4j.LoggerFactory;
27+
28+
import org.springframework.util.Assert;
29+
30+
/**
31+
* {@link MeterFilter} to log only once a warning message and deny {@link Meter.Id}.
32+
*
33+
* @author Dmytro Nosan
34+
* @since 2.0.5
35+
*/
36+
public final class OnlyOnceLoggingDenyMeterFilter implements MeterFilter {
37+
38+
private final Logger logger = LoggerFactory
39+
.getLogger(OnlyOnceLoggingDenyMeterFilter.class);
40+
41+
private final AtomicBoolean alreadyWarned = new AtomicBoolean(false);
42+
43+
private final Supplier<String> message;
44+
45+
public OnlyOnceLoggingDenyMeterFilter(Supplier<String> message) {
46+
Assert.notNull(message, "Message must not be null");
47+
this.message = message;
48+
}
49+
50+
@Override
51+
public MeterFilterReply accept(Meter.Id id) {
52+
if (this.logger.isWarnEnabled()
53+
&& this.alreadyWarned.compareAndSet(false, true)) {
54+
this.logger.warn(this.message.get());
55+
}
56+
return MeterFilterReply.DENY;
57+
}
58+
59+
}

spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/web/client/RestTemplateMetricsAutoConfiguration.java

Lines changed: 5 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -16,17 +16,12 @@
1616

1717
package org.springframework.boot.actuate.autoconfigure.metrics.web.client;
1818

19-
import java.util.concurrent.atomic.AtomicBoolean;
20-
21-
import io.micrometer.core.instrument.Meter.Id;
2219
import io.micrometer.core.instrument.MeterRegistry;
2320
import io.micrometer.core.instrument.config.MeterFilter;
24-
import io.micrometer.core.instrument.config.MeterFilterReply;
25-
import org.slf4j.Logger;
26-
import org.slf4j.LoggerFactory;
2721

2822
import org.springframework.boot.actuate.autoconfigure.metrics.MetricsAutoConfiguration;
2923
import org.springframework.boot.actuate.autoconfigure.metrics.MetricsProperties;
24+
import org.springframework.boot.actuate.autoconfigure.metrics.OnlyOnceLoggingDenyMeterFilter;
3025
import org.springframework.boot.actuate.autoconfigure.metrics.export.simple.SimpleMetricsExportAutoConfiguration;
3126
import org.springframework.boot.actuate.metrics.web.client.DefaultRestTemplateExchangeTagsProvider;
3227
import org.springframework.boot.actuate.metrics.web.client.MetricsRestTemplateCustomizer;
@@ -76,43 +71,12 @@ public MetricsRestTemplateCustomizer metricsRestTemplateCustomizer(
7671
@Order(0)
7772
public MeterFilter metricsHttpClientUriTagFilter(MetricsProperties properties) {
7873
String metricName = properties.getWeb().getClient().getRequestsMetricName();
79-
MeterFilter denyFilter = new MaximumUriTagsReachedMeterFilter(metricName);
74+
MeterFilter denyFilter = new OnlyOnceLoggingDenyMeterFilter(() -> String.format(
75+
"Reached the maximum number of URI tags for '%s'. "
76+
+ "Are you using uriVariables on RestTemplate calls?",
77+
metricName));
8078
return MeterFilter.maximumAllowableTags(metricName, "uri",
8179
properties.getWeb().getClient().getMaxUriTags(), denyFilter);
8280
}
8381

84-
/**
85-
* {@link MeterFilter} to deny further URI tags and log a warning.
86-
*/
87-
private static class MaximumUriTagsReachedMeterFilter implements MeterFilter {
88-
89-
private final Logger logger = LoggerFactory
90-
.getLogger(MaximumUriTagsReachedMeterFilter.class);
91-
92-
private final String metricName;
93-
94-
private final AtomicBoolean alreadyWarned = new AtomicBoolean(false);
95-
96-
MaximumUriTagsReachedMeterFilter(String metricName) {
97-
this.metricName = metricName;
98-
}
99-
100-
@Override
101-
public MeterFilterReply accept(Id id) {
102-
if (this.alreadyWarned.compareAndSet(false, true)) {
103-
logWarning();
104-
}
105-
return MeterFilterReply.DENY;
106-
}
107-
108-
private void logWarning() {
109-
if (this.logger.isWarnEnabled()) {
110-
this.logger.warn(
111-
"Reached the maximum number of URI tags for '" + this.metricName
112-
+ "'. Are you using uriVariables on HTTP client calls?");
113-
}
114-
}
115-
116-
}
117-
11882
}

spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/web/reactive/WebFluxMetricsAutoConfiguration.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,11 @@
1717
package org.springframework.boot.actuate.autoconfigure.metrics.web.reactive;
1818

1919
import io.micrometer.core.instrument.MeterRegistry;
20+
import io.micrometer.core.instrument.config.MeterFilter;
2021

2122
import org.springframework.boot.actuate.autoconfigure.metrics.MetricsAutoConfiguration;
2223
import org.springframework.boot.actuate.autoconfigure.metrics.MetricsProperties;
24+
import org.springframework.boot.actuate.autoconfigure.metrics.OnlyOnceLoggingDenyMeterFilter;
2325
import org.springframework.boot.actuate.autoconfigure.metrics.export.simple.SimpleMetricsExportAutoConfiguration;
2426
import org.springframework.boot.actuate.metrics.web.reactive.server.DefaultWebFluxTagsProvider;
2527
import org.springframework.boot.actuate.metrics.web.reactive.server.MetricsWebFilter;
@@ -31,12 +33,14 @@
3133
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
3234
import org.springframework.context.annotation.Bean;
3335
import org.springframework.context.annotation.Configuration;
36+
import org.springframework.core.annotation.Order;
3437

3538
/**
3639
* {@link EnableAutoConfiguration Auto-configuration} for instrumentation of Spring
3740
* WebFlux applications.
3841
*
3942
* @author Jon Schneider
43+
* @author Dmytro Nosan
4044
* @since 2.0.0
4145
*/
4246
@Configuration
@@ -59,4 +63,14 @@ public MetricsWebFilter webfluxMetrics(MeterRegistry registry,
5963
properties.getWeb().getServer().getRequestsMetricName());
6064
}
6165

66+
@Bean
67+
@Order(0)
68+
public MeterFilter metricsHttpServerUriTagFilter(MetricsProperties properties) {
69+
String metricName = properties.getWeb().getServer().getRequestsMetricName();
70+
MeterFilter filter = new OnlyOnceLoggingDenyMeterFilter(() -> String
71+
.format("Reached the maximum number of URI tags for '%s'.", metricName));
72+
return MeterFilter.maximumAllowableTags(metricName, "uri",
73+
properties.getWeb().getServer().getMaxUriTags(), filter);
74+
}
75+
6276
}

spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/web/servlet/WebMvcMetricsAutoConfiguration.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,12 @@
1919
import javax.servlet.DispatcherType;
2020

2121
import io.micrometer.core.instrument.MeterRegistry;
22+
import io.micrometer.core.instrument.config.MeterFilter;
2223

2324
import org.springframework.boot.actuate.autoconfigure.metrics.MetricsAutoConfiguration;
2425
import org.springframework.boot.actuate.autoconfigure.metrics.MetricsProperties;
2526
import org.springframework.boot.actuate.autoconfigure.metrics.MetricsProperties.Web.Server;
27+
import org.springframework.boot.actuate.autoconfigure.metrics.OnlyOnceLoggingDenyMeterFilter;
2628
import org.springframework.boot.actuate.autoconfigure.metrics.export.simple.SimpleMetricsExportAutoConfiguration;
2729
import org.springframework.boot.actuate.metrics.web.servlet.DefaultWebMvcTagsProvider;
2830
import org.springframework.boot.actuate.metrics.web.servlet.WebMvcMetricsFilter;
@@ -38,6 +40,7 @@
3840
import org.springframework.context.annotation.Bean;
3941
import org.springframework.context.annotation.Configuration;
4042
import org.springframework.core.Ordered;
43+
import org.springframework.core.annotation.Order;
4144
import org.springframework.web.context.WebApplicationContext;
4245
import org.springframework.web.servlet.DispatcherServlet;
4346

@@ -46,6 +49,7 @@
4649
* MVC servlet-based request mappings.
4750
*
4851
* @author Jon Schneider
52+
* @author Dmytro Nosan
4953
* @since 2.0.0
5054
*/
5155
@Configuration
@@ -78,4 +82,14 @@ public FilterRegistrationBean<WebMvcMetricsFilter> webMvcMetricsFilter(
7882
return registration;
7983
}
8084

85+
@Bean
86+
@Order(0)
87+
public MeterFilter metricsHttpServerUriTagFilter(MetricsProperties properties) {
88+
String metricName = properties.getWeb().getServer().getRequestsMetricName();
89+
MeterFilter filter = new OnlyOnceLoggingDenyMeterFilter(() -> String
90+
.format("Reached the maximum number of URI tags for '%s'.", metricName));
91+
return MeterFilter.maximumAllowableTags(metricName, "uri",
92+
properties.getWeb().getServer().getMaxUriTags(), filter);
93+
}
94+
8195
}

spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/web/client/RestTemplateMetricsAutoConfigurationTests.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ public void afterMaxUrisReachedFurtherUrisAreDenied() {
9797
.hasSize(maxUriTags);
9898
assertThat(this.out.toString())
9999
.contains("Reached the maximum number of URI tags "
100-
+ "for 'http.client.requests'");
100+
+ "for 'http.client.requests'. Are you using uriVariables on RestTemplate calls?");
101101
});
102102
}
103103

spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/web/reactive/WebFluxMetricsAutoConfigurationTests.java

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,17 +16,26 @@
1616

1717
package org.springframework.boot.actuate.autoconfigure.metrics.web.reactive;
1818

19+
import io.micrometer.core.instrument.MeterRegistry;
20+
import org.junit.Rule;
1921
import org.junit.Test;
22+
import reactor.core.publisher.Mono;
2023

2124
import org.springframework.boot.actuate.autoconfigure.metrics.MetricsAutoConfiguration;
2225
import org.springframework.boot.actuate.autoconfigure.metrics.export.simple.SimpleMetricsExportAutoConfiguration;
2326
import org.springframework.boot.actuate.metrics.web.reactive.server.DefaultWebFluxTagsProvider;
2427
import org.springframework.boot.actuate.metrics.web.reactive.server.MetricsWebFilter;
2528
import org.springframework.boot.actuate.metrics.web.reactive.server.WebFluxTagsProvider;
2629
import org.springframework.boot.autoconfigure.AutoConfigurations;
30+
import org.springframework.boot.autoconfigure.web.reactive.WebFluxAutoConfiguration;
2731
import org.springframework.boot.test.context.runner.ReactiveWebApplicationContextRunner;
32+
import org.springframework.boot.test.context.runner.WebApplicationContextRunner;
33+
import org.springframework.boot.test.rule.OutputCapture;
2834
import org.springframework.context.annotation.Bean;
2935
import org.springframework.context.annotation.Configuration;
36+
import org.springframework.test.web.reactive.server.WebTestClient;
37+
import org.springframework.web.bind.annotation.GetMapping;
38+
import org.springframework.web.bind.annotation.RestController;
3039

3140
import static org.assertj.core.api.Assertions.assertThat;
3241
import static org.mockito.Mockito.mock;
@@ -35,6 +44,7 @@
3544
* Tests for {@link WebFluxMetricsAutoConfiguration}
3645
*
3746
* @author Brian Clozel
47+
* @author Dmytro Nosan
3848
*/
3949
public class WebFluxMetricsAutoConfigurationTests {
4050

@@ -43,6 +53,18 @@ public class WebFluxMetricsAutoConfigurationTests {
4353
SimpleMetricsExportAutoConfiguration.class,
4454
WebFluxMetricsAutoConfiguration.class));
4555

56+
@Rule
57+
public OutputCapture output = new OutputCapture();
58+
59+
@Test
60+
public void backsOffWhenMeterRegistryIsMissing() {
61+
new WebApplicationContextRunner()
62+
.withConfiguration(
63+
AutoConfigurations.of(WebFluxMetricsAutoConfiguration.class))
64+
.run((context) -> assertThat(context)
65+
.doesNotHaveBean(WebFluxMetricsAutoConfiguration.class));
66+
}
67+
4668
@Test
4769
public void shouldProvideWebFluxMetricsBeans() {
4870
this.contextRunner.run((context) -> {
@@ -58,6 +80,28 @@ public void shouldNotOverrideCustomTagsProvider() {
5880
.hasSize(1).containsKey("customWebFluxTagsProvider"));
5981
}
6082

83+
@Test
84+
public void afterMaxUrisReachedFurtherUrisAreDenied() {
85+
this.contextRunner
86+
.withConfiguration(AutoConfigurations.of(WebFluxAutoConfiguration.class))
87+
.withUserConfiguration(TestController.class)
88+
.withPropertyValues("management.metrics.web.server.max-uri-tags=2")
89+
.run((context) -> {
90+
WebTestClient webTestClient = WebTestClient
91+
.bindToApplicationContext(context).build();
92+
93+
for (int i = 0; i < 3; i++) {
94+
webTestClient.get().uri("/test" + i).exchange().expectStatus()
95+
.isOk();
96+
}
97+
MeterRegistry registry = context.getBean(MeterRegistry.class);
98+
assertThat(registry.get("http.server.requests").meters()).hasSize(2);
99+
assertThat(this.output.toString())
100+
.contains("Reached the maximum number of URI tags "
101+
+ "for 'http.server.requests'");
102+
});
103+
}
104+
61105
@Configuration
62106
protected static class CustomWebFluxTagsProviderConfig {
63107

@@ -68,4 +112,24 @@ public WebFluxTagsProvider customWebFluxTagsProvider() {
68112

69113
}
70114

115+
@RestController
116+
static class TestController {
117+
118+
@GetMapping("test0")
119+
public Mono<String> test0() {
120+
return Mono.just("test0");
121+
}
122+
123+
@GetMapping("test1")
124+
public Mono<String> test1() {
125+
return Mono.just("test1");
126+
}
127+
128+
@GetMapping("test2")
129+
public Mono<String> test2() {
130+
return Mono.just("test2");
131+
}
132+
133+
}
134+
71135
}

0 commit comments

Comments
 (0)