本文主要内容:
1. 概述
我们为了分析程序的执行情况,需要把我们关心的一些调试信息、错误信息等保存到日志中,Log4j就是一个优秀的日志记录框架。本文基于Log4j 2.x版本,简要介绍Log4j 2.x的架构,讲述几个常用的Appender,Layout,Logger的配置方法,最后给出log4j2.xml的最简配置实例。
本文是Log4j的入门文章,不会讲解诸如异步机制、多进程共享、自定义Level、Filter、插件机制等高级特性,通过阅读本文,读者可以初步掌握Log4j 2.x的使用方法。
Log4j 2.x 拥有如下特性[1]:
- 支持异步机制,基于LMAX Disruptor Libaray(高吞吐量、低延时),比
Log4j 1.x
快10倍。 - 独立的垃圾回收机制,在Web应用中产生的GC压力小,使Web应用有更好的响应性能。
- 支持插件机制,具有很好的扩展性,配置文件简单,不需要指定类名。
- Appender支持自定义Layout,输出格式可定制。
- 使用JDK1.5提供的并发机制,解决
Log4j 1.x
的死锁问题,降低同步开销。
2. 架构图[2]
我们使用 LogManager 的静态方法LogManager.getLogger("name")
获取 Logger 实例时,LogManager 首先分配一个 LoggerContext,由 LoggerContext 来实例化具体的Logger,并给 Logger 绑定一个 LoggerConfig,LoggerConfig的属性由关联的 Configuration 决定,包括 Appenders 和 Filters。
3. Configuration配置
Configuration包含了Log4j的所有配置,可以通过配置文件和编写代码来配置。Log4j在初始化时,会自动加载classpath
路径中的配置文件来配置Configuration,Log4j支持4种格式的配置文件:XML,JSON,YAML 和 properties,Log4j根据文件名称按照优先级从高到低进行搜索,直到匹配到一个配置文件为止,顺序如下:
1. 系统属性`log4j.configurationFile`指定的配置文件
2. `log4j2-test.properties`
3. `log4j2-test.yaml` or `log4j2-test.yml`
4. `log4j2-test.json` or `log4j2-test.jsn`
5. `log4j2-test.xml`
6. `log4j2.properties`
7. `log4j2.yaml` or `log4j2.yml`
8. `log4j2.json` or `log4j2.jsn`
9. `log4j2.xml`
10. 以上配置文件都没有找到,或者文件配置格式错误,实例化默认的`DefaultConfiguration`输出日志到`console`
本文只讲解 XML 配置文件的使用方法,其它3种方式请参见官方文档 JSON,YAML,Properties。
3.1 XML配置文件
xml配置文件支持 简明(concise)模式 和 严格(strict)模式,简明模式不能使用XML Schema[3]校验正确性,为了保证log4j2.xml配置的正确性,建议使用严格模式来编写xml配置文件。
XML Schema又称为XML Schema Definition(XSD),基于XML编写,用于校验xml文档语法的正确性。
通过分析Log4j-config.xsd定义的Schema,我们可以得出log4j2.xml配置文件模板:
<?xml version="1.0" encoding="UTF-8"?>;
<Configuration name="name" status="trace" monitorInterval="60" ... >
<!-- CustomLevel和CustomLevels 可选,2选1且最多出现1个 -->
<CustomLevel name="name1" intLevel=""/>
<CustomLevels>
<CustomLevel name="name1" intLevel=""/>
...
</CustomLevels>
<!--可选 最多1个-->
<Properties>
<Property name="name1">value</property>
<Property name="name2" value="value2"/>
...
</Properties>
<!-- Filter和Filters 可选,2选1且最多出现1个 -->
<Filter type="type" ... >
<!--可选,最多1个-->
<KeyValuePair key="" value=""/>
</Filter>
<Filters>
<Filter type="type" ... />
...
</Filters>
<!--可选,最多1个,且它的属性都为可选-->
<ThresholdFilter level="level" .../>
<!--必须,有且1个-->
<Appenders>
<!--任意个-->
<Appender type="type" name="name">
<!--可选,最多1个-->
<Layout type="type" pattern="" >
<!--属性和标签任选1个-->
<Pattern>value</Pattern>
</Layout>
<!--可选,最多1个-->
<Filter type="type" ... />
</Appender>
...
<!--可选,最多1个-->
<Console name="name" target="STD_OUT" ... >
<!--至少1个-->
<PatternLayout pattern="">
</Console>
</Appenders>
<!--必须,有且1个-->
<Loggers>
<!--可选 多个-->
<Logger name="name" level="info">
<!--Filter和Filters 可选,2选1且最多出现1个-->
<Filter type="type" ... />
<!--任意个-->
<AppenderRef ref="appender_name"/>
</Logger>
...
<!--必须-->
<Root level="level">
<AppenderRef ref="appender_name"/>
</Root>
</Loggers>
</Configuration>
注意,所有节点的定义顺序不能改变,下面简要说明每个节点的作用,之后介绍详细用法。
- 根节点Configuration对应类图的Configuration类,表示LoggerContext关联的所有配置。
- [可选] CustomLevel/CustomLevels自定义日志级别。
-
[可选] Properties 自定义变量集合,作用域为整个Configuration,可以被Filter,Appender,Layout等其他节点通过
${变量名}
引用。 - [可选] Filter/Filters 分级过滤事件(events),只过滤传递给关联节点的事件,Configuration节点下的Filter过滤所有事件。
- Appenders 定义日志输出方式和格式,支持控制台、文件、网络等多种输出方式,通过Layout节点指定丰富的输出格式。
- Loggers 定义所有LoggerConfig配置,一个Logger节点对应一个LoggerConfig,Root节点对应root logger的LoggerConfig,通过AppenderRef节点指定Appender。
3.1.1 Appender
Appender指定了日志的输出地方,Log4j 2.x内置了各种各样的Appender,可以把日志输出到控制台,文件,网络服务器,数据库等等,我们先看看Appender的类继承关系:
每个Appender都有一个name属性,被LoggerConfig通过AppenderRef引用。通常我们会把日志打印到控制台和文件中,下面我们就来学习如何配置它们。
ConsoleAppender
我们使用ConsoleAppender把日志内容输出到标准控制台,System.out
或者System.err
,默认输出到System.out
,同时ConsoleAppender必须指定一个Layout
来格式化输出样式。
表1. ConsoleAppender常用参数表
参数名 | 类型 | 必须配置xml | 默认值 | 用途 |
---|---|---|---|---|
name | String | true | 无 | 名称,Logger通过AppenderRef属性引用 |
layout | Layout | false | "%m%n" | 输出格式化样式 |
filter | Filter | false | 无 | 过滤传递给Appender的LogEvents |
ignoreExceptions | boolean | false | true | 在LogEvents处理过程中发生的异常是否传递给调用者 |
target | String | false | "SYSTEM_OUT" | 输出目的地,"SYSTEM_OUT"或者"SYSTEM_ERR",二选一 |
配置实例,Console
是ConsoleAppender的插件名称(参考插件机制[4]),我们在xml配置文件中只能配置Appender的插件名,不能直接配置Appender的类名:
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="warn">
<Appenders>
<!-- Console是ConsoleAppender的插件名称 -->
<Console name="STDOUT" target="SYSTEM_OUT">
<PatternLayout pattern="%m%n"/>
</Console>
</Appenders>
<Loggers>
<Root level="error">
<AppenderRef ref="STDOUT"/>
</Root>
</Loggers>
</Configuration>
PatternLayout是一个Layout实例,指定格式化输出样式, "%m%n" 相当于 System.out.println("msg") ,后续章节会详细讲解Layout的配置方法。
FileAppender
FileAppender把日志内容输出到fileName
指定的文件中。
表2. FileAppender常用参数表
参数名 | 类型 | 必须配置xml | 默认值 | 用途 |
---|---|---|---|---|
name | String | true | 无 | 名称,Logger通过AppenderRef属性引用 |
fileName | String | true | "" | 输出文件的文件名,如果文件不存在,会自动创建 |
layout | Layout | false | "%m%n" | 输出格式化样式 |
filter | Filter | false | 无 | 过滤传递给Appender的LogEvents |
ignoreExceptions | boolean | false | true | 在LogEvents处理过程中发生的异常是否传递给调用者 |
配置示例,FileAppender的插件名称是File
:
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="warn">
<Appenders>
<File name="MyFile" fileName="logs/app.log">
<PatternLayout>
<Pattern>%d %p %c{1.} [%t] %m%n</Pattern>
</PatternLayout>
</File>
</Appenders>
<Loggers>
<Root level="error">
<AppenderRef ref="MyFile"/>
</Root>
</Loggers>
</Configuration>
fileName指定的文件名,相对路径和绝对路径都可以。
RandomAccessFileAppender
RandomAccessFileAppender和FileAppender功能相同,但是具有更好的性能表现,RandomAccessFileAppender内部使用ByteBuffer + RandomAccessFile
,默认bufferSize为256Kb,FileAppender内部使用BufferedOutputStream
,默认bufferSize为8Kb。
配置方法和FileAppender一样,只需要把File
换成RandomAccessFile
,其他属性不变:
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="warn">
<Appenders>
<RandomAccessFile name="MyFile" fileName="logs/app.log">
<PatternLayout>
<Pattern>%d %p %c{1.} [%t] %m%n</Pattern>
</PatternLayout>
</RandomAccessFile>
</Appenders>
<Loggers>
<Root level="error">
<AppenderRef ref="MyFile"/>
</Root>
</Loggers>
</Configuration>
☆ RollingFileAppender
FileAppender和RandomAccessFileAppender会把所有日志写入到fileName指定的文件中,随着写入日志越来越多,日志文件会变得越来越大,此时写入操作就会很耗时,导致性能极速下降。为了解决这个问题,我们可以把日志文件按照某种规则拆分成多个小文件,这就是RollingFileAppender
的特性。
RollingFileAppender依赖TriggeringPolicy
和RolloverStrategy
这两个机制来实现日志文件拆分归档。TriggeringPolicy作为触发器,决定日志拆分的触发条件;RolloverStrategy决定日志文件的归档策略。
相对于FileAppender,我们看看RollingFileAppender增加的几个参数:
参数名 | 类型 | 必须配置xml | 默认值 | 用途 |
---|---|---|---|---|
filePattern | String | true | 无 | 定义归档日志的文件名 |
policy | TriggeringPolicy | true | 无 | 指定日志拆分归档(rollover)的触发条件 |
strategy | RolloverStrategy | false | DefaultRolloverStrategy | 指定日志归档文件的执行策略 |
1) TriggeringPolicy
Log4j 2.x已经帮我们实现了几个常用的TriggeringPolicy,基于时间、基于存储空间、在JVM启动时、定时任务调度等触发机制,我们先看TriggeringPolicy的类图,然后依次分析每种Triggering的用法。
TriggeringPolicy按照触发方式可以分为两种:事件触发 和 自动触发。
事件触发(TimeBasedTriggeringPolicy,SizeBasedTriggeringPolicy)
当调用logger的写入日志方法时,LoggerConfig生成LogEvent事件,传递给RollingFileAppender,Appender再把事件传递给RollingFileManager,由RollingFileManager调用isTriggeringEvent
方法,如果该方法返回true
就触发rollover,返回false
则不触发。自动触发(OnStartupTriggeringPolicy,CronTriggeringPolicy)
这两个触发器的isTriggeringEvent
方法始终返回false
,它们在initialize
方法中启动自己的调度逻辑,按照自己的规则自动触发rollover。
OnStartupTriggeringPolicy
顾名思义,OnStartupTriggeringPolicy会在每次启动应用程序时触发归档动作。
参数名 | 类型 | 必须配置xml | 默认值 | 用途 |
---|---|---|---|---|
minSize | long | false | 1 | 文件大小(字节数)>=minSize时,触发归档操作,如果minSize等于0,强制创建一个空的归档文件。 |
必须满足下面2个条件:
- 日志文件创建时间早于程序启动时间,一般都满足
- 日志文件大小大于等于minSize(字节)
以下配置示例,当程序启动时,且文件大小达到1KB时触发归档操作。
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="warn">
<Appenders>
<RollingFile name="RollingFile" fileName="logs/app.log" filePattern="logs/$${date:yyyy-MM}/app-%d{MM-dd-yyyy}-%i.log.gz">
<PatternLayout>
<Pattern>%d %p %c{1.} [%t] %m%n</Pattern>
</PatternLayout>
<!-- 最小日志文件大小为 1KB -->
<OnStartupTriggeringPolicy minSize="1024"/>
</RollingFile>
</Appenders>
<Loggers>
<Root level="error">
<AppenderRef ref="RollingFile"/>
</Root>
</Loggers>
</Configuration>
RollinfFile
是RollingFileAppender的插件名称,fileName
是日志文件名称,filePattern
是归档的文件名称策略。
SizeBasedTriggeringPolicy
按照日志文件的大小来触发,当日志文件达到预设的大小时,自动触发归档(rollover)。
参数名 | 类型 | 必须配置xml | 默认值 | 用途 |
---|---|---|---|---|
size | String | false | 10MB | 当前日志文件的最大size,支持单位:KB/MB/GB |
size
属性的语法,我们分析源代码可以得出3个原则,见代码注释:
public final class FileSize {
private static final long KB = 1024;
private static final long MB = KB * KB;
private static final long GB = KB * MB;
// 1. 匹配size的正则表达式
private static final Pattern VALUE_PATTERN =
Pattern.compile("([0-9]+([\\.,][0-9]+)?)\\s*(|K|M|G)B?", Pattern.CASE_INSENSITIVE);
public static long parse(final String string, final long defaultValue) {
final Matcher matcher = VALUE_PATTERN.matcher(string);
// Valid input?
if (matcher.matches()) {
try {
// 2. size解析成long类型,忽略小数部分
final long value = NumberFormat.getNumberInstance(Locale.getDefault()).parse(
matcher.group(1)).longValue();
// Get units specified
final String units = matcher.group(3);
if (units.isEmpty()) {
return value;
} else if (units.equalsIgnoreCase("K")) { // 3. 单位忽略大小写
return value * KB;
} else if (units.equalsIgnoreCase("M")) {
return value * MB;
} else if (units.equalsIgnoreCase("G")) {
return value * GB;
} else {
LOGGER.error("FileSize units not recognized: " + string);
return defaultValue;
}
} catch (final ParseException e) {
LOGGER.error("FileSize unable to parse numeric part: " + string, e);
return defaultValue;
}
}
LOGGER.error("FileSize unable to parse bytes: " + string);
return defaultValue;
}
}
以下配置示例,当日志文件大小达到250MB时,触发归档动作。
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="warn">
<Appenders>
<RollingFile name="RollingFile" fileName="logs/app.log" filePattern="logs/$${date:yyyy-MM}/app-%d{MM-dd-yyyy}-%i.log.gz">
<PatternLayout>
<Pattern>%d %p %c{1.} [%t] %m%n</Pattern>
</PatternLayout>
<!-- 日志文件大小配置成 250MB -->
<SizeBasedTriggeringPolicy size="250 MB"/>
</RollingFile>
</Appenders>
<Loggers>
<Root level="error">
<AppenderRef ref="RollingFile"/>
</Root>
</Loggers>
</Configuration>
size
会被解析成long
类型,单位不区分大小,空格和B可以省略,以下写法都表示 250MB。
size="250 MB"
size="250.1 MB"
size="250mb"
size="250m"
TimeBasedTriggeringPolicy
按照时间间隔周期性触发归档,上一次归档后,间隔时间达到设定的时间后自动触发归档。
参数名 | 类型 | 必须配置xml | 默认值 | 用途 |
---|---|---|---|---|
interval | int | false | 1 | 触发归档的时间间隔,单位由filePattern 的%d 日期格式指定 |
modulate | boolean | false | false | 是否对interval取模,决定了下一次触发的时间点 |
maxRandomDelay | int | false | 0 | 单位:秒,下一次触发时间会在interval基础上,增加一个随机的毫秒数Random.nextLong(0, 1+maxRandomDelay*1000) |
- interval 支持SimpleDateFormat[5]格式的大部分符号,按匹配优先级从高到低,见下表:
匹配优先级 | 格式符 | 含义 | |
---|---|---|---|
最高 | S | 毫秒 | millis |
s | 秒 | second | |
m | 分 | minute | |
H,K,h,k | 时 | hour | |
D,d,F,E | 日 | day | |
w,W | 星期 | week | |
M | 月 | month | |
最低 | y | 年 | year |
举几个匹配的例子:
%d{yyyy-MM-dd-HH}
匹配单位是时
%d{yyyy-MM-dd}
匹配单位是日
%d{yyyy-MM-dd HHmmss}
匹配单位是秒
我们来看看匹配filePattern
的源码,已省略不相关代码:
/**
* Parses the rollover pattern.
*/
public class PatternProcessor {
private static final char YEAR_CHAR = 'y';
private static final char MONTH_CHAR = 'M';
private static final char[] WEEK_CHARS = {'w', 'W'};
private static final char[] DAY_CHARS = {'D', 'd', 'F', 'E'};
private static final char[] HOUR_CHARS = {'H', 'K', 'h', 'k'};
private static final char MINUTE_CHAR = 'm';
private static final char SECOND_CHAR = 's';
private static final char MILLIS_CHAR = 'S';
private RolloverFrequency frequency = null;
// pattern 就是RollingFileAppender中,我们配置的 filePattern 属性
public PatternProcessor(final String pattern) {
this.pattern = pattern;
... //预处理filePattern字符串
for (final ArrayPatternConverter converter : patternConverters) {
if (converter instanceof DatePatternConverter) {
final DatePatternConverter dateConverter = (DatePatternConverter) converter;
// 匹配 interval 的计时单位
frequency = calculateFrequency(dateConverter.getPattern());
}
}
}
// 基于 interval, modulate 计算下一次归档时间点
public long getNextTime(final long currentMillis, final int increment, final boolean modulus) {
prevFileTime = nextFileTime;
long nextTime;
// 这里说明 filePattern 参数必须指定,且格式正确。
if (frequency == null) {
throw new IllegalStateException("Pattern does not contain a date");
}
final Calendar currentCal = Calendar.getInstance();
currentCal.setTimeInMillis(currentMillis);
final Calendar cal = Calendar.getInstance();
currentCal.setMinimalDaysInFirstWeek(7);
cal.setMinimalDaysInFirstWeek(7);
cal.set(currentCal.get(Calendar.YEAR), 0, 1, 0, 0, 0);
cal.set(Calendar.MILLISECOND, 0);
// 根据 frequency 匹配的单位来增加时间间隔 interval
if (frequency == RolloverFrequency.ANNUALLY) {
increment(cal, Calendar.YEAR, increment, modulus);
nextTime = cal.getTimeInMillis();
cal.add(Calendar.YEAR, -1);
nextFileTime = cal.getTimeInMillis();
return debugGetNextTime(nextTime);
}
cal.set(Calendar.MONTH, currentCal.get(Calendar.MONTH));
if (frequency == RolloverFrequency.MONTHLY) {
increment(cal, Calendar.MONTH, increment, modulus);
nextTime = cal.getTimeInMillis();
cal.add(Calendar.MONTH, -1);
nextFileTime = cal.getTimeInMillis();
return debugGetNextTime(nextTime);
}
...
cal.set(Calendar.MILLISECOND, currentCal.get(Calendar.MILLISECOND));
increment(cal, Calendar.MILLISECOND, increment, modulus);
nextTime = cal.getTimeInMillis();
cal.add(Calendar.MILLISECOND, -1);
nextFileTime = cal.getTimeInMillis();
return debugGetNextTime(nextTime);
}
// 匹配 interval 计时单位,毫秒优先级最高,依次递减到年,如果都没有匹配到,则返回null,会导致Appedner抛date格式不正确的异常。
private RolloverFrequency calculateFrequency(final String pattern) {
if (patternContains(pattern, MILLIS_CHAR)) {
return RolloverFrequency.EVERY_MILLISECOND;
}
if (patternContains(pattern, SECOND_CHAR)) {
return RolloverFrequency.EVERY_SECOND;
}
if (patternContains(pattern, MINUTE_CHAR)) {
return RolloverFrequency.EVERY_MINUTE;
}
if (patternContains(pattern, HOUR_CHARS)) {
return RolloverFrequency.HOURLY;
}
if (patternContains(pattern, DAY_CHARS)) {
return RolloverFrequency.DAILY;
}
if (patternContains(pattern, WEEK_CHARS)) {
return RolloverFrequency.WEEKLY;
}
if (patternContains(pattern, MONTH_CHAR)) {
return RolloverFrequency.MONTHLY;
}
if (patternContains(pattern, YEAR_CHAR)) {
return RolloverFrequency.ANNUALLY;
}
return null;
}
}
- modulate 以小时举例,当前系统时间是上午3点,interval是4,如果modulate=true,那么上午4点触发下一次归档,如果modulate=false,那么上午7点触发下一次归档,之后每间隔4小时触发一次归档,如下图所示。
以下配置示例,下一次归档时间是4天后,之后每隔4天触发一次归档。
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="warn">
<Appenders>
<RollingFile name="RollingFile" fileName="logs/app.log" filePattern="logs/$${date:yyyy-MM}/app-%d{MM-dd-yyyy}-%i.log.gz">
<PatternLayout>
<Pattern>%d %p %c{1.} [%t] %m%n</Pattern>
</PatternLayout>
<!-- 时间间隔为4天 -->
<TimeBasedTriggeringPolicy interval="4"/>
</RollingFile>
</Appenders>
<Loggers>
<Root level="error">
<AppenderRef ref="RollingFile"/>
</Root>
</Loggers>
</Configuration>
CronTriggeringPolicy
TimeBasedTriggeringPolicy按照时间间隔来触发,控制力度太单一,为了实现多样化的定时调度,基于cron 表达式实现了CronTriggeringPolicy。
参数名 | 类型 | 必须配置xml | 默认值 | 用途 |
---|---|---|---|---|
schedule | String | false | "0 0 0 * * ?" | CronExpression[6],语法非常灵活,默认值表示:每天0时0分0秒触发归档 |
evaluateOnStartup | boolean | false | false | true,在TriggeringPolicy初始时评估是否需要补充一次归档;false,不检测 |
- 为什么需要
evaluateOnStartup
?
当程序因为某些原因重启了,或者log4j2.xml配置文件触发了Reconfiguration,在这段时间内可能存在满足cron表达式的时间点,所以在启动CronTriggeringPolicy时需要做一次评估。
evaluateOnStartup
评估策略由cron表达式来决定,如果在上一次归档时间(日志文件创建时间)和系统当前时间这段时间内,存在满足cron表达式规则的时间点,那么在CronTriggeringPolicy启动时先直接触发一次归档,否则不触发。
以下配置示例,表示每天的0时0分自动触发归档,且不做启动评估。
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="warn">
<Appenders>
<RollingFile name="RollingFile" fileName="logs/app.log" filePattern="logs/$${date:yyyy-MM}/app-%d{MM-dd-yyyy}-%i.log.gz">
<PatternLayout>
<Pattern>%d %p %c{1.} [%t] %m%n</Pattern>
</PatternLayout>
<!-- 表示每天的0时0分调度 -->
<CronTriggeringPolicy schedule="0 0 * * * ?"/>
</RollingFile>
</Appenders>
<Loggers>
<Root level="error">
<AppenderRef ref="RollingFile"/>
</Root>
</Loggers>
</Configuration>
CompositeTriggeringPolicy
单一的触发机制有时并不能满足我们的要求,使用CompositeTriggeringPolicy组合多个TriggeringPolicy,其中所有的TriggeringPolicy可以同时触发归档。
以下配置示例,同时支持4种触发机制:1)程序启动时,如果文件大于0触发归档。2)日志文件大于20MB时触发归档。3)每2天触发一次归档。4)每天8点触发一次归档。
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="warn">
<Appenders>
<RollingFile name="RollingFile" fileName="logs/app.log" filePattern="logs/$${date:yyyy-MM}/app-%d{MM-dd-yyyy}-%i.log.gz">
<PatternLayout>
<Pattern>%d %p %c{1.} [%t] %m%n</Pattern>
</PatternLayout>
<Policies>
<OnStartupTriggeringPolicy />
<SizeBasedTriggeringPolicy size="20 MB" />
<TimeBasedTriggeringPolicy interval="2" />
<CronTriggeringPolicy schedule="0 0 8 * * ?"/>
</Policies>
</RollingFile>
</Appenders>
<Loggers>
<Root level="error">
<AppenderRef ref="RollingFile"/>
</Root>
</Loggers>
</Configuration>
2) RolloverStrategy
RolloverStrategy
负责执行日志归档(rollover),它定义了多个执行策略,每一个策略定义成Action
,最后依次执行这些Action
来完成具体的归档操作。
从类图可以看出,每个Strategy包括多个Action,这些Action可以分为两类:自动匹配 和 手动配置。
- 自动匹配Action
包含3个压缩Action,负责压缩日志文件,由filePattern
的后缀名决定使用哪种压缩算法。
FileRenameAction
修改日志文件名,由filePattern
的date/time、%i计数和Strategy的参数共同决定归档文件名。 - 手动配置Action
包括DeleteAction
和PosixViewAttributeAction
,在xml中配置,本文不介绍具体配置语法。
log4j内置两种Strategy:DefaultRolloverStrategy
和 DirectWriteRolloverStrategy
,我们来看RollingFileAppenderf创建RolloverStrategy的流程:
- filePattern属性
filePattern
属性支持如下匹配规则:
- date/time模式: 用
%d{}
指定,如%d{yyyyMMdd}
会被替换成20181001。 - %i整数: 每次归档依次递增1。
- 压缩模式后缀:支持
.gz
,.zip
,.bz2
,.deflate
,.pack200
和.xz
,分别匹配对应的压缩算法。 - 匹配Lookups:比如Date,支持SimpleDateFormat。
以下配置示例,如果程序启动时间是2018年7月12日1点0时0分,那么归档文件使用gzip算法压缩,保存在logs/2018-07
目录下,归档文件依次是:app-2018071203-1.log.gz
,app-2018071205-2.log.gz
,app-2018071207-2.log.gz
...
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="warn">
<Appenders>
<RollingFile name="RollingFile" fileName="logs/app.log" filePattern="logs/$${date:yyyy-MM}/app-%d{yyyyMMddHH}-%i.log.gz">
<PatternLayout>
<Pattern>%d %p %c{1.} [%t] %m%n</Pattern>
</PatternLayout>
<TimeBasedTriggeringPolicy interval="2" />
</RollingFile>
</Appenders>
<Loggers>
<Root level="error">
<AppenderRef ref="RollingFile"/>
</Root>
</Loggers>
</Configuration>
-
DefaultRolloverStrategy
DefaultRolloverStrategy
归档策略由filePattern
属性和以下属性共同决定:
参数名 | 类型 | 必须配置xml | 默认值 | 用途 |
---|---|---|---|---|
fileIndex | String | false | "nomax" | 指定归档文件的起始编号,取值范围:"min","max","nomax" |
min | int | false | 1 | 归档文件的最小编号 |
max | int | false | 7 | 归档文件的最大编号。当归档数量达到max时,执行下一个归档时,会删除最先的日志归档。 |
compressionLevel | int | false | -1 | 只有zip 压缩模式有效。取值范围0-9,0 不压缩,1 压缩速度最快,9 压缩率最高。 |
fileIndex三种模式的区别:
min 归档文件从小到大编号,最大不超过max
max 归档文件从大到小编号,最小不超过min
nomax 编号从小到大编号,没有上限。
-
DirectWriteRolloverStrategy
DirectWriteRolloverStrategy
归档策略由filePattern
属性和以下属性共同决定:
参数名 | 类型 | 必须配置xml | 默认值 | 用途 |
---|---|---|---|---|
maxFiles | int | false | Integer.MAX_VALUE | 指定归档文件的最大数量。如果归档数量超过最大值,那么删除最早的归档文件。注意:如果配置的值<0 或者省略,会被设置成Integer.MAX_VALUE ,即不限制数量;如果配置的值是0 和1 ,会被强制设置成7 。 |
compressionLevel | int | false | -1 | 只有zip 压缩模式有效。取值范围0-9,0 不压缩,1 压缩速度最快,9 压缩率最高。 |
DefaultRolloverStrategy
和DirectWriteRolloverStrategy
的流程区别:
- DefaultRolloverStrategy指定fileName,日志写入到fileName指定的文件中,触发归档时,根据filePattern计算出归档文件名,然后把fileName文件重命名为归档名,进行压缩,然后创建一个新的fileName文件,之后的日志写入到新创建的fileName文件中。
- DirectWriteRolloverStrategy没有fileName,日志文件名由filePattern匹配所得,日志直接写入到filePattern匹配的归档文件中,触发归档时,进行压缩,然后由filePattern重新匹配一个文件名,之后的日志写入到重新匹配的文件中。
以下配置实例,使用DefaultRolloverStrategy
归档策略,日志保存到logs/app.log
文件中,每天触发一次归档,同时日志文件每次达到250MB
触发一次归档,归档文件依次为:logs/2018-07/app-20180717-1.log.gz
,logs/2018-07/app-20180718-2.log.gz
… ,最多只有20
个归档。
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="warn">
<Appenders>
<RollingFile name="RollingFile" fileName="logs/app.log"
filePattern="logs/$${date:yyyy-MM}/app-%d{yyyyMMdd}-%i.log.gz">
<PatternLayout>
<Pattern>%d %p %c{1.} [%t] %m%n</Pattern>
</PatternLayout>
<Policies>
<TimeBasedTriggeringPolicy />
<SizeBasedTriggeringPolicy size="250 MB"/>
</Policies>
<DefaultRolloverStrategy max="20"/>
</RollingFile>
</Appenders>
<Loggers>
<Root level="error">
<AppenderRef ref="RollingFile"/>
</Root>
</Loggers>
</Configuration>
以下配置实例,使用DirectWriteRolloverStrategy
归档策略,日志保存到logs/app-20180717-1.log
文件中,1天后创建归档logs/app-20180717-1.log.gz
,同时创建新日志文件logs/app-20180718-2.log
,依次类推,没有归档文件数量限制 。
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="warn">
<Appenders>
<RollingFile name="RollingFile" filePattern="logs/app-%d{yyyyMMdd}-%i.log.gz">
<PatternLayout>
<Pattern>%d %p %c{1.} [%t] %m%n</Pattern>
</PatternLayout>
<Policies>
<TimeBasedTriggeringPolicy />
<SizeBasedTriggeringPolicy size="250 MB"/>
</Policies>
</RollingFile>
</Appenders>
<Loggers>
<Root level="error">
<AppenderRef ref="RollingFile"/>
</Root>
</Loggers>
</Configuration>
3.1.2 Layout
Layout
负责格式化日志的输出方式,Log4j 2.x内置了多种Layout,如:CsvLogEventLayout
,PatternLayout
,JsonLayout
,XmlLayout
等等,我们之前的例子使用的就是PatternLayout
,Appender
的默认layout也是PatternLayout("%m%n"),本文只介绍PatternLayout
的基本使用方法。
参数名 | 类型 | 必须配置xml | 默认值 | 用途 |
---|---|---|---|---|
pattern | String | false | "%m%n" | 输出格式化字符串。 |
PatternLayout
由pattern
参数指定输出格式,pattern
字符串由字面字符串和多个转换说明符(conversion specifiers)组成,
转换说明符的格式为:
%[format]conversion{param}
% 必须,起始符号。
[format] 可选,部分conversion需要,用于指定输出宽度,对齐方式等
conversion 必须,转换符号
param 可选,部分转换符号需要指定参数,如日期格式等
例如:
%d{yyyy-MM-dd HH:mm:ss} 打印当前时间
%-5level 打印日记级别,并且占5个字符宽度
%M 打印方法名称
下面是一个pattern的示例:
%d{yyyy.MM.dd 'at' HH:mm:ss z} %-5level %class{36} %L %M - %msg%xEx%n
输出为:
2018.07.17 at 14:40:10 CST INFO com.alibaba.druid.pool.DruidDataSource 669 init - {dataSource-1} inited
这里是详细的pattern说明。
3.1.3 Logger
一个LoggerConfig对应一个Logger实例,我们通过LogManager.getLogger(Class.getName())
来获取Logger实例时,LogManager通过name查找响应的LoggerConfig,然后实例化相应的Logger。
我们来看LoggerConfig的几个重要属性:
参数名 | 类型 | 必须配置xml | 默认值 | 用途 |
---|---|---|---|---|
name | String | true | "" | 指定LoggerConfig的名称,匹配同名的Logger。注意,root LoggerConfig不能配置该属性,它有默认值"root",是所有Logger的父Logger。 |
level | String | false | "ERROR" | 日志输出级别 |
appender-ref 或者 AppenderRef | Appender | true | null | 关联的Appender |
additivity | boolean | false | true | LogEvent的传递性。true LogEvent处理后传递给父Logger。false LogEvent处理后不再向上传递给父Logger。 |
下面配置示例,定义了名称为com.foo.bar
的Logger,日志级别为trace
,日志输出到Console控制台,additivity
配置为false,通过com.foo.bar
Logger打印的日志不会再次传递到root
Logger。如果additivity
配置成true,那么通过com.foo.bar
Logger打印的日志会先打印到控制台,然后传递给root
Logger,再一次打印到控制台。
<?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="info" additivity="false">
<AppenderRef ref="Console"/>
</Logger>
<Root level="info">
<AppenderRef ref="Console"/>
</Root>
</Loggers>
</Configuration>
3.1.4 log4j2.xml简明配置
下面是log4j2.xml最简配置的一个示例,输出配置log4j的所有过程日志,配置了Console
和RollingFile
2个Appender,都关联到root
Logger,日志级别为info
,程序所有的info日志都会输出到控制台和log.log文件中,每32MB创建一个日志归档,日志的输出格式是:时间 级别 线程名 类名#方法名(文件名:行号):消息
。
<?xml version="1.0" encoding="UTF-8"?>
<configuration status="all">
<appenders>
<Console name="Console" target="SYSTEM_OUT">
<PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss,SSS} %-5level [%t] %class{36}#%M(%F:%L): %m%n"/>
</Console>
<RollingFile name="RollingFile" fileName="../logs/log.log"
filePattern="logs/$${date:yyyy-MM}/%d{yyyy-MM-dd}-%i.log.gz">
<PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss,SSS} %-5level [%t] %class{36}#%M(%F:%L): %m%n"/>
<SizeBasedTriggeringPolicy size="32 MB"/>
</RollingFile>
</appenders>
<loggers>
<root level="info">
<appender-ref ref="RollingFile"/>
<appender-ref ref="Console"/>
</root>
</loggers>
</configuration>
输出格式:
2018-07-19 17:31:33,642 INFO [localhost-startStop-1] com.alibaba.druid.pool.DruidDataSource#init(DruidDataSource.java:669): {dataSource-1} inited
后续
接下来有时间,会讲解Filter
,java api,以及SLF4J
适配等内容。
附录一 Log4j-config.xsd
帮助分析 log4j2.xml 的结构,XSD语法。
参考资料
-
Log4j2 概述 http://logging.apache.org/log4j/2.x/manual/index.html ↩
-
Log4j2 架构 http://logging.apache.org/log4j/2.x/manual/architecture.html ↩
-
XML Schema 教程 http://www.w3school.com.cn/schema/index.asp ↩
-
插件机制 http://logging.apache.org/log4j/2.x/manual/plugins.html ↩
-
日期格式化 https://docs.oracle.com/javase/7/docs/api/java/text/SimpleDateFormat.html ↩
-
Cron Expression http://logging.apache.org/log4j/2.x/log4j-core/apidocs/org/apache/logging/log4j/core/util/CronExpression.html ↩