Log4j2分析与实践-配置详解

Log4j2提供了4种配置方式:

(1)使用XML、JSON、YAML或properties格式的配置文件

(2)以硬编码的方式实现ConfigurationFactory和Configuration接口。

(3)以硬编码的方式调用Configuration暴露的API,可向默认配置中添加组件。

(4)以硬编码的方式调用Logger类的方法。

配置示例请查看上篇文章,本文主要讲解各个配置项的含义。本文仅介绍常用的XML配置文件的使用方法,其它配置方式请关注后续文章。

加载配置文件

Log4j2在启动的时候,会扫描实现了ConfigurationFactory接口的插件,并且以设定的优先级进行排序。Log4j2包含4种类型的ConfigurationFactory,分别用于JSON、YAML、properties和XML格式的配置文件的解析和构建。

(1)如果设置了系统变量log4j.configurationFile(即配置文件所在的路径),Log4j2会尝试使用ConfigurationFactory来加载配置。

(2)若没有设置系统变量log4j.configurationFile,ConfigurationFactory在classpath(可以是Java代码文件根路径)查找log4j2-test.properties文件。

(3)若没有找到log4j2-test.properties文件,YAML ConfigurationFactory将会在classpath查找log4j2-test.yaml或者log4j2-test.yml。

(4)若没有找到log4j2-test.yaml或者log4j2-test.yml,JSON ConfigurationFactory将会在classpath查找log4j2-test.json或者log4j2-test.jsn。

(5)若没有找到log4j2-testlog4j2-test.json或者log4j2-test.jsn,XML ConfigurationFactory将会在classpath查找log4j2-test.xml。

(6)若没有找到log4j2-test.xml,properties ConfigurationFactory将会在classpath查找log4j2.properties(注意有个2,不是log4j.properties)。

(7)若没有找到log4j2.properties,YAML ConfigurationFactory将会在classpath查找log4j2.yaml或者log4j2.yml。

(8)若没有找到log4j2.yaml或者log4j2.yml,JSON ConfigurationFactory将会在classpath查找log4j2.json或者log4j2.jsn。

(9)若没有找到log4j2.json或者log4j2.jsn,XML ConfigurationFactory将会在classpath查找log4j2.xml。

(10)若没有找到任何配置文件,Log4j2将会使用默认配置DefaultConfiguration,这将会把日志信息输出到控制台。

默认配置

若Log4j2找不到配置文件,就会使用类DefaultConfiguration中默认的配置。默认配置会将所有的日志输出到控制台,输出的默认日志级别可由系统参数

org.apache.logging.log4j.level来进行指定。若没有指定默认日志级别,那么Log4j2会使用ERROR级别。日志事件将会以Message类中提供的基本格式打印出来。默认配置对应的XML等价配置如下:

<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN">
<Appenders>
<Console name="Console" target="SYSTEM_OUT">
<PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
</Console>
</Appenders>
<Loggers>
<Root level="ERROR">
<AppenderRef ref="Console"/>
</Root>
</Loggers>
</Configuration>

其中,status表示Log4j框架内部日志的输出级别,Appenders表示输出日志目的地,target="SYSTEM_OUT"表示输出到标准系统输出(可理解为控制台),Loggers表示一个日志记录器,PatternLayout即为日志格式,AppenderRef为日志记录器关联的Appender。具体信息后文会讲解!

自动重新加载配置

若是通过文件的方式配置的,Log4j2可以自动检测配置文件发生的变化,然后重新加载配置。要达到这种效果,需要在Configuration标签上设置一个属性monitorInterval,它表示每隔多少秒检测一次配置文件的变化,最小值为5秒。下面的示例表示每隔30秒检测一次配置变更:

<?xml version="1.0" encoding="UTF-8"?>
<Configuration monitorInterval="30">
...
</Configuration>

Configuration标签

Configuration是XML配置文件的根标签,所有的其它元素均需包含在这个标签中,这个标签的属性如下:

advertiser:(可选)Advertiser的插件名称,用于通过IP和端口向外广播FileAppender或SocketAppender的配置,Log4j2提供的唯一Advertiser插件是multicastdns。若需要使用Chainsaw实时查看日志,就需要将此属性设置为multicastdns,即允许向外广播Appender的路径和格式信息,关于广播机制的具体讲解请关注后续文章。

