Log4j Configuration

本文用来学习关于Log4j的配置(通过配置文件的方式),原文档连接

Configuration

将日志请求插入到application代码中需要相当多的计划和努力。观察表明,大约百分之四的代码用于记录日志。因此,即使是一般大小的application也将会有数以千计的日志片段嵌套在代码中。考虑到这个数量,如何不需要手动修改就能管理这些日志片段就显得十分重要。
配置Log4j 2版本,能够通过下面四种方法中的任意一种来完成:
1、通过一个以XML、JSON、YAML或properties格式的配置文件。
2、编程方式,通过创建一个ConfigurationFactory和Configuration实现。
3、编程方式,通过调用在Configuration接口中的APIs来添加组件到默认配置。
4、编程方式,通过调用内部Logger类的方法。
本文主要关注通过一个配置文件来配置Log4j。对于通过编程方式来配置Log4j,可以在Extending Log4j 2Programmatic Log4j Configuration
注意,不同于Log4j 1.x,Log4j 2的公共API没有公开关于添加、修改和移除appenders和filter的方法,或者以任何方式来操作配置。

Automatic Configuation

Log4j有在初始化期间进行自我配置的能力。当Log4j启动时,它将安装所有的ConfigurationFactory插件,并且按照权重从高到底对插件进行整理。正如之前所说的,Log4j包含四种ConfigurationFactory实现:JSON、YAML、properties和XML。
1、Log4j将会检查”log4j.configurationFile”系统属性,如果设置了该属性,将使用与文件扩展名匹配的ConfigurationFactory来尝试加载配置。
2、如果没有设置系统属性,properties ConfigurationFactory将在classpath中查找log4j2-test.properties文件。
3、如果没有发现properties的配置文件,将会使用YAML ConfigurationFactory在classpath中查找log4j2-test.yaml文件或log4j2-test.yml文件。
4、如果没有发现YAML配置文件,将会使用JSON ConfiguationFactory在classpath中查找log4j2-test.json文件或log4j2-test.jsn文件。
5、如果没有发现JSON配置文件,将会使用XML ConfigurationFactory在classpath中查找log4j2-test.xml文件。
6、如果没有发现任何测试的配置文件,那么properties ConfigurationFactory将会在classpath中查找log4j2.properties文件。
7、如果没有任何properties配置文件被装载,那么YAML ConfigurationFactory将会在classpath中查找log4j2.yaml文件或log4j2.yml文件。
8、如果没有任何YAML配置文件被装载,那么JSON ConfigurationFactory将在classpath中查找log4j2.json文件或log4j2.jsn文件。
9、如果没有任何JSON配置文件被装载,那么XML ConfigurationFactory将在classpath中查找log4j2.xml文件。
10、如果没有任何配置文件被装载,那么将使用DefaultConfiguration。这将使被记录的日志输出到控制台。

一个用例,例子将解释名为MyApp的application将如何使用log4j。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import com.foo.Bar;
// Import log4j classes.
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.LogManager;
public class MyApp {
// Define a static logger variable so that it references the
// Logger instance named "MyApp".
private static final Logger logger = LogManager.getLogger(MyApp.class);
public static void main(final String... args) {
// Set up a simple configuration that logs on the console.
logger.trace("Entering application.");
Bar bar = new Bar();
if (!bar.doIt()) {
logger.error("Didn't do it.");
}
logger.trace("Exiting application.");
}
}

MyApp通过导入log4j相关的类开始。然后使用类的完全限定名定义了一个名为MyApp的静态logger变量。
MyApp使用在com.foo包中定义的Bar类。

1
2
3
4
5
6
7
8
9
10
11
12
13
package com.foo;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.LogManager;
public class Bar {
static final Logger logger = LogManager.getLogger(Bar.class.getName());
public boolean doIt() {
logger.entry();
logger.error("Did it again!");
return logger.exit(false);
}
}

如果没有装配到任何配置文件,Log4j将提供默认配置。默认配置,在类DefaultConfiguration中提供,将会启动:
1、一个附加到root logger的ConsoleAppender。
2、一个附加到ConsoleAppender的格式为”%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n”的PatternLayout。
注意,默认分配给Log4j的root logger的是 Level.ERROR。
MyApp的输出类似:

