- the final goal:
log request body string in RestController's @ExceptionHandler.
- explanations
By default, when request is invalid json, springboot throws a HttpMessageNotReadableException
, but the message is very generic, and not including specific request body. This makes investigating hard. On the other hand, I can log every request string using Filters, but this way logs will be flooded with too many success ones. I only want to log the request when it is invalid. What I really want is in @ExceptionHandler
I'll get that string(previously got somewhere) and log as ERROR.
To illustrate the problem, I created a demo project in github.
- the controller:
@RestController
public class GreetController {
protected static final Logger log = LogManager.getLogger();
@PostMapping("/")
public String greet(@RequestBody final WelcomeMessage msg) {
// if controller successfully returned (valid request),
// then don't want any request body logged
return "Hello " + msg.from;
}
@ExceptionHandler({HttpMessageNotReadableException.class})
public String addStudent(HttpMessageNotReadableException e) {
// this is what I really want!
log.error("{the request body string got somewhere, such as Filters }");
return "greeting from @ExceptionHandler";
}
}
- the client
valid request
curl -H "Content-Type: application/json" http://localhost:8080 --data '{"from":"jim","message":"nice to meet you!"}'
invalid request(invalid json)
curl -H "Content-Type: application/json" http://localhost:8080 --data '{"from":"jim","message""nice to meet you!"}'
I once tried HandlerInterceptor
but will get some error like
'java.lang.IllegalStateException: Cannot call getInputStream() after getReader() has already been called for the current request'.
after some searching 1 2, I decided to use Filter
with ContentCachingRequestWrapper
.
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
final HttpServletRequest httpServletRequest = (HttpServletRequest) request;
ContentCachingRequestWrapper cachedRequest = new ContentCachingRequestWrapper(httpServletRequest);
chain.doFilter(cachedRequest, response);
String requestBody = IOUtils.toString(cachedRequest.getContentAsByteArray(), cachedRequest.getCharacterEncoding());
log.info(requestBody);
}
This code works well except that the log is after the RestController
. if I change the order:
String requestBody = IOUtils.toString(cachedRequest.getReader());
log.info(requestBody);
chain.doFilter(cachedRequest, response);
Works for invalid request, but when request is valid, got following exception:
com.example.demo.GreetController : Required request body is missing: public java.lang.String com.example.demo.GreetController.greet(com.example.demo.WelcomeMessage)
I also tried getContentAsByteArray
, getInputStream
and getReader
methods since some tutorials say the framework checks for specific method call.
Tried CommonsRequestLoggingFilter
as suggested by @M. Deinum.
But all in vain.
Now I'm bit confused. Can anyone explain the executing order of RestController
and Filter
, when request is valid and invalid?
Is there any easier way(less code) to achive my ultimate goal? thanks!
I'm using springboot 2.6.3, jdk11.