Skip to content

Commit 368ec1b

Browse files
wilkinsonabsodzik
authored andcommitted
Update MetricFilter to treat an unsuccessful call to doFilter as a 500
Previously, if a call to doFilter in MetricFilter failed (i.e. it threw an exception), it would be handled as if it had a response status of 200. This is because the servlet container was yet to handle the exception and set the response status to 500. This commit updates MetricFilter to assume that an exception thrown from doFilter will result in a response with a status of 500. Strictly speaking, even though the filter has highest precedence and will therefore run last on the way back out, this may not always be the case. For example, a custom Tomcat Valve could handle the exception and result in a 200 response but that’s an edge case that’s into shooting yourself in the foot territory. Closes spring-projectsgh-2818
1 parent f02da4a commit 368ec1b

File tree

2 files changed

+48
-11
lines changed

2 files changed

+48
-11
lines changed

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

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2014 the original author or authors.
2+
* Copyright 2012-2015 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -49,6 +49,7 @@
4949
*
5050
* @author Dave Syer
5151
* @author Phillip Webb
52+
* @author Andy Wilkinson
5253
*/
5354
@Configuration
5455
@ConditionalOnBean({ CounterService.class, GaugeService.class })
@@ -86,25 +87,19 @@ protected void doFilterInternal(HttpServletRequest request,
8687
String suffix = helper.getPathWithinApplication(request);
8788
StopWatch stopWatch = new StopWatch();
8889
stopWatch.start();
90+
int status = HttpStatus.INTERNAL_SERVER_ERROR.value();
8991
try {
9092
chain.doFilter(request, response);
93+
status = getStatus(response);
9194
}
9295
finally {
9396
stopWatch.stop();
94-
int status = getStatus(response);
9597
Object bestMatchingPattern = request
9698
.getAttribute(HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE);
97-
HttpStatus httpStatus = HttpStatus.OK;
98-
try {
99-
httpStatus = HttpStatus.valueOf(status);
100-
}
101-
catch (Exception ex) {
102-
// not convertible
103-
}
10499
if (bestMatchingPattern != null) {
105100
suffix = fixSpecialCharacters(bestMatchingPattern.toString());
106101
}
107-
else if (httpStatus.is4xxClientError()) {
102+
else if (is4xxClientError(status)) {
108103
suffix = UNKNOWN_PATH_SUFFIX;
109104
}
110105
String gaugeKey = getKey("response" + suffix);
@@ -139,6 +134,17 @@ private int getStatus(HttpServletResponse response) {
139134
}
140135
}
141136

137+
private boolean is4xxClientError(int status) {
138+
HttpStatus httpStatus = HttpStatus.OK;
139+
try {
140+
httpStatus = HttpStatus.valueOf(status);
141+
}
142+
catch (Exception ex) {
143+
// not convertible
144+
}
145+
return httpStatus.is4xxClientError();
146+
}
147+
142148
private String getKey(String string) {
143149
// graphite compatible metric names
144150
String value = string.replace("/", ".");

spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/MetricFilterAutoConfigurationTests.java

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2013 the original author or authors.
2+
* Copyright 2012-2015 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -37,6 +37,7 @@
3737
import org.springframework.web.bind.annotation.ResponseBody;
3838
import org.springframework.web.bind.annotation.ResponseStatus;
3939
import org.springframework.web.bind.annotation.RestController;
40+
import org.springframework.web.util.NestedServletException;
4041

4142
import static org.hamcrest.Matchers.equalTo;
4243
import static org.junit.Assert.assertThat;
@@ -53,6 +54,7 @@
5354
* Tests for {@link MetricFilterAutoConfiguration}.
5455
*
5556
* @author Phillip Webb
57+
* @author Andy Wilkinson
5658
*/
5759
public class MetricFilterAutoConfigurationTests {
5860

@@ -138,6 +140,29 @@ public void skipsFilterIfMissingServices() throws Exception {
138140
context.close();
139141
}
140142

143+
@Test
144+
public void controllerMethodThatThrowsUnhandledException() throws Exception {
145+
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(
146+
Config.class, MetricFilterAutoConfiguration.class);
147+
Filter filter = context.getBean(Filter.class);
148+
MockMvc mvc = MockMvcBuilders.standaloneSetup(new MetricFilterTestController())
149+
.addFilter(filter).build();
150+
151+
try {
152+
mvc.perform(get("/unhandledException")).andExpect(
153+
status().isInternalServerError());
154+
}
155+
catch (NestedServletException ex) {
156+
// Expected
157+
}
158+
159+
verify(context.getBean(CounterService.class)).increment(
160+
"status.500.unhandledException");
161+
verify(context.getBean(GaugeService.class)).submit(
162+
eq("response.unhandledException"), anyDouble());
163+
context.close();
164+
}
165+
141166
@Configuration
142167
public static class Config {
143168

@@ -169,4 +194,10 @@ public String testTemplateVariableResolution(@PathVariable String someVariable)
169194
public String testKnownPathWith404Response(@PathVariable String someVariable) {
170195
return someVariable;
171196
}
197+
198+
@ResponseBody
199+
@RequestMapping("unhandledException")
200+
public String testException() {
201+
throw new RuntimeException();
202+
}
172203
}

0 commit comments

Comments
 (0)