日志的作用

对于日志,每一个程序员应该都不会陌生,不管是实现业务功能代码,还是各种框架,或是实现底层数据存储,都会和日志打交道。如果线上运行的系统出问题了,查日志往往是一步必不可少的操作。 另外,对于业务的特定日志的收集汇总分析也是日志的一大应用。比如可以将接口访问和监控数据记录在日志中,然后将这些非结构化的日志数据收集到一个统一的系统中(比如 ELK),做结构化的处理,再基于结构化的日志做统计分析,这就是实现各种大盘监控的方法。

如何打好日志

如何打好日志,其实是一个值得重视的问题。在很多时候,日志往往不会引起程序员的注意,毕竟对于访问量不大的软件系统来说,只要清理日志的时间合理,磁盘空间和打日志开销不会成为瓶颈,日志是打得越多越好,因为可以通过日志很快速的排查出问题的原因。比如因为依赖的一个应用系统响应不符合期望而报错了,通过请求响应日志,可以很方便的定位。但是对于访问量比较大的软件系统,打印日志过多,往往会带来很多问题,最典型的就是日志文件过大或过多,导致 grep 日志文件耗时长。更严重一点的就是磁盘空间被打满,磁盘 IO 消耗高,导致请求响应的 RT 变高或者服务不可用。日志本来是用来辅助排查问题和优化应用的,但是由于打日志导致影响了正常服务就得不偿失了。

还有一类容器化部署带来的问题,如果使用容器化部署,特别是在线离线混合部署的场景,物理机上部分容器使用磁盘较多的时候,可能会因为物理机磁盘 IO 太高导致其他容器的在线应用同步打日志时受到影响,最终在线请求因为打日志失败而报错。

所以,对于访问量比较大的系统,特别是 DAU 比较高的系统,打日志并不是打得越多越好,在合适的地方打合适的日志才是最重要的。哪些地方是合适的?而怎么样打日志才是合适的呢?这很难有统一的标准,但是,在工作中看过太多异常日志随意处理,导致排查问题特别费劲的例子。比如 catch 到 Exception 之后直接打了这样的日志。

LOG.error(e.getMessage()) 或者 LOG.error(e.getMessage(), e)

其他什么参数都没有。所以根据平时排查问题的经验,总结了几点。

  • 记录运行时必要状态的地方打 info 日志,这种日志不宜过多。
  • 参数变量不符合预期的地方打 warn 日志。
  • 捕捉到异常的地方,打 error 日志,注意异常栈一定要在日志中打印。
  • 将必要的参数打印出来,写代码打日志时,必须考虑以后排查问题的需要。
  • 尽量有一个地方 catch 到 Throwable。
  • 对于业务比较复杂的功能单独打一个日志文件。

关于日志的级别,可以参考日志框架的文档,比如 logback,log4j, 对于 info 级别(对于日志的级别,可以参考日志框架的文档,比如 logback,log4j)的日志,不宜过多,太多容易引起磁盘问题。所以针对 info 级别的日志,请加上对 日志级别的判断(同样 DEBUG 级别也同理),这也是我们在开源软件中常常会看到打日志方法。比如

if(LOG.isInfoEnabled()) {
  LOG.info("xxxx {} ", param); 
}

在线上环境,将日志级别设置为 WARN 级别,而在非线上环境,将日志级别设置为 INFO(或 DEBUG) 级别,这样也容易排查测试过程中的问题。对于执行逻辑比较复杂的一段代码,在执行中间,打印运行过程中的一些关键信息,也有助于问题的排查,这类日志也最好是能以 INFO 日志级别来打印。比如导出一个大文件,每导出一部分页数的时候将当前的页码,参数等信息打印在日志中,当遇到导出失败时,也能根据日志中的页码大致缩小问题排查范围。

对于 error 日志,有异常时一定要将异常栈也打印出来,往往通过异常栈就很容易定位问题出现的地方,再通过日志中的参数进一步排查。所以,打日志时带上相关参数也是一个非常重要点,否则通过这行日志,知道了大致的问题,但是需要进一步排查时,却不知道具体的信息,这也会让排查问题的同学非常无奈。比如对于日志调用接口报错,参数是什么?返回结果是什么?错误码是什么?等等这些信息,都需要记录在日志中。

对于高 DAU 和高并发的系统,还有一个比较好的打日志实践是将允许丢失的日志设置为异步打印,异步打印日志不会阻塞同步请求,有助于进一步保障同步请求的 RT 和健壮。

关于日志的高阶话题

除了定位问题,日志还可以用来分析接口性能,接口健康度监控以及配置整体的大盘监控。对于大规模的服务化的系统,性能和健康度是非常重要的,线上难免会出现各种各样的问题,比如某个应用的容量规划不足,由于流量突增导致上游的系统出现调用问题,完善的监控可以很快的缩小问题的反问并快速定位问题。 另一方面,对于大规模的系统,每次排查问题,都在机器上查找和检索(tail,grep 等)日志肯定不现实了,所以就需要一套日志采集分析的工具,目前业界用的比较多的就是 ELK Stack(链接:https://www.elastic.co/cn/what-is/elk-stack)。 更高阶的日志使用就是全链路跟踪了,感兴趣可以去看看 Google 的 Dapper,阿里的 EagleEye 等比较成熟的全链路跟踪系统介绍。

总结

本文首先简单介绍了日志的作用,然后从排查问题的角度分析了日常开发过程中应该如何打印日志,最后介绍了日志的高阶用法。关于日志,还有很多内容没有覆盖到,比如日志文件的拆分,压缩,滚动删除等等,这些在日常使用过程中基本不会出大的问题,感兴趣的可以去看看日志框架的文档。