什么是日志记录?

从高层次上讲,日志记录用于捕获应用程序运行期间的信息。这可能包括有关应用程序状态和应用程序输入的不同类型的信息。例如,HTTP 服务器可能会记录有关传入连接的信息(例如 IP 地址)。
日志记录也可以具有不同的详细程度,以便应用程序用户不会被过多的信息淹没。

日志记录可以大致分为两类。日志记录的两大类别是调试日志记录和审计日志记录。调试日志记录通常对应用程序开发人员有用,他们可以打开最终用户不关心的消息 - 例如,如果数据正在被正确解析,或者如果抛出了异常。审计日志记录对系统管理员更有意义。此类数据将包括诸如某人何时登录、谁更改了某条信息等信息。调试日志记录和审计日志记录并不相互排斥,这两类之间可能存在大量重叠。

日志框架应该做什么?

至少,日志框架需要能够执行以下操作

但是,仅执行这些操作的日志框架并不特别有用,并且不比使用 System.out.println()printf() 好。日志框架的常见功能通常包括

这不是所有可能存在的功能的详尽列表。存在许多不同的日志框架,它们可能实现这些功能的不同子集,并且可能还具有此处列出的更多功能。

日志级别和子系统分类

在记录信息时,最重要的是了解日志消息的级别和日志消息所属的子系统。
这使您可以确定某件事的严重程度以及它的来源。

传统的日志级别如下(从最详细到最不详细):TRACE DEBUG INFO WARN ERROR

此外,可能还存在 CRITICAL 和 FATAL 级别。通常,FATAL 表示应用程序即将退出,而 CRITICAL 与 ERROR 相似。

级别使快速过滤消息变得容易。由于更详细的消息仍然允许打印更不详细的消息,因此通过提高详细程度,您不会丢失信息。当您处理特定子系统时,您可能希望该子系统处于 DEBUG 级别以查看所有消息,或者如果需要查看某些内容,则处于 TRACE 级别。作为一般经验法则,当您的应用程序处于生产环境中时,所有记录器的默认级别应为 INFO 级别。

子系统也有助于对数据进行分类。在 Java 中,这通常由类的包完成。能够将您正在调试的特定子系统设置为 DEBUG 级别(而不仅仅是一个类)是能够轻松过滤信息并且不会使调试应用程序的程序员过载的重要部分。

在 Apache 日志服务项目(log4j2、log4cxx 和 log4net)中,此子系统分类遵循 Java 层次结构方法。如果您有一个记录器 org.apache.Foo 和 org.apache.Bar,您可以将 org.apache 的级别设置为 DEBUG,这两个记录器都将处于 DEBUG 级别,但也可以根据需要单独设置。这种层次化的日志记录方式并非所有日志框架都具备的功能,但是如果您的代码以所有类都具有单独的记录器并按逻辑适当地划分为子系统的方式组织,那么它非常有用。这也允许您更精细地控制调试语句。

日志应该放在哪里?

将日志记录到不同的位置(有时称为接收器)是日志框架的重要组成部分。对于简单的应用程序,您可能不需要执行比将数据发送到标准输出和/或文件更复杂的操作。但是,更复杂的应用程序很快就会需要在如何以及在何处记录信息方面具有更大的灵活性。除了本地日志机制(如 syslog)之外,现代应用程序还可以记录到基于 Web 的日志聚合器。

通过配置文件等外部源配置这些日志位置,还可以使最终用户能够为其特定用例配置系统。

透明日志记录

日志记录最基本原则之一是它必须对您的应用程序完全透明。您的应用程序必须不关心日志记录是否启用:它所做的只是将消息发送到日志框架,然后日志框架处理所有必要的过滤和路由,以将消息发送到其最终目的地。这也使您可以编写完全不关心日志记录的单元测试。如果日志系统未初始化,最糟糕的情况是不会记录任何内容。

如何进行正确的日志记录

现在我们了解了日志库的正常功能,是时候谈谈如何进行正确的日志记录了。正如我们之前提到的,在日志记录时需要关注的两件事是消息的级别和日志消息的分类。对于日志消息的分类,这通常是执行日志记录的类的名称。假设您已经拥有良好的设计,那么分类将清楚地源自类的名称以及类所在的包/命名空间。

选择日志消息的级别可能有点棘手。一个好的经验法则是将日志消息在正常运行期间保留在 INFO 级别,那么我们应该在该级别上放置什么以及什么应该更详细?假设我们正在打开一个用于远程通信的网络套接字。在 INFO 级别,我们可能会通知网络子系统已启动并准备就绪。DEBUG 信息可能包括来自客户端的新连接及其 IP/端口组合,而 TRACE 信息可能在每次从客户端收到数据包时输出。此外,如果我们无法打开套接字进行读写,我们可能会收到 ERROR 消息。

