Allow to manually tag request metrics with exceptions

Prior to this commit, some exceptions handled at the controller or
handler function level would:

* not bubble up to the Spring Boot error handling support
* not be tagged as part of the request metrics

This situation is inconsistent because in general, exceptions handled at
the controller level can be considered as expected behavior.
Also, depending on how the exception is handled, the request metrics
might not be tagged with the exception.
This will be reconsidered in gh-23795.

This commit prepares a transition to the new situation. Developers can
now opt-in and set the handled exception as a request attribute. This
well-known attribute will be later read by the metrics support and used
for tagging the request metrics with the exception provided.

This mechanism is automatically used by the error handling support in
Spring Boot.

Closes gh-24028
This commit is contained in:
Brian Clozel
2021-04-01 16:33:31 +02:00
parent 66e9619d65
commit 72a1eb6384
14 changed files with 177 additions and 12 deletions

View File

@@ -28,6 +28,7 @@ import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
import org.springframework.boot.actuate.metrics.AutoTimer;
import org.springframework.boot.web.reactive.error.ErrorAttributes;
import org.springframework.mock.http.server.reactive.MockServerHttpRequest;
import org.springframework.mock.web.server.MockServerWebExchange;
import org.springframework.web.reactive.HandlerMapping;
@@ -94,6 +95,18 @@ class MetricsWebFilterTests {
assertMetricsContainsTag("exception", anonymous.getClass().getName());
}
@Test
void filterAddsTagsToRegistryForHandledExceptions() {
MockServerWebExchange exchange = createExchange("/projects/spring-boot", "/projects/{project}");
this.webFilter.filter(exchange, (serverWebExchange) -> {
exchange.getAttributes().put(ErrorAttributes.ERROR_ATTRIBUTE, new IllegalStateException("test error"));
return exchange.getResponse().setComplete();
}).block(Duration.ofSeconds(30));
assertMetricsContainsTag("uri", "/projects/{project}");
assertMetricsContainsTag("status", "200");
assertMetricsContainsTag("exception", "IllegalStateException");
}
@Test
void filterAddsTagsToRegistryForExceptionsAndCommittedResponse() {
MockServerWebExchange exchange = createExchange("/projects/spring-boot", "/projects/{project}");

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012-2020 the original author or authors.
* Copyright 2012-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -57,6 +57,7 @@ import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.actuate.metrics.AutoTimer;
import org.springframework.boot.web.servlet.error.ErrorAttributes;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
@@ -264,7 +265,8 @@ class WebMvcMetricsFilterTests {
@Test
void endpointThrowsError() throws Exception {
this.mvc.perform(get("/api/c1/error/10")).andExpect(status().is4xxClientError());
assertThat(this.registry.get("http.server.requests").tags("status", "422").timer().count()).isEqualTo(1L);
assertThat(this.registry.get("http.server.requests").tags("status", "422", "exception", "IllegalStateException")
.timer().count()).isEqualTo(1L);
}
@Test
@@ -491,6 +493,8 @@ class WebMvcMetricsFilterTests {
@ExceptionHandler(IllegalStateException.class)
@ResponseStatus(HttpStatus.UNPROCESSABLE_ENTITY)
ModelAndView defaultErrorHandler(HttpServletRequest request, Exception e) {
// this is done by ErrorAttributes implementations
request.setAttribute(ErrorAttributes.ERROR_ATTRIBUTE, e);
return new ModelAndView("myerror");
}