架构

原文地址

主要组件

Log4j使用如下图所示的类:

应用程序要使用Log4j 2的API,需要从LogManager中获取一个有明确名称的Logger。LogManager将会定位到一个合适的LoggerContext并且从中获取Logger。如果Logger必须被创建,那么它会和包含这些信息的LogConfig相关联:a)与Logger相同的名称;b)父包的名称;c)根LoggerConfig。LoggerConfig对象根据配置中的Logger声明而创建。LoggerConfig与实际处理LogEvent事件的Appender关联。

日志层级

使用所有基于System.out.println的日志API的首要条件,在于当允许其他无障碍打印时禁用明确的日志声明的能力。这项能力要求日志空间,也就是包含所有可能日志声明的空间,是根据某些开发者选择的规范而组织起来的。

在Log4j 1.x中,层级是由Logger之间的关系维护的。在Log4j 2中,这种关系不再存在。取而代之地,层级由LoggerConfig对象之间的关系维护。

Logger和LoggerConfig都是命名的实体。Logger的名称大小写敏感,且遵循层级命名规则:

命名层级

如果一个LoggerConfig的名称加上句点符号被作为前缀,那它就是另一个 后代 LoggerConfig的 祖先 。如果在它自身和后代LoggerConfig之间没有其他的祖先,那么前者就是 LoggerConfig,后者为 LoggerConfig。

例如,被命名为com.foo的LoggerConfig是被命名为com.foo.Bar的LoggerConfig的父。类似地,java也是java.utiljava.util.Vector的父。这种命名模式应该对很多开发者来说都很熟悉。

根LoggerConfig位于LoggerConfig层级的顶层。这意味着它总是存在的,并且是每一个层级的一部分。一个Logger可以通过下面的方式直接导向根LoggerConfig:

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

或者更简单地:

Logger logger = LogManager.getRootLogger();

所有其他的Logger都可以通过静态方法LogManager.getLogger传入对应的名称而获取。更多关于日志API的信息可以参考Log4j 2 API

LoggerContext

LoggerContext就像是日至系统中的一个锚点。然而视情况,一个应用程序中可能有多个活跃的LoggerContext。更多关于LoggerContext的细节在日志分离一节中。

Configuration

每一个LoggerContext都有一个活跃的Configuration。这个Configuration包含所有的Appender、上下文范围的Filter、LoggerConfig以及StrSubstitutor的引用。当重新配置时,将会有两个Configuration对象存在。一旦所有的Logger都已经被导向到新的Configuration上时,旧的Configuration就会被停止且丢弃。

Logger

此前的状态,Logger是由调用LogManager.getLogger方法被创建的。Logger本身并不做转向的动作。它只是有一个名字并且和LoggerConfig关联。它继承了AbstractLogger并且实现了所需的方法。当配置被修改时,Logger可能变为与一个不同的LoggerConfig关联,这样会导致他们的表现被改变。

获取Logger

用相同的名称调用LogManager.getLogger方法总是会得到同一个完全相同的Logger对象。例如:

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

xy是完全相同的Logger对象。

Log4j环境的配置是在应用初始化时一次性完成的。一种更倾向使用的方式是读取配置文件。这将在配置中讨论。

Log4j可以很容易地通过 软件组件 来命名Logger。这可以通过在每一个类中用于类名完全一样的名称来定义一个Logger来达到。这是定义Logger简单实用的方法。由于日志输出中承载了生成Logger的名称,这种命名策略使得在日志信息定位源头很简单。然而,这只是一种可能的、惯用的Logger命名策略。Log4j并不限制Logger的可能性。开发者可以按想要的方式自由命名Logger。

由于根据当前类来命名Logger特别常用,所以提供一个简便方法LogManager.getLogger()来自动使用当前类的完整名称以命名Logger。

尽管如此,通过当前类来命名Logger看上去都是一个目前最好的策略。

LoggerConfig

LoggerConfig对象在日志配置中声明Logger时被创建。LoggerConfig包含一个Filter集合,以允许LogEvent在到达任何Appender之前被传递。它包含一个Appender集合的引用,用于处理事件。

日志级别

LoggerConfig会分配一个日志级别。内建的级别包含TRACE、DEBUG、INFO、WARN、ERROR和FATAL。Log4j 2也支持自定义日志级别。另一个获取更细粒度的机制是用Markers作为替代。

