Skip to content

Commit df0ba5c

Browse files
committed
Guard against metric failures in MetricsFilter
Update MetricsFilter so that failures to record metrics are logged and ignored. Fixes gh-2777
1 parent 1d5a62b commit df0ba5c

File tree

3 files changed

+198
-124
lines changed

3 files changed

+198
-124
lines changed

spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/MetricFilterAutoConfiguration.java

Lines changed: 1 addition & 101 deletions
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,9 @@
1616

1717
package org.springframework.boot.actuate.autoconfigure;
1818

19-
import java.io.IOException;
20-
2119
import javax.servlet.Filter;
22-
import javax.servlet.FilterChain;
2320
import javax.servlet.Servlet;
24-
import javax.servlet.ServletException;
2521
import javax.servlet.ServletRegistration;
26-
import javax.servlet.http.HttpServletRequest;
27-
import javax.servlet.http.HttpServletResponse;
2822

2923
import org.springframework.beans.factory.annotation.Autowired;
3024
import org.springframework.boot.actuate.metrics.CounterService;
@@ -35,13 +29,8 @@
3529
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
3630
import org.springframework.context.annotation.Bean;
3731
import org.springframework.context.annotation.Configuration;
38-
import org.springframework.core.Ordered;
39-
import org.springframework.core.annotation.Order;
40-
import org.springframework.http.HttpStatus;
41-
import org.springframework.util.StopWatch;
4232
import org.springframework.web.filter.OncePerRequestFilter;
4333
import org.springframework.web.servlet.HandlerMapping;
44-
import org.springframework.web.util.UrlPathHelper;
4534

