拓展阅读

Log4j2 系统学习

Logback 系统学习

Slf4j

Slf4j-02-slf4j 与 logback 整合

SLF4j MDC-日志添加唯一标识

分布式链路追踪-05-mdc 等信息如何跨线程? Log4j2 与 logback 的实现方式

日志开源组件(一)java 注解结合 spring aop 实现自动输出日志

日志开源组件(二)注解结合 spring aop 实现日志traceId唯一标识

日志开源组件(三)java 注解结合 spring aop 自动输出日志新增拦截器与过滤器

日志开源组件(四)如何动态修改 spring aop 切面信息?让自动日志输出框架更好用

日志开源组件(五)如何将 dubbo filter 拦截器原理运用到日志拦截器中?

日志开源组件(六)Adaptive Sampling 自适应采样

阅读更多

介绍

几乎每个大型应用程序都包含自己的日志记录或跟踪 API。

根据这一规则,E.U. SEMPER 项目决定编写自己的跟踪 API。

那是在 1996 年初。经过无数次的改进、多次改进和大量工作,API 已经发展成为 log4j,一个流行的 Java 日志记录包。

该软件包根据 Apache 软件许可证分发,这是一个由开源计划认证的成熟的开源许可证。

最新的 log4j 版本,包括完整的源代码、类文件和文档,可以在 https://logging.apache.org/log4j/2.x/index.html 找到。

将日志语句插入代码是一种低技术含量的调试方法。 这也可能是唯一的方法,因为调试器并不总是可用或适用。

这通常是多线程应用程序和大型分布式应用程序的情况。

经验表明,日志记录是开发周期的重要组成部分。 它具有几个优点。 它提供有关应用程序运行的精确上下文。

一旦插入到代码中,日志输出的生成就不需要人为干预。

此外,日志输出可以保存在持久性介质中以供日后研究。 除了在开发周期中使用之外,足够丰富的日志记录包也可以被视为审计工具。

正如 Brian W. Kernighan 和 Rob Pike 在他们真正优秀的著作《编程实践》中所说的那样:

作为个人选择,除了获取堆栈跟踪或一两个变量的值之外,我们倾向于不使用调试器。 一个原因是很容易迷失在复杂的数据结构和控制流的细节中; 我们发现单步执行一个程序比更努力地思考并在关键位置添加输出语句和自检代码效率要低。 单击语句比扫描精心放置的显示器的输出要花费更长的时间。 与单步执行到代码的关键部分相比,决定在哪里放置 print 语句花费的时间更少,即使假设我们知道关键部分在哪里。 更重要的是,调试语句留在程序中; 调试会话是短暂的。

日志记录确实有其缺点。 它可以减慢应用程序。

如果过于冗长,可能会导致滚动失明。

为了减轻这些担忧,log4j 被设计为可靠、快速和可扩展的。 由于日志记录很少是应用程序的主要焦点,因此 log4j API 力求易于理解和使用。

Log4j 2

Log4j 1.x 已在许多应用程序中得到广泛采用和使用。

然而,多年来它的发展已经放缓。

由于需要与非常旧的 Java 版本兼容,它变得更加难以维护,并于 2015 年 8 月终止使用。

它的替代品 SLF4J/Logback 对框架进行了许多必要的改进。