1
2
17:13:01.540 [main] ERROR com.foo.Bar - Did it again!
17:13:01.540 [main] ERROR MyApp - Didn't do it.

正如之前描述的,Log4j将首先尝试从配置文件配置它自己。与默认配置相同的配置看起来是这样的:

1
2
3
4
5
6
7
8
9
10
11
12
13
<?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>

一旦将上面的配置作为log4j2.xml放到classpath下,你将会得到与上面所列出的相同的结果。将root的级别修改为trace将得到类似的结果:

1
2
3
4
5
6
17:13:01.540 [main] TRACE MyApp - Entering application.
17:13:01.540 [main] TRACE com.foo.Bar - entry
17:13:01.540 [main] ERROR com.foo.Bar - Did it again!
17:13:01.540 [main] TRACE com.foo.Bar - exit with (false)
17:13:01.540 [main] ERROR MyApp - Didn't do it.
17:13:01.540 [main] TRACE MyApp - Exiting application.

注意,在使用默认配置时,记录日志的状态将被禁用。

Additivity

获取希望消除来自除了com.foo.Bar之外的所有TRACE输出。简单的修改日志级别不能达到这个目的。而是需要在配置中增加一个新的logger:

1
2
3
4
<Logger name="com.foo.Bar" level="TRACE"/>
<Root level="ERROR">
<AppenderRef ref="STDOUT">
</Root>

使用这个配置,来自com.foo.Bar的所有日志事件将被记录,而来只有自其他组件的事件,只有error的会被记录。
在之前的例子中,所有来自com.foo.Bar的事件将继续被写到控制台。这是因为com.foo.Bar的logger还没有添加任何appender,而它的父级添加了。实际上,下面的配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?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>
<Logger name="com.foo.Bar" level="trace">
<AppenderRef ref="Console"/>
</Logger>
<Root level="error">
<AppenderRef ref="Console"/>
</Root>
</Loggers>
</Configuration>

结果如下:

1
2
3
4
5
6
17:13:01.540 [main] TRACE com.foo.Bar - entry
17:13:01.540 [main] TRACE com.foo.Bar - entry
17:13:01.540 [main] ERROR com.foo.Bar - Did it again!
17:13:01.540 [main] TRACE com.foo.Bar - exit (false)
17:13:01.540 [main] TRACE com.foo.Bar - exit (false)
17:13:01.540 [main] ERROR MyApp - Didn't do it.

注意,来自com.foo.Bar的message出现了两次。这是因为与名为com.foo.Bar的logger关联的appender首先被使用,它将第一个实例(记录)写到控制台。接着,com.foo.Bar的父级,在本例中是root logger,被使用。接着事件传递到它的appender,也将日志写到控制台,就是控制台中的第二个实例(记录)。虽然附加性是一个相当方便的特性(例如在前面的例子中,不需要配置配置appender),但是在很多的例子中,这个行为被认为是讨厌的,并且会通过在logger上设置附加性的属性为false对其进行禁用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?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>
<Logger name="com.foo.Bar" level="trace" additivity="false">
<AppenderRef ref="Console"/>
</Logger>
<Root level="error">
<AppenderRef ref="Console"/>
</Root>
</Loggers>
</Configuration>

一旦一条事件到达附加性被设置为false的logger,将不会传递给它的父级logger,而不管父级logger的附加性设置为true还是false。

Automatic Reconfiguration

当我根据一个文件来配置log4j,log4j能够自动的发现配置文件的变更并重新配置它自己。如果monitorInterval属性被指定并设置为非零值,那么配置文集将被检查,下一次日志事件被评估并且被记录,并且会因为上一次的检查而被推迟。这个例子展示了如何配置这个属性,因此那个配置文件将会在至少30秒后才被检查是否有更改。最小的间隔为5秒。

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

Chainsaw can automatically process your log files (Advertising appender configurations)