dest:Log4j2框架内的StatusLogger打印日志的目的地,取值可为out,err或者一个文件的URI,默认为标准的输出。

monitorInterval:检测配置文件的最小间隔时间,用于自动重新配置更新的配置文件。

name:配置的名称,对于打印日志的功能而言没有实质的作用。

packages:插件所在的包路径,使用逗号分割多个包,每个类加载器仅加载一次,所以即使这个值在运行期被修改了,也不会自动重新配置。

schema:指定用于验证配置的XML Schema,仅在strict被设置为true时生效。若没有设置,将不会有验证。

shutdownHook:指定当JVM停止的时候,Log4j是否应该停止。停止钩子默认是启用的,也可以通过设置这个属性点值为disable来禁用它。

shutdownTimeout:指定当JVM停止时,appenders和后台任务需要等多少毫秒再停止。默认值为0,也就是说每个appender将会使用自己的默认超时时间,不会等待后台任务。若把这个值设置得太小的话,会增加丢失重要日志的风险。

status:Log4j2内部日志事件的级别。当需要排查Log4j2的问题时,建议设置为trace,因为它会将Log4j2初始化、切换和其它内部操作打印出来。

strict:启用严格的XML格式,为了防止可能会遇到的一些严格模式与简单模式的问题,推荐启用严格模式。在properties和JSON文件配置中均不支持。后文会讲解此属性。

verbose:加载插件时启用诊断信息。

简单格式与严格格式

Log4j2可以使用两种XML格式进行配置:简单格式和严格格式。简单格式显得一目了然,因为元素名称和它们的实际组件含义是一致的(这条规则也不是绝对的,因为在严格模式里配置strategy时,也是将组件的名称作为XML元素的名称),但是不能使用XML Schema进行校验。比如,对于ConsoleAppender而言,就可以在父标签appenders元素下定义一个名为Console的标签,然而元素和属性名称是大小写不敏感的。另外,属性既可以作为XML元素的属性,也可以作为一个带有值的XML标签元素。比如

<PatternLayout pattern="%m%n"/>


<PatternLayout>
<Pattern>%m%n</Pattern>
</PatternLayout>

是等价的。前文所述的默认配置等价XML配置中,即为简单格式。
严格格式可以使用XML Schema来校验,每个Appender、Logger等组件的XML元素名称只能使用指定的类型名称,将前文默认配置改为严格模式如下:

<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN">
<Appenders>
<Appender type="Console" target="SYSTEM_OUT">
<PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
</Appender>
</Appenders>
<Loggers>
<Root level="ERROR">
<AppenderRef ref="Console"/>
</Root>
</Loggers>
</Configuration>

这里的Root Logger是例外,必须用这种方式配置。推荐使用严格模式,因为简单模式可能会造成单个appender的配置会占用较大篇幅,且不支持使用XML Schema进行校验。后续文章的讲解也使用严格模式。

配置Loggers

LoggerConfig是使用logger元素来配置的,必须给logger元素指定一个名称,也通常会指定一个日志级别属性和additivity属性。日志级别可以是TRACE、DEBUG、INFO、WARN、ERROR、ALL或者OFF,若没有指定日志级别,就会使用默认级别ERROR。可以为additivity指定值true或者false,若没有指定,则默认为false。

LoggerConfig也可以使用属性(properties)文件来配置,这些配置的属性将会被添加到从ThreadContextMap拷贝而来的属性中。这些配置的属性可以被Appenders、Filters、Layout等组件引用,就像它们来自于ThreadContext Map一样。配置的属性变量将会在配置被解析时,或者每次日志事件被打印时生效。

LoggerConfig可以配置一个或多个AppenderRef元素,每个引用的appender就会和LoggerConfig关联起来。如果配置了多个appender,那么在执行日志事件时,每个appender都会被调用。

每个配置必须有一个根logger,即使没有显式地配置,Log4j2会自动添加一个默认配置,默认的级别为ERROR,默认的appender为Console。根logger与其它logger的主要不同点为:

(1)根logger没有name属性。

(2)根logger不支持additivity属性,因为它对应的LoggerConfig没有父亲。

配置Appenders

配置Appenders的两种方式:

(1)直接使用appender的插件名称作为XML的元素名称。

(2)使用appender作为XML的元素名称。

