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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/2158647/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-13 04:13:23  来源:igfitidea点击:

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

javaspringspring-mvcservlets

提问by csamuel

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:

我正在尝试记录(为了简单起见,现在只是为了控制台写入)将由 HttpServletResponse 返回的最终呈现的 HTML。(即主体)为此,我使用 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());
    }
}

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 Hymans with PrintWriters, OutputStream's and the like.

这按预期工作,我在控制台中看到了 HTTP 响应标头。我的问题是,是否有一种相对简单的方法可以将整个响应主体(即最终呈现的 HTML)记录到控制台,而无需借助 PrintWriter、OutputStream 等进行跳转。

Thanks in advance.

提前致谢。

采纳答案by skaffman

This would be better done using a Servlet Filterrather than a Spring HandlerInterceptor, for the reason that a Filteris 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.

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

This would involve writing a subclass of HttpServletResponseWrapper, overriding getOutputStream(and possibly also getWriter()). These methods would return OutputStream/PrintWriterimplementations 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 TeeOutputStreamfrom Apache Commons IO, but it's not hard to implement yourself.

这将涉及编写HttpServletResponseWrapper, 覆盖getOutputStream(也可能是getWriter())的子类。除了发送到其原始目的地之外,这些方法还将返回OutputStream/PrintWriter实现将响应流抽取到日志中。一个简单的方法来做到这一点是使用TeeOutputStreamApache的共享IO,但它并不难实现自己。

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

这是您可以做的事情的一个例子,利用 Spring 的GenericFilterBeanand 和DelegatingServletResponseStreamTeeOutputStream让事情变得更容易:

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.

这会将所有内容记录到 STDOUT。如果你想记录到一个文件,它会变得更加复杂,确保流关闭等等,但原理保持不变。

回答by Zoran Regvart

If you're using (or considering) logbackas your logging framework, there is a nice servlet filter already available that does exactly that. Checkout the TeeFilter chapter in the documentation.

如果您正在使用(或考虑)logback作为您的日志记录框架,那么已经有一个很好的 servlet 过滤器可以做到这一点。查看文档中的 TeeFilter 章节。

回答by Mr Twiggs

I've been looking for a way to log full HTTP Request/Response for a while and discovered it has been solved for me in the Tomcat 7 RequestDumperFilter. It works as advertised from a Tomcat 7 container. If you want to use it in Jetty, the class works fine stand-alone or, as I did, copied and adapted to the specific needs of my environment.

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

回答by Israel Zalmanov

I made a small library spring-mvc-loggeravailable via maven central.

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

Add to pom.xml:

添加到 pom.xml:

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

Add to web.xml:

添加到 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>

Add to log4j.xml:

添加到 log4j.xml:

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

回答by Andrea Saba

the code pasted below works with my tests and can be downloaded from my github project, sharing after applying a solution based on that on a production project

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

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

    }
}