打日志是门艺术

日志是用来暴露系统内部状态的一种手段,好的日志可以帮助开发人员快速定位问题所在,然后找到合适的方式解决掉问题,一个优秀的日志输出不会包含无用输出,也不会缺少任何有用信息。然而现在的日志框架(log4j, logrus等众多框架库)是按照等级划分日志的。那么如果选择等级,输出什么样的信息就是我们需要认真思考。
大多数系统中日志的问题

日志过度

日志过度理论上会包含所有需要输出的信息,不仅是对系统暴露有用的,也有对暴露系统无用的,这些日志慢慢累积,最终会冲爆日志存储方。过多的日志主要原因是输出了大量重复的数据。

日志不足

日志缺少关键信息,导致根据目前日志无法准确定位日志问题。此类日志缺少相应的输出内容,导致无法直接或不能确定系统出现问题的原因、时间。
值得注意的是,大多数的日志问题同时包含日志过度以及日志不足这两点本身并不冲突。即输出了大量无用的日志。

日志等级错误

输出的内容与日志等级不符(部分框架提供了等级能力),导致根据等级无法快速定位问题。最终表现为定位无力。

日志的等级

日志的等级本身提供了基本的筛选能力,帮助我们快速筛选不同类型的问题。而每种类型的日志所关心的内容并非全部相同。那么日志的每个等级的核心表达应该是什么呢?
目前常见的日志等级

1. Off & All

这两种等级表示不输出或者全输出日志。这两类不包含日志输出内容,只是标指标,用来告诉系统该如何输出日志。

2. Trace & Debug 调试追踪程序状态

这两种日志是最低限制的日志。通常系统发生某些异常,或者可能存在潜在问题需要开启这种日志进行问题的追踪。
Trace 等级常常用来跟踪程序状态,比如程序执行到了哪一步,它是最细力度的。通过这种日志可以完全了解目前程序正在处理的任务工作。值得注意的是 Trace 理论上会产生大量的日志内容(因为为了跟踪系统的状态,甚至包括每次赋值都会被输出),需要强大的日志框架接收日志,否则Trace很容易引爆日志存储。注意Trace应该跟踪程序状态,而不是用户输入的数据经过了怎样的处理。
而 Debug 更关心的是数据本身,而不是程序状态以及运行阶段。Debug 应该总是输出和数据相关的内容。

3. Info

Info 等级是日志中最为常用的等级,他的粒度应该是系统进入了某些阶段,或产生了某些操作并给出相应的输出结果。Info等级应该只是输出一条纯粹的信息。当然包括一些基本的信息,比如:时间戳、上下文等。该日志的输出与系统稳定状态、系统运行情况无关,只与系统本身正在进行的行为有关。比如:用户A登录失败/成功。

4. Warn

Warn表示系统出现了预期内的异常情况,该异常情况不会导致系统崩溃,并且系统完全可以自行修复该问题。Warn的出现表示系统出现了某些异常,但是这些异常已经被考虑到,有可能会终止此次行为、时间、操作等,但不会导致系统的崩溃,系统仍然处于稳定状态中。例如:用户A输入空密码,无法登录。
然而很多时候,我们常常采用变量输出: 用户A输入密码为 ‘’。这种无法直接判定出现 Warn 的原因,Warn级别应该输出足够的信息表明出现警告的原因和上下文,而不是含糊其辞。Warn本身必须输出产生Warn的原因,上述内容中,用户A输入密码为’’ 本身只能作为 Warn的一部分,因为输入密码为空导致无法登录,最终是因为无法登录而进行Warn,Warn更多关心的是什么原因导致了什么结果。

6. Error

Error表示系统出现了预期外的情况,该情况导致系统出现了不稳定,且无法确定能否恢复,比如数据库连接中断,但是系统仍然可运行。Error的输出应该小心些,并不是所有的异常都是预期之外,有相当一部分的输出都是预期之内的。只有当需要开发人员接入的时候才考虑是否需要输出Error。当出现Error后,开发人员必须做出相应维护当前系统的稳定性。

7. Fatal

系统出现不可恢复的错误状态,可能会直接停止运行时,需要输出该信息。注意与Error相似,此种情况出现后,开发人员必须介入,但是Error只是用来输出部分功能受限制时的状态,而Fatal的出现表示系统所有功能全部停止无法正常工作。比如系统网络中断,无法向内向外链接任何输入输出。
因此输出Fatal时,应该注意尽可能的快速输出完整的信息,因为系统的不稳定将会导致无法输出Fatal的原因。

日志应该输出的信息

每种等级的日志输出的内容不同,但是所有的等级的日志都会有几个共通关心的字段。这些字段表明了输出日志的上下文信息等。

时间戳

输出日志时应该带有完整时间戳,并且格式需要统一。最好输出完整的位数,例如: 2021/09/08 07:06:05 PM可以考虑是否带上时区等。这样出现歧义的情况会少很多。

日志等级

不同等级的日志需要注意与其他等级的日志进行区分,这样在日志量很大的情况下,仍然能够快速找到有用的信息。
等级的区分可以用字符串(方便查找)、颜色(肉眼可观察)、区分不同文件等多种方式操作。

日志报点位置

大多数的日志都会含有该字段,用来帮助开发人员快速定位问题代码位置。根据具体情况选择相对或绝对路径。这对于定位问题有很大的帮助。

携带上下文

每一条日志都应该携带上下文,比如后台系统中一条情况应该可以产生一条请求ID,根据该ID可以完整跟踪该请求的全部日志,帮助快速理清问题。

日志的重复性问题

往往很多时候,Info级别输出后,就不再使用其他级别进行输出,这样的做法我个人认为是完全错误的。不同等级的日志本质上并非是等级划分,而是目的划分,根据不同的目的选择输出不同的日志。那么实际场景中不应该出现等级不同但内容相同的日志,但是可以出现语义类似的日志。而且某些时候,保证日志系统的稳定性,选择忽略一些日志,那么如果核心日志在被忽略的日志中,将会导致问题出现后无法准确获取问题原因。

关于日志的开关

往往本地调试需要开启Trace日志用来跟踪系统状态,线上测试环境可能会更多关注Debug的信息,线上生产环境可能更多关注Info、Warn 、Error 、Fatal的信息。

如有问题请随时联系进行修改。