How to log the exception information while using the ResponseStatusException

Viewed 1343

I am using both the ExceptionAdvice and ResponseStatusException to handle the exception situation in my web application. Now I'd like to log the exception information while throwing the ResponseStatusException in my Controller class.

I can always write the log code near the line that throw the exception in my Controller class:

controllerMethod(){
    logger.error("some thing happens here!");
    throw new ResponseStatusException(HttpStatus.FORBIDDEN, "some reason");
}

But writing code all over the place is too tedious, In fact, i'd like some pattern that i used in my ExceptionAdvice class:

@ResponseBody
@ExceptionHandler(MyException.class)
@ResponseStatus(HttpStatus.FORBIDDEN)
String myExceptionHandler(MyException e){
    logger.error("oops!", e);
    return "something";
}

However, the ReponseStatusException response that Spring generated has the format which i want to maintain like:

{
    "timestamp": "2018-02-01T04:28:32.917+0000",
    "status": 400,
    "error": "Bad Request",
    "message": "Provide correct Actor Id",
    "path": "/actor/8/BradPitt"
}

So is there anyway that i can use advice class to log for the ResponseStatusException while still maintaining its generated response, or, on the contrast, using other class to add log ability around all the ReponseStatusException without typing the logger.error everywhere that the exception is thrown?

2 Answers

You can enable the logging with the property

spring.mvc.log-resolved-exception=true

involved classes are

  • WebMvcAutoConfiguration
  • AbstractHandlerExceptionResolver
  • ResponseStatusExceptionResolver

Here's one way to do it (tested in Spring Boot 2.3.3).

Create a class that extends ResponseStatusExceptionResolver:

    /**
     * Extended implementation that adds logging of {@link ResponseStatusException}s.
     * <p>
     * Note: {@link #setMessageSource(MessageSource)} has to be called if reason is a message code, rather than a message itself
     */
    private static class LoggingResponseStatusExceptionResolver extends ResponseStatusExceptionResolver {
        @Override
        protected ModelAndView resolveResponseStatus(ResponseStatus responseStatus, HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
            log(responseStatus.code(), responseStatus.reason(), ex);
            return super.resolveResponseStatus(responseStatus, request, response, handler, ex);
        }

        @Override
        protected ModelAndView resolveResponseStatusException(ResponseStatusException ex, HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
            log(ex.getStatus(), ex.getReason(), ex);
            return super.resolveResponseStatusException(ex, request, response, handler);
        }

        private void log(HttpStatus status, String reason, Exception exception) {
            if (status.isError()) {
                logger.error(status + ": " + reason + "\n", exception);
            }
        }
    }

Then in one of your application configuration classes implement WebMvcConfigurer and replace the default resolver with the extended one:

    @Override
    public void extendHandlerExceptionResolvers(List<HandlerExceptionResolver> resolvers) {
        resolvers.replaceAll(resolver ->
                resolver instanceof ResponseStatusExceptionResolver
                        ? new LoggingResponseStatusExceptionResolver()
                        : resolver
        );
    }
Related