那么为什么要使用 Log4j 2 呢? 以下是一些原因。

  • Log4j 2 旨在用作审计日志记录框架。 Log4j 1.x 和 Logback 在重新配置时都会丢失事件。 Log4j 2 不会。 在 Logback 中,应用程序永远不会看到 Appender 中的异常。 在 Log4j 2 Appenders 中,可以配置为允许异常渗透到应用程序。

  • Log4j 2 包含基于 LMAX Disruptor 库的下一代异步记录器。 在多线程场景中,Asynchronous Loggers 的吞吐量比 Log4j 1.x 和 Logback 高 10 倍,延迟低几个数量级。

  • Log4j 2 对于独立应用程序来说是无垃圾的,在稳态日志记录期间对于 Web 应用程序来说是低垃圾。 这减少了垃圾收集器的压力并且可以提供更好的响应时间性能。

  • Log4j 2 使用插件系统,通过添加新的 Appender、过滤器、布局、查找和模式转换器,无需对 Log4j 进行任何更改,就可以非常轻松地扩展框架。

  • 由于插件系统配置更简单。 配置中的条目不需要指定类名。

  • 支持自定义日志级别。 可以在代码或配置中定义自定义日志级别。

  • 支持 lambda 表达式。 仅当启用请求的日志级别时,在 Java 8 上运行的客户端代码才可以使用 lambda 表达式延迟构造日志消息。 不需要显式级别检查,从而产生更清晰的代码。

  • 支持消息对象。 消息允许支持有趣和复杂的构造通过日志系统传递并被有效地操作。 用户可以自由创建自己的消息类型并编写自定义布局、过滤器和查找来操作它们。

  • Log4j 1.x 支持 Appender 上的过滤器。 Logback 添加了 TurboFilters 以允许在事件被 Logger 处理之前对其进行过滤。 Log4j 2 支持可以配置为在事件由 Logger 处理之前处理事件的过滤器,因 为它们是由 Logger 或 Appender 处理的。

  • 许多 Logback Appender 不接受布局,只会以固定格式发送数据。 大多数 Log4j 2 Appenders 接受布局,允许以任何需要的格式传输数据。

  • Log4j 1.x 和 Logback 中的布局返回一个字符串。 这导致了在 Logback 编码器中讨论的问题。 Log4j 2 采用布局始终返回字节数组的更简单方法。 这样做的好处是,这意味着它们几乎可以在任何 - Appender 中使用,而不仅仅是写入 OutputStream 的 Appender。

  • Syslog Appender 支持 TCP 和 UDP,并支持 BSD syslog 和 RFC 5424 格式。

  • Log4j 2 利用 Java 5 并发支持并在可能的最低级别执行锁定。 Log4j 1.x 有已知的死锁问题。 其中许多已在 Logback 中修复,但许多 Logback 类仍然需要相当高级别的同步。

  • 它是一个 Apache 软件基金会项目,遵循所有 ASF 项目使用的社区和支持模型。 如果您想贡献或获得提交更改的权利,只需遵循贡献中概述的路径即可。

架构

Log4j 使用下图中显示的类。

主要组件

struct

使用 Log4j 2 API 的应用程序将从 LogManager 请求具有特定名称的 Logger。

LogManager 将找到合适的 LoggerContext,然后从中获取 Logger。 如果必须创建 Logger,它将与 LoggerConfig 相关联,该 LoggerConfig 包含 a) 与 Logger 相同的名称,b) 父包的名称,或 c) 根 LoggerConfig。 LoggerConfig 对象是根据配置中的 Logger 声明创建的。 LoggerConfig 与实际传递 LogEvents 的 Appenders 相关联。

记录器层次结构 Logger Hierarchy

与普通 System.out.println 相比,任何日志记录 API 的首要优势在于它能够禁用某些日志语句,同时允许其他日志语句不受阻碍地打印。

此功能假定日志记录空间,即所有可能的日志记录语句的空间,是根据某些开发人员选择的标准进行分类的。

在 Log4j 1.x 中,Logger 层次结构是通过 Logger 之间的关系来维护的。

在 Log4j 2 中,这种关系不再存在。 相反,层次结构是在 LoggerConfig 对象之间的关系中维护的。

Loggers 和 LoggerConfigs 是命名实体。

记录器名称区分大小写,并遵循分层命名规则:

  • 命名层次结构

如果一个 LoggerConfig 的名称后跟一个点是后代记录器名称的前缀,则称该 LoggerConfig 是另一个 LoggerConfig 的祖先。

如果 LoggerConfig 与后代 LoggerConfig 之间没有祖先,则称其为子 LoggerConfig 的父代。

例如,名为“com.foo”的 LoggerConfig 是名为“com.foo.Bar”的 LoggerConfig 的父级。

同样,“java”是“java.util”的父级和“java.util.Vector”的祖先。 大多数开发人员应该熟悉这种命名方案。

根 LoggerConfig 位于 LoggerConfig 层次结构的顶部。 它的特殊之处在于它始终存在并且是每个层次结构的一部分。 直接链接到根LoggerConfig的Logger可以通过如下方式获取:

Logger logger = LogManager.getLogger(LogManager.ROOT_LOGGER_NAME);

或者更加简单的:

Logger logger = LogManager.getRootLogger();

可以使用 LogManager.getLogger 静态方法通过传递所需记录器的名称来检索所有其他记录器。 可以在 Log4j 2 API 中找到有关日志记录 API 的更多信息。

日志上下文 LoggerContext

LoggerContext 充当日志系统的定位点。 但是,根据情况,应用程序中可能有多个活动的 LoggerContext。 有关 LoggerContext 的更多详细信息,请参阅日志分离部分。

