Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Spring MVC not logging all exceptions

I have Spring MVC setup to log exceptions using commons logging, but find that some runtime exceptions aren't being logged.

Here's my bean configuration for the default exception resolver provided by spring:

<bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
    <property name="exceptionMappings">
        <props>
            <prop key="java.lang.Exception">error</prop>
        </props>
    </property>
</bean>
like image 968
Brad Parks Avatar asked Jul 27 '12 17:07

Brad Parks


3 Answers

To get this to log most exceptions, I had to add the following line to my config:

    <property name="warnLogCategory" value="someCategoryStringYouMakeUp" />

So ultimately it became the following:

<bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
    <property name="warnLogCategory" value="apperror" />
    <property name="exceptionMappings">
        <props>
            <prop key="java.lang.Exception">error</prop>
        </props>
    </property>
</bean>

warnLogCategory is described in detail here.

like image 127
Brad Parks Avatar answered Oct 12 '22 22:10

Brad Parks


To augment what @Brad Parks said I recommend that you also make a HttpServlet Filter that will run first (and finish last) to catch exceptions that Spring will miss. Also because there is Spring code that can fail like Spring Security and custom Http Interceptors that fall outside of the SimpleMappingException Resolver (ie if the DispatchServlet or Interceptors fail you will not get the exception).

Here's an example filter I use on top of the SimpleMappingExceptionResolver.

public class ExceptionLoggerServletFilter implements Filter {

    private CommonAccessLogFormatter requestFormat = new CommonAccessLogFormatter();

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException,
            ServletException {
        try {
            chain.doFilter(request, response);
        } catch (Exception e) {
            StringBuilder sb = new StringBuilder("Very Bad shit has happened:");
            if (request instanceof HttpServletRequest)
                requestFormat.appendLogEntry((HttpServletRequest) request, sb);
            log.fatal(sb.toString(), e);
            throw new ServletException(e);
        }
    }

    @Override
    public void destroy() {
    }


    private static final Logger log = Logger.getLogger(ExceptionLoggerServletFilter.class);

}

The reason I recommend this is that for most servlet containers uncaught exception may or may not be logged with probably not your logging framework (for Tomcat its JULI).

The commons log access formatter just logs the request similar to Apache log files.

like image 44
Adam Gent Avatar answered Oct 12 '22 22:10

Adam Gent


In my test, use SimpleMappingExceptionResolver can't log MissingServletRequestParameterException, I combined @ControllerAdvice and Filter to do log.

Use ControllerAdvice to catch Throwable raised in Spring MVC Controller.

@ControllerAdvice
public class GlobalDefaultExceptionHandler {

    private static final Logger logger = LoggerFactory.getLogger("global_controller_exception_logger");

    @ExceptionHandler(value = Throwable.class)
    public void defaultErrorHandler(Throwable e) throws Throwable {
        // If the exception is annotated with @ResponseStatus rethrow it and let
        // the framework handle it.
        // AnnotationUtils is a Spring Framework utility class.
        if (AnnotationUtils.findAnnotation
                (e.getClass(), ResponseStatus.class) != null) {
            throw e;
        }

        // Otherwise log exception
        logger.error("global controller default exception handler", e);
        throw e;
    }

    @ExceptionHandler(MissingServletRequestParameterException.class)
    public void httpBadRequest(Exception e, HttpServletRequest request) throws Exception {
        StringBuffer requestURL = request.getRequestURL();
        logger.warn("{}   HTTP Status 400 - {}", requestURL, e.getMessage());
        throw e;
    }
}

Use Filter to catch additional Exception.

@WebFilter(
        filterName = "ExceptionLogFilter",
        urlPatterns = "/*",
        dispatcherTypes = {DispatcherType.REQUEST, DispatcherType.ASYNC, DispatcherType.ERROR}
)
public class ExceptionLogFilter implements Filter {

    private static final Logger logger = LoggerFactory.getLogger("global_filter_exception_logger");


    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        try {
            chain.doFilter(request, response);
        } catch (IOException | ServletException e) {
            logger.error("bad thing happened during doFilter", e);
            throw e;
        }
    }

    ......
}

logback configuration

<logger name="global_controller_exception_logger" level="info"/>

<logger name="global_filter_exception_logger" level="info"/>

You can view my gist for full code.

like image 25
cmicat Avatar answered Oct 12 '22 22:10

cmicat