官网原文标题《Architecture》
翻译时间:2017-11-14
官网原文地址:http://logging.apache.org/log4j/2.x/manual/architecture.html
译者:本文介绍了log4j的主要构成组件和核心概念,并就每个组件分别进行了讲解。尤其需要读者重点理解的是log level的继承概念,以及appender的additivity属性。仔细理解本篇后,可继续学习配置的文章。
前序阅读《Welcome to Log4j 2!》
后续阅读:《Configuration》(还未更新)
log4j使用下图中所展示的class
使用Log4j2 API的程序需要向LogManager请求一个指定名称的Logger。LogManager会定位到合适的LoggerContext,然后从它获取logger。如果必须去创建logger,这会关联到包含下面因素的LoggerConfig a)同样名称的logger,b)父包的名称 或者c)root LoggerConfig。LoggerConfig对象通过配置中的Logger声明来创建。LoggerConfig会关联上appender,实际上由他负责传递LogEvent。
Logger的层级
任何超越System.out.println的日志API,最首要的优势就是它有能力使某些指定的日志代码片段失效,同时允许其他的正常打印。这种能力假定日志的空间,所有可能的日志片段的空间,基于开发者给出的条件进行分类。
在Log4j 1.x中,Logger 层级通过Logger之间的关系进行维护。在Log4j2中,这种关系不复存在。与之代替的,层级结构围护在LoggerConfig对象之间。
Logger和LoggerConfig是一组被命名的实体。Logger的命名是大小写敏感的,并且遵守层级命名规范:
命名层级
一个LoggerConfig可以被称为另外一个LoggerConfig的祖先,如果他的名字后面跟了一个点作为子孙logger名称的前缀。一个LoggerConfig可以称为孩子LoggerConfig的父亲,如果在他们之间没有任何祖先。
例如,一个loggerConfig称为"com.foo",是"com.foo.bar"这个logger的父亲。相似的,"java"是"java.util"的父亲,是"java.util.Vector"的祖先。这种命名的方式应该对于绝大多数开发者是很熟悉的。
root loggerConfig存在于LoggerConfig层级的顶层。它作为一个例外,它一直存在并且它是任何层级的一部分。直接关联到root LoggerConfig上的logger,可以按如下方式得到:
Logger logger = LogManager.getLogger(LogManager.ROOT_LOGGER_NAME);
其实,还可以更为简单:
Logger logger = LogManager.getRootLogger();
所有其他的Logger也可以通过传递Logger名称给LogManager.getLogger静态方法被查到。更多关于Logging API的信息可以在访问Log4j 2 API.
LoggerContext
LoggerContext扮演着日志系统中的锚点角色。其实,我们可能在程序中有多个激活的LoggerContext,这取决于环境。关于LoggerContext更多的细节,在Log Separation 章节。
Configuration
每个LoggerContext都有一个激活的Configuration。这个configuration含有所有的Appender,context-wide Filter,LoggerConfig,并且含有StrSubstitutor的引用。在重新配置的过程中,会存在两个Configuration对象。一旦所有的Logger都重新关联到新的配置上,旧的configuraion就会被停掉并被抛弃。
Logger
像之前所说,Logger通过调用LogManager.getLogger来创建。Logger本身并不会执行任何直接的动作。他只是有个名称并且关联到LoggerConfig上。它继承于AbstractLogger ,实现了必要的方法。在当配置发生变化时,Logger可能被关联到另外的loggerConfig上。这会使得他们的行为被修改。
Retrieving Logger
通过传入同样名称,调用LogManager.getLogger,将会返回给你同样的Logger对象引用。
例如:
Logger x = LogManager.getLogger("wombat");
Logger y = LogManager.getLogger("wombat");
X和y关联到同一个logger对象上。
Log4j环境的配置一般会在程序初始化的时候完成。最佳的方式是读取配置文件。这些在 Configuration中讨论。
Log4j使得通过软件组件命名logger变得很容易。我们可以在class中通过初始化Logger来完成,使用class的全路径名称作为logger的名称。这是很有用,并且直接的定义logger的方式。当log输出产生日志的logger名称时,这种命名策略使得识别log消息的来源变得很简单。然而,这只是一个可能的,普通的,命名logger的策略。Log4j并不会限制,开发者可以自由命名。
用自己class的名称来命名logger是通用的习惯,所以LogManager.getLogger()非常的方便,可以自动使用class名称作为Logger的名称。
虽然如此,以logger所在的class来命名logger,看起来还是目前最好的策略。
LoggerConfig
日志配置中声明Logger的时候,LoggerConfig对象被创建。LoggerConfig包含了一组Filter,在LogEvent传递给任何的appender之前,他必须允许LogEvent通过。他含有一组用于处理event的appender引用。
Log Level
LoggerConfig会被指定Log Level。内置的Level包含 TRACE,DEBUG,INFO,WARN,ERROR,及FATAL。Log4j 2也支持客制化log level。另外一种得到更细粒度的机制是使用Markers 代替。
Log4j 1.x及Logback都有"Level继承" 的概念,在Log4j 2中,Logger及LoggerConfig是两个不同的对象。所以这个概念实现的不太一样。每个Logger关联到适合的LoggerConfig上,LoggerConfig可以反过来关联到他的父亲上,这可以达到同样的效果。
下面五张表中,展示了很多给定的level值,以及会造成的关联到每个Logger上的level。注意下面所有的情况,如果root LoggerConfig没有被配置,默认的层级将会被指定给它。
Logger 名称 | 被分配的 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中,只有root logger被配置了,并且有log level。所有其他的Logger关联到root LoggerConfig上,并且使用它的level
Logger 名称 | 被分配的 LoggerConfig | LoggerConfig Level | Logger Level |
root | root | DEBUG | DEBUG |
X | X | ERROR | ERROR |
X.Y | X.Y | INFO | INFO |
X.Y.Z | X.Y.Z | WARN | WARN |
例2中,所有的logge都有配置的LoggerConfig,并且从它那里获取level
Logger 名称 | 被分配的 LoggerConfig | LoggerConfig Level | Logger Level |
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。X.Y Logger没有配置匹配名称的LoggerConfig,所以使用了X这个LoggerConfig,因为这个LoggerConfig的名称最长匹配了Logger的名称的开始。
Logger 名称 | 被分配的 LoggerConfig | LoggerConfig Level | Logger Level |
root | root | DEBUG | DEBUG |
X | X | ERROR | ERROR |
X.Y | X | ERROR | ERROR |
X.Y.Z | X | ERROR | ERROR |
例4中,root和X logger都有自己配置的同名LoggerConfig。X.Y、X.Y.Z没有配置的LoggerConfig,所以她们的level来自于分配给它们的X这个LoggerConfig。因为他的名称最长匹配了logger名称的开始
Logger 名称 | 被分配的 LoggerConfig | LoggerConfig Level | Logger Level |
root | root | DEBUG | DEBUG |
X | X | ERROR | ERROR |
X.Y | X.Y | INFO | INFO |
X.YZ | X.Y | ERROR | ERROR |
例5中,root、X、X.Y都有各自配置好的同名LoggerConfig。X.YZ这个logger没有配置好的LoggerConfig,所以他的level来自于指定给他的loggerConfig X。因为X名称最长匹配了logger名称的开始。他并没有关联到X.Y这个loggertConfig,因为句点后的字符并没有精确匹配。
Logger 名称 | 被分配的 LoggerConfig | LoggerConfig Level | Logger Level |
root | root | DEBUG | DEBUG |
X | X | ERROR | ERROR |
X.Y | X.Y | ERROR | |
X.Y.Z | X.Y | ERROR |
例6中,X.Y这个LoggerConfig没有配置level,所以他继承了X这个LoggerConfig的level。X.Y.Z这个logger使用X.Y这个LoggerConfig,因为他没有同名匹配的LoggerConfig。他也从LoggerConfig X那里继承了他的日志level。
下面的表格表明了Level过滤是如何工作的。表各种纵向表头是logEvent的level,横向的表头是关联了相应level的LoggerConfig。相交点指出了是否LogEvent允许被传递做进一步处理,还是被丢弃。
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可以应用在把控制传递给任何LoggerConfig之前。在控制传递给LoggerConfig之后但是调用任何appender之前,在控制传递给LoggerConfig之前,但是在调用指定的Apeender之前。方式上很像防火墙filter,每个filter可以返回三种结果,Accept、Deny、Neutral。Accept含义是其他的Filter都不用再调用了,event应该被处理。Deny意味着event要立即被忽略,控制返回给调用者。Neutral表明event要被传递给其他的Filter。如果这里没有其他的filter,那么event将被处理。
尽管event可能被filter接收,但是仍旧有可能不被记录。这可能发生在,当event被前置的LoggerConfig Filter所接受,但是被LoggerConfig的filter所拒绝或者被所有的appender所拒绝。
Appender
基于logger配置,选择性启用或者禁用日志请求的能力只是整体的一小部分。Log4j允许日志请求来记录日志到不同的目的地。在Log4j中的叫法,输出的目的地称为Appender。当前,Appender有console,file,remote scoket服务器,Apache Flume,JMS,remote UNIX Syslog daemon,还有很多种DB的API。学习 Appenders 章节来获取更多可用类型的信息。一个Logger可以分配不止一个Appender。
Appender可以被添加进logger,通过调用当前configuration的addLoggerAppender 方法。如果匹配Logger名称的LoggerConfig并不存在,那么会创建一个,Appender会被附上,之后所有Logger都会被通知更新他们的LoggerConfig引用。
对于给定的logger,任何启用的日志请求,都会被传送给这个Logger的LoggerConfig中所有的appender,同样传递给LoggerConfig的父亲的appender。换句话讲,Appender通过LoggerConfig的层级被追加继承。举个例子,如果console Appender被添加进root Logger,那么所有启用的日志请求都会至少在console中打印。如果一个额外的文件appender被添加到一个叫做C的LoggerConfig中,那么对于C及C的孩子,所有的可用的日志请求都会被记录到文件里及console中。我们也可以通过在配置文件中,关于Logger的声明部分设置addtivity="false",以此来重写默认的行为。
支配appender追加属性的规则总结如下:
Appender追加性
Logger L的日志输出会进入和L关联的LoggerConfig以及这个loggerConfig祖先的所有Appender。这就是appender追加性的含义。
然而,如果一个Logger L关联的LoggerConfig的祖先,称之为P吧,他的additivity标识被设为了false,之后L的输出将会直接给所有L的loggerConfig中的appender,也会给向上直到P的祖先的appender,包含P。但是并不会给P的任何祖先的appender。
Logger的additivity标识默认设为true
下表展示了一个例子:
Logger Name |
Added Appenders |
Additivity Flag |
Output Targets | Comment |
root | A1 | not applicable | A1 | The root logger has no parent so additivity does not apply to it. |
x | A-x1, A-x2 | true | A1, A-x1, A-x2 | Appenders of "x" and root. |
x.y | none | true | A1, A-x1, A-x2 | Appenders of "x" and root. It would not be typical to configure a Logger with no Appenders. |
x.y.z | A-xyz1 | true | A1, A-x1, A-x2, A-xyz1 | Appenders in "x.y.z", "x" and root. |
security | A-sec | false | A-sec | No appender accumulation since the additivity flag is set to false. |
security.access | none | true | A-sec | Only appenders of "security" because the additivity flag in "security" is set to false. |
Layout
通常,用户想要客制化的不仅仅是输出的目的地,还有输出的格式。这可以通过给appender关联layout来达成。Layout负责根据用户希望的那样来格式化LogEvent,反之,appender负责把格式好的输出发送到目的地。PatternLayout,log4j标准发布版中的一部分,可以让用户指定输出的格式,通过类似C语言print函数中约定的pattern。
例如,使用转换pattern的PatternLayout "%r [%t] %-5p %c - %m%n" 将会输出类似下面的内容:
176 [main] INFO org.foo.Bar - Located nearest gas station.
第一个字段是程序启动后,经过的毫秒数。第二个字段是产生日志的线程。第三个字段是日志片段的等级,第四个字段是logger关联的名称。‘-’后面的文本是消息片段。
Log4j 带有许多不同的Layout,用于各种情况,如JSON、XML、HTML及Syslog。其他database连接器appender,需要输入制定的字段,而不是普通的文本layout。
重要的是,log4j可以渲染 log消息的内容,通过用户指定的条件。例如,如果你需要频繁的记录Oranges,你当前项目中的一种对象类型。你可以创建Orangemessage,它可以接受Orange的实体并且把它传递给Log4j。这样在需要的时候,Orange对象可以被格式化适合的byte数组。
StrSubstitutor and StrLookup
StrSubstitutor 类 和 StrLookup 接口,其实是借自 Apache Commons Lang ,然后经过修改后支持LogEvent的求值。另外Interpolator class也是借自Apache Commons Configuration,来让StrSubstitutor通过各种StrLookup去查变量的值。他也被修改来支持logEvent求值。放在一起,它们提供了一个机制,允许相关变量的配置来自于System Properties,配置文件,ThreadContext Map,LogEvent中的StructuredData。在配置处理或者在每个event被处理的时候,如果组件有处理变量的能力,那么变量将被赋值。查看Lookups 获取更多信息。