1

I have a @RestControllerAdvice where I handle an Exception in Spring Boot. I would like to log an information that is sent through request body. How can I get this information from a spring WebRequest?

This is my sample exception handler.

@RestControllerAdvice
public class CustomExceptionHandler extends ResponseEntityExceptionHandler {

@Override
protected ResponseEntity<Object> handleMethodArgumentNotValid(MethodArgumentNotValidException ex,
        HttpHeaders headers, HttpStatus status, WebRequest request) {

    // I want to add something here that I could log an info that is in the request body.
    return super.handleMethodArgumentNotValid(ex, headers, status, request);
}

}

@M.Deinum I tried to use ContentCachingRequestWrapper, But I could not have acess to body content. The method contentCachingRequestWrapper.getContentAsByteArray() returns null.

@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
      throws IOException, ServletException {

    try {
        ContentCachingRequestWrapper wrappedRequest = new ContentCachingRequestWrapper((HttpServletRequest) request);
        wrappedRequest.getContentAsByteArray();
        wrappedRequest.getInputStream();
        chain.doFilter(wrappedRequest, response);
    } finally {
        LoggingContext.clear();
    }
Diego Victor
  • 23
  • 1
  • 4
  • Maybe this could help https://stackoverflow.com/questions/21193380/get-requestbody-and-responsebody-at-handlerinterceptor – Aman Singh Rajpoot Apr 13 '22 at 09:49
  • This link can help you out with this I guess https://stackoverflow.com/a/46046430/16156784 –  Apr 13 '22 at 10:03
  • A request body can only be consumed once, if you want to read it multiple times, you need to think about that. You need a filter that will map the request with a request that allows the body to be read multiple times (like the `ContentCachingRequestWrapper` in Spring). If you have that, you can just add the `HttpServletRequest` and read the body again. Ifyou don't, you won't have a way of reading it again. – M. Deinum Apr 13 '22 at 11:37
  • @M.Deinum thank you. I tried to use ContentCachingRequestWrapper, But I could not have acess to body content. The method contentCachingRequestWrapper.getContentAsByteArray() returns null. – Diego Victor Apr 13 '22 at 13:57
  • Ofcourse it returns `null` at that position as nothing has been read yet. It will only have content **after** things have been read through the inputstream. – M. Deinum Apr 13 '22 at 14:15
  • @M.Deinum how can I guarantee that things have been read in that part in order to log body data? – Diego Victor Apr 13 '22 at 14:28
  • You just need to get the inputstream and read it. Also where did I say you need to write/log it in the filter. You need to wrap it so you can read it again in your exception handler, that is what you were trying to achieve. – M. Deinum Apr 13 '22 at 14:40
  • Yeah. I will get the data I want and store them in a LogginContext. In the exception handler, I will get the values in LogginContext. So, to get the body data, I just need to do: httpRequest.getInputStream() ? – Diego Victor Apr 13 '22 at 15:10
  • I solved this problem with this answer [https://stackoverflow.com/a/74705186/9560693] – Yehouda Dec 16 '22 at 12:54

3 Answers3

4

The comments regarding using the ContentCachingRequestWrapper are accurate, this is the implementation using your controller advice that should work.

@Component
public class MyFilter implements Filter {
  @Override
  public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
  throws IOException, ServletException {
     ContentCachingRequestWrapper contentCachingRequestWrapper = new ContentCachingRequestWrapper(
    (HttpServletRequest) servletRequest);

     filterChain.doFilter(contentCachingRequestWrapper, servletResponse);
  }
}

The advice

@RestControllerAdvice
public class CustomExceptionHandler extends ResponseEntityExceptionHandler {

  @Override
  protected ResponseEntity<Object> handleMethodArgumentNotValid(MethodArgumentNotValidException ex, HttpHeaders headers,
  HttpStatus status, WebRequest request) {

    ContentCachingRequestWrapper nativeRequest = (ContentCachingRequestWrapper) ((ServletWebRequest) request).getNativeRequest();
    String requestEntityAsString = new String(nativeRequest.getContentAsByteArray());

    log.debug(requestEntityAsString);

    return super.handleMethodArgumentNotValid(ex, headers, status, request);
  }
}
lane.maxwell
  • 5,002
  • 1
  • 20
  • 30
1

Try this code:

@RestControllerAdvice
@Component
public class CustomExceptionHandler {

    @ExceptionHandler(HttpMessageNotReadableException.class)
    @ResponseBody
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public Object handleRequestBodyException(HttpMessageNotReadableException ex, HttpServletRequest request) {
        Map<String, Object> responseBody = new LinkedHashMap<>();
        responseBody.put("timestamp", new Date());
        responseBody.put("status", HttpStatus.BAD_REQUEST.value());
        responseBody.put("error", ex.getMessage());
        responseBody.put("path", request.getServletPath());
        return responseBody;
    }
}
Asror
  • 31
  • 3
0

RequestBody and ResponseBody can be read-only once so other ways to achieve the body in the exception handler Visit here

Imagica
  • 11
  • 2
  • That doesn't get you the body, that gets you the resulting object, which in case of an error might not even exist... So that solution won't work. – M. Deinum Apr 13 '22 at 11:36
  • That's true. In case of a MethodArgumentNotValidException, the request body object would not be constructed. In this scenario, you will not be able to have the object annotated with @RequestBody – Diego Victor Apr 13 '22 at 13:13