Log4j 2.x 简明配置(一)

本文主要内容:


image.png

1. 概述

我们为了分析程序的执行情况,需要把我们关心的一些调试信息、错误信息等保存到日志中,Log4j就是一个优秀的日志记录框架。本文基于Log4j 2.x版本,简要介绍Log4j 2.x的架构,讲述几个常用的Appender,Layout,Logger的配置方法,最后给出log4j2.xml的最简配置实例。

本文是Log4j的入门文章,不会讲解诸如异步机制、多进程共享、自定义Level、Filter、插件机制等高级特性,通过阅读本文,读者可以初步掌握Log4j 2.x的使用方法。

Log4j 2.x 拥有如下特性[1]

  1. 支持异步机制,基于LMAX Disruptor Libaray(高吞吐量、低延时),比Log4j 1.x快10倍。
  2. 独立的垃圾回收机制,在Web应用中产生的GC压力小,使Web应用有更好的响应性能。
  3. 支持插件机制,具有很好的扩展性,配置文件简单,不需要指定类名。
  4. Appender支持自定义Layout,输出格式可定制。
  5. 使用JDK1.5提供的并发机制,解决Log4j 1.x的死锁问题,降低同步开销。

2. 架构图[2]

Log4j 2.x 类图

我们使用 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种方式请参见官方文档 JSONYAMLProperties

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类图.jpg

每个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依赖TriggeringPolicyRolloverStrategy这两个机制来实现日志文件拆分归档。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类图

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个条件:

  1. 日志文件创建时间早于程序启动时间,一般都满足
  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来完成具体的归档操作。

RolloverStrategy类图

从类图可以看出,每个Strategy包括多个Action,这些Action可以分为两类:自动匹配手动配置

  • 自动匹配Action
    包含3个压缩Action,负责压缩日志文件,由filePattern的后缀名决定使用哪种压缩算法。
    FileRenameAction修改日志文件名,由filePattern的date/time、%i计数和Strategy的参数共同决定归档文件名。
  • 手动配置Action
    包括DeleteActionPosixViewAttributeAction,在xml中配置,本文不介绍具体配置语法。

log4j内置两种Strategy:DefaultRolloverStrategyDirectWriteRolloverStrategy,我们来看RollingFileAppenderf创建RolloverStrategy的流程:

RolloverStrategy创建流程
  • filePattern属性

filePattern属性支持如下匹配规则:

  1. date/time模式: 用%d{}指定,如%d{yyyyMMdd}会被替换成20181001。
  2. %i整数: 每次归档依次递增1。
  3. 压缩模式后缀:支持.gz, .zip, .bz2, .deflate, .pack200.xz ,分别匹配对应的压缩算法。
  4. 匹配Lookups:比如Date,支持SimpleDateFormat。

以下配置示例,如果程序启动时间是2018年7月12日1点0时0分,那么归档文件使用gzip算法压缩,保存在logs/2018-07目录下,归档文件依次是:app-2018071203-1.log.gzapp-2018071205-2.log.gzapp-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,即不限制数量;如果配置的值是01,会被强制设置成7
compressionLevel int false -1 只有zip压缩模式有效。取值范围0-9,0 不压缩,1 压缩速度最快,9 压缩率最高。

DefaultRolloverStrategyDirectWriteRolloverStrategy的流程区别:

  1. DefaultRolloverStrategy指定fileName,日志写入到fileName指定的文件中,触发归档时,根据filePattern计算出归档文件名,然后把fileName文件重命名为归档名,进行压缩,然后创建一个新的fileName文件,之后的日志写入到新创建的fileName文件中。
  2. DirectWriteRolloverStrategy没有fileName,日志文件名由filePattern匹配所得,日志直接写入到filePattern匹配的归档文件中,触发归档时,进行压缩,然后由filePattern重新匹配一个文件名,之后的日志写入到重新匹配的文件中。

以下配置实例,使用DefaultRolloverStrategy归档策略,日志保存到logs/app.log文件中,每天触发一次归档,同时日志文件每次达到250MB触发一次归档,归档文件依次为:logs/2018-07/app-20180717-1.log.gzlogs/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,如:CsvLogEventLayoutPatternLayoutJsonLayoutXmlLayout等等,我们之前的例子使用的就是PatternLayoutAppender的默认layout也是PatternLayout("%m%n"),本文只介绍PatternLayout的基本使用方法。

参数名 类型 必须配置xml 默认值 用途
pattern String false "%m%n" 输出格式化字符串。

PatternLayoutpattern参数指定输出格式,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.barLogger打印的日志不会再次传递到rootLogger。如果additivity配置成true,那么通过com.foo.barLogger打印的日志会先打印到控制台,然后传递给rootLogger,再一次打印到控制台。

<?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的所有过程日志,配置了ConsoleRollingFile2个Appender,都关联到rootLogger,日志级别为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语法

参考资料


  1. Log4j2 概述 http://logging.apache.org/log4j/2.x/manual/index.html

  2. Log4j2 架构 http://logging.apache.org/log4j/2.x/manual/architecture.html

  3. XML Schema 教程 http://www.w3school.com.cn/schema/index.asp

  4. 插件机制 http://logging.apache.org/log4j/2.x/manual/plugins.html

  5. 日期格式化 https://docs.oracle.com/javase/7/docs/api/java/text/SimpleDateFormat.html

  6. Cron Expression http://logging.apache.org/log4j/2.x/log4j-core/apidocs/org/apache/logging/log4j/core/util/CronExpression.html

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,236评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,867评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,715评论 0 340
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,899评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,895评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,733评论 1 283
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,085评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,722评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,025评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,696评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,816评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,447评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,057评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,009评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,254评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,204评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,561评论 2 343

推荐阅读更多精彩内容

  • 在应用程序中添加日志记录总的来说基于三个目的:监视代码中变量的变化情况,周期性的记录到文件中供其他应用进行统计分析...
    时待吾阅读 4,961评论 0 6
  • 在应用程序中添加日志记录总的来说基于三个目的:监视代码中变量的变化情况,周期性的记录到文件中供其他应用进行统计分析...
    时待吾阅读 4,944评论 1 13
  • 一、Log4j简介 Log4j有三个主要的组件:Loggers(记录器),Appenders (输出源)和Layo...
    默默守护阅读 1,898评论 2 8
  • from:https://www.cnblogs.com/ITtangtang/p/3926665.html一、L...
    enshunyan阅读 3,276评论 0 0
  • 作为一名热爱学习的学生党,学习必然是每天我需要做的事儿。除了平常的文化课专业课的学习,我还会通过各种各样的途径来学...
    树树咖啡屋阅读 763评论 0 0