Java 如何使用 Spring MVC 正确记录 http 请求

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/6631257/
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-16 08:24:53  来源:igfitidea点击:

How to log properly http requests with Spring MVC

javaspringloggingspring-mvc

提问by London

Hello I've been trying to figure out generic way to log http requests in my application, so far no luck, here is how I handle the logging right now i.e:

您好,我一直在尝试找出在我的应用程序中记录 http 请求的通用方法,到目前为止还没有运气,这是我现在处理日志记录的方式,即:

@RequestMapping(value="register", method = RequestMethod.POST)
    @ResponseBody
    public String register(@RequestParam(value="param1",required=false) String param1, @RequestParam("param2") String param2, @RequestParam("param3") String param3, HttpServletRequest request){
        long start = System.currentTimeMillis();
        logger.info("!--REQUEST START--!");

        logger.info("Request URL: " + request.getRequestURL().toString());

        List<String> requestParameterNames = Collections.list((Enumeration<String>)request.getParameterNames());
        logger.info("Parameter number: " + requestParameterNames.size()); 

 for (String parameterName : requestParameterNames){
           logger.info("Parameter name: " + parameterName + " - Parameter value: " + request.getParameter(parameterName));
        }
                  //Some processing logic, call to the various services/methods with different parameters, response is always String(Json)
        String response = service.callSomeServiceMethods(param1,param2,param3);

logger.info("Response is: " + response);

        long end = System.currentTimeMillis();
        logger.info("Requested completed in: " + (end-start) + "ms");
        logger.info("!--REQUEST END--!");   

        return response;
    }

So what I do right now for different controllers/methods is copy everything from beginning of the inside of the method until the processing logic which differs from method to method and then copy everything from below of that as showed in above template.

所以我现在对不同的控制器/方法所做的是复制从方法内部开始的所有内容,直到不同方法的处理逻辑,然后从下面复制所有内容,如上面的模板所示。

It is kind of messy, and there is a lot of code repetition(which I don't like). But I need to log everything.

它有点乱,并且有很多代码重复(我不喜欢)。但我需要记录一切。

Does anyone have more experience with this kinds of logging, can anyone shed some light on this?

有没有人对这种日志记录有更多的经验,有人可以对此有所了解吗?

采纳答案by Bozho

Use an interceptor:

使用拦截器

  • extend HandlerInterceptorAdapterand override preHandle
  • define it with <mvc:interceptors>in dispatcher-servlet.xml
  • 扩展HandlerInterceptorAdapter和覆盖preHandle
  • <mvc:interceptors>in定义它dispatcher-servlet.xml

It will run for every request.

它将为每个请求运行。

回答by Master Chief

As any tech answer ... it depends .. on the tech stack you are using and what your requirements are.

作为任何技术答案......这取决于......你正在使用的技术堆栈以及你的要求是什么。

for example the more generic you want to make your logging, the further upfront you would want to do it. in your case, you are logging only requests which are logging enabled and being handled in the spring context. So you could be "missing" other requests.

例如,您想要进行的日志记录越通用,您就越需要提前进行。在您的情况下,您只记录启用日志记录并在 spring 上下文中处理的请求。所以你可能会“遗漏”其他请求。

I would look at the container or the web server you are using to run your app. That will remove this dependency on Spring. Plus containers provide you the flexibility of plugging in a logging provider and then configuring the format of the log outside code. For example, if you are using Apache Web server, use Apache web server logging to log all HTTP requests in the access logging layer. But be careful, some of the logging options have performance penalties. Log only what you seriously need for an access pattern monitoring perspective.

我会查看用于运行应用程序的容器或 Web 服务器。这将消除对 Spring 的这种依赖。Plus 容器为您提供了插入日志提供程序,然后在代码之外配置日志格式的灵活性。例如,如果您使用 Apache Web 服务器,请使用 Apache Web 服务器日志记录访问日志记录层中的所有 HTTP 请求。但请注意,某些日志记录选项会降低性能。仅记录访问模式监控角度真正需要的内容。

If you are using tomcat, then tomcat also will allow you to log stuff. Search for Access Valve in the tomcat documentation for the tomcat you are using. That will open up a world of possibilities.

如果您使用的是 tomcat,那么 tomcat 也将允许您记录内容。在您使用的 tomcat 的 tomcat 文档中搜索 Access Valve。这将打开一个充满可能性的世界。

More extensive logging should be the domain of the exception strategy ie the kind of detail you want to see when a problem occurs in the system.

更广泛的日志记录应该是异常策略的领域,即当系统出现问题时您想要查看的详细信息。

回答by Israel Zalmanov

Here's a small library I wrote you can use: spring-mvc-logger

这是我写的一个小库,你可以使用:spring-mvc-logger

I made it available via maven central:

我通过 maven central 提供了它:

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

回答by David Groomes

EDIT: Also, see @membersound's comment on this answer, which improves this answer.

编辑:另外,请参阅@membersound 对此答案的评论,它改进了这个答案。

Spring supports this. See CommonsRequestLoggingFilter. If using Spring Boot, just register a bean of that type and Boot will apply it to the filter chain. Like:

Spring 支持这一点。请参阅CommonsRequestLoggingFilter。如果使用 Spring Boot,只需注册该类型的 bean,Boot 会将其应用到过滤器链。喜欢:

@Bean
public Filter logFilter() {
    CommonsRequestLoggingFilter filter = new CommonsRequestLoggingFilter();
    filter.setIncludeQueryString(true);
    filter.setIncludePayload(true);
    filter.setMaxPayloadLength(5120);
    return filter;
}

Also, this logging filter requires the log level be set to DEBUG. E.g. do this in a logback.xml with:

此外,此日志过滤器要求将日志级别设置为 DEBUG。例如在 logback.xml 中执行此操作:

<logger name="org.springframework.web.filter.CommonsRequestLoggingFilter" level="DEBUG"/>

回答by B. Ali

The main issue with reading request is that as soon as the input stream is consumed its gone whoof... and cannot be read again. So the input stream has to be cached. Instead of writing your own classes for caching (which can be found at several places on web), Spring provides a couple of useful classes i.e. ContentCachingRequestWrapperand ContentCachingResponseWrapper. These classes can be utilized very effectively, for example, in filters for logging purposes.

读取请求的主要问题是,一旦输入流被消耗,它就消失了……并且无法再次读取。所以输入流必须被缓存。Spring 提供了一些有用的类,即ContentCachingRequestWrapperContentCachingResponseWrapper ,而不是编写自己的缓存类(可以在网络上的多个位置找到)。这些类可以非常有效地利用,例如,用于记录目的的过滤器。

Define a filter in web.xml:

在 web.xml 中定义过滤器:

<filter>
    <filter-name>loggingFilter</filter-name>
    <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
    <filter-name>loggingFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

Since the filter is declared as DelegatingFilterProxy, it can be declared as a bean using @Component or @Bean annotations. In the loggingFilter's doFilter method, wrap the request and response with spring provided classes before passing it to the filter chain:

由于过滤器声明为 DelegatingFilterProxy,因此可以使用@Component 或@Bean 批注将其声明为bean。在 loggingFilter 的 doFilter 方法中,在将请求和响应传递给过滤器链之前,使用 spring 提供的类包装请求和响应:

HttpServletRequest requestToCache = new ContentCachingRequestWrapper(request);
HttpServletResponse responseToCache = new ContentCachingResponseWrapper(response);
chain.doFilter(requestToCache, responseToCache);
String requestData = getRequestData(requestToCache);
String responseData = getResponseData(responseToCache);

The input stream will be cached in the wrapped request as soon as the input stream is consumed after chain.doFilter(). Then it can be accessed as below:

一旦输入流在 chain.doFilter() 之后被消费,输入流将被缓存在包装的请求中。然后它可以如下访问:

public static String getRequestData(final HttpServletRequest request) throws UnsupportedEncodingException {
    String payload = null;
    ContentCachingRequestWrapper wrapper = WebUtils.getNativeRequest(request, ContentCachingRequestWrapper.class);
    if (wrapper != null) {
        byte[] buf = wrapper.getContentAsByteArray();
        if (buf.length > 0) {
            payload = new String(buf, 0, buf.length, wrapper.getCharacterEncoding());
        }
    }
    return payload;
}

However, things are a bit different for response. Since the response was also wrapped before passing it to the filter chain, it will also be cached to the output stream as soon as it is written on its way back. But since the output stream will also be consumed so you have to copy the response back to the output stream using wrapper.copyBodyToResponse(). See below:

然而,事情的反应有点不同。由于响应在将其传递到过滤器链之前也被包装了,因此一旦在返回的路上写入,它也会被缓存到输出流中。但由于输出流也将被消耗,因此您必须使用 wrapper.copyBodyToResponse() 将响应复制回输出流。见下文:

public static String getResponseData(final HttpServletResponse response) throws IOException {
    String payload = null;
    ContentCachingResponseWrapper wrapper =
        WebUtils.getNativeResponse(response, ContentCachingResponseWrapper.class);
    if (wrapper != null) {
        byte[] buf = wrapper.getContentAsByteArray();
        if (buf.length > 0) {
            payload = new String(buf, 0, buf.length, wrapper.getCharacterEncoding());
            wrapper.copyBodyToResponse();
        }
    }
    return payload;
}

Hope it helps!

希望能帮助到你!

回答by Abe

Adding to what @B.Ali has answered. If you are using this in a spring asynchronous request (serlvet 3.0 or greater) handling scenario, then the following code is what worked for me.

添加@B.Ali 的回答。如果您在 spring 异步请求(serlvet 3.0 或更高版本)处理场景中使用它,那么以下代码对我有用。

public class OncePerRequestLoggingFilter extends OncePerRequestFilter {

@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
    boolean isFirstRequest = !isAsyncDispatch(request);
    HttpServletRequest requestToUse = request;
    HttpServletResponse responseToUse = response;

    // The below check is critical and if not there, then the request/response gets corrupted.
    // Probably because in async case the filter is invoked multiple times.
    if (isFirstRequest && !(request instanceof ContentCachingRequestWrapper)) {
        requestToUse = new ContentCachingRequestWrapper(request);
    }

    if (isFirstRequest && !(response instanceof ContentCachingResponseWrapper)) {
        responseToUse = new ContentCachingResponseWrapper(response);
    }

    filterChain.doFilter(requestToUse, responseToUse);

    if (!isAsyncStarted(request)) {
        ContentCachingResponseWrapper responseWrapper =
                WebUtils.getNativeResponse(response, ContentCachingResponseWrapper.class);
        responseWrapper.copyBodyToResponse(); // IMPORTANT to copy it back to response
    }
}

@Override
protected boolean shouldNotFilterAsyncDispatch() {
    return false; // IMPORTANT this is true by default and wont work in async scenario.
}

}

}