配置 Configuration

每个 LoggerContext 都有一个活动的配置。 Configuration 包含所有 Appenders、context-wide Filters、LoggerConfigs 并包含对 StrSubstitutor 的引用。 在重新配置期间,将存在两个配置对象。 一旦所有记录器都被重定向到新配置,旧配置将被停止并丢弃。

记录仪 Logger

如前所述,Loggers 是通过调用 LogManager.getLogger 创建的。 Logger 本身不执行任何直接操作。 它只有一个名称并与 LoggerConfig 相关联。 它扩展了 AbstractLogger 并实现了所需的方法。 随着配置的修改,Loggers 可能会与不同的 LoggerConfig 相关联,从而导致其行为被修改。

检索记录器 Retrieving Loggers

使用相同名称调用 LogManager.getLogger 方法将始终返回对完全相同的 Logger 对象的引用。

例如,在

Logger x = LogManager.getLogger("wombat");
Logger y = LogManager.getLogger("wombat");

x 和 y 指的是完全相同的 Logger 对象。

log4j 环境的配置通常在应用程序初始化时完成。

首选方法是读取配置文件。 这在配置中讨论。

Log4j 使得通过软件组件命名记录器变得容易。

这可以通过在每个类中实例化一个记录器来实现,记录器名称等于类的完全限定名称。

这是定义记录器的一种有用且直接的方法。 由于日志输出带有生成 Logger 的名称,因此这种命名策略可以轻松识别日志消息的来源。

然而,这只是一种可能的命名记录器的策略,尽管很常见。 Log4j 不限制可能的记录器集。 开发人员可以根据需要自由命名记录器。

由于在其所属类之后命名 Loggers 是一种常见的习惯用法,因此提供了方便的方法 LogManager.getLogger() 以自动使用调用类的完全限定类名作为 Logger 名称。

尽管如此,以记录器所在的类命名记录器似乎是迄今为止已知的最佳策略。

记录器配置 LoggerConfig

LoggerConfig 对象是在日志记录配置中声明 Loggers 时创建的。

LoggerConfig 包含一组过滤器,这些过滤器必须允许 LogEvent 在传递给任何 Appender 之前通过。 它包含对应用于处理事件的 Appenders 集的引用。

日志级别

LoggerConfigs 将被分配一个日志级别。

内置级别集包括 ALL、TRACE、DEBUG、INFO、WARN、ERROR、FATAL 和 OFF。

Log4j 2 还支持自定义日志级别。 获得更多粒度的另一种机制是改用标记。

OFF 和 ALL 级别不适用于调用日志记录 API。

在配置中指定 OFF 意味着没有日志记录事件应该匹配,而指定 ALL 意味着所有事件都匹配,包括自定义事件。

但是,OFF 可用于在特殊情况下记录 API 调用,在这种情况下,无论配置如何,都应始终记录事件。

但是,通常建议改用具有相应全局标记过滤器的标记。

Log4j 1.x 和 Logback 都有“Level Inheritance”的概念。

在 Log4j 2 中,Loggers 和 LoggerConfigs 是两个不同的对象,因此这个概念的实现方式不同。

每个 Logger 引用适当的 LoggerConfig,后者又可以引用其父级,从而达到相同的效果。

下面是五个表,其中包含各种分配的级别值以及将与每个 Logger 关联的结果级别。

请注意,在所有这些情况下,如果未配置根 LoggerConfig,则会为其分配默认级别。

Logger Name Assigned LoggerConfig LoggerConfig Level Logger Level
root root DEBUG DEBUG
X root DEBUG DEBUG
X.Y root DEBUG DEBUG
X.Y.Z root DEBUG DEBUG

Filter 过滤器

除了如前一节所述发生的自动日志级别过滤之外,Log4j 还提供了可以在控制传递给任何 LoggerConfig 之前、在控制传递给 LoggerConfig 之后但在调用任何 Appender 之前、在控制传递之后应用的过滤器 到 LoggerConfig 但在调用特定 Appender 之前,以及在每个 Appender 上。 以与防火墙过滤器非常相似的方式,每个过滤器可以返回三种结果之一:接受、拒绝或中立。 Accept 的响应意味着不应调用其他过滤器并且事件应该进行。 Deny 的响应意味着应该立即忽略事件并将控制权返回给调用者。 Neutral 的响应表示应该将事件传递给其他过滤器。 如果没有其他过滤器,将处理该事件。