通过将数据流的不同部分置于不同的日志级别,可以轻松快速地查看日志并确定问题的严重程度以及系统在运行期间的操作。

日志记录时需要注意的其他事项

正如我们所见,拥有良好的日志框架对于能够找到和修复应用程序中的错误至关重要。但是,仅仅因为我们有日志记录并不意味着它是万无一失的。由于您插入应用程序的日志记录代码仍然是您编写的代码,因此您获得的日志消息始终有可能由于日志语句中的错误而对您撒谎。

Apache 日志服务项目具有的功能

虽然存在许多不同的日志框架用于 Java、C++ 和 .NET,但 Log4j2、Log4cxx 和 Log4net 有一些内置功能通常很有用。

层次化日志记录

如前所述,层次化日志记录允许轻松地对子系统信息进行分组。这使您可以对正在记录的信息进行细粒度控制,并轻松地允许每个类使用一个记录器。

这种层次化的日志记录还意味着您不需要显式地在记录器之间共享接收器。虽然可以将 Log4j2 和 Log4cxx 配置为将每个记录器发送到单独的位置,但父子关系意味着子级将将其消息记录到其父级拥有的任何接收器。

位置信息

准确地了解日志语句的位置对于跟踪日志语句的位置至关重要。Log4j2 和 Log4cxx 都能够确定日志消息来自的源文件名、类名、方法名和行号。使用 Log4cxx,这可以在编译时完成。

静态和单实例记录器

使用 Log4j2 和 Log4cxx 创建的所有记录器都可以声明为源文件中的静态变量,从而无需为每个类使用专用的记录器实例变量。这样做的一个副作用是存在一个全局记录器注册表,因此您可以通过不同类中的静态 LogManager 访问同一个记录器,而无需传递记录器。

嵌套和映射诊断上下文

当您具有多个相同类型的对象时,当您需要仅查看类的一个实例的数据时,使用静态记录器可能会出现问题。通过提供嵌套诊断上下文和映射诊断上下文,可以将上下文信息添加到日志语句中。

灵活的日志消息格式

当将日志消息发送到纯文本文件时,通常希望消息以易于理解的格式呈现。这些信息可以包括日志消息的日期/时间、消息级别、生成消息的记录器以及消息本身。使用 Log4j2 和 Log4cxx 的 PatternLayout 类,可以快速配置日志系统以方便的格式输出信息。

当需要将消息发送到不同位置时,不同类型的日志消息格式也很重要。创建日志消息的代码不应该了解消息在后端如何格式化。这允许相同的日志消息以不同的方式格式化,并可能包含不同的元数据 - 例如,用于日志聚合服务的 JSON 格式,或用于本地配置文件的纯文本格式。

通过配置文件进行配置

Log4j2 和 Log4cxx 都可以通过配置文件进行配置,配置文件可以采用多种不同的格式。虽然始终可以通过代码进行配置,但配置文件可以轻松地重新配置系统,而无需重新编译代码。这也意味着可以编辑配置文件并在运行时动态更改配置。例如,如果正在调试一个实时系统,可以打开之前关闭的记录器。

无垃圾(Log4j2)

在 Java 中,减少垃圾收集器运行时间非常重要。通过不分配和释放对象,日志语句将对应用程序的运行时开销几乎没有影响。

使用替换进行格式化

在创建日志语句时,执行字符串连接以创建日志语句通常很慢且令人讨厌。在 Java 中,最简单的字符串连接方式是使用 + 运算符。

log("Logging information: " + someVariable + " and the other variable is " + otherVariable);

而在 C++ 中,可能会遇到尖括号重载问题。

log("Logging information: " << someVariable << " and the other variable is " << otherVariable);

Log4j2 和 Log4cxx 都允许在字符串中进行变量替换,这样上面的日志语句都可以写成类似以下形式:

log("Logging information: {} and the other variable is {}", someVariable, otherVariable);

过滤

当需要理解许多不同的日志消息时,可能难以关注正确的信息。因此,可以配置 Log4j2 和 Log4cxx 来根据各种属性过滤某些消息,例如日志消息中包含的文本字符串。由于这些过滤器也可以通过配置文件进行配置,因此添加或删除过滤器是一个简单的操作。

结论

日志记录是一个相当复杂的主题,涵盖了许多不同的方面。虽然不同的应用程序对日志语句有不同的要求,但 Apache Logging Services 委员会的项目为各种应用程序提供了通用的、广泛适用的日志记录实现。