Skip to content

Commit c2361ae

Browse files
committed
Make WebMvcMetricsFilter set status consistently for all exceptions
Closes gh-27988
1 parent c0895be commit c2361ae

File tree

2 files changed

+39
-24
lines changed

2 files changed

+39
-24
lines changed

spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/web/servlet/WebMvcMetricsFilter.java

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -104,13 +104,9 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse
104104
record(timingContext, request, response, exception);
105105
}
106106
}
107-
catch (NestedServletException ex) {
107+
catch (Exception ex) {
108108
response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
109-
record(timingContext, request, response, ex.getCause());
110-
throw ex;
111-
}
112-
catch (ServletException | IOException | RuntimeException ex) {
113-
record(timingContext, request, response, ex);
109+
record(timingContext, request, response, (ex instanceof NestedServletException) ? ex.getCause() : ex);
114110
throw ex;
115111
}
116112
}

spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/web/servlet/WebMvcMetricsFilterTests.java

Lines changed: 37 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
import io.micrometer.core.annotation.Timed;
3939
import io.micrometer.core.instrument.Clock;
4040
import io.micrometer.core.instrument.Meter;
41+
import io.micrometer.core.instrument.Meter.Id;
4142
import io.micrometer.core.instrument.MeterRegistry;
4243
import io.micrometer.core.instrument.MockClock;
4344
import io.micrometer.core.instrument.Tag;
@@ -126,8 +127,8 @@ class WebMvcMetricsFilterTests {
126127

127128
@BeforeEach
128129
void setupMockMvc() {
129-
this.mvc = MockMvcBuilders.webAppContextSetup(this.context)
130-
.addFilters(this.filter, new RedirectAndNotFoundFilter()).build();
130+
this.mvc = MockMvcBuilders.webAppContextSetup(this.context).addFilters(this.filter, new CustomBehaviorFilter())
131+
.build();
131132
}
132133

133134
@Test
@@ -164,15 +165,15 @@ void badClientRequest() throws Exception {
164165

165166
@Test
166167
void redirectRequest() throws Exception {
167-
this.mvc.perform(get("/api/redirect").header(RedirectAndNotFoundFilter.TEST_MISBEHAVE_HEADER, "302"))
168+
this.mvc.perform(get("/api/redirect").header(CustomBehaviorFilter.TEST_STATUS_HEADER, "302"))
168169
.andExpect(status().is3xxRedirection());
169170
assertThat(this.registry.get("http.server.requests").tags("uri", "REDIRECTION").tags("status", "302").timer())
170171
.isNotNull();
171172
}
172173

173174
@Test
174175
void notFoundRequest() throws Exception {
175-
this.mvc.perform(get("/api/not/found").header(RedirectAndNotFoundFilter.TEST_MISBEHAVE_HEADER, "404"))
176+
this.mvc.perform(get("/api/not/found").header(CustomBehaviorFilter.TEST_STATUS_HEADER, "404"))
176177
.andExpect(status().is4xxClientError());
177178
assertThat(this.registry.get("http.server.requests").tags("uri", "NOT_FOUND").tags("status", "404").timer())
178179
.isNotNull();
@@ -186,13 +187,23 @@ void unhandledError() {
186187
.isEqualTo(1L);
187188
}
188189

190+
@Test
191+
void unhandledServletException() {
192+
assertThatCode(() -> this.mvc
193+
.perform(get("/api/filterError").header(CustomBehaviorFilter.TEST_SERVLET_EXCEPTION_HEADER, "throw"))
194+
.andExpect(status().isOk())).isInstanceOf(ServletException.class);
195+
Id meterId = this.registry.get("http.server.requests").tags("exception", "ServletException").timer().getId();
196+
assertThat(meterId.getTag("status")).isEqualTo("500");
197+
}
198+
189199
@Test
190200
void streamingError() throws Exception {
191201
MvcResult result = this.mvc.perform(get("/api/c1/streamingError")).andExpect(request().asyncStarted())
192202
.andReturn();
193203
assertThatIOException().isThrownBy(() -> this.mvc.perform(asyncDispatch(result)).andReturn());
194-
assertThat(this.registry.get("http.server.requests").tags("exception", "IOException").timer().count())
195-
.isEqualTo(1L);
204+
Id meterId = this.registry.get("http.server.requests").tags("exception", "IOException").timer().getId();
205+
// Response is committed before error occurs so status is 200 (OK)
206+
assertThat(meterId.getTag("status")).isEqualTo("200");
196207
}
197208

198209
@Test
@@ -208,8 +219,10 @@ void anonymousError() {
208219
}
209220
catch (Throwable ignore) {
210221
}
211-
assertThat(this.registry.get("http.server.requests").tag("uri", "/api/c1/anonymousError/{id}").timer().getId()
212-
.getTag("exception")).endsWith("$1");
222+
Id meterId = this.registry.get("http.server.requests").tag("uri", "/api/c1/anonymousError/{id}").timer()
223+
.getId();
224+
assertThat(meterId.getTag("exception")).endsWith("$1");
225+
assertThat(meterId.getTag("status")).isEqualTo("500");
213226
}
214227

215228
@Test
@@ -357,8 +370,8 @@ public MeterFilterReply accept(@NonNull Meter.Id id) {
357370
}
358371

359372
@Bean
360-
RedirectAndNotFoundFilter redirectAndNotFoundFilter() {
361-
return new RedirectAndNotFoundFilter();
373+
CustomBehaviorFilter redirectAndNotFoundFilter() {
374+
return new CustomBehaviorFilter();
362375
}
363376

364377
@Bean(name = "callableBarrier")
@@ -472,8 +485,10 @@ String alwaysThrowsUnhandledException(@PathVariable Long id) {
472485
}
473486

474487
@GetMapping("/streamingError")
475-
ResponseBodyEmitter streamingError() {
488+
ResponseBodyEmitter streamingError() throws IOException {
476489
ResponseBodyEmitter emitter = new ResponseBodyEmitter();
490+
emitter.send("some data");
491+
emitter.send("some more data");
477492
emitter.completeWithError(new IOException("error while writing to the response"));
478493
return emitter;
479494
}
@@ -522,20 +537,24 @@ String successful(@PathVariable Long id) {
522537

523538
}
524539

525-
static class RedirectAndNotFoundFilter extends OncePerRequestFilter {
540+
static class CustomBehaviorFilter extends OncePerRequestFilter {
541+
542+
static final String TEST_STATUS_HEADER = "x-test-status";
526543

527-
static final String TEST_MISBEHAVE_HEADER = "x-test-misbehave-status";
544+
static final String TEST_SERVLET_EXCEPTION_HEADER = "x-test-servlet-exception";
528545

529546
@Override
530547
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
531548
FilterChain filterChain) throws ServletException, IOException {
532-
String misbehave = request.getHeader(TEST_MISBEHAVE_HEADER);
533-
if (misbehave != null) {
534-
response.setStatus(Integer.parseInt(misbehave));
549+
String misbehaveStatus = request.getHeader(TEST_STATUS_HEADER);
550+
if (misbehaveStatus != null) {
551+
response.setStatus(Integer.parseInt(misbehaveStatus));
552+
return;
535553
}
536-
else {
537-
filterChain.doFilter(request, response);
554+
if (request.getHeader(TEST_SERVLET_EXCEPTION_HEADER) != null) {
555+
throw new ServletException();
538556
}
557+
filterChain.doFilter(request, response);
539558
}
540559

541560
}

0 commit comments

Comments
 (0)