Spring在Filter中记录Web请求Request和返回Response的内容及时长

科技资讯 投稿 6100 0 评论

Spring在Filter中记录Web请求Request和返回Response的内容及时长

1 简介

Spring MVC中,我们有时需要记录一下请求和返回的内容,方便出现问题时排查。比较Header、Request Body等。这些在Controller也可以记录,但在Filter中会更方便。而我们使用的是OncePerRequestFilter

2 记录请求

2.1 流重复读的问题

byte[] requestBody = StreamUtils.copyToByteArray(request.getInputStream(;
log.info("request body = {}", new String(requestBody, StandardCharsets.UTF_8;

但是这里从流读取了一次内容后,后续不可再读了。这就造成了真正处理请求的时候,报错失败,我们需要把Request对象改造成可重复读的类。

2.2 通过Wrapper解决流重复读的问题

public class PkslowRequestWrapper extends HttpServletRequestWrapper {
    private final byte[] body;
    public PkslowRequestWrapper(HttpServletRequest request throws IOException {
        super(request;
        body = StreamUtils.copyToByteArray(request.getInputStream(;
    }

    @Override
    public ServletInputStream getInputStream( throws IOException {
        ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(body;
        return new ServletInputStream( {
            @Override
            public int read( throws IOException {
                return byteArrayInputStream.read(;
            }

            @Override
            public boolean isFinished( {
                return true;
            }

            @Override
            public boolean isReady( {
                return true;
            }

            @Override
            public void setReadListener(ReadListener readListener {

            }
        };
    }

    @Override
    public BufferedReader getReader( throws IOException {
        return new BufferedReader(new InputStreamReader(getInputStream(;
    }
}

这里主要在构造时读了流,然后存在变量body里,每次返回流的时候从body构造回去即可。

PkslowRequestWrapper request = new PkslowRequestWrapper(req;
ServletInputStream servletInputStream = request.getInputStream(;
String body = StreamUtils.copyToString(servletInputStream, Charset.defaultCharset(;
log.info("Request Body(PkslowRequestWrapper: {}", body;

2.3 内置Filter

其实,针对Request,Spring Boot提供了内置的Filter可以直接记录请求,使用如下:

package com.pkslow.springboot.common.web.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.filter.CommonsRequestLoggingFilter;

@Configuration
public class PkslowConfig {
    @Bean
    public CommonsRequestLoggingFilter loggingFilter( {
        CommonsRequestLoggingFilter filter = new CommonsRequestLoggingFilter(;
        filter.setIncludeHeaders(true;
        filter.setIncludeClientInfo(true;
        filter.setIncludePayload(true;
        filter.setIncludeQueryString(true;

        filter.setAfterMessagePrefix("CommonsRequestLoggingFilter Request: ";

        return filter;
    }
}

但要开debug级别的日志才会打出来。

logging:
  level:
    root: debug

日志如下:

DEBUG 20356 --- [nio-8080-exec-1] o.s.w.f.CommonsRequestLoggingFilter      : Before request [POST /hello/pkslow, client=127.0.0.1, headers=[authorization:"Basic xxxxxx", content-length:"37", host:"localhost:8080", connection:"Keep-Alive", user-agent:"Apache-HttpClient/4.5.13 (Java/17.0.5", accept-encoding:"gzip,deflate", Content-Type:"application/json;charset=UTF-8"]]

3 记录返回

返回也是一样,有流不可重复读的问题,使用Spring自带的ContentCachingResponseWrapper即可。

ContentCachingResponseWrapper response = new ContentCachingResponseWrapper(res;
log.info("Response Code: {}", response.getStatus(;
String responseBody = new String(response.getContentAsByteArray(, response.getCharacterEncoding(;
log.info("Response Body: {}", responseBody;
response.copyBodyToResponse(;

特别注意一定要调用copyBodyToResponse(这个方法,不然无法返回body给请求端了。

4 记录时间

5 测试

测试一下:

POST http://localhost:8080/hello/pkslow
Content-Type: application/json
Authorization: Basic xxxxxx

{
  "id": 999,
  "value": "content"
}

执行日志结果如下:

6 总结

ContentCachingRequestWrapper来解决请求流不可重复读的问题,但这个Wrapper是有限制的,具体可以看它源码。也有人提了Issue。

代码请看GitHub: https://github.com/LarryDpk/pkslow-samples

编程笔记 » Spring在Filter中记录Web请求Request和返回Response的内容及时长

赞同 (37) or 分享 (0)
游客 发表我的评论   换个身份
取消评论

表情
(0)个小伙伴在吐槽