V2EX = way to explore
V2EX 是一个关于分享和探索的地方
Sign Up Now
For Existing Member  Sign In
springmarker
V2EX  ›  Java

如何优雅的在 SpringBoot 中打印 Request&Response 日志

  •  
  •   springmarker · Sep 22, 2020 · 8335 views
    This topic created in 2045 days ago, the information mentioned may be changed or developed.

    本来想写个 springboot-starter 来做 Request&Response 日志打印的,但是发现逛了一圈谷歌,发现基本都是用拦截器做的。

    使用拦截器做本身没什么问题,但是 HttpServletRequest 读取过一次后,body 就不能再读取了,解决办法就是在 Filter 中自己提前包装一个可重复读的 Request,但是觉得这样做有点麻烦而且不那么优雅。

    请教一下有什么优雅的办法在 SpringBoot 中打印 Request&Response 日志吗?

    27 replies    2020-09-27 12:00:27 +08:00
    MoHen9
        1
    MoHen9  
       Sep 22, 2020 via Android
    springmarker
        3
    springmarker  
    OP
       Sep 22, 2020
    @MoHen9 #1 AOP 早就试过了,但是 ctrl 层传入传出都是对象,打印日志还得序列化一下,感觉有点浪费

    @Kyle18Tang #2 这个在 stackoverflow 见过,还没试过,想自己写个轻量级的
    frankly123
        4
    frankly123  
       Sep 22, 2020
    /**
    * @author wangyiwen
    * @version 1.0 Created in 2020/7/14 18:36
    * 该注入是为了可以获取到包装过的 httpRequest
    */
    public class WrapperRequestFilter extends AbstractRequestLoggingFilter {


    /**
    * Concrete subclasses should implement this method to write a log message
    * <i>before</i> the request is processed.
    *
    * @param request current HTTP request
    * @param message the message to log
    */
    @Override
    protected void beforeRequest(HttpServletRequest request, String message) {
    //do nothing
    }

    /**
    * Concrete subclasses should implement this method to write a log message
    * <i>after</i> the request is processed.
    *
    * @param request current HTTP request
    * @param message the message to log
    */
    @Override
    protected void afterRequest(HttpServletRequest request, String message) {
    //do nothing
    }
    }
    ---------------------------------------------------------------------------------------------------
    @Bean
    public WrapperRequestFilter wrapperRequestFilter() {
    WrapperRequestFilter wrapperRequestFilter = new WrapperRequestFilter();
    wrapperRequestFilter.setIncludeQueryString(true);
    wrapperRequestFilter.setIncludeClientInfo(true);
    wrapperRequestFilter.setIncludeHeaders(true);
    wrapperRequestFilter.setIncludePayload(true);
    wrapperRequestFilter.setMaxPayloadLength(99999);
    return wrapperRequestFilter;
    }
    frankly123
        5
    frankly123  
       Sep 22, 2020
    @frankly123 然后想在哪里读就在哪里读
    springmarker
        6
    springmarker  
    OP
       Sep 22, 2020
    @frankly123 #4 request 的 inputstream 只能读取一次
    hugedata
        7
    hugedata  
       Sep 22, 2020
    要么就在每个方法里手动打,要么就过滤器。
    手动打有个好处:提高你的代码量从而提高绩效考核的分数。/doge
    letitbesqzr
        8
    letitbesqzr  
       Sep 22, 2020
    @springmarker #6 使用 org.springframework.web.util.ContentCachingRequestWrapper
    springmarker
        9
    springmarker  
    OP
       Sep 22, 2020
    @letitbesqzr #8 这个用过,他的 InputStream 也是只能读取一次,应该是 contentBytes 可以读取多次,但是多次读取 contentBytes 的前提又是需要先做读取 InputStream 。
    JadeLove
        10
    JadeLove  
       Sep 22, 2020
    Spring MVC 的话,感觉可以考虑使用 RequestBodyAdvice 和 ResponseBodyAdvice 来实现,之前做参数加解密的时候用过这个。
    打印日志不想序列化可以在 RequestBodyAdvice::beforeBodyRead 中做处理,处理完了之后返回一个新的 inputMessage 出来继续后面的 convert 应该就可以。
    没试过,感觉可以尝试一下。
    springmarker
        11
    springmarker  
    OP
       Sep 22, 2020
    @urzz #10 这个我也试过,好像不支持 Form 表单
    frankly123
        12
    frankly123  
       Sep 22, 2020
    @springmarker 看我代码,spring 帮你 wrapper 过了
    CoderGeek
        13
    CoderGeek  
       Sep 22, 2020
    简洁 抽象类程序入口 befor 打印 request -> 你的各种逻辑 -> after 打印 response 打印 对象都写 toString 少用 json
    xuanbg
        14
    xuanbg  
       Sep 22, 2020
    最好是网关上写 Filter 打印,其次是各服务 AOP 打印。可以看: https://github.com/xuanbg/gateway
    changdy
        15
    changdy  
       Sep 23, 2020
    打印请求日志 可以使用 CommonsRequestLoggingFilter 或者调整 org.apache.coyote.http11.Http11InputBuffer 级别 . 不过最合适的
    打印 Response 我也没找到很好的办法 网上找到一些都是获取的 pojo 类 . 插眼同蹲.
    ps 这种日志是不是在上层记录更合适?
    cs419
        16
    cs419  
       Sep 23, 2020   ❤️ 1
    想要对 req 打印日志 打印的信息自然是要包含所有请求数据
    因此需要读取 inputstream
    那问题变成了 inputstream 能重复读取吗
    看下接口设计 默认是不支持 但实现类可以调为支持重复读
    一般 servlet inputstream 不支持重复读 想要支持就需要自己备份

    觉着自己去备份不优雅 无非是觉着亲自给服务添加了个消耗性能的东西是脏活
    最好 tomcat 中有个开关,配置下,就支持重复读了,这心里就好受了
    脏活总得有人干 说白了君子远庖厨 眼不见为净
    tanrenye
        17
    tanrenye  
       Sep 23, 2020
    我的经验是用 filter,用 AOP 会有很多特定情境无法进入,日志丢失,例如参数不匹配之类的
    springmarker
        18
    springmarker  
    OP
       Sep 23, 2020
    @frankly123 #12 这个看了一下还是由抽象类封装为 ContentCachingRequestWrapper 的,这个类读取 InputStream 也是只能读取一次,读取 ContentBytes 有数据的前提也得是先读取 InputStream 。


    @changdy #15 打日志就是为了方便一条龙查看,而且很多服务都是内部服务,没有网关。


    @tanrenye #17 用 filter 的问题就是 Body 只能读取一次
    aguesuka
        19
    aguesuka  
       Sep 23, 2020 via Android
    不要在 spring 这一层做,要在网关做,今天记 requestBody 明天就想记 url header,后天就想记 tcp packet 。
    springmarker
        20
    springmarker  
    OP
       Sep 23, 2020
    @aguesuka #19 内部服务之间都是直连的,基本木得 gateway
    tiankongzhe
        21
    tiankongzhe  
       Sep 23, 2020
    哥,springboot 都有这个功能的提供的,只需要配置的开关打开就好了
    spring.http.log-request-details=true
    springmarker
        22
    springmarker  
    OP
       Sep 23, 2020
    @tiankongzhe #21 这个已经过时了,而且在 2.3.4 上没有生效
    tiankongzhe
        23
    tiankongzhe  
       Sep 23, 2020
    spring boot 2.1
    logging.level.org.springframework.web=DEBUG
    spring.http.log-request-details=true

    spring boot 2.2
    logging.level.org.springframework.web=DEBUG
    spring.mvc.log-request-details=true

    配置上就好了,请求日志就有了
    tiankongzhe
        24
    tiankongzhe  
       Sep 23, 2020
    2.3 的官网找下吧,应该有这个配置的,之前的版本我是在官网 doc 上看到的
    springmarker
        25
    springmarker  
    OP
       Sep 23, 2020
    @tiankongzhe #23
    这个是没有 Body 的,早就试过了。
    简略写法:logging.level.web=debug
    tiankongzhe
        26
    tiankongzhe  
       Sep 23, 2020
    @springmarker 尝试用 javassist 吧,这到请求的总入口,加入 agent,这样比 filter 和 aop 都要简单,性能也高
    fanyiaa
        27
    fanyiaa  
       Sep 27, 2020
    参考这个,把其中加密解密部分去掉就行了
    https://gitee.com/ishuibo/rsa-encrypt-body-spring-boot
    About   ·   Help   ·   Advertise   ·   Blog   ·   API   ·   FAQ   ·   Solana   ·   1454 Online   Highest 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 80ms · UTC 16:54 · PVG 00:54 · LAX 09:54 · JFK 12:54
    ♥ Do have faith in what you're doing.