4635
/**
4736
* {@link EnableAutoConfiguration Auto-configuration} that records Servlet interactions
@@ -58,10 +47,6 @@
5847
@AutoConfigureAfter(MetricRepositoryAutoConfiguration.class)
5948
public class MetricFilterAutoConfiguration {
6049

61-
private static final int UNDEFINED_HTTP_STATUS = 999;
62-
63-
private static final String UNKNOWN_PATH_SUFFIX = "/unmapped";
64-
6550
@Autowired
6651
private CounterService counterService;
6752

@@ -70,91 +55,6 @@ public class MetricFilterAutoConfiguration {
7055

7156
@Bean
7257
public Filter metricFilter() {
73-
return new MetricsFilter();
58+
return new MetricsFilter(this.counterService, this.gaugeService);
7459
}
75-
76-
/**
77-
* Filter that counts requests and measures processing times.
78-
*/
79-
@Order(Ordered.HIGHEST_PRECEDENCE)
80-
private final class MetricsFilter extends OncePerRequestFilter {
81-
82-
@Override
83-
protected void doFilterInternal(HttpServletRequest request,
84-
HttpServletResponse response, FilterChain chain) throws ServletException,
85-
IOException {
86-
UrlPathHelper helper = new UrlPathHelper();
87-
String suffix = helper.getPathWithinApplication(request);
88-
StopWatch stopWatch = new StopWatch();
89-
stopWatch.start();
90-
int status = HttpStatus.INTERNAL_SERVER_ERROR.value();
91-
try {
92-
chain.doFilter(request, response);
93-
status = getStatus(response);
94-
}
95-
finally {
96-
stopWatch.stop();
97-
Object bestMatchingPattern = request
98-
.getAttribute(HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE);
99-
if (bestMatchingPattern != null) {
100-
suffix = fixSpecialCharacters(bestMatchingPattern.toString());
101-
}
102-
else if (is4xxClientError(status)) {
103-
suffix = UNKNOWN_PATH_SUFFIX;
104-
}
105-
String gaugeKey = getKey("response" + suffix);
106-
MetricFilterAutoConfiguration.this.gaugeService.submit(gaugeKey,
107-
stopWatch.getTotalTimeMillis());
108-
String counterKey = getKey("status." + status + suffix);
109-
MetricFilterAutoConfiguration.this.counterService.increment(counterKey);
110-
}
111-
}
112-
113-
private String fixSpecialCharacters(String value) {
114-
String result = value.replaceAll("[{}]", "-");
115-
result = result.replace("**", "-star-star-");
116-
result = result.replace("*", "-star-");
117-
result = result.replace("/-", "/");
118-
result = result.replace("-/", "/");
119-
if (result.endsWith("-")) {
120-
result = result.substring(0, result.length() - 1);
121-
}
122-
if (result.startsWith("-")) {
123-
result = result.substring(1);
124-
}
125-
return result;
126-
}
127-
128-
private int getStatus(HttpServletResponse response) {
129-
try {
130-
return response.getStatus();
131-
}
132-
catch (Exception ex) {
133-
return UNDEFINED_HTTP_STATUS;
134-
}
135-
}
136-
137-
private boolean is4xxClientError(int status) {
138-
try {
139-
return HttpStatus.valueOf(status).is4xxClientError();
140-
}
141-
catch (Exception ex) {
142-
return false;
143-
}
144-
}
145-
146-
private String getKey(String string) {
147-
// graphite compatible metric names
148-
String value = string.replace("/", ".");
149-
value = value.replace("..", ".");
150-
if (value.endsWith(".")) {
151-
value = value + "root";
152-
}
153-
if (value.startsWith("_")) {
154-
value = value.substring(1);
155-
}
156-
return value;
157-
}
158-
}
159-
16060
}
Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
/*
2+
* Copyright 2012-2015 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;
18+
19+
import java.io.IOException;
20+
21+
import javax.servlet.FilterChain;
22+
import javax.servlet.ServletException;
23+
import javax.servlet.http.HttpServletRequest;
24+
import javax.servlet.http.HttpServletResponse;
25+
26+
import org.apache.commons.logging.Log;
27+
import org.apache.commons.logging.LogFactory;
28+
import org.springframework.boot.actuate.metrics.CounterService;
29+
import org.springframework.boot.actuate.metrics.GaugeService;
30+
import org.springframework.core.Ordered;
31+
import org.springframework.core.annotation.Order;
32+
import org.springframework.http.HttpStatus;
33+
import org.springframework.util.StopWatch;
34+
import org.springframework.web.filter.OncePerRequestFilter;
35+
import org.springframework.web.servlet.HandlerMapping;
36+
import org.springframework.web.util.UrlPathHelper;
37+
38+
/**
39+
* Filter that counts requests and measures processing times.
40+
*/
41+
@Order(Ordered.HIGHEST_PRECEDENCE)
42+
final class MetricsFilter extends OncePerRequestFilter {
43+
44+
private static final int UNDEFINED_HTTP_STATUS = 999;
45+
46+
private static final String UNKNOWN_PATH_SUFFIX = "/unmapped";
47+
48+
private static final Log logger = LogFactory.getLog(MetricsFilter.class);
49+
50+
private final CounterService counterService;
51+
52+
private final GaugeService gaugeService;
53+
54+
public MetricsFilter(CounterService counterService, GaugeService gaugeService) {
55+
this.counterService = counterService;
56+
this.gaugeService = gaugeService;
57+
}
58+
59+
@Override
60+
protected void doFilterInternal(HttpServletRequest request,
61+
HttpServletResponse response, FilterChain chain) throws ServletException,
62+
IOException {
63+
StopWatch stopWatch = new StopWatch();
64+
stopWatch.start();
65+
String path = new UrlPathHelper().getPathWithinApplication(request);
66+
int status = HttpStatus.INTERNAL_SERVER_ERROR.value();
67+
try {
68+
chain.doFilter(request, response);
69+
status = getStatus(response);
70+
}
71+
finally {
72+
stopWatch.stop();
73+
recordMetrics(request, path, status, stopWatch.getTotalTimeMillis());
74+
}
75+
}
76+
77+
private int getStatus(HttpServletResponse response) {
78+
try {
79+
return response.getStatus();
80+
}
81+
catch (Exception ex) {
82+
return UNDEFINED_HTTP_STATUS;
83+
}
84+
}
85+
86+
private void recordMetrics(HttpServletRequest request, String path, int status,
87+
long time) {
88+
String suffix = getFinalStatus(request, path, status);
89+
submitToGauge(getKey("response" + suffix), time);
90+
incrementCounter(getKey("status." + status + suffix));
91+
}
92+
93+
private String getFinalStatus(HttpServletRequest request, String path, int status) {
94+
Object bestMatchingPattern = request
95+
.getAttribute(HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE);
96+
if (bestMatchingPattern != null) {
97+
return fixSpecialCharacters(bestMatchingPattern.toString());
98+
}
99+
if (is4xxClientError(status)) {
100+
return UNKNOWN_PATH_SUFFIX;
101+
}
102+
return path;
103+
}
104+
105+
private String fixSpecialCharacters(String value) {
106+
String result = value.replaceAll("[{}]", "-");
107+
result = result.replace("**", "-star-star-");
108+
result = result.replace("*", "-star-");
109+
result = result.replace("/-", "/");
110+
result = result.replace("-/", "/");
111+
if (result.endsWith("-")) {
112+
result = result.substring(0, result.length() - 1);
113+
}
114+
if (result.startsWith("-")) {
115+
result = result.substring(1);
116+
}
117+
return result;
118+
}
119+
120+
private boolean is4xxClientError(int status) {
121+
try {
122+
return HttpStatus.valueOf(status).is4xxClientError();
123+
}
124+
catch (Exception ex) {
125+
return false;
126+
}
127+
}
128+
129+
private String getKey(String string) {
130+
// graphite compatible metric names
131+
String value = string.replace("/", ".");
132+
value = value.replace("..", ".");
133+
if (value.endsWith(".")) {
134+
value = value + "root";
135+
}
136+
if (value.startsWith("_")) {
137+
value = value.substring(1);
138+
}
139+
return value;
140+
}
141+
142+
private void submitToGauge(String metricName, double value) {
143+
try {
144+
this.gaugeService.submit(metricName, value);
145+
}
146+
catch (Exception ex) {
147+
logger.warn("Unable to submit gauge metric '" + metricName + "'", ex);
148+
}
149+
}
150+
151+
private void incrementCounter(String metricName) {
152+
try {
153+
this.counterService.increment(metricName);
154+
}
155+
catch (Exception ex) {
156+
logger.warn("Unable to submit counter metric '" + metricName + "'", ex);
157+
}
158+
}
159+
160+
}

0 commit comments

Comments
 (0)