引言

在Web开发中,日志主要是用来做:

  • 问题分析和研究
  • 监控系统活动
  • 监控系统状态变化

Web应用里的所有日志必须是一致的、简洁的,有丰富的上下文且有相应的标识。

常见的日志架构标准和准则:

  • 使用日志框架或集于日志框架的抽象,不要用STDOUT或者STDERR。
    • 包括e.printStackTrace()这样的代码,它会输出到标准错误流中。
  • 使用一致的格式
  • 简明扼要地使用上下文使用和意图信息足够清晰之间平衡
  • 输出到生产环境的日志必须对潜在的看到日志输出的受众有用
  • 正确且一致地使用日志级别
  • 在INFO或更高级别上的日志输出对其他人必须有用。他们可以从日志得到什么信息?或者他们根据日志需要采取什么行为?
  • 在任何日志级别都不能输出敏感个人用户数据。
  • 不要对异常记录日志然后重新抛出去。抛出一个异常(或者包装一下原始异常然后抛出去)已经足够了。抓住异常且不再抛出去的代码需要做日志。

一致性

日志的一致性可以应用在所有方面,这里所说的一致性主要说的是格式。在一个应用里,日志输出的格式应该是一致的,理想情况下应该跨应用套件和服务/组件套件。这可以使得索引更加容易,也帮助人眼和大脑扫描模式和寻找信息。
日志输出的格式应该包括:日期/时间、日志级别、日志上下文、信息和关联数据(在有用的地方包含stacktraces)
不同的日志类型可能会要求不同的格式。比如应用日志、访问日志、垃圾回收日志、类加载器日志等。

简洁

日志输出不便宜。它需要消耗时间和资源去创建、格式化、流式化、存储、索引、处理。尽管现代日志框架非常擅于从同步的应用处理过程中消除这些消耗,但是这些成本还是存在的。(比如Log4J异步日志功能可以「节省」性能开销,但是如果在刷新日志缓冲区之间进程被杀死或者挂掉,那么日志数据可能丢失)
日志必须简洁明了,输出的内容不应该超过达到日志目的所需的内容。这是一个公认的棘手的平衡,因为日志天生就是用来传达将来可能有用的信息。这里有一些准则:

  • 不要对同样的东西记录多次日志。这似乎很明显,但是通常有人记录异常后将其再次抛出去,然后被记录调用栈。
  • 不要在INFO级别记录trace或debug的数据。
  • 不要为了针对一个可能的错误保护自己而记录日志为INFO级别。相反的是,你应该抓住这个错误案例并在那个时候把全部上下文记录下来。

有用

主要是针对日志级别。

日志级别 描述 用途
TRACE 代码执行的细节,为了帮助你和其他人更好地理解JVM在做什么、代码执行某项步骤耗时等。受众仅仅是开发者——大多数情况下在生产环境从来没打开过 方法调用、计时/microbenchmarking、代码执行waypoints
DEBUG 代码执行实现层面的细节,为了帮助你和他人理解代码如何处理一些输入以及产生了什么输出。受众是开发者,也可能是售后支持。大多数情况下不会在生产环境打开,一些异常情况下可能会临时打开 复杂逻辑流里的详细过程;跟踪变量值的变化;对方法计时;方法的输入输出等
INFO 高维度的“当前发生什么”信息。在任何时候都可以了解此级别的日志从而知道系统中正在发生哪些活动和重要的状态变化。受众包括运维/系统管理员 配置、缓存或其他重要状态设置/变化、系统活动(计时器、任务/异步线程生命周期、用户请求)等
WARN 不是最终用户会看到的错误的活动,但是对于运维来说足够严重。为避免警告可能会被滥用,考虑这个条件:你希望别人看到警告后做什么?这个对他们来说有帮助吗?能否修复?能否提供数据以反馈给工程部门来改善系统。不要做警告不了任何人的警告日志 使用了过期API、API使用不善、特殊参数(但不会引起错误)、从DB查找得到非预期的结果、其他非预期的运行时情景,但不是必须要警告
ERROR 系统级别的异常和错误情况——预期的或者非预期的。用户可以改正的业务逻辑错误不属于系统日志。理想情况下系统日志里的错误是应该能吸引运维、QA或工程师关注去改正的活动。系统的稳定状态永远不会产生错误 系统异常

上下文丰富

日志输出需要充足的上下文数据来让它有意义。简单的一个错误信息“NullPointer Exception”并没有告诉我们哪个对象是null。
日志输出应该保护充足的上下文信息来帮助人去理解日志输出时的状态。比如,一个错误信息可能包含方法执行时的参数。(注意不能包含个人敏感信息或者安全敏感的数据)
当你重新抛异常的时候,记得不要丢了原始的异常调用栈。

上下文识别

日志分析需要关联日志输出到上下文,这样能够帮助诊断和分析。例如大多数日志输出都会用时间戳去关联日志到时间上下文。另一方面,人们可能希望通过业务流程或用户获得甚至长期存在的用户会话来关联日志条目。
可以用做日志上下文识别的字段:

  • 时间戳
  • 日志级别
  • 日志类别(通常是类名)
  • 线程名字
  • 活动ID(如HTTP request、后台作业、异步任务、事件)
  • 用户ID
  • 用户session ID
  • 系统correlation ID

大多数现代日志框架提供了嵌套诊断上下文(NDC)和映射诊断上下文(MDC)。
系统correlation ID是系统在一个活动开始的时候生成的唯一的识别符,并在系统中作为该活动的一部分进行的所有通信中一直带着它。