技术分享一个关于springweb的日志统一打印方案
分享一个关于spring-web的日志统一打印方案。
先解释一下,这里说的日志统一打印,是说基于追踪定位问题的需要,在框架层面将我们关心的请求处理过程相关信息进行统一规划打印输出到日志里的行为。
相信大家在各自的工作中都见过或者解决过类似的问题。方案很多,当然,效率和复杂度也有很大的差别。有自定义注解的、自定义filter的、扩展spring的AbstractRequestLoggingFilter(处理请求参数)和MappingJackson2HttpMessageConverter(处理返回值)的等等...
无非两个方向:第一种,自己想办法拦截处理。第二种,借助spring提供的扩展点来定制。我是倾向于第二种方案的,不过在解决过程中发现,如果既要打印请求参数、又要打印返回值的话,利用的spring扩展点比较零散,形成的落地方案不是很令人满意。不过在这期间翻看spring源码的过程中,也慢慢酝酿了目前的解决方案。
且听我慢慢道来...
在寻找spring-web关于controller方法拦截处理的扩展点的时候,找到了InvocableHandlerMethod这个类,如下图:
上图这个类是负责通过注册的HandlerMethodArgumentResolver解析请求参数后调用对应的controller方法的,他里边最核心的方法,如下图:
上图方法的处理过程很清晰,先解析并获得controller的入参,然后调用controller的对应方法,最后获取到返回值。而且重点是,spring在调用controller方法前后已经将入参和返回值打印了出来。
惊不惊喜,意不意外!!!是不是你想要的都已经达到了(姑且先不论长度截取、信息脱敏等的其他日志打印需求),我还费那个劲干嘛?
先别高兴太早,虽然spring是给打印了,仔细看一下,log是TRACE级别的。这里有必要啰嗦的普及一下log级别,一共8种,优先级由高到低依次为:OFF、FATAL、ERROR、WARN、INFO、DEBUG、TRACE、ALL。也就是说,spring打印的这个级别比DEBUG级别还低,根本不能在生产上使用。
那怎么办,就这样眼睁睁地用不上吗?当然不。这就有了我们的最终解决方案:既然你都已经做了那么多了,我也往前迈一步,我们不就是幸福的一家人了吗?给你把源码改一下吧,这时候字节码增强技术就是一个比较合适的选择。你不是TRACE级别吗,我给你定制扩展一个INFO级别的甚至FATAL级别的打印。
以下是使用javassist对invokeForRequest方法里调用的doInvoke方法进行字节码增强的代码示例截图:
里边最核心的,是在调用doInvoke方法的前后引入了自己的处理工具类方法HeartvoicesLogTools.printParems和HeartvoicesLogTools.printResult,将spring处理好的controller方法入参和返回值传给他们,让它们来处理log的打印逻辑。在自己写的类里拿到了需要的东西,那就想怎么处理就怎么处理了,都是合法夫妻了嘛哈哈,log级别、长度截取、信息脱敏、特殊方法跳过(比如:文件下载方法就不适合打印返回值)之类的需求随意你去扩展定制,这里就不多说了。