V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
springmarker
V2EX  ›  Java

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

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

    本来想写个 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 。
    urzz
        10
    urzz  
       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   ·   实用小工具   ·   920 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 23ms · UTC 22:17 · PVG 06:17 · LAX 14:17 · JFK 17:17
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.