使用Spring MVC HandlerInterceptorAdapter从HttpServletResponse记录响应主体(HTML)

时间:2010-01-28 22:54:51

标签: java spring spring-mvc servlets

我正在尝试记录(仅为了简单起见,现在是控制台写入)最终呈现的HTML将由HttpServletResponse返回。 (即正文)为此,我正在使用Spring MVC中的HandlerInterceptorAdapter,如下所示:

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

这可以按预期工作,我在控制台中看到HTTP响应头。我的问题是,是否有一种相对简单的方法可以将整个响应体(即最终呈现的HTML)记录到控制台,而不必使用PrintWriters,OutputStream等进行跳跃。

提前致谢。

5 个答案:

答案 0 :(得分:21)

使用Servlet Filter而不是Spring HandlerInterceptor会更好,因为允许Filter替换请求和/或响应对象,而您可以使用此机制用记录响应输出的包装器替换响应。

这将涉及编写HttpServletResponseWrapper的子类,覆盖getOutputStream(可能还有getWriter())。除了发送到其原始目标之外,这些方法还会返回OutputStream / PrintWriter实现,这些实现会将响应流中的虹吸带入日志中。一种简单的方法是使用TeeOutputStream中的Apache Commons IO,但实现起来并不难。

这是一个你可以做的事情的例子,利用Spring的GenericFilterBeanDelegatingServletResponseStream以及TeeOutputStream来简化事情:

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;
   }
}

这会将所有内容记录到STDOUT。如果你想登录一个文件,它会变得更复杂,确保流关闭等等,但原则仍然是相同的。

答案 1 :(得分:9)

如果你正在使用(或考虑)logback作为你的日志记录框架,那么有一个很好的servlet过滤器就可以完成。查看documentation中的TeeFilter章节。

答案 2 :(得分:6)

我一直在寻找一种方法来记录完整的HTTP请求/响应一段时间,并发现它已在Tomcat 7 RequestDumperFilter中为我解决了。它的工作方式与Tomcat 7容器中的广告相同。如果你想在Jetty中使用它,这个类可以很好地独立工作,或者像我一样,复制并适应我环境的特定需求。

答案 3 :(得分:1)

我通过maven central制作了一个小型库spring-mvc-logger

添加到pom.xml:

<dependency>
    <groupId>com.github.isrsal</groupId>
    <artifactId>spring-mvc-logger</artifactId>
    <version>0.2</version>
</dependency>

添加到web.xml:

<filter>
    <filter-name>loggingFilter</filter-name>
    <filter-class>com.github.isrsal.logging.LoggingFilter</filter-class>
</filter>

<filter-mapping>
    <filter-name>loggingFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

添加到log4j.xml:

<logger name="com.github.isrsal.logging.LoggingFilter">
    <level value="DEBUG"/>
</logger>

答案 4 :(得分:1)

下面粘贴的代码适用于我的测试,可以从我的github project下载,在基于生产项目的解决方案上应用解决方案后可以共享

    @Configuration
public class LoggingFilter extends GenericFilterBean {

    /**
     * It's important that you actually register your filter this way rather then just annotating it
     * as @Component as you need to be able to set for which "DispatcherType"s to enable the filter
     * (see point *1*)
     * 
     * @return
     */
    @Bean
    public FilterRegistrationBean<LoggingFilter> initFilter() {
        FilterRegistrationBean<LoggingFilter> registrationBean = new FilterRegistrationBean<>();
        registrationBean.setFilter(new LoggingFilter());

        // *1* make sure you sett all dispatcher types if you want the filter to log upon
        registrationBean.setDispatcherTypes(EnumSet.allOf(DispatcherType.class));

        // *2* this should put your filter above any other filter
        registrationBean.setOrder(Ordered.HIGHEST_PRECEDENCE);

        return registrationBean;
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {

        ContentCachingRequestWrapper wreq = 
            new ContentCachingRequestWrapper(
                (HttpServletRequest) request);

        ContentCachingResponseWrapper wres = 
            new ContentCachingResponseWrapper(
                (HttpServletResponse) response);

        try {

            // let it be ...
            chain.doFilter(wreq, wres);

            // makes sure that the input is read (e.g. in 404 it may not be)
            while (wreq.getInputStream().read() >= 0);

            System.out.printf("=== REQUEST%n%s%n=== end request%n",
                    new String(wreq.getContentAsByteArray()));

            // Do whatever logging you wish here, in this case I'm writing request 
            // and response to system out which is probably not what you wish to do
            System.out.printf("=== RESPONSE%n%s%n=== end response%n",
                    new String(wres.getContentAsByteArray()));

            // this is specific of the "ContentCachingResponseWrapper" we are relying on, 
            // make sure you call it after you read the content from the response
            wres.copyBodyToResponse();

            // One more point, in case of redirect this will be called twice! beware to handle that
            // somewhat

        } catch (Throwable t) {
            // Do whatever logging you whish here, too
            // here you should also be logging the error!!!
            throw t;
        }

    }
}