每个appender必须配置一个name属性,且在appender集合中保持唯一,在logger中就可以通过这个名称来引用appender。

大部分appender都支持配置layout,layout的配置也有两种方式,与appender的两种方式类似。

配置Filters

Log4j2可以在四个位置配置过滤器:

(1)跟appenders、loggers和properties一个层级,这些过滤器会接受或拒绝被传递给LoggerConfig之前的日志事件。

(2)在一个logger元素中,这些过滤器会接受或拒绝指定logger的日志事件。

(3)在一个appender元素中,这些过滤器会接受或拒绝即将传递给指定appender执行的日志事件。

(4)在一个logger的AppenderRef元素中,这些过滤器被用于决定是否应该将日志事件路由到对应的appender。

filters元素里可以配置任意数量个filter,配置示例:

<filters>
<MarkerFilter marker="FLOW" onMatch="ACCEPT" onMismatch="NEUTRAL"/>
<MarkerFilter marker="EXCEPTION" onMatch="ACCEPT" onMismatch="DENY"/>
</filters>

使用properties文件配置

从Log4j 2.4开始支持通过properties文件来配置,且配置语法与Log4j1不同。就跟XML的配置一样,properties文件也是使用插件或者插件属性来配置的。

在Log4j 2.6以前,properties配置需要使用唯一标识列举出appenders、filters和loggers列表,并且使用逗号分隔。每个元素在properties配置文件中的配置方式为<元素类型>.<唯一标识>,比如appender.rolling.xxx,唯一标识中不得包含点号。

从Log4j 2.6开始,这个唯一标识列表不再必须,Log4j2会自动推导出这个列表。

与根元素appenders等不同的是,在创建子元素的时候,不能在子元素里指定一个唯一标识列表。你必须根据子元素类型定义一个包装元素,示例如下:

#唯一标识为rolling,time为定义的包装元素,TimeBasedTriggeringPolicy为基于时间触发切换日志文件的策略。
appender.rolling.policies.time.type = TimeBasedTriggeringPolicy

属性替换

Log4j2中支持使用占位符来引用其它地方定义的属性,其中一些属性会在配置文件被解析时就生效,其它的可能会传递给组件而在运行时生效。Log4j2使用到了Apache Commons Lang的StrSubstitutor和StrLookup类。从某种程度上来讲跟Ant和Maven相似,类似${xxx}的变量会被配置中定义属性所替换。示例:

<Appender type="RollingFile" name="demoAppender" fileName="${fileName}"
filePattern="${fileName}.%d{yyyyMMdd}">
<Layout type="PatternLayout" pattern="%m%n" />
</Appender>

其中的${fileName}就是一个占位符变量,Log4j2会自动使用properties属性中定义的值替换这个占位符。属性的来源有很多,Log4j2支持语法${prefix:name},prefix表示Log4j2应该从哪个上下文去取得变量值。常见的上下文如下:

(1)bundle,即资源包,格式为${bundle:BundleName:BundleKey},BundleName为完整的类路径,比如:${bundle:com.domain.Messages:MyKey}。

(2)ctx,ThreadContext Map。

(3)date,根据指定格式插入当前日期或时间。

(4)env,系统环境变量。

(5)jndi,默认JNDI上下文。

(6)jvmrunargs,JVM通过JMX访问的输入参数。

(7)log4j,Log4j的配置属性。

(8)main,MapLookup.setMainArguments(String[])设置的值。

(9)map,来自MapMessage的值。

(10)sd,来自StructuredDataMessage的值。

(11)sys,系统属性。

以两个$符号开头的占位符

StrLookup的一个有趣的过程是,当一个变量的引用以两个$符号开头时,每次解析这个变量就会将最后一个$删除。为什么要删除呢?因为Log4j2在解析配置文件的时候,就会尝试替换$占位符,如果我们需要占位符在运行时替换,而不是解析配置文件的时候替换,那么我们就在占位符前多加一个$。在解析配置文件的时候把第一个$当作转义字符,即配置文件被解析之后只会剩下一个$符号,另一个就被删除了,而在运行时,这个占位符只包含一个$符号,就可被顺利替换了。示例:

<Properties>
<Property name="filename">target/rolling1/rollingtest-$${sd:type}.log</Property>
</Properties>

脚本

