日志框架作为大型系统最基础的组件之一,用于快速定位和分析程序的问题。当项目采用多线程编程时,调试变的异常困难,在合适的地方打上日志,可以提高解决问题的效率。在分布式系统中,进行分布式追踪也要依靠日志框架。
先来谈一下日志框架设计几要素,既然日志是用于定位与分析问题的,所以记录信息需要很详细,一条日志应由 当前时间、日志等级、线程名称、类、方法名、行号和 Message 部分构成。日志框架采用同步还是异步写,存储方式、过滤器等等要素都需要考虑,接下来来介绍几个重要点。
同步与异步
倘若将日志写入到数据库中,采用同步写的方式,发生阻塞的操作时会将线程给阻塞住,从而影响业务。对于日志框架,一般是采用异步的模式,异步提供了四种模式。
- SPSC:单生产单消费
- SPMC:单生产多消费
- MPSC:多生产单消费
- MPMC:多生产多消费
日志框架该采用那种异步模式呢?多个线程同时记录日志这是多生产的方式,如果采用多消费的话,那么需要维护多个线程开销和同步问题,而且系统日志记录的并不是很频繁,无需增加额外的复杂度,综合以上场景使用 MPSC 模式最优。
使用单消费者模式,需要使用一个容器来存储日志,当进行日志记录时,其它线程只需要先将日志存储到消费者容器即可,后续由消费者慢慢消费。从容器存储与获取日志都需要进行加锁操作,而容器最好不使用阻塞队列,如果某条日志写入的时候过长,在此期间导致阻塞队列满了,那么就会阻塞其他线程,从而影响业务正常运行。
过滤器
过滤器用于对日志进行过滤操作,从而筛选出不满足条件的记录。比如不需要记录 main 线程的日志,那么需要对线程名进行一个过滤操作,来筛选出不是 main 的线程日志。
针对过滤器的设计,下面列举几个常见的过滤器
- 日志等级过滤器
- 线程名过滤器
- Message 过滤器
- 时间过滤器
存储方式
日志框架不仅仅只将日志打印在控制台,日志可以很好反应程序的运行状态,所以需要将日志进行持久化,用于后续的分析与优化,对于存储方式就有多种多样了,如下所示。
- FileSystem
- Database
- Http
- Kafka
- Console
- SMTP
- Json
下面对 FileSystem 存储方式进行分析,这种方式是将日志写入到文件中,这必然会涉及序列化与反序列化。我们知道当一个文件很大的时候,对操作系统也不友好,并且也不利于日志文件读取。所以需要对日志文件进行分段的设计,可以按照时间点、文件大小、日志 level 来进行分段,从而减少日志文件的大小。
涉及文件操作,那么必然逃不过缓冲区的设计,将 data 写入到文件中,会产生系统调用,如果每次记录日志都进行系统调用,这会极大的降低日志框架的性能。那么在应用层采用缓冲区是一个明智的选择,首先日志序列化之后放入到缓冲区中,当缓冲区满了或定时将缓冲区内容写入到文件中。倘若当前机器宕机了,那么存储在缓冲区的日志数据就会丢失,所以缓冲区的大小需要采用适当的大小,倘若采用 Java 进行实现,可以使用 Buffer 的输出流。
日志框架将 data 写入到文件,操作系统首先将 data 写入到文件系统的缓冲区,之后由文件系统定时或缓冲区满了将缓冲区 flush 到 disk 。如果发生宕机,那么也会发生数据丢失,日志框架丢失数据是不可容忍的,倘若丢的数据就是分析问题所需关键日志,那么日志框架的意义何在。针对这种问题,可以定时手动调用 flush 系统调用来将缓冲区强制刷新到 disk。
日志框架接收外部化信号或者因为异常而退出,那么需要将缓冲区数据进行刷新,并且将一些分配的资源进行关闭,面对这种情况,需要借助 Shutdown Hook 来实现。
以上设计只能尽量的避免数据丢失,并不能完全避免数据丢失情况,对于系统的设计需要在性能和可靠性进行折中的设计,寻求一个最优解。