I am using Spring Boot 1.4.1 which includes spring-web-4.3.3. I have a class annotated with @ControllerAdvice
and methods annotated with @ExceptionHandler
to handle exceptions thrown by the service code. When handling these exceptions, I would like to log the @RequestBody
that was part of the request for PUT and POST operations so I can see the request body that caused the problem which in my case is crucial for diagnosis.
Per Spring Docs the method signature for @ExceptionHandler
methods can include various things including the HttpServletRequest
. The request body can normally be obtained from here via getInputStream()
or getReader()
, but if my controller methods parse the request body like "@RequestBody Foo fooBody"
as all of mine do, the HttpServletRequest's
input stream or reader is already closed by the time my exception handler method is called. Essentially the request body has already been read by Spring, similar to the issue described here. It is a common problem working with servlets that the request body can only be read once.
Unfortunately @RequestBody
is not one of the options available for the exception handler method, if it were then I could use that.
I can add an InputStream
to the exception handler method, but that ends up being the same thing as the HttpServletRequest's InputStream and so has the same issue.
I also tried getting the current request with ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest()
which is another trick for getting the current request, but this ends up being the same HttpServletRequest that Spring passes into the exception handler method and so has the same problem.
I have read about a few solutions like this and this that involve inserting a custom request wrapper in the filter chain that will read the contents of the request and cache them so they can be read more than once. I don't like this solution because I don't want to interrupt the entire filter/request/response chain (and potentially introduce performance or stability problems) just to implement logging, and if I have any large requests such as uploaded documents (which I do), I don't want to cache that in memory. Besides, Spring probably has the @RequestBody
cached somewhere already if I could only find it.
Incidentally many solutions recommend using the ContentCachingRequestWrapper
Spring class but in my experience this does not work. Aside from not being documented, looking at its source code it looks like it only caches the parameters, but not the request body. Trying to get the request body from this class always results in an empty string.
So I am looking for any other options that I may have missed. thanks for reading.
You can reference the request body object to a request-scoped bean. And then inject that request-scoped bean in your exception handler to retrieve the request body (or other request-context beans that you wish to reference).
// @Component // @Scope("request") @ManagedBean @RequestScope public class RequestContext { // fields, getters, and setters for request-scoped beans } @RestController @RequestMapping("/api/v1/persons") public class PersonController { @Inject private RequestContext requestContext; @Inject private PersonService personService; @PostMapping public Person savePerson(@RequestBody Person person) throws PersonServiceException { requestContext.setRequestBody(person); return personService.save(person); } } @ControllerAdvice public class ExceptionMapper { @Inject private RequestContext requestContext; @ExceptionHandler(PersonServiceException.class) protected ResponseEntity<?> onPersonServiceException(PersonServiceException exception) { Object requestBody = requestContext.getRequestBody(); // ... return responseEntity; } }
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With