JAVA-日志的异步收集输出

本文探讨了一个高并发Web项目中遇到的日志串行化问题。项目使用sl4j+log4j,当并发调用时,日志出现交错。作者分析了log4j2的异步输出并未解决此问题,提出通过动态代理和反射,创建自定义Handler,以线程为单位收集并按顺序输出日志的解决方案,确保每个请求的日志不会混淆。同时,注意到多线程业务场景下可能存在的问题,并提出了相应的改进思路。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

记录一下最近做的一个Web项目中的日志问题

项目使用的日志体系如下:使用sl4j作为门面,log的实际实现是log4j

问题如下

  在请求高并发的情况下,推测日志会出现串的情况。

举个例子

  

public void echo() {
  log.info("Function begin");
  //...省略若干行代码...
  log.info("Function running");
  //...省略若干行代码...
  log.info("Function end");
}

如果多个请求调用方法echo,最终的输出情况可能如下

  Thread-1: Function begin

  Thread-2: Function begin

  Thread-3: Function begin

  Thread-3: Function running

  Thread-1: Function running

  Thread-1: Function end

  Thread-2: Function running

  Thread-3: Function end

  Thread-2: Function end

不同线程输出的日志会串到一起,很乱

网上查找资料,很少有提这一块的。基本上都是log4j2的异步输出

可是log4j2的异步输出只是先放到buf中,buf满了再一次输出。提高的是写的性能,向buf中加入的顺序仍不能保证,仍然会串。并不是用来解决目前的这个情况的

此外还有使用日志分析工具的,在日志输出时加上某些标志 如IP、用户名、线程名等业务信息,之后使用日志分析工具筛选。这样做还有一个问题 就是每次使用需要手动拼接业务信息输出。log4j倒是有ThreadContext做这个事情,每次设置ThreadContext输出时自动加上。

But!!!我们用的是sl4j,sl4j是一个日志门面,实现了统一的接口 后面可以去找各个实现,logback、jul、log4j等等。sl4j没有ThreadContext这个东西,如果使用ThreadContext意味着什么?我们可以洗洗睡了,sl4j的使用就没有意义了

我的解决思路:

解题基础是动态代理、反射、多线程,新建一个自定义handler,在这个handler中存放一个sl4j的Logger对象

通过反射获取Logger的Method列表(最好是static的 不要重复获取)

定义一个static Map用于存储线程间的信息

在invoke中,对比method,如果当前调用的method在Logger的method列表中,那么不要执行这个method,而是将method和参数args记录下来(这里可以创建一个类 专门存放这些信息)。

然后问题来了 都存放下来不执行 那么我的日志怎么输出呢?

所以要做一个判断,根据线程ID从前面提到的map中查看一个标志是否存在(标志的作用后面就明白了)

如果不存在 获取当前线程 caller,新建一个线程commitTask,并设置这个标志(为了不重复创建commitTask),在commitTask中执行caller.join(),在caller.join()后获取前面拦截的所有method和arg信息,然后使用logger实例依次执行。

所以提交的关键在于这个commitTask,因为执行了caller.join(),所以commitTask会等待线程caller执行完后再执行caller.join()之后的代码。

自己做一个LoggerFactory,在这个LoggerFactory中实例化上面创建的handler,调用sl4j的LoggerFactory创建一个sl4j的Logger实例,将Logger实例赋值给handler对象,根据handler对象创建Logger接口(可以自己定义接口继承sl4j的Logger接口,这样可以添加commit等自定义方法)的动态代理,返回这个动态代理

上述思路的效果:

在一个线程的运行期间,所有logger调用的info、warn等日志方法都不输出而是依次记录下来,在线程执行完毕后,自动按照记录的顺序一起输出(记得加锁)。这样一次请求期间产生的log日志会放在一起输出 不会出现串的现象

PS:

1、这种思路有利有弊,需要根据实际情况自己取舍

2、上述思路还有漏洞,即在一次业务请求中,如果业务代码中有多线程的部分,会因为根据线程为单位收集日志而使一个业务流程中的几个线程间的日志无法一起输出。

  解决:使用自己定义的ID替换线程ID,这里和事务有点像,不开启事务就使用线程ID作为单位收集。开启后根据业务ID为单位收集日志(但是这样没办法自动输出,需要添加方法手动调用输出。如 commit)

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

纵马饮白虹

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值