如何优雅的在 SpringBoot 中打印 Request&Response 日志 - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
springmarker
V2EX    Java

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

  •  
  •   springmarker 2020-09-22 11:53:28 +08:00 8067 次点击
    这是一个创建于 1876 天前的主题,其中的信息可能已经有所发展或是发生改变。

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

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

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

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

    @Kyle18Tang #2 这个在 stackoverflow 见过,还没试过,想自己写个轻量级的
    frankly123
        4
    frankly123  
       2020-09-22 12:42:56 +08:00
    /**
    * @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  
       2020-09-22 12:58:08 +08:00
    @frankly123 然后想在哪里读就在哪里读
    springmarker
        6
    springmarker  
    OP
       2020-09-22 13:00:18 +08:00
    @frankly123 #4 request 的 inputstream 只能读取一次
    hugedata
        7
    hugedata  
       2020-09-22 14:16:21 +08:00
    要么就在每个方法里手动打,要么就过滤器。
    手动打有个好处:提高你的代码量从而提高绩效考核的分数。/doge
    letitbesqzr
        8
    letitbesqzr  
       2020-09-22 15:42:37 +08:00
    @springmarker #6 使用 org.springframework.web.util.ContentCachingRequestWrapper
    springmarker
        9
    springmarker  
    OP
       2020-09-22 16:09:27 +08:00
    @letitbesqzr #8 这个用过,他的 InputStream 也是只能读取一次,应该是 contentBytes 可以读取多次,但是多次读取 contentBytes 的前提又是需要先做读取 InputStream 。
    JadeLove
        10
    JadeLove  
       2020-09-22 16:36:59 +08:00
    Spring MVC 的话,感觉可以考虑使用 RequestBodyAdvice 和 ResponseBodyAdvice 来实现,之前做参数加解密的时候用过这个。
    打印日志不想序列化可以在 RequestBodyAdvice::beforeBodyRead 中做处理,处理完了之后返回一个新的 inputMessage 出来继续后面的 convert 应该就可以。
    没试过,感觉可以尝试一下。
    springmarker
        11
    springmarker  
    OP
       2020-09-22 18:18:46 +08:00
    @urzz #10 这个我也试过,好像不支持 Form 表单
    frankly123
        12
    frankly123  
       2020-09-22 20:56:35 +08:00
    @springmarker 看我代码,spring 帮你 wrapper 过了
    CoderGeek
        13
    CoderGeek  
       2020-09-22 21:16:38 +08:00
    简洁 抽象类程序入口 befor 打印 request -> 你的各种逻辑 -> after 打印 response 打印 对象都写 toString 少用 json
    xuanbg
        14
    xuanbg  
       2020-09-22 22:50:41 +08:00
    最好是网关上写 Filter 打印,其次是各服务 AOP 打印。可以看: https://github.com/xuanbg/gateway
    changdy
        15
    changdy  
       2020-09-23 08:29:44 +08:00
    打印请求日志 可以使用 CommonsRequestLoggingFilter 或者调整 org.apache.coyote.http11.Http11InputBuffer 级别 . 不过最合适的
    打印 Response 我也没找到很好的办法 网上找到一些都是获取的 pojo 类 . 插眼同蹲.
    ps 这种日志是不是在上层记录更合适?
    cs419
        16
    cs419  
       2020-09-23 08:55:13 +08:00   1
    想要对 req 打印日志 打印的信息自然是要包含所有请求数据
    因此需要读取 inputstream
    那问题变成了 inputstream 能重复读取吗
    看下接口设计 默认是不支持 但实现类可以调为支持重复读
    一般 servlet inputstream 不支持重复读 想要支持就需要自己备份

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


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


    @tanrenye #17 用 filter 的问题就是 Body 只能读取一次
    aguesuka
        19
    aguesuka  
       2020-09-23 11:54:48 +08:00 via Android
    不要在 spring 这一层做,要在网关做,今天记 requestBody 明天就想记 url header,后天就想记 tcp packet 。
    springmarker
        20
    springmarker  
    OP
       2020-09-23 13:43:35 +08:00
    @aguesuka #19 内部服务之间都是直连的,基本木得 gateway
    tiankongzhe
        21
    tiankongzhe  
       2020-09-23 14:22:48 +08:00
    哥,springboot 都有这个功能的提供的,只需要配置的开关打开就好了
    spring.http.log-request-details=true
    springmarker
        22
    springmarker  
    OP
       2020-09-23 14:47:18 +08:00
    @tiankongzhe #21 这个已经过时了,而且在 2.3.4 上没有生效
    tiankongzhe
        23
    tiankongzhe  
       2020-09-23 14:51:00 +08:00
    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  
       2020-09-23 14:52:22 +08:00
    2.3 的官网找下吧,应该有这个配置的,之前的版本我是在官网 doc 上看到的
    springmarker
        25
    springmarker  
    OP
       2020-09-23 15:02:28 +08:00
    @tiankongzhe #23
    这个是没有 Body 的,早就试过了。
    简略写法:logging.level.web=debug
    tiankongzhe
        26
    tiankongzhe  
       2020-09-23 15:04:29 +08:00
    @springmarker 尝试用 javassist 吧,这到请求的总入口,加入 agent,这样比 filter 和 aop 都要简单,性能也高
    fanyiaa
        27
    fanyiaa  
       2020-09-27 12:00:27 +08:00
    参考这个,把其中加密解密部分去掉就行了
    https://gitee.com/ishuibo/rsa-encrypt-body-spring-boot
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     5226 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 30ms UTC 08:20 PVG 16:20 LAX 00:20 JFK 03:20
    Do have faith in what you're doing.
    ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86