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

2020-09-22 11:53:28 +08:00
 springmarker

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

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

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

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

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

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


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


@tanrenye #17 用 filter 的问题就是 Body 只能读取一次
aguesuka
2020-09-23 11:54:48 +08:00
不要在 spring 这一层做,要在网关做,今天记 requestBody 明天就想记 url header,后天就想记 tcp packet 。
springmarker
2020-09-23 13:43:35 +08:00
@aguesuka #19 内部服务之间都是直连的,基本木得 gateway

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/709352

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX