本来想写个 springboot-starter 来做 Request&Response 日志打印的,但是发现逛了一圈谷歌,发现基本都是用拦截器做的。
使用拦截器做本身没什么问题,但是 HttpServletRequest 读取过一次后,body 就不能再读取了,解决办法就是在 Filter 中自己提前包装一个可重复读的 Request,但是觉得这样做有点麻烦而且不那么优雅。
请教一下有什么优雅的办法在 SpringBoot 中打印 Request&Response 日志吗?
|  |      1MoHen9      2020-09-22 11:58:10 +08:00 via Android | 
|      2Kyle18Tang      2020-09-22 12:00:50 +08:00 | 
|  |      3springmarker OP @MoHen9 #1 AOP 早就试过了,但是 ctrl 层传入传出都是对象,打印日志还得序列化一下,感觉有点浪费 @Kyle18Tang #2 这个在 stackoverflow 见过,还没试过,想自己写个轻量级的 | 
|  |      4frankly123      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; } | 
|  |      5frankly123      2020-09-22 12:58:08 +08:00 @frankly123 然后想在哪里读就在哪里读 | 
|  |      6springmarker OP @frankly123 #4 request 的 inputstream 只能读取一次 | 
|  |      7hugedata      2020-09-22 14:16:21 +08:00 要么就在每个方法里手动打,要么就过滤器。 手动打有个好处:提高你的代码量从而提高绩效考核的分数。/doge | 
|  |      8letitbesqzr      2020-09-22 15:42:37 +08:00 @springmarker #6  使用 org.springframework.web.util.ContentCachingRequestWrapper | 
|  |      9springmarker OP @letitbesqzr #8 这个用过,他的 InputStream 也是只能读取一次,应该是 contentBytes 可以读取多次,但是多次读取 contentBytes 的前提又是需要先做读取 InputStream 。 | 
|  |      10JadeLove      2020-09-22 16:36:59 +08:00 Spring MVC 的话,感觉可以考虑使用 RequestBodyAdvice 和 ResponseBodyAdvice 来实现,之前做参数加解密的时候用过这个。 打印日志不想序列化可以在 RequestBodyAdvice::beforeBodyRead 中做处理,处理完了之后返回一个新的 inputMessage 出来继续后面的 convert 应该就可以。 没试过,感觉可以尝试一下。 | 
|  |      11springmarker OP @urzz #10 这个我也试过,好像不支持 Form 表单   | 
|  |      12frankly123      2020-09-22 20:56:35 +08:00 @springmarker 看我代码,spring 帮你 wrapper 过了 | 
|  |      13CoderGeek      2020-09-22 21:16:38 +08:00 简洁 抽象类程序入口 befor 打印 request ->  你的各种逻辑 -> after 打印 response 打印 对象都写 toString 少用 json | 
|  |      14xuanbg      2020-09-22 22:50:41 +08:00 最好是网关上写 Filter 打印,其次是各服务 AOP 打印。可以看: https://github.com/xuanbg/gateway | 
|  |      15changdy      2020-09-23 08:29:44 +08:00 打印请求日志 可以使用 CommonsRequestLoggingFilter 或者调整 org.apache.coyote.http11.Http11InputBuffer 级别 . 不过最合适的  打印 Response 我也没找到很好的办法 网上找到一些都是获取的 pojo 类 . 插眼同蹲. ps 这种日志是不是在上层记录更合适? | 
|      16cs419      2020-09-23 08:55:13 +08:00  1 想要对 req 打印日志 打印的信息自然是要包含所有请求数据 因此需要读取 inputstream 那问题变成了 inputstream 能重复读取吗 看下接口设计 默认是不支持 但实现类可以调为支持重复读 一般 servlet inputstream 不支持重复读 想要支持就需要自己备份 觉着自己去备份不优雅 无非是觉着亲自给服务添加了个消耗性能的东西是脏活 最好 tomcat 中有个开关,配置下,就支持重复读了,这心里就好受了 脏活总得有人干 说白了君子远庖厨 眼不见为净 | 
|      17tanrenye      2020-09-23 09:43:10 +08:00 我的经验是用 filter,用 AOP 会有很多特定情境无法进入,日志丢失,例如参数不匹配之类的 | 
|  |      18springmarker OP @frankly123 #12  这个看了一下还是由抽象类封装为 ContentCachingRequestWrapper 的,这个类读取 InputStream 也是只能读取一次,读取 ContentBytes 有数据的前提也得是先读取 InputStream 。 @changdy #15 打日志就是为了方便一条龙查看,而且很多服务都是内部服务,没有网关。 @tanrenye #17 用 filter 的问题就是 Body 只能读取一次 | 
|      19aguesuka      2020-09-23 11:54:48 +08:00 via Android 不要在 spring 这一层做,要在网关做,今天记 requestBody 明天就想记 url header,后天就想记 tcp packet 。 | 
|  |      20springmarker OP @aguesuka #19 内部服务之间都是直连的,基本木得 gateway | 
|  |      21tiankongzhe      2020-09-23 14:22:48 +08:00 哥,springboot 都有这个功能的提供的,只需要配置的开关打开就好了 spring.http.log-request-details=true | 
|  |      22springmarker OP @tiankongzhe #21 这个已经过时了,而且在 2.3.4 上没有生效 | 
|  |      23tiankongzhe      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 配置上就好了,请求日志就有了 | 
|  |      24tiankongzhe      2020-09-23 14:52:22 +08:00 2.3 的官网找下吧,应该有这个配置的,之前的版本我是在官网 doc 上看到的 | 
|  |      25springmarker OP | 
|  |      26tiankongzhe      2020-09-23 15:04:29 +08:00 @springmarker 尝试用 javassist 吧,这到请求的总入口,加入 agent,这样比 filter 和 aop 都要简单,性能也高 | 
|      27fanyiaa      2020-09-27 12:00:27 +08:00 参考这个,把其中加密解密部分去掉就行了 https://gitee.com/ishuibo/rsa-encrypt-body-spring-boot |