架构

主要组件

Log4j 使用下图所示的类。

Log4j 2 Class Relationships

使用 Log4j 2 API 的应用程序将从 LogManager 请求具有特定名称的记录器。LogManager 将定位相应的 LoggerContext,然后从中获取记录器。如果必须创建记录器,它将与包含以下内容的 LoggerConfig 关联:a) 与记录器相同的名称,b) 父包的名称,或 c) 根 LoggerConfig。LoggerConfig 对象是从配置中的记录器声明创建的。LoggerConfig 与实际传递 LogEvent 的附加器相关联。

记录器层次结构

任何日志记录 API 相对于简单的 System.out.println 的首要优势在于它能够禁用某些日志语句,同时允许其他语句不受阻碍地打印。此功能假定日志空间,即所有可能的日志语句的空间,根据某些开发人员选择的标准进行分类。

在 Log4j 1.x 中,记录器层次结构通过记录器之间的关系来维护。在 Log4j 2 中,这种关系不再存在。相反,层次结构在 LoggerConfig 对象之间的关系中维护。

记录器和 LoggerConfig 是命名实体。记录器名称区分大小写,并遵循分层命名规则

命名层次结构
如果 LoggerConfig 的名称后跟一个点是后代记录器名称的前缀,则称该 LoggerConfig 是另一个 LoggerConfig 的祖先。如果 LoggerConfig 本身与其后代 LoggerConfig 之间没有祖先,则称该 LoggerConfig 是 LoggerConfig 的

例如,名为 "com.foo" 的 LoggerConfig 是名为 "com.foo.Bar" 的 LoggerConfig 的父级。类似地,"java""java.util" 的父级,也是 "java.util.Vector" 的祖先。这种命名方案应该为大多数开发人员所熟悉。

根 LoggerConfig 位于 LoggerConfig 层次结构的顶部。它很特殊,因为它始终存在并且是每个层次结构的一部分。可以直接链接到根 LoggerConfig 的记录器可以通过以下方式获取

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

或者,更简单地

Logger logger = LogManager.getRootLogger();

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

LoggerContext

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

配置

每个 LoggerContext 都有一个活动的 Configuration。Configuration 包含所有附加器、上下文范围的过滤器、LoggerConfig,并包含对 StrSubstitutor 的引用。在重新配置期间,将存在两个 Configuration 对象。一旦所有记录器都重定向到新的 Configuration,旧的 Configuration 将被停止并丢弃。

记录器

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

检索记录器

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

例如,在

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

xy 指的是完全相同的记录器对象。

log4j 环境的配置通常在应用程序初始化时完成。首选方法是通过读取配置文件。这将在 配置 中讨论。

Log4j 使得通过软件组件轻松命名记录器成为可能。这可以通过在每个类中实例化一个记录器来实现,记录器名称等于类的完全限定名称。这是一种定义记录器的有用且直接的方法。由于日志输出带有生成记录器的名称,因此这种命名策略使识别日志消息的来源变得容易。但是,这只是命名记录器的一种可能的,尽管很常见的策略。Log4j 不会限制可能的记录器集。开发人员可以自由地根据需要命名记录器。

由于根据其所属类命名记录器是一种非常常见的习惯用法,因此提供了便利方法 LogManager.getLogger() 来自动使用调用类的完全限定类名作为记录器名称。

尽管如此,根据记录器所在的类命名记录器似乎是迄今为止最好的策略。

LoggerConfig

LoggerConfig 对象是在日志记录配置中声明记录器时创建的。LoggerConfig 包含一组过滤器,这些过滤器必须允许 LogEvent 通过才能将其传递给任何附加器。它包含对应该用于处理事件的附加器集的引用。

日志级别

LoggerConfig 将被分配一个日志 Level。内置级别集包括 ALL、TRACE、DEBUG、INFO、WARN、ERROR、FATAL 和 OFF。Log4j 2 还支持 自定义日志级别。另一种获得更多粒度的机制是使用 Markers。OFF 和 ALL 级别不打算用于对日志记录 API 的调用。在配置中指定 OFF 意味着不应匹配任何日志记录事件,而指定 ALL 意味着所有事件都匹配,包括自定义事件。但是,OFF 可以用于日志记录 API 调用,在特殊情况下,无论配置如何,事件都应始终被记录。但是,通常建议使用具有相应全局标记过滤器的标记。

Log4j 1.xLogback 都具有“级别继承”的概念。在 Log4j 2 中,记录器和 LoggerConfig 是两个不同的对象,因此此概念的实现方式不同。每个记录器都引用相应的 LoggerConfig,而 LoggerConfig 又可以引用其父级,从而实现相同的效果。

以下是五个表,其中包含各种分配的级别值以及与每个记录器关联的结果级别。请注意,在所有这些情况下,如果未配置根 LoggerConfig,则将为其分配默认级别。

示例 1
记录器名称 分配的 LoggerConfig LoggerConfig 级别 记录器级别
root root DEBUG DEBUG
X root DEBUG DEBUG
X.Y root DEBUG DEBUG
X.Y.Z root DEBUG DEBUG

在上面的示例 1 中,只配置了根记录器,并且它有一个日志级别。所有其他记录器都引用根 LoggerConfig 并使用它的级别。

示例 2
记录器名称 分配的 LoggerConfig LoggerConfig 级别 级别
root root DEBUG DEBUG
X X 错误 错误
X.Y X.Y 信息 信息
X.Y.Z X.Y.Z 警告 警告

在示例 2 中,所有记录器都具有配置的 LoggerConfig,并从它获取它们的级别。

示例 3
记录器名称 分配的 LoggerConfig LoggerConfig 级别 级别
root root DEBUG DEBUG
X X 错误 错误
X.Y X 错误 错误
X.Y.Z X.Y.Z 警告 警告

