架构
架构
主要组件
Log4J类结构如下图所示:
使用Log4j 2 API的应用程序将从LogManager请求具有特定名称的Logger。LogManager将找到相应的LoggerContext,然后从中获取Logger。 如果必须去创建logger,这会关联到包含下面因素的LoggerConfig a)同样名称的logger,b)父包的名称 或者c)root LoggerConfig。LoggerConfig对象是从配置中的Logger声明创建的。 LoggerConfig与实际传递LogEvents的Appender相关联。
Logger 层次结构
任何日志API优于普通System.out.println的首要优势在于它能够禁用某些日志语句,同时允许,其日志输出不受阻碍地打印。此功能假定日志记录空间(即所有可能的日志记录语句的空间)根据开发人员选择的某些条件进行分类。
在Log4j 1.x中,Logger层次结构通过Loggers之间的关系进行维护。 在Log4j 2中,这种关系不再存在。 而是在LoggerConfig对象之间的关系中维护层次结构。
Logger和LoggerConfigs是命名实体。 记录器名称区分大小写,它们遵循分层命名规则:
命名层次结构
如果LoggerConfig的名称后跟一个点是后代logger名称的前缀,则称其为另一个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();
通过传递所需Logger的名称,可以使用LogManager.getLogger静态方法检索所有其他Logger。 有关Logging API的更多信息可以在Log4j 2 API中找到.
LoggerContext
LoggerContext充当Logging系统的锚点。 但是,根据具体情况,可以在应用程序中具有多个LoggerContexts。 有关LoggerContext的更多详细信息,请参见“Log Separation”部分。
Configuration
每个LoggerContext都有一个有效的Configuration。Configuration 包含所有Appenders、上下文范围的Filters、LoggerConfigs,并包含对StrSubstitutor的引用。在重新配置期间,将存在两个配置对象。一旦所有日志记录器都被重定向到新的配置,旧的配置将被停止并丢弃。
Logger
如前所述,Loggers是通过调用LogManager.getLogger创建的。 Logger本身不执行任何直接操作。 它只是一个名称,并与LoggerConfig相关联。 它扩展了AbstractLogger并实现了所需的方法。 随着配置的修改,Logger可能会与不同的LoggerConfig关联,从而导致其行为被修改。
检索 Loggers
调用LogManager.getLogger方法传入相同的名称将始终返回对完全相同的Logger对象的引用
例如:
Logger x = LogManager.getLogger("wombat");
Logger y = LogManager.getLogger("wombat");
x和y引用的是完全相同的Logger对象。
log4j环境的配置通常在应用程序初始化时完成。 首选方法是读取配置文件。 这在Configuration章节中讨论。
Log4j可以轻松地按软件组件命名Loggers。 这可以通过在每个类中实例化Logger来实现,其中Logger名称等于类的完全限定名称。 这是定义Logger的有用且直接的方法。 由于日志输出带有生成Logger的名称,因此该命名策略可以轻松识别日志消息的来源。 但是,尽管很常见,这只是命名记录器的一种可能的策略。 Log4j不限制各种可能的命名loggers方式。 开发人员可以根据需要自由命名记录器。
由于以Logger所属类的名称命名Logger是一种非常常见的习惯用法,因此提供了方便的方法LogManager.getLogger()来自动使用调用类的完全限定类名作为Logger名称。
尽管如此,根据Logger所在的类来命名日志记录器似乎是目前已知的最佳策略
LoggerConfig
在日志记录配置中声明Logger时,将创建LoggerConfig对象。 LoggerConfig包含一组Filters,它们必须允许LogEvent在传递给任何Appender之前先通过Filters。 它包含对应该一组用于处理事件的Appender的引用。
示例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引用根LoggerConfig并使用其Level。
示例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中,所有记录器都具有已配置的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中,记录器root,X和X.Y.Z均具有已配置的具有相同名称的LoggerConfig。Logger X.Y没有配置的具有匹配名称的LoggerConfig,因此使用LoggerConfig X的配置,LoggerConfig会按照其名称与Logger名称的开头最长匹配。
示例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中,root和X 都有自己配置的同名LoggerConfig。 记录器X.Y和X.Y.Z没有配置LoggerConfigs,因此从分配给它们的LoggerConfig获取它们的日志级别,LoggerConfig按照名称与Logger名称开头的进行匹配。
示例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中,记录器root,X和X.Y都有自己配置的同名LoggerConfig。日志记录器X.YZ 没有配置LoggerConfig,因此从分配给它的LoggerConfig X获取其日志级别,LoggerConfig按照名称与Logger名称开头的进行匹配。它不与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它没有配置级别,因此它从LoggerConfig X继承其级别.Logger X.Y.Z使用LoggerConfig X.Y,因为它没有名称完全匹配的LoggerConfig。 它也从LoggerConfig X继承了它的日志级别。
下表说明了日志级别过滤的工作原理。 在表中,垂直标题显示LogEvent的级别,而水平标题显示与相应的LoggerConfig关联的级别。 交集标识是否允许LogEvent被进行进一步处理(YES)或丢弃(NO)。
EventLevel/LoggerConLevel | 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可以应用在把控制传递给任何LoggerConfig之前,在控制传递给LoggerConfig之后但在调用任何Appender之前,在控制传递到一个LoggerConfig之后,但在调用特定的Appender之前,以及每个Appender。 以一种与防火墙过滤器非常相似的方式,每个过滤器可以返回三个结果中的一个,即Accept(接受),Deny(拒绝)或Neutral(中立)。 Accept的响应意味着不应该调用其他Filter,事件应该被处理。Deny的响应意味着应该立即忽略事件,并将控制权返回给调用方。Neutral的响应表示事件应该传递给其他过Filter。如果没有其他过Filter,事件将被处理。
虽然事件可能被一个Filter接受,但事件仍然可能未被记录。 当事件被前LoggerConfig Filter接受但是被LoggerConfig Filter拒绝或被所有Appender拒绝时,可能会发生这种情况。
Appender
根据logger,程序有选择地启用或禁用日志记录请求只是Log4j能力其中的一部分。Log4j允许将日志请求打印到多个目的地。在log4j中,输出目的地称为Appender。目前,存在用于控制台、文件、远程套接字服务器、Apache Flume、JMS、远程UNIX Syslog守护进程和各种数据库api的appenders。有关可用的各种类型appender的更多详细信息,请参见appender一节。Logger可以绑定多个Appender。
可以通过调用当前Configuration 的addLoggerAppender方法将Appender添加到Logger。如果不存在与Logger名称匹配的LoggerConfig,将创建一个并将Appender与它绑定,然后将通知所有Logger更新其LoggerConfig引用。
对于给定的logger,每个启用的日志记录请求都将转发给该Logger的LoggerConfig中的所有appender以及LoggerConfig父项的Apperders。换句话说,Appender是从LoggerConfig结构中附加的继承的。例如,如果将控制台appender添加到root logger,则所有启用的日志记录请求将至少在控制台上打印。如果另外将文件appender添加到LoggerConfig,例如C,则对C和C的子节点启用的日志记录请求将打印在文件和控制台上。 通过在配置文件中的Logger声明上设置additivity =“false”,可以覆盖此默认行为,以便Appender累积不再是累加的。
有关appender可加性的规则总结如下:
Appender 可加性
Logger L的日志语句的输出将转到与L关联的LoggerConfig中的所有Appender以及该LoggerConfig的祖先。 这就是术语“appender additivity”的含义。
但是,如果与Logger L关联的LoggerConfig的祖先(比如P)将additivity标志设置为false,那么L 输出将被定向到L 的LoggerConfig中的所有appender(包含P但不包含P的祖先Appender)。
Logger 默认情况下将其可加性标志设置为true。
下表显示了一个示例:
Logger Name | Added Appenders | Additivity Flag | Output Targets | Comment |
---|---|---|---|---|
root | A1 | not applicable | A1 | root logger 没有父项所以不能使用additivity属性 |
x | A-x1 , A-x2 | true | A1, A-x1, A-x2 | Appenders属于 x 和root |
x.y | none | true | A1, A-x1 , A-x2 | Appenders属于x和root, 配置没有Appender的Logger是不常见的 |
x.y.z | A-xyz1 | true | A1, A-x1, A-x2 , A-xyz1 | Appenders属于x, xy.z和root |
security | A-sec | false | A-sec | 由于additivity标志设置为false,因此没有appender累积 |
security.access | none | true | A-sec | 只有"security"的appenders因为additivity标志在"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.
第一个字段是自程序启动以来经过的毫秒数。 第二个字段是发出日志请求的线程。 第三个字段是日志语句的级别。 第四字段是与日志请求关联的记录器的名称。 ' - '后面的文字是日志打印的内容。
Log4j为各种不同的情况供了许多不同的Layout,例如JSON,XML,HTML和Syslog(包括新的RFC 5424版本)。 其他appender(如数据库连接器)填充指定的字段而不是特定的文本布局。
同样重要的是,log4j将根据用户指定的条件渲染日志消息的内容。例如,如果您经常需要对当前项目中使用的对象类型Orange进行日志记录,那么您可以创建一个OrangeMessage,它接受一个Orange实例,并将其传递给Log4j,以便在需要时将Orange对象格式化为适当的字节数组。
StrSubstitutor and StrLookup
StrSubstitutor类和StrLookup接口是从Apache Commons Lang借用的,然后进行了修改以支持评估LogEvents。 此外,Interpolator类是从Apache Commons Configuration借来的,允许StrSubstitutor评估来自多个StrLookup的变量。 它也被修改为支持评估LogEvents。 它们共同提供了一种机制,允许配置引用来自系统属性,配置文件,ThreadContext Map,LogEvent中的StructuredData的变量。 如果组件能够处理它,则可以在处理配置时或在处理每个事件时解析变量。 有关更多信息,请参阅Lookups。
- log4j2使用手册(中文)第一章 介绍
- log4j2使用手册(中文)第二章 架构
- log4j2使用手册(中文)第三章 Log4j 1.x 迁移至Log4j 2
- log4j2使用手册(中文)第四章 API
- log4j2使用手册(中文)第五章 Configuration
- log4j2使用手册(中文)第六章 使用
- log4j2使用手册(中文)第七章 Web Applications and JSPs
- log4j2使用手册(中文)第八章 Lookups