目的
NestJS
自带了 Logger
模块,这个模块可以将内容打印到控制台,也可以将内容持久化到文件中。这种设计符合主流的日志文件的方案,但是实践下来发现其有非常多的限制。
node
是一个单主线程的环境,线上单台服务器一般都会使用PM2
多开服务,更不用提集群部署了。这样就会造成日志零散的问题。出了问题,不知道日志会写入到那个实例中,只能将所有的日志文件都查看一遍,运气好,一下就找到,运气不好那就呵呵了。- 写入文件的形式在
docker
容器环境下要注意将日志文件挂载到volume
中进行持久化,否则一旦容器重启岂不是日志都丢了。 - 日志文件文件一般未被压缩,会占用比较大的空间,所以都会定期清理。如果不做特殊操作,清理起来还是很麻烦的。
- 日志文件一般很难查看,查找分类等功能都比较欠缺。也就是vim、记事本等有的功能可以拿来使用。想要使用统计分析结果那就呵呵了。
基于以上限制,我们的日志系统要做到易于查看,易于管理,易于统计分析,精准快速定位问题点。
设计
不采用文件的形式很容易就想到了数据库存储,存储日志,当然是文档型数据库更加合适,文档型数据库首推 MongoDB
,因此,我们采用 MongoDB
来存储系统日志。这样在查询的时候借助 MongoDB
提供的查询方式可以做到很大的灵活度。
对于不同的部署方式也要有单独的考量。单体、集群、微服务的方式都会产生略微的差异。
单体服务
单体服务最为容易,只需要在产生日志的时候将日志信息写入数据库即可,这里的写入也没有什么需要特殊处理的。
可以考虑一个小优化,将一段时间内的日志聚合到一起,批量通过 insert
来插入到 MongoDB
数据库中。
集群服务
多开集群的日志在本质上和单体没有太大差别,因此完全可以使用单体那套方案。
这里需要考虑一点,要考虑系统日志的产生情况,如果产生的速度很快,还没有设计批量落盘,就有可能造成数据库同时写入请求太多的情况,影响数据库性能。
也可以更进一步的优化,将日志收集到集群的某一个服务上,由这个服务统一将数据落盘,具体操作详见下面的微服务方式。
微服务
微服务是将单体服务拆成多个更细粒度的服务进行部署,各个服务之间独立运行(当然,也可以独立多开、集群),这样就会导致每个服务都产生日志。如果每个服务都采用和数据库直连的方式,就会造成数据库连接数量过多,而且代码量也相应的增加很多(主要是指重复的代码)。如果修改表结构的话,那对于 code first
方案更是一种灾难(我们使用的都是 code first
方式)。
基于这些方面考虑,我们采用队列的方式来将数据收集到同一个服务上,并进行数据落盘操作。
队列可以采用 Kafka
这样的成熟消息队列,其性能和稳定性毋庸置疑,并且其生产消费的控制方案非常强大,给到我们控制的最高灵活度。但是缺点就是很重,在 Kafka 3.x
版本之前都是需要借助 Zookeeper
的,在 Kafka 3.x
之后不需要 Zookeeper
了,但是也很重。仅仅为了一个日志而引入这么庞大的东西是不划算的。当然,如果系统中本来就有 Kafka
的需求,那么在这种环境下就很舒服了。
NestJS
也提供了一套轻量级的队列方案,参考官方文档——Queues,这套方案使用了bull软件包,借助 Redis
的发布订阅功能实现了消息队列的核心。我们的每个项目基本上 Redis
就是必备,因此使用此方案就很容易了。