log4j提供了为基于文件的appender和基于socket的appender”公布”appender配置细节的能力。例如,对于基于文件的appender,文件的位置和文件内的模式布局都包含在公布的信息中。其他外部系统能够发现这些公布的信息并使用这些信息聪明的处理日志文件。
这个机制通过一个广告来实现,而广告的格式是通过每个Advertiser实现来指定的。一个外部系统想要使用特定的Advertiser的实现来工作,就必须理解如何装载已公布的配置以及公布信息的格式。例如,一个”database” Advertiser可能需要在一个数据库表格中存储配置信息。一个外部系统能够读取数据库表格,以便发现文件的位置和文件的格式。
Log4j提供了一个Advertiser实现,一个’多路广播DNS’Advertiser,它使用http://jmdns.sourceforge.net库,通过IP的多路广播来公布appender配置细节。
Chainsaw自动发现log4j的由多路广播DNS生成的公布信息并将这些发现的公布信息放到Chainsaw的Zeroconf tab中(如果jmdns库包含在Chainsaw的classpath中)。要开始格式化并tail一个在公布信息中的日志文件,只需要双击Chainsaw的Zeroconf tab中的公布项。当前,Chainsaw只支持FileAppender的公告信息。
要公布一个appender配置:
1、添加来自http://jmdns.sourceforge.net的JmDns库到application的classpath中。
2、设置配置项的’advertiser’属性为’multicastdns’。
3、在appender项的’advertiser’属性为’true’。
4、如果公布一个基于FileAppender的配置,设置appender项的’advertiserURI’为一个合适的URI。
基于FileAppender配置要求在appender上指定一个额外的’advertiseURI’属性。这个’advertiseURI’属性为Chainsaw提供如何访问对应文件的信息。例如,这个文件对于Chainsaw来说,可能通过指定一个通用的VFS(http://commons.apache.org/proper/commons-vfs/)sftp://URI使用ssh/sftp可访问的,如果文件可以通过一个web服务来访问,可能需要使用http://URI,如果是从本地运行的Chainsaw实例来访问文件,可能需要指定file://URI。
这里有一个例子,启用了公布功能的appender的配置,可以被本地运行的Chainsaw使用来自动的tail日志文件(注意file://advertiseURI):
请注意,你必须添加来自http://jmdns.sourceforge.net的JmDns库到你application的classpath中,以便使用’nulticastdns’ advertiser来公布信息。

1
2
3
4
5
6
7
8
9
<?xml version="1.0" encoding="UTF-8"?>
<Configuration advertiser="multicastdns">
...
</Configuration>
<Appenders>
<File name="File1" fileName="output.log" bufferedIO="false" advertiseURI="file://path/to/output.log" advertise="true">
...
</File>
</Appenders>

Configuration Syntax

正如前面已经展示的以及后面将要展示的,Log4j允许你们轻松的定义日志记录的行为,而不需要修改你的application。可以为application的部分代码禁用日志记录,只在满足特定条件时才记录日志,例如特定用户执行的操作,转发输出到Flume或输出到日志报告系统等。
在XML文件中配置项有如下属性:

Attribute Name Description
advertiser (可选) Advertiser插件的名称,这个插件用来公布独特的FileAppender或SocketAppender配置。提供的唯一的Advertiser插件是’multicastdns’
dest 如果是”err”则输出标准错误,或者是一个文件路径或URL
monitorInterval 以秒为单位,时间的总数,在检查配置文件是否变更之前等待的时间
name 配置的名称
packages 用于搜索插件的以逗号分隔的包名称的列表。每个类加载器只加载插件一次,因此更改这个值可能对重新配置没有任何影响
schema 用来标识类加载器加载XML的模式,用来验证配置信息。只有当strict被设置为true时有效。如果没有设置,则验证不会发生。
shutdownHook 指定当JVM关闭时Log4j是否也自动关闭关闭。该功能是默认启用的,但是可以通过设置为”disable” 来禁用该功能。
status 应该被记录到控制台的内部Log4j事件的级别。该属性有效的值为”trace”、”debug”、”info”、”warn”、”error”和”fatal”。Log4j会将有关初始化、反转和其他内部操作的详细信息记录到状态logger。
strict 启用严格的XML格式。在JSON配置中不支持。
verbose 在加载插件的时候启用诊断信息。

Log4j能够使用两种XML特色来进行配置:简洁的和严格的。简洁的格式是配置更加容易,因为元素名称匹配它们代表的组件,然而它不能使用XML模式进行验证。例如,ConsoleAppender通过宣告一个名为Console的XML元素,将它自己配置在它父级appender元素下。然而,元素和属性的名称大小写不敏感。另外,属性可以做一个XML属性被指定也可以作为一个没有属性但是有一个字符串值的XML元素来指定。因此:

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


1
2
3
<PatternLayout>
<Pattern>%m%n</Pattern>
</PatternLayout>

是相同的。
下面的文件相当于一个XML结构的配置,但是注意斜体的元素代表了简洁的元素名称。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<?xml version="1.0" encoding="UTF-8"?>;
<Configuration>
<Properties>
<Property name="name1">value</property>
<Property name="name2" value="value2"/>
</Properties>
<filter ... />
<Appenders>
<appender ... >
<filter ... />
</appender>
...
</Appenders>
<Loggers>
<Logger name="name1">
<filter ... />
</Logger>
...
<Root level="level">
<AppenderRef ref="name"/>
</Root>
</Loggers>
</Configuration>

查看本页面中更过关于appender、filter和logger的声明的例子。

Strict XML

除了上面简洁的XML格式,Log4j允许配置被指定为一个更加“通用”的XML方式,这种方式可以使用XML模式对配置进行验证。这是通过使用下面所示的对象类型来替换友好的元素名称来实现的。例如,使用一个带有”Console”的类型属性的appender元素来替换使用元素名的Console来配置ConsoleAppender。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<?xml version="1.0" encoding="UTF-8"?>;
<Configuration>
<Properties>
<Property name="name1">value</property>
<Property name="name2" value="value2"/>
</Properties>
<Filter type="type" ... />
<Appenders>
<Appender type="type" name="name">
<Filter type="type" ... />
</Appender>
...
</Appenders>
<Loggers>
<Logger name="name1">
<Filter type="type" ... />
</Logger>
...
<Root level="level">
<AppenderRef ref="name"/>
</Root>
</Loggers>
</Configuration>

下面是使用了严格格式的配置示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="debug" strict="true" name="XMLConfigTest"
packages="org.apache.logging.log4j.test">
<Properties>
<Property name="filename">target/test.log</Property>
</Properties>
<Filter type="ThresholdFilter" level="trace"/>
<Appenders>
<Appender type="Console" name="STDOUT">
<Layout type="PatternLayout" pattern="%m MDC%X%n"/>
<Filters>
<Filter type="MarkerFilter" marker="FLOW" onMatch="DENY" onMismatch="NEUTRAL"/>
<Filter type="MarkerFilter" marker="EXCEPTION" onMatch="DENY" onMismatch="ACCEPT"/>
</Filters>
</Appender>
<Appender type="Console" name="FLOW">
<Layout type="PatternLayout" pattern="%C{1}.%M %m %ex%n"/><!-- class and line number -->
<Filters>
<Filter type="MarkerFilter" marker="FLOW" onMatch="ACCEPT" onMismatch="NEUTRAL"/>
<Filter type="MarkerFilter" marker="EXCEPTION" onMatch="ACCEPT" onMismatch="DENY"/>
</Filters>
</Appender>
<Appender type="File" name="File" fileName="${filename}">
<Layout type="PatternLayout">
<Pattern>%d %p %C{1.} [%t] %m%n</Pattern>
</Layout>
</Appender>
<Appender type="List" name="List">
</Appender>
</Appenders>
<Loggers>
<Logger name="org.apache.logging.log4j.test1" level="debug" additivity="false">
<Filter type="ThreadContextMapFilter">
<KeyValuePair key="test" value="123"/>
</Filter>
<AppenderRef ref="STDOUT"/>
</Logger>
<Logger name="org.apache.logging.log4j.test2" level="debug" additivity="false">
<AppenderRef ref="File"/>
</Logger>
<Root level="trace">
<AppenderRef ref="List"/>
</Root>
</Loggers>
</Configuration>

Configuration with JSON

除了XML,Log4j还可以使用JSON进行配置。JSON格式与宽松的XML格式类似。每个关键字代表了插件的名称并且以key/value对作为它的属性。如果一个key包含多个简单的值,那么它将是一个底层插件。在下面的例子中,ThresholdFilter,Console和PatternLayout全都是插件,而Console插件为它的name属性将会被分配一个值为STDOUT的值,ThresholdFilter将被分配一个debug级别。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
{ "configuration": { "status": "error", "name": "RoutingTest",
"packages": "org.apache.logging.log4j.test",
"properties": {
"property": { "name": "filename",
"value" : "target/rolling1/rollingtest-$${sd:type}.log" }
},
"ThresholdFilter": { "level": "debug" },
"appenders": {
"Console": { "name": "STDOUT",
"PatternLayout": { "pattern": "%m%n" }
},
"List": { "name": "List",
"ThresholdFilter": { "level": "debug" }
},
"Routing": { "name": "Routing",
"Routes": { "pattern": "$${sd:type}",
"Route": [
{
"RollingFile": {
"name": "Rolling-${sd:type}", "fileName": "${filename}",
"filePattern": "target/rolling1/test1-${sd:type}.%i.log.gz",
"PatternLayout": {"pattern": "%d %p %c{1.} [%t] %m%n"},
"SizeBasedTriggeringPolicy": { "size": "500" }
}
},
{ "AppenderRef": "STDOUT", "key": "Audit"},
{ "AppenderRef": "List", "key": "Service"}
]
}
}
},
"loggers": {
"logger": { "name": "EventLogger", "level": "info", "additivity": "false",
"AppenderRef": { "ref": "Routing" }},
"root": { "level": "error", "AppenderRef": { "ref": "STDOUT" }}
}
}
}

注意,在RoutingAppender中,Route元素作为一个数组被声明。这是有效的,因为每个数组元素将是一个Route组件。这种方式对于appender和filter是不好使的,因为其中的每个元素都有一个简洁格式的不同的名字。如果appender或filter声明一个名为type的属性,这个属性包含了appender的类型,那么appender和filter可以作为数组元素来定义。下面的例子说明了如何以数组的方式定义多个logger。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
{ "configuration": { "status": "debug", "name": "RoutingTest",
"packages": "org.apache.logging.log4j.test",
"properties": {
"property": { "name": "filename",
"value" : "target/rolling1/rollingtest-$${sd:type}.log" }
},
"ThresholdFilter": { "level": "debug" },
"appenders": {
"appender": [
{ "type": "Console", "name": "STDOUT", "PatternLayout": { "pattern": "%m%n" }},
{ "type": "List", "name": "List", "ThresholdFilter": { "level": "debug" }},
{ "type": "Routing", "name": "Routing",
"Routes": { "pattern": "$${sd:type}",
"Route": [
{
"RollingFile": {
"name": "Rolling-${sd:type}", "fileName": "${filename}",
"filePattern": "target/rolling1/test1-${sd:type}.%i.log.gz",
"PatternLayout": {"pattern": "%d %p %c{1.} [%t] %m%n"},
"SizeBasedTriggeringPolicy": { "size": "500" }
}
},
{ "AppenderRef": "STDOUT", "key": "Audit"},
{ "AppenderRef": "List", "key": "Service"}
]
}
}
]
},
"loggers": {
"logger": [
{ "name": "EventLogger", "level": "info", "additivity": "false",
"AppenderRef": { "ref": "Routing" }},
{ "name": "com.foo.bar", "level": "error", "additivity": "false",
"AppenderRef": { "ref": "Console" }}
],
"root": { "level": "error", "AppenderRef": { "ref": "STDOUT" }}
}
}
}

JSON支持使用Jackson Data Processor来格式化JSON文件。如果想要使用JSON作为配置,这些依赖必须要添加到项目中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.8.3</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.8.3</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>2.8.3</version>
</dependency>

Configuring loggers