Spring Boot - How to log all requests and responses with exceptions in single place? Spring Boot - How to log all requests and responses with exceptions in single place? java java

Spring Boot - How to log all requests and responses with exceptions in single place?


Don't write any Interceptors, Filters, Components, Aspects, etc., this is a very common problem and has been solved many times over.

Spring Boot has a modules called Actuator, which provides HTTP request logging out of the box. There's an endpoint mapped to /trace (SB1.x) or /actuator/httptrace (SB2.0+) which will show you last 100 HTTP requests. You can customize it to log each request, or write to a DB.

To get the endpoints you want, you'll need the spring-boot-starter-actuator dependency, and also to "whitelist" the endpoints you're looking for, and possibly setup or disable security for it.

Also, where will this application run? Will you be using a PaaS? Hosting providers, Heroku for example, provide request logging as part of their service and you don't need to do any coding whatsoever then.


Spring already provides a filter that does this job. Add following bean to your config

@Beanpublic CommonsRequestLoggingFilter requestLoggingFilter() {    CommonsRequestLoggingFilter loggingFilter = new CommonsRequestLoggingFilter();    loggingFilter.setIncludeClientInfo(true);    loggingFilter.setIncludeQueryString(true);    loggingFilter.setIncludePayload(true);    loggingFilter.setMaxPayloadLength(64000);    return loggingFilter;}

Don't forget to change log level of org.springframework.web.filter.CommonsRequestLoggingFilter to DEBUG.


You could use javax.servlet.Filter if there wasn't a requirement to log java method that been executed.

But with this requirement you have to access information stored in handlerMapping of DispatcherServlet. That said, you can override DispatcherServlet to accomplish logging of request/response pair.

Below is an example of idea that can be further enhanced and adopted to your needs.

public class LoggableDispatcherServlet extends DispatcherServlet {    private final Log logger = LogFactory.getLog(getClass());    @Override    protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {        if (!(request instanceof ContentCachingRequestWrapper)) {            request = new ContentCachingRequestWrapper(request);        }        if (!(response instanceof ContentCachingResponseWrapper)) {            response = new ContentCachingResponseWrapper(response);        }        HandlerExecutionChain handler = getHandler(request);        try {            super.doDispatch(request, response);        } finally {            log(request, response, handler);            updateResponse(response);        }    }    private void log(HttpServletRequest requestToCache, HttpServletResponse responseToCache, HandlerExecutionChain handler) {        LogMessage log = new LogMessage();        log.setHttpStatus(responseToCache.getStatus());        log.setHttpMethod(requestToCache.getMethod());        log.setPath(requestToCache.getRequestURI());        log.setClientIp(requestToCache.getRemoteAddr());        log.setJavaMethod(handler.toString());        log.setResponse(getResponsePayload(responseToCache));        logger.info(log);    }    private String getResponsePayload(HttpServletResponse response) {        ContentCachingResponseWrapper wrapper = WebUtils.getNativeResponse(response, ContentCachingResponseWrapper.class);        if (wrapper != null) {            byte[] buf = wrapper.getContentAsByteArray();            if (buf.length > 0) {                int length = Math.min(buf.length, 5120);                try {                    return new String(buf, 0, length, wrapper.getCharacterEncoding());                }                catch (UnsupportedEncodingException ex) {                    // NOOP                }            }        }        return "[unknown]";    }    private void updateResponse(HttpServletResponse response) throws IOException {        ContentCachingResponseWrapper responseWrapper =            WebUtils.getNativeResponse(response, ContentCachingResponseWrapper.class);        responseWrapper.copyBodyToResponse();    }}

HandlerExecutionChain - contains the information about request handler.

You then can register this dispatcher as following:

    @Bean    public ServletRegistrationBean dispatcherRegistration() {        return new ServletRegistrationBean(dispatcherServlet());    }    @Bean(name = DispatcherServletAutoConfiguration.DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)    public DispatcherServlet dispatcherServlet() {        return new LoggableDispatcherServlet();    }

And here's the sample of logs:

http http://localhost:8090/settings/testi.g.m.s.s.LoggableDispatcherServlet      : LogMessage{httpStatus=500, path='/error', httpMethod='GET', clientIp='127.0.0.1', javaMethod='HandlerExecutionChain with handler [public org.springframework.http.ResponseEntity<java.util.Map<java.lang.String, java.lang.Object>> org.springframework.boot.autoconfigure.web.BasicErrorController.error(javax.servlet.http.HttpServletRequest)] and 3 interceptors', arguments=null, response='{"timestamp":1472475814077,"status":500,"error":"Internal Server Error","exception":"java.lang.RuntimeException","message":"org.springframework.web.util.NestedServletException: Request processing failed; nested exception is java.lang.RuntimeException","path":"/settings/test"}'}http http://localhost:8090/settings/paramsi.g.m.s.s.LoggableDispatcherServlet      : LogMessage{httpStatus=200, path='/settings/httpParams', httpMethod='GET', clientIp='127.0.0.1', javaMethod='HandlerExecutionChain with handler [public x.y.z.DTO x.y.z.Controller.params()] and 3 interceptors', arguments=null, response='{}'}http http://localhost:8090/123i.g.m.s.s.LoggableDispatcherServlet      : LogMessage{httpStatus=404, path='/error', httpMethod='GET', clientIp='127.0.0.1', javaMethod='HandlerExecutionChain with handler [public org.springframework.http.ResponseEntity<java.util.Map<java.lang.String, java.lang.Object>> org.springframework.boot.autoconfigure.web.BasicErrorController.error(javax.servlet.http.HttpServletRequest)] and 3 interceptors', arguments=null, response='{"timestamp":1472475840592,"status":404,"error":"Not Found","message":"Not Found","path":"/123"}'}

UPDATE

In case of errors Spring does automatic error handling. Therefore, BasicErrorController#error is shown as request handler. If you want to preserve original request handler, then you can override this behavior at spring-webmvc-4.2.5.RELEASE-sources.jar!/org/springframework/web/servlet/DispatcherServlet.java:971 before #processDispatchResult is called, to cache original handler.