尽管过滤器可能会接受事件,但事件仍可能不会被记录。 当事件被 pre-LoggerConfig 过滤器接受但随后被 LoggerConfig 过滤器拒绝或被所有 Appender 拒绝时,可能会发生这种情况。

Appender 追加器

根据他们的记录器有选择地启用或禁用日志记录请求的能力只是图片的一部分。

Log4j 允许记录请求打印到多个目的地。

在 log4j 中,输出目的地称为 Appender。

目前,存在用于控制台、文件、远程套接字服务器、Apache Flume、JMS、远程 UNIX Syslog 守护程序和各种数据库 API 的附加程序。

有关可用的各种类型的更多详细信息,请参阅 Appenders 部分。 多个 Appender 可以附加到一个 Logger。

通过调用当前 Configuration 的 addLoggerAppender 方法,可以将 Appender 添加到 Logger。 如果与 Logger 名称匹配的 LoggerConfig 不存在,将创建一个,Appender 将附加到它,然后将通知所有 Loggers 更新其 LoggerConfig 引用。

给定记录器的每个启用的记录请求都将转发到该记录器的 LoggerConfig 中的所有附加程序以及 LoggerConfig 父项的附加程序。

换句话说,Appenders 是从 LoggerConfig 层次结构中附加继承的。 例如,如果将控制台附加程序添加到根记录器,则所有启用的日志记录请求至少会在控制台上打印。 如果另外将文件附加程序添加到 LoggerConfig,比如 C,那么为 C 和 C 的子级启用的日志记录请求将打印在文件中和控制台上。 可以通过在配置文件中的 Logger 声明上设置 additivity=”false” 来覆盖此默认行为,以便 Appender 累积不再累加。

管理 appender 可加性的规则总结如下。

Appender 可加性

Logger L 的日志语句的输出将转到与 L 关联的 LoggerConfig 中的所有 Appender 以及该 LoggerConfig 的祖先。 这就是术语“appender additivity”的含义。

但是,如果与 Logger L 关联的 LoggerConfig 的祖先,比如 P,将可加性标志设置为 false,那么 L 的输出将被定向到 L 的 LoggerConfig 中的所有附加程序,并且它的祖先直到并包括 P 但不是 P的任何祖先。

默认情况下,记录器将其可加性标志设置为 true。

layout 布局

通常情况下,用户不仅希望自定义输出目标,还希望自定义输出格式。

这是通过将 Layout 与 Appender 相关联来实现的。

Layout 负责根据用户的意愿格式化 LogEvent,而 appender 负责将格式化的输出发送到其目的地。

PatternLayout 是标准 log4j 发行版的一部分,它允许用户根据类似于 C 语言 printf 函数的转换模式指定输出格式。

例如,具有转换模式 %r [%t] %-5p %c - %m%n 的 PatternLayout 将输出类似于:

176 [main] INFO  org.foo.Bar - Located nearest gas station.

第一个字段是自程序启动以来经过的毫秒数。 第二个字段是发出日志请求的线程。 第三个字段是日志语句的级别。 第四个字段是与日志请求关联的记录器的名称。 ‘-‘ 之后的文本是语句的消息。

Log4j 为各种用例提供了许多不同的布局,例如 JSON、XML、HTML 和 Syslog(包括新的 RFC 5424 版本)。

其他附加程序(例如数据库连接器)填充指定的字段而不是特定的文本布局。

同样重要的是,log4j 将根据用户指定的标准呈现日志消息的内容。

例如,如果您经常需要记录 Oranges,这是您当前项目中使用的一种对象类型,那么您可以创建一个接受 Orange 实例的 OrangeMessage 并将其传递给 Log4j,这样当 Orange 对象可以格式化为适当的字节数组时 必需的。

StrSubstitutor 和 StrLookup

StrSubstitutor 类和 StrLookup 接口是从 Apache Commons Lang 借来的,然后进行了修改以支持评估 LogEvents。

此外,Interpolator 类是从 Apache Commons Configuration 借用的,以允许 StrSubstitutor 评估来自多个 StrLookup 的变量。

它也被修改为支持评估 LogEvents。

这些一起提供了一种机制,允许配置引用来自系统属性、配置文件、ThreadContext Map、LogEvent 中的 StructuredData 的变量。

如果组件能够处理变量,则可以在处理配置时或在处理每个事件时解析变量。

参考资料