javaspringspring-mvcservlets

Logging response body (HTML) from HttpServletResponse using Spring MVC HandlerInterceptorAdapter


I am trying to log (just to console write now for simplicity sake) the final rendered HTML that will be returned by the HttpServletResponse. (i.e. the body) To this end, I am using the HandlerInterceptorAdapter from Spring MVC like so:

public class VxmlResponseInterceptor extends HandlerInterceptorAdapter {
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println(response.toString());
    }
}

This works as expected and I see the HTTP response headers in the console. My question is if there is a relatively simple way to log the entire response body (i.e. final rendered HTML) to the console without having to resort to doing jumping jacks with PrintWriters, OutputStream's and the like.

Thanks in advance.


Solution

  • This would be better done using a Servlet Filter rather than a Spring HandlerInterceptor, for the reason that a Filter is allowed to substitute the request and/or response objects, and you could use this mechanism to substitute the response with a wrapper which logs the response output.

    This would involve writing a subclass of HttpServletResponseWrapper, overriding getOutputStream (and possibly also getWriter()). These methods would return OutputStream/PrintWriter implementations that siphon off the response stream into a log, in addition to sending to its original destination. An easy way to do this is using TeeOutputStream from Apache Commons IO, but it's not hard to implement yourself.

    Here's an example of the sort of thing you could do, making use of Spring's GenericFilterBean and DelegatingServletResponseStream, as well as TeeOutputStream, to make things easier:

    public class ResponseLoggingFilter extends GenericFilterBean {
    
       @Override
       public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException, ServletException {
          HttpServletResponse responseWrapper = loggingResponseWrapper((HttpServletResponse) response);     
          filterChain.doFilter(request, responseWrapper);
       }
    
       private HttpServletResponse loggingResponseWrapper(HttpServletResponse response) {
          return new HttpServletResponseWrapper(response) {
             @Override
             public ServletOutputStream getOutputStream() throws IOException {
                return new DelegatingServletOutputStream(
                   new TeeOutputStream(super.getOutputStream(), loggingOutputStream())
                );
             }
          };
       }
    
       private OutputStream loggingOutputStream() {
          return System.out;
       }
    }
    

    This logs everything to STDOUT. If you want to log to a file, it'll get a big more complex, what with making sure the streams get closed and so on, but the principle remains the same.