今天读了一篇 Java 日志管理最佳实践深受启发,原先很多都只是模糊不清,停留在实践上的概念也豁然开朗了。

文章介绍的是基于 log4j 的,虽然我常用的是其 C++ 实现 log4cplus,但是其中的思想还是值得借鉴学习的。下面摘录一些关键内容备忘:

1. MDC功能

这个功能在 log4cplus 是没有的,MDC(Mapped Diagnostic Context,映射调试上下文)是 log4j 和 logback 提供的一种方便在多线程条件下记录日志的功能。MDC 可以看成是一个与当前线程绑定的哈希表,可以往其中添加键值对。MDC 中包含的内容可以被同一线程中执行的代码所访问。当前线程的子线程会继承其父线程中的 MDC 的内容。当需要记录日志时,只需要从 MDC 中获取所需的信息即可。MDC 的内容则由程序在适当的时候保存进去。对于一个 Web 应用来说,通常是在请求被处理的最开始保存这些数据。

public class MdcSample { 
    private static final Logger LOGGER = Logger.getLogger("mdc"); 
    public void log() { 
    MDC.put("username", "Alex"); 
    if (LOGGER.isInfoEnabled()) { 
            LOGGER.info("This is a message."); 
        } 
    } 
}

使用MDC中的数据。

log4j.appender.stdout.layout.ConversionPattern=%X{username} %d{yyyy-MM-dd HH:mm:ss} [%p] %c - %m%n

这样一来,username 就只有在最开始接受请求的类需要知道,并设置 MDC。对于之后的类都不是必须知道的。这样做有效的防止了由于 Log 对原有类结构的破坏,同时使整个请求的线程中输出的 Log 内都包含有了 username 这个信息。

2. 准则-检查日志是否可以被记录

日志消息中通常包含与当前上下文相关的信息。为了获取这些信息并构造相应的消息文本,不可避免会产生额外的开销。尤其对于 DEBUG 和 TRACE 级别的日志消息来说,它们所出现的频率很高,累加起来的开销比较大。因此在记录 INFO、DEBUG 和 TRACE 级别的日志时,首先进行相应的检查是一个好的实践。而 WARN 及其以上级别的日志则一般不需要进行检查。

3. 准则-日志中包含充分的信息

日志中所包含的信息应该是充分的。在记录日志消息时应该尽可能多的包含当前上下文中的各种信息,以方便在遇到问题时可以快速的获取到所需的信息。应该尽可能在一条日志记录中包含足够多的信息。

4. 准则-使用合适的日志记录器名称

一般的日志记录实践是使用当前 Java 类的全名作为其使用的日志记录器的名称。这样做可以得到一个与 Java 类和包的层次结构相对应的日志记录器的层次结构。可以很方便的按照不同的模块来设置相应的日志记录级别。不过对于某些全局的或是横切的功能,如安全和性能等,则推荐使用功能相关的名称。比如程序中可能包含用来提供性能剖析信息的日志记录。对于这样的日志记录,应该使用同一名称的日志记录器,如类似 “performance” 或 “performance.web”。这样当需要启用和禁用性能剖析时,只需要配置这些名称的记录器即可。

5. 准则-使用半结构化的日志消息

日志记录中除了基本的日志消息之外,还包括由日志框架提供的其他元数据。这些数据按照给定的格式出现在日志记录中。这些半结构化的格式使得可以通过工具提取日志记录中的相关信息进行分析。在使用日志 API 进行记录时,对于日志消息本身,也推荐使用半结构化的方式来组织。 比如一个电子商务的网站,当用户登录之后,该用户所产生的不同操作所对应的日志记录中都可以包含该用户的用户名,并以固定的格式出现在日志记录中:

[user1] 用户登录成功。
[user1] 用户成功购买产品 A。
[user2] 订单 003 付款失败。

当需要通过日志记录来排查某个用户所遇到的问题时,只需要通过正则表达就可以很快地查询到用户相关的日志记录。

6. 准则-日志聚合与分析

日志聚合的作用就在于可以把来自不同服务器上不同应用程序产生的日志聚合起来,存放在单一的服务器上,方便进行搜索和分析。在日志聚合方面,已经有不少成熟的开源软件可以很好的满足需求。