Log4j2在一些组件中支持使用JSR 223脚本语言,任何支持JSR 223脚本引擎的语言都可以在Log4j2组件中使用。如果Configuration元素的status属性设置为DEBUG,当前安装的脚本引擎列表会在控制台中显示出来。尽管一些引擎不是线程安全的,但Log4j2采取措施来确保脚本在线程安全的环境下执行。

支持使用脚本的组件使用<script>、<scriptFile>或者<scriptRef>元素来配置。script元素包含名称name、脚本的语言language和脚本内容text。scriptFile包含名称name、脚本文件位置path和语言language等。scriptRef表示引用脚本的名称。
配置示例如下:

<Scripts>
<Script name="selector" language="javascript"><![CDATA[
var result;
if (logEvent.getLoggerName().equals("JavascriptNoLocation")) {
result = "NoLocation";
} else if (logEvent.getMarker() != null && logEvent.getMarker().isInstanceOf("FLOW")) {
result = "Flow";
}
result;
]]></Script>
<ScriptFile name="groovy.filter" path="scripts/filter.groovy"/>
</Scripts>
<Appenders>
<Console name="STDOUT">
<ScriptPatternSelector defaultPattern="%d %p %m%n">
<ScriptRef ref="selector"/>
<PatternMatch key="NoLocation" pattern="[%-5level] %c{1.} %msg%n"/>
<PatternMatch key="Flow" pattern="[%-5level] %c{1.} ====== %C{1.}.%M:%L %msg ======%n"/>
</ScriptPatternSelector>
<PatternLayout pattern="%m%n"/>
</Console>
</Appenders>

脚本中使用到的变量可在组件后续的单独介绍文章中找到。

XInclude

当我们实际应用系统中配置的日志过多时,一个配置文件就显得太大,我们可以将appenders和loggers拆为两个文件进行配置,最后再log4j2.xml中合并起来,log4j2.xml文件示例如下:

<?xml version="1.0" encoding="UTF-8"?>
<configuration xmlns:xi="http://www.w3.org/2001/XInclude"
status="warn" name="XIncludeDemo">
<properties>
<property name="filename">xinclude-demo.log</property>
</properties>
<ThresholdFilter level="debug"/>
<xi:include href="log4j-xinclude-appenders.xml" />
<xi:include href="log4j-xinclude-loggers.xml" />
</configuration>

log4j-xinclude-appenders.xml和log4j-xinclude-loggers.xml就被合并到一起了。

组合配置

上面所讲的XInclude仅仅将多个文件的内容按顺序合并到一起,那么有没有办法将多个完整的log4j2.xml的内容合并到一起呢?是有的!只需要在环境变量log4j.configurationFile指定多个配置文件的路径,且以逗号分开。默认的合并规则如下:

(1)全局配置中的属性attribute取最后扫描到的值,例外的是,采用最高的日志级别和最低的非零monitorInterval。

(2)properties属性值取最后扫描到的值。

(3)多个filter会被合并到CompositeFilter下面。

(4)Scripts和ScriptFile取最后扫描到的值。

(5)Appenders取最后扫描到的值。

(6)Loggers取最后扫描到的值。

status消息

我们在配置Log4j2的过程中,可能会遇到一些问题,比如日志打印不出来,但是又没有报错信息,那么该如何定位问题呢?很简单,只需要设置一下Configuration元素的status属性(Log4j2框架内部日志级别)就好了,若设置为trace,将会把Log4j2所有的启动流程日志打印出来。

系统属性

Log4j2可以引用大量系统属性来控制Log4j2行为的方方面面,可以在classpath目录下的log4j2.component.properties配置系统属性的值,以下列举部分常见的系统属性变量:

(1)log4j.configurationFile,配置文件所在位置,若log4j2.xml不在classpath目录下,那么可以通过这个系统属性指定log4j2.xml的路径。

(2)log4j.mergeFactory用于合并多个配置文件的插件,这个插件需要实现MergeStrategy接口。默认使用DefaultMergeStrategy。

(3)Log4jContextSelector用于创建LoggerContext,默认为org.apache.logging.log4j.core.selector .ClassLoaderContextSelector,即为不同的web应用创建独立的LoggerContext。也可以指定为org.apache.logging.log4j.core.async .AsyncLoggerContextSelector,它创建的LoggerContext会使得所有的logger都是异步的,可以大大提升日志打印性能。