Log4j 1.xLogback都有“级别继承”的概念。在Log4j 2中,Logger和LoggerConfig是两个不同的对象,所以这个概念是用不同方式实现的。每一个Logger都引用一个合适的、可间接引用其父对象的LoggerConfig,这样以达到相同的效果。

下面六张表格展示了许多分配的级别取值与每一个Logger被分配的结果级别。注意在这些举例中,如果根LoggerConfig没有被配置,一个默认的级别将会被分配。

示例1:

Logger名称 分配的LoggerConfig LoggerConfig级别 Logger级别
root root DEBUG DEBUG
X root DEBUG DEBUG
X.Y root DEBUG DEBUG
X.Y.Z root DEBUG DEBUG

在上面的示例1中,只有根logger被配置且有日志级别。所有其他的Logger都引用根LoggerConfig且使用它的级别。

示例2:

Logger名称 分配的LoggerConfig LoggerConfig级别 Logger级别
root root DEBUG DEBUG
X X ERROR ERROR
X.Y X.Y INFO INFO
X.Y.Z X.Y.Z WARN WARN

在示例2中,所有的logger都配置了一个LoggerConfig,并从中获取级别。

示例3:

Logger名称 分配的LoggerConfig LoggerConfig级别 Logger级别
root root DEBUG DEBUG
X X ERROR ERROR
X.Y X ERROR ERROR
X.Y.Z X.Y.Z WARN WARN

在示例3中,logger rootXX.Y.Z都配置了相同名称的LoggerConfig。Logger X.Y没有配置匹配名称的LoggerConfig,因而使用X的LogConfig,因为那是最长前缀匹配的LoggerConfig。

示例4:

Logger名称 分配的LoggerConfig LoggerConfig级别 Logger级别
root root DEBUG DEBUG
X X ERROR ERROR
X.Y X ERROR ERROR
X.Y.Z. X ERROR ERROR

在示例4中,logger rootX都配置了相同名称的LoggerConfig。而X.YX.Y.Z都没有配置LoggerConfig,所以它们的日志级别都将从X中获取,因为那是最长前缀匹配的LoggerConfig。

示例5:

Logger名称 分配的LoggerConfig LoggerConfig级别 Logger级别
root root DEBUG DEBUG
X X ERROR ERROR
X.Y X.Y INFO INFO
X.YZ X ERROR ERROR

在示例5中,logger rootXX.Y都配置了相同名称的LogConfig。但X.YZ没有配置,所以它的日志级别将从X中获取,因为那是最长前缀匹配的LoggerConfig。它并不匹配X.Y,因为匹配的段非常严格。

示例6:

Logger名称 分配的LoggerConfig LoggerConfig级别 Logger级别
root root DEBUG DEBUG
X X ERROR ERROR
X.Y X.Y ERROR
X.Y.Z X.Y ERROR

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

下面的表格展示了级别过滤是如何工作的。在表格中,垂直列为LogEvent的级别,水平列为从合适的LoggerConfig中分配到的级别。二者的交点处标识了LogEvent是否会被通过并传递给下一步处理,是(YES)或否(NO)。

事件级别 TRACE DEBUG INFO WARN ERROR FATAL OFF
ALL YES YES YES YES YES YES YES
TRACE YES NO NO NO NO NO NO
DEBUG YES YES NO NO NO NO NO
INFO YES YES YES NO NO NO NO
WARN YES YES YES YES NO NO NO
ERROR YES YES YES YES YES NO NO
FATAL YES YES YES YES YES YES NO
OFF NO NO NO NO NO NO NO

Filter

作为上一节中对自动日志级别过滤的补充,Log4j提供Filter并可应用于:控制被传递到任何LoggerConfig之前、控制被传递到达一个LoggerConfig但在调用任何Appender之前、控制被传递到一个LoggerConfig单在调用一个指定的Appender和每一个Appender之前。与防火墙过滤的方式类似,每一个Filter都将返回三个结果之一:Accept(接受)、Deny(拒绝)或Neutral(中立)。响应Accept意味着其他的Filter都不应该再被调用,而事件应该被处理。响应Deny意味着事件应该被立即忽略,且将控制讲给调用处。响应Neutral代表事件应该被传递给其他的Filter。如果没有其他Filter,则事件将被处理。

尽管一个事件可能被Filter接受,但事件仍然可能不被记录。这种情况会发生于事件被LoggerConfig之前的Filter接受,但被LoggerConfig的Filter拒绝或者被所有的Appender拒绝。

Appender

基于Logger可选地启用或禁用日志请求只是一部分而已。Log4j允许将日志请求打印到多个目标。用Log4j的说法,一个输出的目标位置被称为Appender。目前,Appender存在几种:控制台、文件、远程Socket服务器、Apache Flume、JMS、远程UNIX系统日志后台以及好几种数据库API。请查看Appender获取更多关于多种可用类型的细节。一个Logger上可以装配多个Appender。

调用当前配置的addLoggerAppender方法可以将一个Appender添加到Logger上。如果名称匹配的LoggerConfig不存在,将会创建一个,Appender将会被装配上去,然后所有的Logger都将被通知去更新他们的LoggerConfig引用。

在一个给定的Logger上,所有启用的日志请求都将被传递到该Logger对应的LoggerConfig上所有的Appender中,也同样作用于它的所有父LoggerConfig上的Appender。换句话说,Appender是根据LoggerConfig的层级继承的。举个例子,如果一个控制台Appender被添加到根Logger上,那么所有启用的日志请求都至少会被打印到控制台。如果一个文件Appender被添加到名为 C 的LoggerConfig上,那么与 CC 的所有孩子对应的启用的日志请求,都将被打印到文件以及控制台中。可以覆盖这种默认的表现,只要在配置文件中声明Logger时设置additivity="false",就可以让Appender不再自动累加。

Appender累加的治理规则总结如下:

Apender累加

日志声明的Logger L 的输出,将会到达与 L 相关联的LoggerConfig上的所有Appender,以及所有该LoggerConfig的祖先。这就是“Appender累加”的意思。

然而,如果一个与Logger L 相关联的名为 P 的祖先LoggerConfig,已经把累加标识设置为false,那么 L 的输出将会被导向到所有 L 以及所有低于 P (包含 P )层级的多个祖先对应的LoggerConfig上的Appender,但不会到达 P 的多个祖先。

Logger的累加标识是默认设置为true的。

下面的表格展示了一个例子:

Logger名称 添加的Appender 累加标识 输出目标 注释
root A1 不可用 A1 根Logger没有父亲,所以累加标识对它不可用。
x A-x1, A-x2 true A1, A-x1, A-x2 “x”和root的Appender。
x.y none true A1, A-x1, A-x2 “x”和root的Appender。配置一个没有Appender的Logger是非典型的做法。
x.y.z A-xyz1 true A1, A-x1, A-x2, A-xyz1 “x.y.z”、“x”和root的Appender。
security A-sec false A-sec 没有Appender被累加,因为累加标识被设置为了false
security.access none true A-sec 只有“security”的Appender,因为“security”的累加标识被设置为false

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.

第一个字段是程序启动以来锁经过的毫秒时间。第二个字段是发出日志请求的线程。第三个字段是日志声明的级别。第四个字段是与日志请求相关联的Logger名称。在“-”之后的文本是日志的消息内容。

Log4j带有很多不同的Layout以支持诸如JSON、XML、HTML和Syslog(包括新的RFC 5424版本)等用例。其他如数据库连接器的Appender等,由一些具体指定的字段填充,以替代特殊的文本布局。

重要的是,Log4j会让日志文本符合用户具体的规范。例如,如果你经常需要记录Oranges(一个你当前项目中使用的对象类型),那么你可以创建一个OrangeMessage来接受一个Orange实例并将它传递给Log4j,这样Orange对象将在需要时被格式化为一个合适的byte数组。

StrSubstitutor与StrLookup

StrSubstitutor类和StrLookup接口是从Apache Commons Lang中借过来并修改以支持LogEvent变量的。添加的Interpolator类从Apache Commons Confuguration中借过来以允许StrSubstitutor支持多个StrLookup的变量,它同样也进行了修改以支持LogEvent变量。这些共同提供了一个机制以允许配置去引用系统属性变量、配置文件、LogEvent中的ThreadContext Map和StructuredData等。这些变量既可以在执行配置时,也可以在执行每一个事件时被处理。请查看Lookup以获取更多信息。

results matching ""

    No results matching ""