关于日志那些事
对于现在的应用程序来说,日志的重要性是不言而喻的。很难想象没有任何日志记录功能的应用程序运行在生产环境中。日志所能提供的功能是多种多样的,包括记录程序运行时产生的错误信息、状态信息、调试信息和执行时间信息等。在生产环境中,日志是查找问题来源的重要依据。本文从日志的定义、作用、分类、级别等角度介绍日志。
什么是日志
日志是用来保存系统发生的事件信息和各种对象执行的操作信息的管理对象,日志记录存放于其中。
在计算机领域,日志文件(logfile)是一个记录了发生在运行中的操作系统或其他软件中的事件的文件,或者记录了在网络聊天软件的用户之间发送的消息。 日志记录(Logging)是指保存日志的行为。 最简单的做法是将日志写入单个存放日志的文件。
为什么要使用日志
由于日志通常不属于系统的核心功能,所以常常不被团队成员所重视。对于一些简单的小程序,可能并不需要在如何记录日志的问题上花费太多精力。但是对于作为基础平台为很多产品提供服务的后端程序,就必须要考虑如何依靠良好的日志来保证系统可靠的运行了。系统上线运行时出现bug,通常不能够终止程序进行Debug,只能通过日志来定位查找问题。
还原现场,追溯问题
在系统出现问题或是为了检查系统是否出问题时,我们常常会去查看日志,此种日志多是human-readable 的。可以是格式化的日志,也常见非格式化的日志。例如:Linux系统日志
监视、统计、取证等的分析
用于这种用途的日志很多,例如访问日志、审计日志等。为了方便系统处理分析,多是格式化的数据。例如:服务器安全日志,IIS日志
数据传递
日志也经常用作系统间的信息传递,一个系统的日志可以是另一个系统的输入。譬如,MySQL 的主从间的数据传递就是借助Binlog 的。由于多是系统间的内部信息传递,二进制格式(协议)比较多。btw,由于事务日志,很多数据库本身就是一个日志系统 (“THE LOG IS THE DATABASE”)。
数据备份及恢复
日志也有用于数据的备份的。数据库系统就常有Write-ahead Logging 等事务日志(如Innodb Log)来恢复状态和数据。MySQL 也有做Binlog 备份及恢复的。
好的日志可以帮助系统的开发和运维人员:
- 了解线上系统的运行状态
- 快速准确定位线上问题
- 发现系统瓶颈
- 预警系统潜在风险
- 挖掘产品最大价值
- ……
不好的日志导致:
- 对系统的运行状态一知半解,甚至一无所知
- 系统出现问题无法定位,或者需要花费巨大的时间和精力
- 无法发现系统瓶颈,不知优化从何做起
- 无法基于日志对系统运行过程中的错误和潜在风险进行监控和报警
- 对挖掘用户行为和提升产品价值毫无帮助
- ……
日志的分类
日志从功能来说,可分为诊断日志、统计日志、审计日志。
诊断日志, 典型的有:
- 请求入口和出口
- 外部服务调用和返回
- 资源消耗操作: 如读写文件等
- 容错行为: 如云硬盘的副本修复操作
- 程序异常: 如数据库无法连接
- 后台操作:定期执行删除的线程
- 启动、关闭、配置加载
统计日志:
- 用户访问统计:用户IP、上传下载的数据量,请求耗时等
- 计费日志(如记录用户使用的网络资源或磁盘占用,格式较为严格,便于统计)
审计日志:
- 管理操作
对于简单的系统,可以将所有的日志输出到同一个日志文件中,并通过不同的关键字进行区分。而对于复杂的系统,将不同需求的日志输出到不同的日志文件中是必要的,通过对不同类型的文件采用不同的日志格式(例如对于计费日志可以直接输出为Json格式),可以方便接入其他的子系统。
日志的级别
对于日志级别的分类,有以下参考:
FATAL — 表示需要立即被处理的系统级错误。当该错误发生时,表示服务已经出现了某种程度的不可用,系统管理员需要立即介入。这属于最严重的日志级别,因此该日志级别必须慎用,如果这种级别的日志经常出现,则该日志也失去了意义。通常情况下,一个进程的生命周期中应该只记录一次FATAL级别的日志,即该进程遇到无法恢复的错误而退出时。当然,如果某个系统的子系统遇到了不可恢复的错误,那该子系统的调用方也可以记入FATAL级别日志,以便通过日志报警提醒系统管理员修复;
ERROR — 该级别的错误也需要马上被处理,但是紧急程度要低于FATAL级别。当ERROR错误发生时,已经影响了用户的正常访问。从该意义上来说,实际上ERROR错误和FATAL错误对用户的影响是相当的。FATAL相当于服务已经挂了,而ERROR相当于好死不如赖活着,然而活着却无法提供正常的服务,只能不断地打印ERROR日志。特别需要注意的是,ERROR和FATAL都属于服务器自己的异常,是需要马上得到人工介入并处理的。而对于用户自己操作不当,如请求参数错误等等,是绝对不应该记为ERROR日志的;
WARN — 该日志表示系统可能出现问题,也可能没有,这种情况如网络的波动等。对于那些目前还不是错误,然而不及时处理也会变为错误的情况,也可以记为WARN日志,例如一个存储系统的磁盘使用量超过阀值,或者系统中某个用户的存储配额快用完等等。对于WARN级别的日志,虽然不需要系统管理员马上处理,也是需要及时查看并处理的。因此此种级别的日志也不应太多,能不打WARN级别的日志,就尽量不要打;
INFO — 该种日志记录系统的正常运行状态,例如某个子系统的初始化,某个请求的成功执行等等。通过查看INFO级别的日志,可以很快地对系统中出现的 WARN,ERROR,FATAL错误进行定位。INFO日志不宜过多,通常情况下,INFO级别的日志应该不大于TRACE日志的10%;
DEBUG or TRACE — 这两种日志具体的规范应该由项目组自己定义,该级别日志的主要作用是对系统每一步的运行状态进行精确的记录。通过该种日志,可以查看某一个操作每一步的执 行过程,可以准确定位是何种操作,何种参数,何种顺序导致了某种错误的发生。可以保证在不重现错误的情况下,也可以通过DEBUG(或TRACE)级别的日志对问题进行诊断。需要注意的是,DEBUG日志也需要规范日志格式,应该保证除了记录日志的开发人员自己外,其他的如运维,测试人员等也可以通过 DEBUG(或TRACE)日志来定位问题
日志记录哪些信息
理想的日志中应该记录不多不少的信息。
所谓不多,是指不要在日志中记录无用的信息。实践中常见到的无用的日志有:
1)能够放在一条日志里的东西,放在多条日志中输出;
2)预期会发生且能够被正常处理的异常,打印出一堆无用的堆栈;
3)开发人员在开发过程中为了调试方便而加入的“临时”日志
所谓不少,是指对于日志的使用者,能够从日志中得到所有需要的信息。在实践中经常发生日志不够的情况,例如:
1)请求出错时不能通过日志直接来定位问题,而需要开发人员再临时增加日志并要求请求的发送者重新发送同样的请求才能定位问题;
2)无法确定服务中的后台任务是否按照期望执行;
3)无法确定服务的内存数据结构的状态;
4)无法确定服务的异常处理逻辑(如重试)是否正确执行;
5)无法确定服务启动时配置是否正确加载;
通常情况下会遗漏的日志:
- 系统的配置参数:系统在启动过程中通常会首先读启动参数,可以在系统启动后将这些参数输出到日志中,方便确认系统是按照期望的参数启动的;
- 后台定期执行的任务:如定期更新缓存的任务,可以记录任务开始时间,任务结束时间,更新了多少条缓存配置等等,这样可以掌握定期执行的任务的状态;
- 异常处理逻辑:如对于分布式存储系统来说,当系统在一个存储节点上读数据失败时,需要去另一个数据节点上进行重试,可以将读数据失败这件事情记录下来,之后可以通过对日志的分析确认是否某些节点的磁盘可能存在故障。再比如,如果系统需要请求一个外部资源,可以将请求这个外部资源偶尔失败又重试成功这件事情记录下来
- 日志中需要记录关键参数,出错时的关键原因等。
如何记录和收集日志
应用程序运行时的产生的各种信息,都应该通过日志 API 来进行记录。很多开发人员习惯于使用 System.out.println、System.err.println 以及异常对象的 printStrackTrace 方法来输出相关信息。这些使用方式虽然简便,但是所产生的信息在出现问题时并不能提供有效的帮助。这些使用方式都应该改为使用日志 API。使用日志 API 并没有增加很多复杂度,但是所提供的好处是显著的。
输出日志需要使用一个或者多个日志框架,这些框架提供了必要的对象、方法和配置来传输消息。Java在java.util.logging包中提供了一个默认的框架。除此之外,还有很多其它第三方框架,包括Log4j、Logback以及tinylog。还有其它一些开发包,例如SLF4J和Apache Commons Logging,它们提供了一些抽象层,对你的代码和日志框架进行解耦,从而允许你在不同的日志框架中进行切换。Java日志框架可以参考文章[4]。
常用的收集日志框架有kafka、flume、logstash等。
日志管理
日志输出到不同的文件
在性能测试时遇到的另一个问题是,当并发量很大时,可能会有一些请求处理失败(如0.5%),为了对这些错误进行分析,需要去查这些错误请求的日志。而由于这种情况下日志量巨大,使得对错误日志的分析变得困难。
这种情况下可以将所有的错误日志同时输出到一个单独的文件之中。
日志文件的大小
日志文件不宜过大,过大的日志文件对于日志监控,问题定位等都会带来不便。因此需要进行日志文件的切分,日志文件应该按天来分割,还是按照小时来分割,应该根据日志量来决定,原则就是方便开发或运维人员能快速查找日志。
为了防止日志文件将整个磁盘空间占满,需要定期对日志文件进行删除。例如,在收到磁盘报警时,可以将两个月以前的日志文件删除。此处比较好的实践是:
- 将所有日志文件收集起来,这样即使在记录日志的机器上删除,也可以通过收集的日志对之前的问题进行定位;
- 每天通过定时任务来删除过期日志,如每天在凌晨4点删除60天前的日志
参考:
[1] https://zh.wikipedia.org/wiki/日志文件
[3] https://zhuanlan.zhihu.com/p/27363484
[4] https://www.cnblogs.com/chanshuyi/p/something_about_java_log_framework.html