环境
操作系统:win7
log4j2版本: 2.8.2
准备
下载jar
包
官网:https://logging.apache.org/log4j/2.x/download.html
jar包
:apache-log4j-2.8.2-bin.zip
源码包
:apache-log4j-2.8.2-src.zip
把jar包放入到项目中去
开始使用
假设我们要使用log4j2
,我们一般是先声明成一个静态成员变量:
private static final Logger logger = LogManager.getLogger(MyApp.class);
// 或者
private static final Logger logger = LogManager.getLogger(MyApp.class.getName());
声明好logger
后,我们就可以开始使用它了。
假设我们有这么一个程序:
package test;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
public class Hello {
private static Logger logger = LogManager.getLogger(Hello.class.getName());
public void getHello() {
logger.entry();
logger.trace("我是trace");
logger.info("我是info信息");
logger.error("我是error");
logger.fatal("我是fatal");
logger.trace("退出程序.");
logger.exit();
}
public static void main(String[] args) {
new Hello().getHello();
}
}
如果没有自定义配置文件,执行上面的方法后,在控制台显示如下:
10:45:05.641 [main] ERROR test.Hello - 我是error
10:45:05.644 [main] FATAL test.Hello - 我是fatal
从结果上看出,只有>=error级别的日志打印出来了。
这是因为Log4j有一个默认的配置,它的日志级别是ERROR,输出只有控制台。
如果我已经定义好了日志,我把日志级别改成了TRACE,输出会变成下面这样:
10:48:21.326 [main] TRACE test.Hello - Enter
10:48:21.330 [main] TRACE test.Hello - 我是trace
10:48:21.331 [main] INFO test.Hello - 我是info信息
10:48:21.331 [main] ERROR test.Hello - 我是error
10:48:21.331 [main] FATAL test.Hello - 我是fatal
10:48:21.331 [main] TRACE test.Hello - 退出程序.
10:48:21.331 [main] TRACE test.Hello - Exit
配置文件
log4j2
默认下载包里是没有配置文件的。下面这个配置文件等于缺省配置:
https://logging.apache.org/log4j/2.x/manual/configuration.html
<?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>
只需要把上面level
改为trace
就可以,看到刚才的效果!
由于这篇是简单使用,就讲这些,下篇接着讲。
本篇是接着上面 Log4j 2使用教程一【简单使用】的讲解。
配置
Log4j 2的配置可以通过4种方式中的1种完成:
1、通过使用XML,JSON,YAML或属性格式编写的配置文件。
2、以编程方式,通过创建一个ConfigurationFactory和配置实现。
3、以编程方式,通过调用配置界面中公开的API将组件添加到默认配置。
4、通过编程方式,通过调用内部Logger类的方法。
我主要是讲解配置文件的方式
编程的方式可以参考: Extending Log4j 2和Programmatic Log4j Configuration.
自动配置
Log4j能够在初始化期间自动配置自身。
当Log4j启动时,将找到所有ConfigurationFactory插件,并按照从最高到最低的加权顺序进行排列。
交付时,Log4j包含四个ConfigurationFactory实现:一个用于JSON,一个用于YAML,一个用于properties,一个用于XML。
1、Log4j将检查log4j.configurationFile
系统属性,如果设置,将尝试使用与文件扩展名匹配的ConfigurationFactory加载配置。
2、如果没有设置系统属性,则properties ConfigurationFactory
将在类路径中查找log4j2-test.properties
。
3、如果没有找到这样的文件,YAML ConfigurationFactory
将在类路径中查找log4j2-test.yaml
或log4j2-test.yml
。
4、如果没有找到这样的文件,JSON ConfigurationFactory
将在类路径中查找log4j2-test.json
或log4j2-test.jsn
。
5、如果没有找到这样的文件,XML ConfigurationFactory
将在类路径中查找log4j2-test.xml
。
6、如果找不到测试文件,则properties ConfigurationFactory
将在类路径中查找log4j2.properties
。
7、如果无法找到属性文件,则YAML ConfigurationFactory
将在类路径上查找log4j2.yaml
或log4j2.yml
。
8、如果无法找到YAML
文件,则JSON ConfigurationFactory
将在类路径上查找log4j2.json
或log4j2.jsn
。
9、如果无法找到JSON
文件,则XML ConfigurationFactory
将尝试在类路径上找到log4j2.xml
。
10、如果没有找到配置文件,将使用DefaultConfiguration
。这将导致日志输出转到控制台。
日志级别
log4j规定了默认的几个级别:trace<debug<info<warn<error<fatal
等。这里要说明一下:
①级别之间是包含的关系,意思是如果你设置日志级别是trace,则大于等于这个级别的日志都会输出。
②基本上默认的级别没多大区别,就是一个默认的设定。你可以通过它的API自己定义级别。你也可以随意调用这些方法,不过你要在配置文件里面好好处理了,否则就起不到日志的作用了,而且也不易读,相当于一个规范,你要完全定义一套也可以,不用没多大必要。
③这不同的级别的含义大家都很容易理解,这里就简单介绍一下:
level | 描述 |
---|---|
trace | 是追踪,就是程序推进以下,你就可以写个trace输出,所以trace应该会特别多,不过没关系,我们可以设置最低日志级别不让他输出。 |
debug | 调试么,我一般就只用这个作为最低级别,trace压根不用。是在没办法就用eclipse或者idea的debug功能就好了么。 |
info | 输出一下你感兴趣的或者重要的信息,这个用的最多了。 |
warn | 有些信息不是错误信息,但是也要给程序员的一些提示,类似于eclipse中代码的验证不是有error 和warn(不算错误但是也请注意,比如以下depressed的方法)。 |
error | 错误信息。用的也比较多。 |
fatal | 级别比较高了。重大错误,这种级别你可以直接停止程序了,是不应该出现的错误么!不用那么紧张,其实就是一个程度的问题。 |
日志使用
紧接上篇博文
例子1:
<?xml version="1.0" encoding="UTF-8"?>
<configuration status="OFF">
<appenders>
<Console name="Console" target="SYSTEM_OUT">
<PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
</Console>
</appenders>
<loggers>
<!--我们只让这个logger输出trace信息,其他的都是error级别-->
<!--
additivity开启的话,由于这个logger也是满足root的,所以会被打印两遍。
-->
<logger name="cn.lsw.base.log4j2.Hello" level="trace" additivity="false">
<appender-ref ref="Console"/>
</logger>
<root level="error">
<appender-ref ref="Console"/>
</root>
</loggers>
</configuration>
先简单介绍一下下面这个配置文件。
1)根节点configuration,然后有两个子节点:appenders和loggers(都是复数,意思就是可以定义很多个appender和logger了)(如果想详细的看一下这个xml的结构,可以去jar包下面去找xsd文件和dtd文件)
2)appenders:这个下面定义的是各个appender,就是输出了,有好多类别,这里也不多说(容易造成理解和解释上的压力,一开始也未必能听懂,等于白讲),先看这个例子,只有一个Console,这些节点可不是随便命名的,Console就是输出控制台的意思。然后就针对这个输出设置一些属性,这里设置了PatternLayout就是输出格式了,基本上是前面时间,线程,级别,logger名称,log信息等,差不多,可以自己去查他们的语法规则。
3)loggers下面会定义许多个logger,这些logger通过name进行区分,来对不同的logger配置不同的输出,方法是通过引用上面定义的logger,注意,appender-ref引用的值是上面每个appender的name,而不是节点名称。
这个例子为了说明什么呢?我们要说说这个logger的name(名称)了(前面有提到)。
name机制
可以参考: http://logging.apache.org/log4j/2.x/manual/architecture.html
我们看到配置文件中的那个name
是非常重要的。这个name
要用好的,就不能随便乱起。
(随便用的话,那就随便取名字)。这个机制很简单,就类似于java package
一样。
上篇中创建logger
对象的时候,名称是通过Hello.class
或者Hello.class.getName()
这样的方法。为什么要这样做呢?很重要的原因就是有所谓的继承问题。比如 如果你给com.Hello
定义了一个logger
,那么它也适用于com.Hello.base
这个logger
。名称的继承是通过(.)点号分隔的。然后你会返现上面的loggers
里面有个子节点不是logger
而是root
,而且这个root
没有name
属性。
这个root
相当于根节点。你所有的logger
都适用于这个logger
,所以,即使你在很多类里面通过类名.class.getName()
或者类名.class
得到很多的logger
,而且你也没有在配置文件中进行任何配置,它们也能够都输出,因为他们都继承了root
的log
配置。
这种继承的说法官网的解释叫做logger 层次结构(Hierarchy)
官网的例子:
例如:Logger配置中name为com.foo是name为com.foo.Bar的父类。类似的有,java是java.util的父类,是java.util.Vector的祖先。
上面的那个配置文件里面还定义了一个logger
,他的名称是cn.lsw.base.log4j2.Hello
,这个名称其实就是通过前面的Hello.class.getName()
或者Hello.class
得到的。上面那个配置文件,我们为了给它做单独配置。意思是:只有cn.lsw.base.log4j2.Hello
这个logger
输出trace
信息。也就是它的日志级别为trace
,其他的logger
则继承root
的日志配置,日志级别为error
,只能打印出error
及以上级别的日志。
那么有人会问,你单独配置的那个logger
不也是继承了root
的配置么,那这样的话,岂不是会打印两遍日志? 这个问题确实是存在的。当然如果你设置了additivity=false
,就不会输出两遍。
我们再写一个测试类:
(我们先对上面的配置文件做下修改,一个是logger的name改为:test.Hello,第二把additivity=false
去掉或改为true)
package test;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
public class Logger2Test {
private static Logger logger = LogManager.getLogger(Logger2Test.class.getName());
public static void main(String[] args){
logger.trace("start programe");
Hello hello = new Hello();
hello.getHello();
logger.trace("end programe");
}
}
- 结果就是:
2017-05-17 11:51:40.783 [main] TRACE test.Hello - Enter
2017-05-17 11:51:40.783 [main] TRACE test.Hello - Enter
2017-05-17 11:51:40.787 [main] TRACE test.Hello - 我是trace
2017-05-17 11:51:40.787 [main] TRACE test.Hello - 我是trace
2017-05-17 11:51:40.787 [main] INFO test.Hello - 我是info信息
2017-05-17 11:51:40.787 [main] INFO test.Hello - 我是info信息
2017-05-17 11:51:40.787 [main] ERROR test.Hello - 我是error
2017-05-17 11:51:40.787 [main] ERROR test.Hello - 我是error
2017-05-17 11:51:40.787 [main] FATAL test.Hello - 我是fatal
2017-05-17 11:51:40.787 [main] FATAL test.Hello - 我是fatal
2017-05-17 11:51:40.787 [main] TRACE test.Hello - 退出程序.
2017-05-17 11:51:40.787 [main] TRACE test.Hello - 退出程序.
2017-05-17 11:51:40.787 [main] TRACE test.Hello - Exit
2017-05-17 11:51:40.787 [main] TRACE test.Hello - Exit
我们可以看出主程序Logger2Test
并没有trace
日志输出,因为它继承了root
的日志配置(error级别及以上)。而Hello
输出了trace及以上级别的日志,但是呢,每个都输出了两遍。为什么会这样呢?前面也说过默认所有的logger
都继承root
的配置的。此时的Hello
既有自己单独的配置,也有从root
那里继承下来的配置,所以会打印两次。这样的特性,在其name
的层次结构中也是同样适用的,比如:
我创建三个logger
名称为test
、test.Hello
和test.Hello.Hello2
<?xml version="1.0" encoding="UTF-8"?>
<configuration status="OFF">
<appenders>
<Console name="Console" target="SYSTEM_OUT">
<PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n" />
</Console>
</appenders>
<loggers>
<logger name="test.Hello" level="info" additivity="true">
<appender-ref ref="Console"/>
</logger>
<logger name="test.Hello" level="info" additivity="true">
<appender-ref ref="Console"/>
</logger>
<logger name="test.foo.Hello2" level="info" additivity="true">
<appender-ref ref="Console"/>
</logger>
<root level="error">
<appender-ref ref="Console" />
</root>
</loggers>
</configuration>
打印结果:
2017-05-17 14:18:09.388 [main] INFO test.Hello - 我是info信息
2017-05-17 14:18:09.388 [main] INFO test.Hello - 我是info信息
2017-05-17 14:18:09.388 [main] INFO test.Hello - 我是info信息
2017-05-17 14:18:09.392 [main] ERROR test.Hello - 我是error
2017-05-17 14:18:09.392 [main] ERROR test.Hello - 我是error
2017-05-17 14:18:09.392 [main] ERROR test.Hello - 我是error
2017-05-17 14:18:09.392 [main] FATAL test.Hello - 我是fatal
2017-05-17 14:18:09.392 [main] FATAL test.Hello - 我是fatal
2017-05-17 14:18:09.392 [main] FATAL test.Hello - 我是fatal
2017-05-17 14:18:09.393 [main] INFO test.foo.Hello2 - 我是info信息
2017-05-17 14:18:09.393 [main] INFO test.foo.Hello2 - 我是info信息
2017-05-17 14:18:09.393 [main] INFO test.foo.Hello2 - 我是info信息
2017-05-17 14:18:09.393 [main] ERROR test.foo.Hello2 - 我是error
2017-05-17 14:18:09.393 [main] ERROR test.foo.Hello2 - 我是error
2017-05-17 14:18:09.393 [main] ERROR test.foo.Hello2 - 我是error
2017-05-17 14:18:09.393 [main] FATAL test.foo.Hello2 - 我是fatal
2017-05-17 14:18:09.393 [main] FATAL test.foo.Hello2 - 我是fatal
2017-05-17 14:18:09.393 [main] FATAL test.foo.Hello2 - 我是fatal
可以看出打印三篇。
在实际使用过程中,我们其实就是需要一个就行了,这时你可以设置:additivity=false
。
它会把父类全部屏蔽掉。官方说法就是把追加性关闭。
现在我们看一个稍微复杂的例子:
<?xml version="1.0" encoding="UTF-8"?>
<configuration status="error">
<!--先定义所有的appender-->
<appenders>
<!--这个输出控制台的配置-->
<Console name="Console" target="SYSTEM_OUT">
<!--控制台只输出level及以上级别的信息(onMatch),其他的直接拒绝(onMismatch)-->
<ThresholdFilter level="trace" onMatch="ACCEPT" onMismatch="DENY"/>
<!--这个都知道是输出日志的格式-->
<PatternLayout pattern="%d{HH:mm:ss.SSS} %-5level %class{36} %L %M - %msg%xEx%n"/>
</Console>
<!--文件会打印出所有信息,这个log每次运行程序会自动清空,由append属性决定,这个也挺有用的,适合临时测试用-->
<File name="log" fileName="log/test.log" append="false">
<PatternLayout pattern="%d{HH:mm:ss.SSS} %-5level %class{36} %L %M - %msg%xEx%n"/>
</File>
<!--这个会打印出所有的信息,每次大小超过size,则这size大小的日志会自动存入按年份-月份建立的文件夹下面并进行压缩,作为存档-->
<RollingFile name="RollingFile" fileName="logs/app.log"
filePattern="log/$${date:yyyy-MM}/app-%d{MM-dd-yyyy}-%i.log.gz">
<PatternLayout pattern="%d{yyyy-MM-dd 'at' HH:mm:ss z} %-5level %class{36} %L %M - %msg%xEx%n"/>
<SizeBasedTriggeringPolicy size="50MB"/>
</RollingFile>
</appenders>
<!--然后定义logger,只有定义了logger并引入的appender,appender才会生效-->
<loggers>
<!--建立一个默认的root的logger-->
<root level="trace">
<appender-ref ref="RollingFile"/>
<appender-ref ref="Console"/>
</root>
</loggers>
</configuration>
–未完待续
说明
rollover
表示的是当日志文件大小满足指定大小后,就生成一个新的文件的过程。
RollingFileAppender
RollingFileAppender
是一个OutputStreamAppender
,它(会把日志)写入到filename
参数命名的文件中,并且会根据TriggeringPolicy
和RolloverPolicy
来rollover
(rolls the file over)。RollingFileAppender
会使用RollingFileManager
(继承OutputStreamManager)来实际执行文件的I/O和执行rollover
。尽管不能共享来做不同配置的RolloverFileAppenders
,但是如果Manager
可以访问的话,那么RolloverFileAppenders
可以(进行共享)。
例如:在一个servlet
容器中的两个web
应用程序,他们有自己的配置,如果他们Log4j
是共用一个类加载器(ClassLoader)那么就可以安全的写入到一个文件中。
RollingFileAppender 需要TriggeringPolicy
和RolloverStrategy
。triggering policy
决定是否应该执行rollover
的操作,而RolloverStrategy
定义了应该如何完成rollover
。如果RolloverStrategy
没有配置的话,RollingFileAppender
将使用DefaultRolloverStrategy
。从log4j
2.5版本开始,在DefaultRolloverStrategy
中配置的自定义删除操作在rollover
时将被执行。
从2.8版本开始,如果在DirectWriteRolloverStrategy
中没有配置文件名,将使用DefaultRolloverStrategy
进行替换。
RollingFileAppender
不支持文件锁的。
这里rollover
的操作可以理解为:当日志文件大小满足指定大小后,就生成一个新的文件。
RollingFileAppender参数
参数名 | 类型 | 描述 |
---|---|---|
append | boolean | 默认为true,记录追加到文件的最后,否则就先清除以前的记录再写入 |
bufferedIO | boolean | 默认true,记录将会写入到缓存区,当缓存区满的时候,就会写入磁盘。或者如果设置immediateFlush 将会立即写入。文件锁定不能和bufferedIO一起使用。 |
bufferSize | int | 当bufferedIO设置为true是,默认是8192 bytes |
createOnDemand | boolean | 默认为false,该appender按需创建文件,当日志事件通过所有的filters并且通过路由指向了该appender,该appender仅仅创建该文件 |
filter | Filter | 过滤器决定事件是否应该由这个Appender来处理。通过使用CompositeFilter 来使用多个Filter |
fileName | String | 要写入的文件的名称。如果文件或其父目录不存在,它们都将被创建出来 |
filePattern | String | 压缩日志文件的文件名的模式。该模式的格式取决于所使用的RolloverPolicy 。DefaultRolloverPolicy 将接受兼容SimpleDateFormat 的日期/时间模式和/或者%i(代表整数计数器)。这个模式也支持运行时插值,所以任何的查找( eg:DateLookup)都可以包含在模式中 |
immediateFlush | boolean | 默认为true,每次写入都会执行flush。这可以保证每次数据都被写入磁盘,但是会影响性能。在同步的loggers中每次写入执行flush,那就非常有用。异步loggers和appenders将会在一系列事件结束后自动执行flush,即使设置为false。这也保证了数据写入到磁盘而且很高效 |
layout | Layout | 这个Layout 用于格式化LogEvent .如果没有提供默认的layout ,默认为layout 模式为%m%n 。 |
name | String | 该Appender名称 |
policy | TriggeringPolicy | 用于决定是否发生rollover 的策略 |
strategy | RolloverStrategy | 用于决定压缩文件的名称和路径 |
ignoreExceptions | boolean | 默认为true,遇到异常时,会将事件追加到内部日志并忽略它。设置false时,异常会传递给调用者,当这个appender被FailoverAppender包裹时,必须设置为false |
Triggering Policies
Composite Triggering Policy
Composite Triggering Policy
组合了多个triggering policies
,如果配置的策略中的任何一个返回true
,则返回true。CompositeTriggeringPolicy
简单的通过在policies
元素包裹其他的policies
来配置。
例如,以下XML片段定义了当JVM启动时,当日志大小达到二十兆字节以及当前日期与日志的开始日期不匹配时滚动日志的策略。
<Policies>
<OnStartupTriggeringPolicy />
<SizeBasedTriggeringPolicy size="20 MB" />
<TimeBasedTriggeringPolicy />
</Policies>
Cron Triggering Policy
基于cron
表达式的CronTriggeringPolicy
触发rollover
。
CronTriggeringPolicy 参数
参数名称 | 类型 | 描述 |
---|---|---|
schedule | String | cron表达式。该表达式和Quartz调度所允许的表达式相同,有关表达式的完整描述,请参阅CronExpression |
evaluateOnStartup | boolean | 在启动时,将根据文件的最后修改时间戳评估cron表达式。如果cron表达式表示在该时间和当前时间之间应该发生rollover ,则文件将立即rollover 。 |
OnStartup Triggering Policy
如果日志文件比当前jvm
启动时间更早以及满足或者超过最小文件的大小就会触发rollover
OnStartupTriggeringPolicy 参数说明:
参数名称 | 类型 | 描述 |
---|---|---|
minSize | long | 文件必定发生rollover 操作的最小尺寸。要是大小为0的话,那么无论文件大小是多少都将引起rollover 。默认值为1,这将阻止空文件发送rollover |
SizeBased Triggering Policy
SizeBased Triggering Policy:一旦文件大小达到指定大小后,就会发送rollover
。
该策略接受一个interval
属性和modulate
布尔属性。其中interval
属性表示的是,基于时间模式应该发送rollover
的频率。
TimeBasedTriggeringPolicy参数说明:
参数名称 | 类型 | 描述 |
---|---|---|
interval | integer | 根据日期格式中最具体的时间单位来决定应该多久发生一次rollover 。例如,在日期模式中小时为具体的时间单位,那么每4小时会发生4次rollover ,默认值为1 |
modulate | boolean | 表示是否调整时间间隔以使在时间间隔边界发生下一个rollover 。例如:假设小时为具体的时间单元,当前时间为上午3点,时间间隔为4,第一次发送rollover 是在上午4点,接下来是上午8点,接着是中午,接着是下午4点等发生。 |
Rollover Strategies
Default Rollover Strategy
默认的rollover strategy
接受一个日期/时间模式和一个整数,其中这个整数,是RollingFileAppender
本身指定的filePattern
属性。如果date/time
模式存在的话,它将会替换当前日期和时间的值。如果这个模式包含整数的话,它将会在每次发生rollover
时,进行递增。如果模式同时包含date/time
和整数,那么在模式中,整数会递增直到结果中的data/time
模式发生改变。如果文件模式是以".gz", ".zip", ".bz2", ".deflate", ".pack200", or ".xz"
结尾的,将会与后缀相匹配的压缩方案进行压缩文件。格式为:bzip2, Deflate, Pack200 和 XZ
需要Apache Commons Compress,此外,XZ
需要XZ for Java
该模式还可以包含可以在运行时解析的查找引用,如下面的示例所示:
默认的rollover
策略支持三种增加计数的方式。第一种叫做:fixed window
策略。为了说明它的工作原理,假设min属性设置为1,max属性设置为3,文件名为“foo.log”,文件名模式为:foo-%i.log。
rollover次数 | 输出的目标 | 压缩的日志文件 | 描述 |
---|---|---|---|
0 | foo.log | - | 所有的日志都输出到初始文件中 |
1 | foo.log | foo-1.log | 在第一次rollover 时,foo.log会被重命名为foo-1.log。同时会创建一个新的foo.log 并开始写入。 |
2 | foo.log | foo-1.log, foo-2.log | 在第二次发生rollover 时,foo-1.log会重命名为foo-2.log并且foo.log会重命名为foo-1.log。同时会创建一个新的foo.log 并开始写入。 |
3 | foo.log | foo-1.log, foo-2.log, foo-3.log | 在第三次发生rollover 时,foo-2.log会重命名为foo-3.log。foo-1.log重命名为foo-2.log,foo.log会重命名为foo-1.log。同时会创建一个新的foo.log 并开始写入。 |
4 | foo.log | foo-1.log, foo-2.log, foo-3.log | 在第四次和随后的rollover 时,foo-3.log会被删除,foo-2.log重命名为foo-3.log。foo-1.log重命名为foo-2.log。foo.log重命名为foo-1.log。后面同理 |
相比之下,当 fileIndex属性设置了max
,其他设置和上面相同,将执行以下操作:
rollover次数 | 输出的目标 | 压缩的日志文件 | 描述 |
---|---|---|---|
0 | foo.log | - | 所有的日志都输出到初始文件中 |
1 | foo.log | foo-1.log | 在第一次rollover时,foo.log会被重命名为foo-1.log。同时会创建一个新的foo.log并开始写入。 |
2 | foo.log | foo-1.log foo-2.log | 在第二次rollover时,foo.log重命名为foo-2.log。同时会创建一个新的foo.log并开始写入。 |
3 | foo.log | foo-1.log foo-2.log foo-3.log | 在第三次发生rollover时,fool.log重命名为foo-3.log,同时会创建一个新的foo.log并开始写入。 |
4 | foo.log | foo-1.log foo-2.log foo-3.log | 在第四次和随后的rollover时,foo-1.log会被删除,foo-2.log重命名为foo-1.log,foo-3.log重命名为foo-2.log,foo.log重命名为foo-3.log。同时会创建一个新的foo.log并开始写入。 |
最后,从2.8版本开始,如果fileIndex
属性设置为nomax
,那么最大和最小值,都将会被忽略掉,文件编号将从1开发增加,并且每次rollover时递增都从编码最大开始(项目于max效果),而且没有文件数的限制。
DefaultRolloverStrategy参数
参数名称 | 类型 | 描述 |
---|---|---|
fileIndex | String | 如果设置了max(默认就是),文件索引(编号)高的比低的更 新些。如果设置min,文件重命名将遵循Fixed Window 策略 |
min | integer | 计数器的最小值。默认值为1。 |
max | integer | 计数器的最大值。一旦达到这个值,旧的档案将在随后的rollover 中被删除。 |
compressionLevel | integer | 设置压缩级别0-9,其中0=无,1=最佳速度,通过9=最佳压缩。只适用于ZIP文件。 |
DirectWrite Rollover Strategy
DirectWriteRolloverStrategy会将日志事件会直接写入文件模式表示的文件中去。使用这个策略不会执行文件重命名。如果基于大小的触发策略导致在指定的时间段内写入多个文件,则它们将从一个编号开始,并持续递增,直到发生基于时间的rollover
。
警告:如果文件模式里有压缩的后缀,那么当应用程序关闭时,当前文件将不被压缩。此外,如果时间变化使得文件模式不再匹配当前文件,则它也不会在启动时被压缩。
DirectWriteRolloverStrategy 参数
参数名称 | 类型 | 描述 |
---|---|---|
maxFiles | String | 在与文件模式(file pattern)匹配的时间段内允许的最大文件数。如果这个数字被突破了,则最旧的文件将被删除。如果指定了,那么这个值必须大于1。如果值小于零或省略,则文件数不受限制。 |
compressionLevel | integer | 设置压缩级别0-9,其中0=无,1=最佳速度,通过9=最佳压缩。只适用于ZIP文件。 |
①下面是使用RollingFileAppender
的一个示例配置,并且是基于时间和大小的触发策略。其将会根据当前年和月份在未来7天内创建7个压缩包,并且使用gzip
进行压缩。
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="warn" name="MyApp" packages="">
<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>
<TimeBasedTriggeringPolicy />
<SizeBasedTriggeringPolicy size="250 MB"/>
</Policies>
</RollingFile>
</Appenders>
<Loggers>
<Root level="error">
<AppenderRef ref="RollingFile"/>
</Root>
</Loggers>
</Configuration>
②第二个例子显示一个rollover
策略,最多保留20个文件:
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="warn" name="MyApp" packages="">
<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>
<TimeBasedTriggeringPolicy />
<SizeBasedTriggeringPolicy size="250 MB"/>
</Policies>
<DefaultRolloverStrategy max="20"/>
</RollingFile>
</Appenders>
<Loggers>
<Root level="error">
<AppenderRef ref="RollingFile"/>
</Root>
</Loggers>
</Configuration>
③下面是使用RollingFileAppender
的一个示例配置,并且是基于时间和大小的触发策略。其将会根据当前年和月份在未来7天内创建7个压缩包,并且每6小时即被6整除时,会使用gzip
进行压缩每个文件。
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="warn" name="MyApp" packages="">
<Appenders>
<RollingFile name="RollingFile" fileName="logs/app.log"
filePattern="logs/$${date:yyyy-MM}/app-%d{yyyy-MM-dd-HH}-%i.log.gz">
<PatternLayout>
<Pattern>%d %p %c{1.} [%t] %m%n</Pattern>
</PatternLayout>
<Policies>
<!--这行区别-->
<TimeBasedTriggeringPolicy interval="6" modulate="true"/>
<SizeBasedTriggeringPolicy size="250 MB"/>
</Policies>
</RollingFile>
</Appenders>
<Loggers>
<Root level="error">
<AppenderRef ref="RollingFile"/>
</Root>
</Loggers>
</Configuration>
④此示例配置使用基于cron和基于大小的触发策略的RollingFileAppender,并且是无限制文件数量的直接写入到归档文件中。cron触发器是每小时发生rollover并且文件大小不得超过250M。
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="warn" name="MyApp" packages="">
<Appenders>
<RollingFile name="RollingFile" filePattern="logs/app-%d{yyyy-MM-dd-HH}-%i.log.gz">
<PatternLayout>
<Pattern>%d %p %c{1.} [%t] %m%n</Pattern>
</PatternLayout>
<Policies>
<CronTriggeringPolicy schedule="0 0 * * * ?"/>
<SizeBasedTriggeringPolicy size="250 MB"/>
</Policies>
</RollingFile>
</Appenders>
<Loggers>
<Root level="error">
<AppenderRef ref="RollingFile"/>
</Root>
</Loggers>
</Configuration>
⑤此示例和上面④大致一样,但是每小时保存的文件数量限制了为10:
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="warn" name="MyApp" packages="">
<Appenders>
<RollingFile name="RollingFile" filePattern="logs/app-%d{yyyy-MM-dd-HH}-%i.log.gz">
<PatternLayout>
<Pattern>%d %p %c{1.} [%t] %m%n</Pattern>
</PatternLayout>
<Policies>
<CronTriggeringPolicy schedule="0 0 * * * ?"/>
<SizeBasedTriggeringPolicy size="250 MB"/>
</Policies>
<DirectWriteRolloverStrategy maxFiles="10"/>
</RollingFile>
</Appenders>
<Loggers>
<Root level="error">
<AppenderRef ref="RollingFile"/>
</Root>
</Loggers>
</Configuration>
Log Archive Retention Policy(日志存档保留策略):Delete on Rollover
log4j-2.5开始引入了删除操作,使得用户更有效的的控制在rollover
时间内删除文件,而不是使用DefaultRolloverStrategy max属性进行删除。删除操作允许用户配置一个或多个条件,选择要删除相对于基本目录的文件。
注意:删除任何文件这是允许的操作。不仅仅是rollover
时的文件。所以使用这个操作时,一定要小心。使用testMode
参数可以测试您的配置,而不会意外删除错误的文件。
Delete 参数:
参数名称 | 类型 | 描述 |
---|---|---|
basePath | String | 必参。从哪里扫描要删除的文件的基本路径。 |
maxDepth | int | 要访问的目录的最大级别数。值为0表示仅访问起始文件(基本路径本身),除非被安全管理者拒绝。Integer.MAX_VALUE的值表示应该访问所有级别。默认为1,意思是指定基本目录中的文件。 |
followLinks | boolean | 是否遵循符号链接默认值为false。 |
testMode | boolean | 默认false。如果为true,文件将不会被删除,而是将信息打印到info级别的status logger,可以利用这个来测试,配置是否和我们预期的一样 |
pathSorter | PathSorter | 一个实现了PathSorter接口的插件在选择删除文件前进行排序。默认是最近修改的文件排在前面 |
pathConditions | PathCondition[] | 如果没有指定ScriptCondition,则为必需。一个或多个PathCondition元素。如果指定了多个条件,在删除之前,他们需要接受全部的路径。条件是可以嵌套的,在这种情况下,只有在外部路径被接受的情况下,才会去评估内部路径。如果条件没有嵌套,则可以按照任何顺序去评估。条件也可以通过IfAll 、IfAny 和IfNot 组合复合条件。他们对于的是AND、OR、NOT 。用户可以创建自定义条件或使用内置条件:IfFileName:接受路径(相对于基本路径)与正则表达式或glob匹配的文件。IfLastModified:接受与指定持续时间相同或更早的文件。IfAccumulatedFileCount :在file tree walk 期间超过一些计数阈值后接受路径。IfAccumulatedFileSize:在file tree walk 期间超过累积文件大小阈值后接受路径。ifAll:如果所有嵌套条件都接受它(逻辑与),则接受路径。嵌套条件可以按任何顺序进行评估。IfAny:如果其中一个嵌套条件接受(OR或OR),则接受路径。嵌套条件可以按任何顺序进行评估。IfNot:如果嵌套条件不接受(逻辑NOT),则接受路径。 |
scriptCondition | ScriptCondition | 如果没有指定PathConditions,则为必需。指定脚本的ScriptCondition元素。ScriptCondition应包含一个Script,ScriptRef或ScriptFile元素,用于指定要执行的逻辑。有关配置ScriptFiles和ScriptRefs的更多示例,请参阅ScriptFilter文档。该脚本传递了许多参数,包括在基本路径下找到的路径列表(最多为maxDepth),并且必须返回具有要删除路径的列表。 |
IfFileName 条件参数:
参数名称 | 类型 | 描述 |
---|---|---|
glob | String | 如果regex 没有指定的话,则必须。使用类似于正则表达式但是又具有更简单的有限模式语言来匹配相对路径(相对于基本路径) |
regex | String | 如果glob 没有指定的话,则必须。使用由Pattern类定义的正则表达式来匹配相对路径(相对于基本路径) |
nestedConditions | PathCondition[] | 一组可选的嵌套PathConditions如果存在任何嵌套条件,则在它们删除文件之前,必须都接受。仅当外部条件接受文件(如果路径名称匹配)时,才会评估嵌套条件。 |
IfLastModified条件参数:
参数名称 | 类型 | 描述 |
---|---|---|
age | String | 必须。指定持续时间duration。该条件接受比指定持续时间更早或更旧的文件。 |
nestedConditions | PathCondition[] | 一组可选的嵌套PathConditions如果存在任何嵌套条件,则在它们删除文件之前,必须都接受。仅当外部条件接受文件(如果文件足够老)时,才会评估嵌套条件。 |
IfAccumulatedFileCount 条件参数
参数名称 | 类型 | 描述 |
---|---|---|
exceeds | int | 必须。将要删除文件的计数阈值。也就是需要保留的文件数。 |
nestedConditions | PathCondition[] | 一组可选的嵌套PathConditions如果存在任何嵌套条件,则在它们删除文件之前,必须都接受。仅当外部条件接受文件(如果超过阈值计数)时,才会评估嵌套条件。 |
IfAccumulatedFileSize 条件参数
参数名称 | 类型 | 描述 |
---|---|---|
exceeds | String | 必须。将删除文件累计阀值的大小。大小可以指定字节。后缀可以是KB, MB or GB 例如:20MB 。也就是要保留最接近该值大小的文件。 |
nestedConditions | PathCondition[] | 一组可选的嵌套PathConditions如果存在任何嵌套条件,则在它们删除文件之前,必须都接受。仅当外部条件接受文件(如果超过了阈值累积文件大小)时,才会评估嵌套条件。 |
下面这个示例配置使用的是RollingFileAppender
,并且使用的是cron
触发策略,是在每天的午夜触发。归档存储的目录是基于当前年和月。在rollover
时间内匹配删除基本目录下所有满足参数glob
等于*/app-*.log.gz
和超过60天或更早的文件。
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="warn" name="MyApp" packages="">
<Properties>
<Property name="baseDir">logs</Property>
</Properties>
<Appenders>
<RollingFile name="RollingFile" fileName="${baseDir}/app.log"
filePattern="${baseDir}/$${date:yyyy-MM}/app-%d{yyyy-MM-dd}.log.gz">
<PatternLayout pattern="%d %p %c{1.} [%t] %m%n" />
<CronTriggeringPolicy schedule="0 0 0 * * ?"/>
<DefaultRolloverStrategy>
<Delete basePath="${baseDir}" maxDepth="2">
<IfFileName glob="*/app-*.log.gz" />
<IfLastModified age="60d" />
</Delete>
</DefaultRolloverStrategy>
</RollingFile>
</Appenders>
<Loggers>
<Root level="error">
<AppenderRef ref="RollingFile"/>
</Root>
</Loggers>
</Configuration>
下面的示例配置使用的是RollingFileAppender
并且触发策略是基于时间和文件大小。在未来100天内会创建100个归档,这些归档存储的目录是基于当前年和月的,并且会以gzip
方式进行压缩每个归档,而且rollover
是每小时发生一次。这个配置也将会删除匹配*/app-*.log.gz
和超过30天或更早的文件。但是会保留大小进100G或者最近的10个文件,先到则为准(whichever comes first.)。我个人理解应该是以文件创建时间为准。
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="warn" name="MyApp" packages="">
<Properties>
<Property name="baseDir">logs</Property>
</Properties>
<Appenders>
<RollingFile name="RollingFile" fileName="${baseDir}/app.log"
filePattern="${baseDir}/$${date:yyyy-MM}/app-%d{yyyy-MM-dd-HH}-%i.log.gz">
<PatternLayout pattern="%d %p %c{1.} [%t] %m%n" />
<Policies>
<TimeBasedTriggeringPolicy />
<SizeBasedTriggeringPolicy size="250 MB"/>
</Policies>
<DefaultRolloverStrategy max="100">
<!--
Nested conditions: the inner condition is only evaluated on files
for which the outer conditions are true.
-->
<Delete basePath="${baseDir}" maxDepth="2">
<IfFileName glob="*/app-*.log.gz">
<IfLastModified age="30d">
<IfAny>
<IfAccumulatedFileSize exceeds="100 GB" />
<IfAccumulatedFileCount exceeds="10" />
</IfAny>
</IfLastModified>
</IfFileName>
</Delete>
</DefaultRolloverStrategy>
</RollingFile>
</Appenders>
<Loggers>
<Root level="error">
<AppenderRef ref="RollingFile"/>
</Root>
</Loggers>
</Configuration>
ScriptCondition 参数:
参数名称 | 类型 | 描述 |
---|---|---|
script | Script, ScriptFile or ScriptRef | Script元素是用于指定需要执行的逻辑。在基本路径下找到的路径列表,该脚本要是通过的,并且要以java.util.List<PathWithAttributes> 返回删除的路径。 参考ScriptFilter文档,如何配置ScriptFilter和ScriptRefs 的示例。 |
Script 参数:
参数名称 | 类型 | 描述 |
---|---|---|
basePath | java.nio.file.Path | 删除操作开始扫描要删除的文件的目录。可用于相对路径列表中的路径。 |
pathList | java.util.List | 在基本路径下找到路径列表直到指定的最大深度,优先排序最近修改的文件。该脚本可以自由修改并返回此列表。 |
statusLogger | StatusLogger | StatusLogger通常用于在脚本执行期间记录内部事件。 |
configuration | Configuration | 拥有此ScriptCondition的配置。 |
substitutor | StrSubstitutor | StrSubstitutor用于替换查找变量。 |
? | String | 在配置中声明的任何属性 |
以下示例配置使用RollingFileAppender
并且是cron
触发策略,触发时间为每天午夜。归档存储目录是基于当前年和月的。该脚本将返回在基本目录下,日期为13号并且是星期五的rollover
文件列表。删除操作将删除脚本返回的所有文件。
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="trace" name="MyApp" packages="">
<Properties>
<Property name="baseDir">logs</Property>
</Properties>
<Appenders>
<RollingFile name="RollingFile" fileName="${baseDir}/app.log"
filePattern="${baseDir}/$${date:yyyy-MM}/app-%d{yyyyMMdd}.log.gz">
<PatternLayout pattern="%d %p %c{1.} [%t] %m%n" />
<CronTriggeringPolicy schedule="0 0 0 * * ?"/>
<DefaultRolloverStrategy>
<Delete basePath="${baseDir}" maxDepth="2">
<ScriptCondition>
<Script name="superstitious" language="groovy"><![CDATA[
import java.nio.file.*;
def result = [];
def pattern = ~/\d*\/app-(\d*)\.log\.gz/;
pathList.each { pathWithAttributes ->
def relative = basePath.relativize pathWithAttributes.path
statusLogger.trace 'SCRIPT: relative path=' + relative + " (base=$basePath)";
// remove files dated Friday the 13th
def matcher = pattern.matcher(relative.toString());
if (matcher.find()) {
def dateString = matcher.group(1);
def calendar = Date.parse("yyyyMMdd", dateString).toCalendar();
def friday13th = calendar.get(Calendar.DAY_OF_MONTH) == 13 \
&& calendar.get(Calendar.DAY_OF_WEEK) == Calendar.FRIDAY;
if (friday13th) {
result.add pathWithAttributes;
statusLogger.trace 'SCRIPT: deleting path ' + pathWithAttributes;
}
}
}
statusLogger.trace 'SCRIPT: returning ' + result;
result;
]] >
</Script>
</ScriptCondition>
</Delete>
</DefaultRolloverStrategy>
</RollingFile>
</Appenders>
<Loggers>
<Root level="error">
<AppenderRef ref="RollingFile"/>
</Root>
</Loggers>
</Configuration>