本文主要是针对Log4j的2.x版本的文档的,链接
Architecture
Main Components
Log4j使用的类下面图表中展示。
使用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可以如下:
或者更加简单:
所有其他的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对象的引用。
例如,在
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.x和Logback都有”级别继承”的概念。在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将会输出类似的一些东西:
第一个字段是毫秒的数字,表示从程序启动到现在过去的时长。第二个字段是产生日志请求的线程。第三个字段是日志的级别。第四个字段是与日志请求相关联的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中结构化数据中引用变量进行配置。这些变量能够当配置被处理或每个事件被处理时来解决。