Log4j Architecture

本文主要是针对Log4j的2.x版本的文档的,链接

Architecture

Main Components

Log4j使用的类下面图表中展示。
Log4j Classes
使用Log4j的applications讲需要使用一个特定的名称向LogManager请求一个Logger。LogManager会定位到适当的LoggerContext,然后从LoggerContext中获取Logger。如果Logger必须被创建,它将与包含了如下内容的LoggerConfig进行关联:1)与Logger相同名称的LoggerConfig;b)父级package的名称的LoggerConfig;3)根级LoggerConfig。LoggerConfig对象根据配置中Logger的声明进行创建。LoggerConfig与日志事件的实际输出源联系在一起。

Logger Hierarchy(日志级别)

首先,任何优于简单的System.out.println的日志记录API可以禁用某些日志语句,而其它的打印不受限制。这种能力假定日志空间,那就是,所有可能的记录日志的语句的空间,根据开发者的选择标准被分类。

在Log4 1.x版本中,日志级别通过Loggers之间的关系来维持。在Log4j 2版本中,这个关系不在存在。取而代之的是通过LoggerConfig对象之间的联系来维持。

Logger和LoggerConfig被整体命名。Logger是大小写敏感的,并且他们遵循分层的命名规则:

分层命名

如果一个LoggerConfig的名称是后代logger名称的点前缀,那么这个LoggerConfig是另一个LoggerConfig的祖先。如果一个LoggerConfig和后代LoggerConfig之间没有祖先,那么就说这个LoggerConfig是子LoggerConfig的父级。

例如,名为”com.foo”的LoggerConfig是名为”com.foo.Bar”的LoggerConfig的父级。同样,”java”是”java.util”的父级,并且是”java.util.Vector”的祖先。这个命名结构是大多数开发者所熟悉的。

根LoggerConfig位于LoggerConfig层次的最顶层。它是一个例外,因为它总是存在并且是每个层级的一部分。一个直接连接到根LoggerConfig的Logger可以如下:

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

或者更加简单:

1
Logger logger = LogManager.getRootLogger();

所有其他的Loggers都能够使用LogManager.getLogger静态方法并传递想要获取的Logger的名称作为参数进行获取。Logging API的更多信息可以在Log4j 2 API中找到。

LoggerContext

LoggerContext为日志系统充当锚点。然而依赖不同的形式,在一个application中可能有多个活跃的LoggerContexts。LoggerContext的更多细节在Log Separation章中介绍。

Configuration

每个LoggerContext有一个活跃的配置。配置包含了所有输出源,context-wide过滤器、LoggerConfigs并包含引用。在重新配置两个配置对象期间将会继续存在。一旦所有的Logger被重新分配到新的配置,老的配置将被停止并消失。

Logger

如前所述,Logger通过调用LogManager.getLogger被创建。Logger本身不直接执行任何action。它简单的有一个名称,并与一个LoggerConfig相关联。它继承AbstractLogger并实现了所要求的方法。因为配置被修改,Logger可能与一个不同的LoggerConfig相关联,从而导致它们的行为被修改。

Retrieving Loggers

使用相同的名称调用LogManager.getLogger方法将总是返回相同Logger对象的引用。
例如,在

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

x和y涉及的是相同的Logger对象。

log4j环境的配置通常是在application初始化的时候完成的。最好的方式是通过阅读一个配置文件。这将在Configuration章节中讨论。

log4j通过软件组件使得命名Loggers很容易。这可以通过在每个class中实例化一个Logger来完成,使用logger名称等同于class的完整的限定名。这是定义Logger的一个有用且简单的方法。由于日志的输出带有Logger的名称,这个名字的策略是易于识别一个日志message的来源。然而这只是一种可能,是用于带有名称logger的一种常见策略。Log4j不限制logger集合的可能性。开发者可以根据需要命名logger。

因为在类中,常常使用类的名称作为Logger的名称,因此提供了LogManager.getLogger()这个简单的方法来自动的使用类的完整的限定名来命名Logger。

LoggerConfig

LoggerConfig对象在Loggers在日志配置中被声明时被创建。LoggerConfig包含了一组filter,LogEvents在传递到任何日志存储器之前会被传递到这些filter。它包含了日志存储器的一组引用,这些日志存储器被用来处理event。

Log Levels

LoggerConfig会被分配一个日志级别。这组内建级别包含TRACE、DEBUG、WARN、ERROR和FATAL。Log4j 2还支持自定义日志级别。获取更多隔离尺寸的机制是使用Markers来代替。

Log4j 1.xLogback都有”级别继承”的概念。在Log4j 2中,Loggers和LoggerConfigs是两个不同的对象,因此这个概念的实现是不相同的。每个Logger引用适当的LoggerConfig,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

在上面的例子1中,只有根logger配置了,并且只有一个日志级别。所有其他引用根LoggerConfig的Logger将使用根LoggerConfig的级别。

Logger Name Assigned LoggerConfig LoggerConfig Level Level
root root DEBUG DEBUG
X X ERROR ERROR
X.Y X.Y INFO INFO
X.Y.Z X.Y.Z WARN WARN

在上面的例子中,所有的logger都配置了一个LoggerConfig,并且从配置的LoggerConfig中取得它们的级别。

Logger Name Assigned LoggerConfig LoggerConfig Level Level
root root DEBUG DEBUG
X X DRROR ERROR
X.Y X ERROR ERROR
X.Y.Z X.Y.Z WARN WARN

在上面的例子中,名为root、X和X.Y.Z的三个logger,每个都有一个与之相同名称的LoggerConfig。名为X.Y的Logger,没有配置与之匹配名称的LoggerConfig,因此使用名为X的Logger的LoggerConfig,因为会匹配与Logger的名称最长匹配的LoggerConfig,名称的匹配是从名字的开始进行匹配。

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

在上面的例子中,名为root和X的logger,每个都配置了一个与之相同名称的LoggerConfig。名为X.Y和X.Y.Z的logger,没有有配置LoggerConfig,因此它们的级别是来自与之关联的LoggerConfig,名为X,因为X是与它们名字最长匹配的LoggerConfig。

Logger Name Assigned LoggerConfig LoggerConfig Level level
root root DEBUG DEBUG
X X ERROR ERROR
X.Y X.Y INFO INFO
X.YZ X ERROR ERROR

在上面的例子中,名为root、X和X.Y的logger,每个都有一个与之相同名称的LoggerConfig。名为X.YZ的logger没有配置LoggerConfig,因此它的级别来自阈值管理的LoggerConfig,名为X,因为X是与它匹配度最长的LoggerConfig。它没有和X.Y关联,因为每个点号后面必须完全匹配。

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

在上面的例子中,名为X.Y的LoggerConfig没有配置级别,因此它的级别从名为X的LoggerConfig继承。名为X.Y.Z使用了名为X.Y的LoggerConfig,因为它没有与之精确匹配的LoggerConfig。它也是从名为X的LoggerConfig那里继承日志级别。
下面的表格说明了级别过滤是如何工作的。在这个表格中,列表头中,显示了LogEvent的级别,而行表头中,展示了相关LoggerConfig的关联级别。交叉表示LogEvent是否允许被传递到进一步的处理(Yes表示允许,No表示不允许)。

Event Level LoggerConfig Level
TRACE DEBUG INFO WARN ERROR FATAL OFF
ALL YES YES YES YES YES YES NO
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,这些Filter能够在控制被传递到任何LoggerConfig之前被应用、控制被传递到一个LoggerConfig之后但是调用任何Appender之前、控制被传递到一个LoggerConfig之后但是在调用一个指定Appender之前和每个Appender上。以类似防火墙过滤器的方式,每个过滤器返回三个结果中的一个:Accept、Deny和Neutral。Accept的答复意味着没有其他Filter会被调用,和事件被处理。Deny的答复意味着事件应该立即被忽略并且控制应该返回给调用者。Neutral的答复意味着事件应该被传递给其他Filter。如果这里没有其他Filter,事件应该被处理。

虽然一个event可能通过一个Filter被接收,但是这个event可能仍然不给记录。这发生在当这个event被一个前置LoggerConfig Filter接收但是接着被一个LoggerConfig filter忽略或被所有Appenders忽略。

Appender

基于Logger,选择性的开启或禁用日志记录请求只是图像的一部分。Log4j允许日志记录请求打印到多个目的地。在Log4j中一个输出目的地成为一个Appender。当前支持的appender有,控制台、文件、远程socket服务、Apache Flume、JMS、远程 UNIX系统日志后台和各种数据库APIs。查看这一章的Appenders获取关于不同类型appender的详细信息。一个Logger可以附加多个Appender。

一个Appender可以通过在当前配置上调用addLoggerAppender方法来添加到一个Logger上。如果一个LoggerConfig没有匹配到Logger(根据Logger的名称匹配),将会创建一个,Appender将被固定到创建的Logger上,然后所有的Logger将会被通知并更新它们的LoggerConfig引用。

对于给定的logger,每个可用的日志记录请求都将被转发给这个logger的LoggerConfig内的所有appender,以及这个LoggerConfig的父级的appender们。换句话说,appenders附加的从LoggerConfig继承中被继承。例如,如果一个控制台appender被添加到根logger,那么所有可用的日志记录请求将至少会输出到控制台。假设将一个文件appender添加到了一个名为C的appender,那么对于C和C的子类的可用的日志记录请求将会输出到一个文件和控制台。通过在配置文件中设置 additivity=”false”来声明Logger不需要额外的添加来重写Logger的默认行为。

控制appender的添加规则汇总如下:

Appender Additivity
名为L的Logger的一个日志片段的输出将去往与L相关的LoggerConfig中的所有Appender以及这个LoggerConfig上一代的所有Appender。这是appender additivity的含义。
然而,如果与L相关联的LoggerConfig的一个上一代,假设是P,设置附加性标识为false,那么L的输出将直接到达L的LoggerConfig的appenders,以及它的上一代,包括P,但是不包含P的上一代中的任何appenders。
Logger有它自己的附加性标识,默认设置为true。

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

Logger Name Added Appenders Additivity Flag Output Targets Comment
root A1 not applicable A1 根logger没有父级,因此附加性应用不到。
x A-x1, A-x2 true A1, A-x1, A-x2 名为x的Logger和根Logger的Appdenders。
x.y none true A1, A-x1, A-x2 名为x的Logger和根Logger的Appdenders。通常不配置没有Appender的Logger。
x.y.z A-xyz1 true A1, A-x1, A-x2, A-xyz1 名为x.y.z和名为x的Logger的Appender,以及根Logger的Appender。
security A-sec false A-sec 没有appender累加,因为附加标识设置为false
security.access none true A-sec 只有名为security的Logger的appender,因为 security的附加标识设置为false。

Layout

通常,用户希望能够定制的不只是输出的目的地,还包含输出的格式。这是通过把一个Layout与一个Appender联系在一起来实现的。这个Layout负责根据用户的意愿格式化LogEvent,因此一个appender会非常注意的发送格式化的输出到它的目的地。PatternLayout,是Log4j标准分发的一部分,让用户指定输出格式来进行一致性的格式转换,类似于C语言的printf函数。

例如,这个使用了%r[%t] %-5p %c - %m %n模式的PatternLayout将会输出类似的一些东西:

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

第一个字段是毫秒的数字,表示从程序启动到现在过去的时长。第二个字段是产生日志请求的线程。第三个字段是日志的级别。第四个字段是与日志请求相关联的logger的名称。“-”之后的文本是日志的message。

Log4j为不同的用例带来了很多的Layouts,例如JSON、XML、HTML和Syslog(包括新的RFC 5424版本).其他appenders(诸如数据库连接)放在了特定的字段中,而非一个特定的文本布局。

同样重要的是,log4j将根据用户指定的表中来呈现日志消息的内容。例如,如果在你当前的项目中需要使用一个对象类型0ranges,并需要常常对它进行记录,那么你可以创建一个OrangeMessage来接收Orange实例,并传递到Log4j,因此哪个Orange对象能在请求的时候被格式化为一个合适的字节数组。

StrSubstitutor and StrLookup

StrSubstitutor和接口StrLookup是从Apache Commons Langs借过来的,然后被修改来支持日志事件。另外,Interpolator从Apache Commons Configuration那里借鉴以允许StrSubstitutor来评估来自多个StrLookup的变量。它还被修改,以支持LogEvent的评估。这些提供的机制允许从System Properties、配置文件、 ThreadContext Map和LogEvent中结构化数据中引用变量进行配置。这些变量能够当配置被处理或每个事件被处理时来解决。