Make WebMvcMetricsFilter set status consistently for all exceptions

Closes gh-27988
pull/28086/head
Andy Wilkinson 3 years ago
parent c0895befc2
commit c2361aeb04

@ -104,13 +104,9 @@ public class WebMvcMetricsFilter extends OncePerRequestFilter {
record(timingContext, request, response, exception); record(timingContext, request, response, exception);
} }
} }
catch (NestedServletException ex) { catch (Exception ex) {
response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value()); response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
record(timingContext, request, response, ex.getCause()); record(timingContext, request, response, (ex instanceof NestedServletException) ? ex.getCause() : ex);
throw ex;
}
catch (ServletException | IOException | RuntimeException ex) {
record(timingContext, request, response, ex);
throw ex; throw ex;
} }
} }

@ -38,6 +38,7 @@ import javax.servlet.http.HttpServletResponse;
import io.micrometer.core.annotation.Timed; import io.micrometer.core.annotation.Timed;
import io.micrometer.core.instrument.Clock; import io.micrometer.core.instrument.Clock;
import io.micrometer.core.instrument.Meter; import io.micrometer.core.instrument.Meter;
import io.micrometer.core.instrument.Meter.Id;
import io.micrometer.core.instrument.MeterRegistry; import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.MockClock; import io.micrometer.core.instrument.MockClock;
import io.micrometer.core.instrument.Tag; import io.micrometer.core.instrument.Tag;
@ -126,8 +127,8 @@ class WebMvcMetricsFilterTests {
@BeforeEach @BeforeEach
void setupMockMvc() { void setupMockMvc() {
this.mvc = MockMvcBuilders.webAppContextSetup(this.context) this.mvc = MockMvcBuilders.webAppContextSetup(this.context).addFilters(this.filter, new CustomBehaviorFilter())
.addFilters(this.filter, new RedirectAndNotFoundFilter()).build(); .build();
} }
@Test @Test
@ -164,7 +165,7 @@ class WebMvcMetricsFilterTests {
@Test @Test
void redirectRequest() throws Exception { void redirectRequest() throws Exception {
this.mvc.perform(get("/api/redirect").header(RedirectAndNotFoundFilter.TEST_MISBEHAVE_HEADER, "302")) this.mvc.perform(get("/api/redirect").header(CustomBehaviorFilter.TEST_STATUS_HEADER, "302"))
.andExpect(status().is3xxRedirection()); .andExpect(status().is3xxRedirection());
assertThat(this.registry.get("http.server.requests").tags("uri", "REDIRECTION").tags("status", "302").timer()) assertThat(this.registry.get("http.server.requests").tags("uri", "REDIRECTION").tags("status", "302").timer())
.isNotNull(); .isNotNull();
@ -172,7 +173,7 @@ class WebMvcMetricsFilterTests {
@Test @Test
void notFoundRequest() throws Exception { void notFoundRequest() throws Exception {
this.mvc.perform(get("/api/not/found").header(RedirectAndNotFoundFilter.TEST_MISBEHAVE_HEADER, "404")) this.mvc.perform(get("/api/not/found").header(CustomBehaviorFilter.TEST_STATUS_HEADER, "404"))
.andExpect(status().is4xxClientError()); .andExpect(status().is4xxClientError());
assertThat(this.registry.get("http.server.requests").tags("uri", "NOT_FOUND").tags("status", "404").timer()) assertThat(this.registry.get("http.server.requests").tags("uri", "NOT_FOUND").tags("status", "404").timer())
.isNotNull(); .isNotNull();
@ -186,13 +187,23 @@ class WebMvcMetricsFilterTests {
.isEqualTo(1L); .isEqualTo(1L);
} }
@Test
void unhandledServletException() {
assertThatCode(() -> this.mvc
.perform(get("/api/filterError").header(CustomBehaviorFilter.TEST_SERVLET_EXCEPTION_HEADER, "throw"))
.andExpect(status().isOk())).isInstanceOf(ServletException.class);
Id meterId = this.registry.get("http.server.requests").tags("exception", "ServletException").timer().getId();
assertThat(meterId.getTag("status")).isEqualTo("500");
}
@Test @Test
void streamingError() throws Exception { void streamingError() throws Exception {
MvcResult result = this.mvc.perform(get("/api/c1/streamingError")).andExpect(request().asyncStarted()) MvcResult result = this.mvc.perform(get("/api/c1/streamingError")).andExpect(request().asyncStarted())
.andReturn(); .andReturn();
assertThatIOException().isThrownBy(() -> this.mvc.perform(asyncDispatch(result)).andReturn()); assertThatIOException().isThrownBy(() -> this.mvc.perform(asyncDispatch(result)).andReturn());
assertThat(this.registry.get("http.server.requests").tags("exception", "IOException").timer().count()) Id meterId = this.registry.get("http.server.requests").tags("exception", "IOException").timer().getId();
.isEqualTo(1L); // Response is committed before error occurs so status is 200 (OK)
assertThat(meterId.getTag("status")).isEqualTo("200");
} }
@Test @Test
@ -208,8 +219,10 @@ class WebMvcMetricsFilterTests {
} }
catch (Throwable ignore) { catch (Throwable ignore) {
} }
assertThat(this.registry.get("http.server.requests").tag("uri", "/api/c1/anonymousError/{id}").timer().getId() Id meterId = this.registry.get("http.server.requests").tag("uri", "/api/c1/anonymousError/{id}").timer()
.getTag("exception")).endsWith("$1"); .getId();
assertThat(meterId.getTag("exception")).endsWith("$1");
assertThat(meterId.getTag("status")).isEqualTo("500");
} }
@Test @Test
@ -357,8 +370,8 @@ class WebMvcMetricsFilterTests {
} }
@Bean @Bean
RedirectAndNotFoundFilter redirectAndNotFoundFilter() { CustomBehaviorFilter redirectAndNotFoundFilter() {
return new RedirectAndNotFoundFilter(); return new CustomBehaviorFilter();
} }
@Bean(name = "callableBarrier") @Bean(name = "callableBarrier")
@ -472,8 +485,10 @@ class WebMvcMetricsFilterTests {
} }
@GetMapping("/streamingError") @GetMapping("/streamingError")
ResponseBodyEmitter streamingError() { ResponseBodyEmitter streamingError() throws IOException {
ResponseBodyEmitter emitter = new ResponseBodyEmitter(); ResponseBodyEmitter emitter = new ResponseBodyEmitter();
emitter.send("some data");
emitter.send("some more data");
emitter.completeWithError(new IOException("error while writing to the response")); emitter.completeWithError(new IOException("error while writing to the response"));
return emitter; return emitter;
} }
@ -522,20 +537,24 @@ class WebMvcMetricsFilterTests {
} }
static class RedirectAndNotFoundFilter extends OncePerRequestFilter { static class CustomBehaviorFilter extends OncePerRequestFilter {
static final String TEST_MISBEHAVE_HEADER = "x-test-misbehave-status"; static final String TEST_STATUS_HEADER = "x-test-status";
static final String TEST_SERVLET_EXCEPTION_HEADER = "x-test-servlet-exception";
@Override @Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException { FilterChain filterChain) throws ServletException, IOException {
String misbehave = request.getHeader(TEST_MISBEHAVE_HEADER); String misbehaveStatus = request.getHeader(TEST_STATUS_HEADER);
if (misbehave != null) { if (misbehaveStatus != null) {
response.setStatus(Integer.parseInt(misbehave)); response.setStatus(Integer.parseInt(misbehaveStatus));
return;
} }
else { if (request.getHeader(TEST_SERVLET_EXCEPTION_HEADER) != null) {
filterChain.doFilter(request, response); throw new ServletException();
} }
filterChain.doFilter(request, response);
} }
} }

Loading…
Cancel
Save