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 gh-2818
This commit is contained in:
parent
4f988d2aec
commit
0c7708bf9f
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2012-2014 the original author or authors.
|
* Copyright 2012-2015 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -49,6 +49,7 @@ import org.springframework.web.util.UrlPathHelper;
|
||||||
*
|
*
|
||||||
* @author Dave Syer
|
* @author Dave Syer
|
||||||
* @author Phillip Webb
|
* @author Phillip Webb
|
||||||
|
* @author Andy Wilkinson
|
||||||
*/
|
*/
|
||||||
@Configuration
|
@Configuration
|
||||||
@ConditionalOnBean({ CounterService.class, GaugeService.class })
|
@ConditionalOnBean({ CounterService.class, GaugeService.class })
|
||||||
|
@ -86,25 +87,19 @@ public class MetricFilterAutoConfiguration {
|
||||||
String suffix = helper.getPathWithinApplication(request);
|
String suffix = helper.getPathWithinApplication(request);
|
||||||
StopWatch stopWatch = new StopWatch();
|
StopWatch stopWatch = new StopWatch();
|
||||||
stopWatch.start();
|
stopWatch.start();
|
||||||
|
int status = HttpStatus.INTERNAL_SERVER_ERROR.value();
|
||||||
try {
|
try {
|
||||||
chain.doFilter(request, response);
|
chain.doFilter(request, response);
|
||||||
|
status = getStatus(response);
|
||||||
}
|
}
|
||||||
finally {
|
finally {
|
||||||
stopWatch.stop();
|
stopWatch.stop();
|
||||||
int status = getStatus(response);
|
|
||||||
Object bestMatchingPattern = request
|
Object bestMatchingPattern = request
|
||||||
.getAttribute(HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE);
|
.getAttribute(HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE);
|
||||||
HttpStatus httpStatus = HttpStatus.OK;
|
|
||||||
try {
|
|
||||||
httpStatus = HttpStatus.valueOf(status);
|
|
||||||
}
|
|
||||||
catch (Exception ex) {
|
|
||||||
// not convertible
|
|
||||||
}
|
|
||||||
if (bestMatchingPattern != null) {
|
if (bestMatchingPattern != null) {
|
||||||
suffix = fixSpecialCharacters(bestMatchingPattern.toString());
|
suffix = fixSpecialCharacters(bestMatchingPattern.toString());
|
||||||
}
|
}
|
||||||
else if (httpStatus.is4xxClientError()) {
|
else if (is4xxClientError(status)) {
|
||||||
suffix = UNKNOWN_PATH_SUFFIX;
|
suffix = UNKNOWN_PATH_SUFFIX;
|
||||||
}
|
}
|
||||||
String gaugeKey = getKey("response" + suffix);
|
String gaugeKey = getKey("response" + suffix);
|
||||||
|
@ -139,6 +134,17 @@ public class MetricFilterAutoConfiguration {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean is4xxClientError(int status) {
|
||||||
|
HttpStatus httpStatus = HttpStatus.OK;
|
||||||
|
try {
|
||||||
|
httpStatus = HttpStatus.valueOf(status);
|
||||||
|
}
|
||||||
|
catch (Exception ex) {
|
||||||
|
// not convertible
|
||||||
|
}
|
||||||
|
return httpStatus.is4xxClientError();
|
||||||
|
}
|
||||||
|
|
||||||
private String getKey(String string) {
|
private String getKey(String string) {
|
||||||
// graphite compatible metric names
|
// graphite compatible metric names
|
||||||
String value = string.replace("/", ".");
|
String value = string.replace("/", ".");
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2012-2013 the original author or authors.
|
* Copyright 2012-2015 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -37,6 +37,7 @@ import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
import org.springframework.web.bind.annotation.ResponseBody;
|
import org.springframework.web.bind.annotation.ResponseBody;
|
||||||
import org.springframework.web.bind.annotation.ResponseStatus;
|
import org.springframework.web.bind.annotation.ResponseStatus;
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
import org.springframework.web.util.NestedServletException;
|
||||||
|
|
||||||
import static org.hamcrest.Matchers.equalTo;
|
import static org.hamcrest.Matchers.equalTo;
|
||||||
import static org.junit.Assert.assertThat;
|
import static org.junit.Assert.assertThat;
|
||||||
|
@ -53,6 +54,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
|
||||||
* Tests for {@link MetricFilterAutoConfiguration}.
|
* Tests for {@link MetricFilterAutoConfiguration}.
|
||||||
*
|
*
|
||||||
* @author Phillip Webb
|
* @author Phillip Webb
|
||||||
|
* @author Andy Wilkinson
|
||||||
*/
|
*/
|
||||||
public class MetricFilterAutoConfigurationTests {
|
public class MetricFilterAutoConfigurationTests {
|
||||||
|
|
||||||
|
@ -138,6 +140,29 @@ public class MetricFilterAutoConfigurationTests {
|
||||||
context.close();
|
context.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void controllerMethodThatThrowsUnhandledException() throws Exception {
|
||||||
|
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(
|
||||||
|
Config.class, MetricFilterAutoConfiguration.class);
|
||||||
|
Filter filter = context.getBean(Filter.class);
|
||||||
|
MockMvc mvc = MockMvcBuilders.standaloneSetup(new MetricFilterTestController())
|
||||||
|
.addFilter(filter).build();
|
||||||
|
|
||||||
|
try {
|
||||||
|
mvc.perform(get("/unhandledException")).andExpect(
|
||||||
|
status().isInternalServerError());
|
||||||
|
}
|
||||||
|
catch (NestedServletException ex) {
|
||||||
|
// Expected
|
||||||
|
}
|
||||||
|
|
||||||
|
verify(context.getBean(CounterService.class)).increment(
|
||||||
|
"status.500.unhandledException");
|
||||||
|
verify(context.getBean(GaugeService.class)).submit(
|
||||||
|
eq("response.unhandledException"), anyDouble());
|
||||||
|
context.close();
|
||||||
|
}
|
||||||
|
|
||||||
@Configuration
|
@Configuration
|
||||||
public static class Config {
|
public static class Config {
|
||||||
|
|
||||||
|
@ -169,4 +194,10 @@ class MetricFilterTestController {
|
||||||
public String testKnownPathWith404Response(@PathVariable String someVariable) {
|
public String testKnownPathWith404Response(@PathVariable String someVariable) {
|
||||||
return someVariable;
|
return someVariable;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ResponseBody
|
||||||
|
@RequestMapping("unhandledException")
|
||||||
|
public String testException() {
|
||||||
|
throw new RuntimeException();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue