日志
好的日志是让程序可维护的关键因素。对于大型程序来说更是如此。日志有以下作用:
- 打印调试:用日志来记录变量或者某一段逻辑,记录程序运行的流程,即程序运行了哪些代码,方便排查逻辑问题。
- 问题定位:程序出异常或者出故障时快速的定位问题,方便后期解决问题。因为线上生产环境无法 debug,在测试环境去模拟一套生产环境费时费力。所以依靠日志记录的信息定位问题,这点非常重要。
- 监控告警 & 用户行为审计:格式化后日志可以通过相关监控系统(AntMonitor)配置多维度的监控视图,让我们可以掌握系统运行情况或者记录用户的操作行为并对日志采集分析,用于建设业务大盘使用。
最佳实践
日志的实践包括三个主要内容:日志产生,日志消费,日志分析。
日志应该包含哪些信息:
- Log Level
- DEBUG: 详细的调试信息,发布时可能需要关闭。
- INFO: 一般信息。
- WARNING: 警告但不影响程序运行。
- ERROR: 错误信息。
- CRITICAL: 严重错误,程序应该停止运行。
- 日志应该记录足够的上下文,如时间戳、进程ID、线程ID、类名、方法名等。
日志不应该:
- 避免在关键路径上过多日志,考虑异步日志。日志也会影响程序的性能。
- 不要在日志中记录一些敏感信息,如果不得不这样做,可能需要对日志引入加密手段和权限控制。
在哪里应该打日志:
- 代码初始化时或进入逻辑入口时:系统或者服务的启动参数。核心模块或者组件初始化过程中往往依赖一些关键配置,根据参数不同会提供不一样的服务。务必在这里记录 INFO 日志,打印出参数以及启动完成态服务表述。
- 编程语言提示异常:这类捕获的异常是系统告知开发人员需要加以关注的,是质量非常高的报错。应当适当记录日志,根据实际结合业务的情况使用 WARN 或者 ERROR 级别。
- 业务流程预期不符:项目代码中结果与期望不符时也是日志场景之一,简单来说所有流程分支都可以加入考虑。取决于开发人员判断能否容忍情形发生。常见的合适场景包括外部参数不正确,数据处理问题导致返回码不在合理范围内等等。
- 系统/业务核心逻辑的关键动作:系统中核心角色触发的业务动作是需要多加关注的,是衡量系统正常运行的重要指标,建议记录 INFO 级别日志。
- 第三方服务远程调用:微服务架构体系中有一个重要的点就是第三方永远不可信,对于第三方服务远程调用建议打印请求和响应的参数,方便在和各个终端定位问题,不会因为第三方服务日志的缺失变得手足无措。
并发
在编写单线程的代码中,日志的顺序就是程序执行的顺序,顺着看下来就能理清逻辑。
对于并发的程序,如果日志中不带有进程ID、线程ID,那么日志对你来说就像是“乱序”的。更糟糕的是,如果程序是异步的,有时候可能会隐式的创造新的线程。理想情况的并发的日志应该是:
a1 -> b1 -> b2 -> a2 -> b3 # raw log stream
a1 -------------> a2 # parsed log stream a
b1 -> b2 -------> b3 # parsed log stream b
那么如何才能知道a1和a2在逻辑上是连续的?可以参考Rust - tracing库的设计。
编程实践
通常有两类与日志相关的 crate:日志接口和日志消费者。接口提供了想要记录某些东西时调用的函数,消费者处理将结构化日志数据格式化到某个地方(stderr 或文件,甚至是消息队列)。
所以,库的编写者应该只使用 日志接口 库,而可执行文件的编写者才会同时使用 日志接口 库 和 日志消费者 库。
日志分析
最简单的日志分析使用grep
就可以了。
fblog
fblog 是一个命令行JSON日志分析工具。JSON作为结构化的数据,肯定更好用来进行条件筛选。
Open Telemetry
OpenTelemetry 是一个开源的观测框架,用于收集和导出跟踪、度量和日志数据。它是由 Cloud Native Computing Foundation (CNCF) 所支持和维护的,旨在提供一套统一的、跨语言的工具和库,以便开发者能够在他们的应用程序中更容易地实现可观测性。
参考资料: