Skip to content

Commit 81a6701

Browse files
nosansnicoll
authored andcommitted
Limit metrics collection of incoming requests
See gh-14173
1 parent f9081a2 commit 81a6701

File tree

9 files changed

+323
-61
lines changed

9 files changed

+323
-61
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
@@ -134,6 +134,13 @@ public static class Server {
134134
*/
135135
private String requestsMetricName = "http.server.requests";
136136

137+
/**
138+
* Maximum number of unique URI tag values allowed. After the max number of
139+
* tag values is reached, metrics with additional tag values are denied by
140+
* filter.
141+
*/
142+
private int maxUriTags = 100;
143+
137144
public boolean isAutoTimeRequests() {
138145
return this.autoTimeRequests;
139146
}
@@ -150,6 +157,14 @@ public void setRequestsMetricName(String requestsMetricName) {
150157
this.requestsMetricName = requestsMetricName;
151158
}
152159

160+
public int getMaxUriTags() {
161+
return this.maxUriTags;
162+
}
163+
164+
public void setMaxUriTags(int maxUriTags) {
165+
this.maxUriTags = maxUriTags;
166+
}
167+
153168
}
154169

155170
}
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 metricsWebClientUriTagFilter(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 RestTemplate 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 MVC annotation-based programming model request mappings.
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: 24 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -75,26 +75,30 @@ public void restTemplateCanBeCustomizedManually() {
7575

7676
@Test
7777
public void afterMaxUrisReachedFurtherUrisAreDenied() {
78-
this.contextRunner.run((context) -> {
79-
MetricsProperties properties = context.getBean(MetricsProperties.class);
80-
int maxUriTags = properties.getWeb().getClient().getMaxUriTags();
81-
MeterRegistry registry = context.getBean(MeterRegistry.class);
82-
RestTemplate restTemplate = context.getBean(RestTemplateBuilder.class)
83-
.build();
84-
MockRestServiceServer server = MockRestServiceServer
85-
.createServer(restTemplate);
86-
for (int i = 0; i < maxUriTags + 10; i++) {
87-
server.expect(requestTo("/test/" + i))
88-
.andRespond(withStatus(HttpStatus.OK));
89-
}
90-
for (int i = 0; i < maxUriTags + 10; i++) {
91-
restTemplate.getForObject("/test/" + i, String.class);
92-
}
93-
assertThat(registry.get("http.client.requests").meters()).hasSize(maxUriTags);
94-
assertThat(this.out.toString())
95-
.contains("Reached the maximum number of URI tags "
96-
+ "for 'http.client.requests'");
97-
});
78+
this.contextRunner
79+
.withPropertyValues("management.metrics.web.client.max-uri-tags=10")
80+
.run((context) -> {
81+
MetricsProperties properties = context
82+
.getBean(MetricsProperties.class);
83+
int maxUriTags = properties.getWeb().getClient().getMaxUriTags();
84+
MeterRegistry registry = context.getBean(MeterRegistry.class);
85+
RestTemplate restTemplate = context.getBean(RestTemplateBuilder.class)
86+
.build();
87+
MockRestServiceServer server = MockRestServiceServer
88+
.createServer(restTemplate);
89+
for (int i = 0; i < maxUriTags + 10; i++) {
90+
server.expect(requestTo("/test/" + i))
91+
.andRespond(withStatus(HttpStatus.OK));
92+
}
93+
for (int i = 0; i < maxUriTags + 10; i++) {
94+
restTemplate.getForObject("/test/" + i, String.class);
95+
}
96+
assertThat(registry.get("http.client.requests").meters())
97+
.hasSize(maxUriTags);
98+
assertThat(this.out.toString())
99+
.contains("Reached the maximum number of URI tags "
100+
+ "for 'http.client.requests'. Are you using uriVariables on RestTemplate calls?");
101+
});
98102
}
99103

100104
private void validateRestTemplate(RestTemplate restTemplate, MeterRegistry registry) {

0 commit comments

Comments
 (0)