在示例 3 中,记录器rootXX.Y.Z 都有一个配置的 LoggerConfig,它们具有相同的名称。记录器 X.Y 没有配置的 LoggerConfig 与其名称匹配,因此使用 LoggerConfig X 的配置,因为该 LoggerConfig 的名称与记录器名称的开头最匹配。

示例 4
记录器名称 分配的 LoggerConfig LoggerConfig 级别 级别
root root DEBUG DEBUG
X X 错误 错误
X.Y X 错误 错误
X.Y.Z X 错误 错误

在示例 4 中,记录器 rootX 都有一个配置的 LoggerConfig,它们具有相同的名称。记录器 X.YX.Y.Z 没有配置的 LoggerConfig,因此从分配给它们的 LoggerConfig X 获取它们的级别,因为它是名称与记录器名称的开头最匹配的 LoggerConfig。

示例 5
记录器名称 分配的 LoggerConfig LoggerConfig 级别 级别
root root DEBUG DEBUG
X X 错误 错误
X.Y X.Y 信息 信息
X.YZ X 错误 错误

在示例 5 中,记录器rootXX.Y 都有一个配置的 LoggerConfig,它们具有相同的名称。记录器 X.YZ 没有配置的 LoggerConfig,因此从分配给它的 LoggerConfig X 获取它的级别,因为它是名称与记录器名称的开头最匹配的 LoggerConfig。它不与 LoggerConfig X.Y 关联,因为句点后的标记必须完全匹配。

示例 6
记录器名称 分配的 LoggerConfig LoggerConfig 级别 级别
root root DEBUG DEBUG
X X 错误 错误
X.Y X.Y 错误
X.Y.Z X.Y 错误

在示例 6 中,LoggerConfig X.Y 没有配置的级别,因此它从 LoggerConfig X 继承其级别。Logger X.Y.Z 使用 LoggerConfig X.Y,因为它没有名称完全匹配的 LoggerConfig。它也从 LoggerConfig X 继承其日志级别。

下表说明了级别过滤的工作原理。在表中,垂直标题显示 LogEvent 的级别,而水平标题显示与相应 LoggerConfig 关联的级别。交集标识 LogEvent 是否允许通过以进行进一步处理(是)或丢弃(否)。

事件级别 LoggerConfig 级别
  跟踪 DEBUG 信息 警告 错误 致命 关闭
全部
跟踪
DEBUG
信息
警告
错误
致命
关闭

过滤器

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

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

追加器

根据其记录器选择性地启用或禁用日志请求的能力只是其中的一部分。Log4j 允许日志请求打印到多个目标。在 log4j 中,输出目标称为 追加器。目前,追加器存在于控制台、文件、远程套接字服务器、Apache Flume、JMS、远程 UNIX Syslog 守护程序和各种数据库 API。有关各种类型的更多详细信息,请参阅有关 追加器 的部分。可以将多个追加器附加到记录器。

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

对于给定记录器的每个启用的日志请求,都将转发到该记录器 LoggerConfig 中的所有追加器以及 LoggerConfig 父级的追加器。换句话说,追加器是从 LoggerConfig 层次结构中累积继承的。例如,如果将控制台追加器添加到根记录器,那么所有启用的日志请求至少都会在控制台上打印。如果另外将文件追加器添加到 LoggerConfig,例如C,那么CC 的子级的启用日志请求将在文件中控制台上打印。可以通过在配置文件中的记录器声明中设置additivity="false" 来覆盖此默认行为,以便追加器累积不再是累加的。

下面总结了控制追加器累加性的规则。

追加器累加性

记录器L 的日志语句的输出将转到与L 关联的 LoggerConfig 中的所有追加器以及该 LoggerConfig 的祖先。这就是“追加器累加性”一词的含义。

但是,如果与记录器L 关联的 LoggerConfig 的祖先,例如P,具有设置为false 的累加性标志,那么L 的输出将被定向到L 的 LoggerConfig 中的所有追加器及其祖先,直到并包括P,但不包括P 的任何祖先中的追加器。

默认情况下,记录器的累加性标志设置为true

下表显示了一个示例

记录器
名称
添加
追加器
累加性
标志
输出目标 注释
root A1 不适用 A1 根记录器没有父级,因此累加性不适用于它。
x A-x1、A-x2 true A1、A-x1、A-x2 "x" 和根的追加器。
x.y true A1、A-x1、A-x2 "x" 和根的追加器。通常不会将没有追加器的记录器配置为。
x.y.z A-xyz1 true A1、A-x1、A-x2、A-xyz1 "x.y.z"、"x" 和根中的追加器。
安全 A-sec false A-sec 由于累加性标志设置为false,因此没有追加器累积。
security.access true A-sec 只有“security”的追加器,因为“security”中的累加性标志设置为false

布局

大多数情况下,用户不仅希望自定义输出目标,还希望自定义输出格式。这是通过将 布局 与追加器关联来实现的。布局负责根据用户的意愿格式化 LogEvent,而追加器负责将格式化的输出发送到其目标。标准 log4j 发行版中的 PatternLayout 允许用户根据类似于 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 中借用的,然后修改为支持评估 LogEvent。此外,Interpolator 类是从 Apache Commons Configuration 中借用的,以允许 StrSubstitutor 评估来自多个 StrLookups 的变量。它也被修改为支持评估 LogEvent。这些共同提供了一种机制,允许配置引用来自系统属性、配置文件、ThreadContext Map、LogEvent 中的结构化数据的变量。这些变量可以在处理配置时解析,也可以在处理每个事件时解析,如果组件能够处理它。有关更多信息,请参阅 查找