试验一下,引入依赖
org.apache.logging.log4j
log4j-api
2.13.1
org.apache.logging.log4j
log4j-core
2.13.1
配置log4j配置文件
D:/logs
首先测试一下日志的滚动更新功能
public class Log4j2Test {
public static void main(String[] args) {
Logger logger = LogManager.getLogger(Log4j2Test.class);
for (int i=0; i<100000; i++) {
logger.info("hello world");
}
}
}
最终输出日志文件如下:
RollingFileAppender是Log4j2中的一种能够实现日志文件滚动更新(rollover)的Appender。
rollover的意思是当满足一定条件(如文件达到了指定的大小,达到了指定的时间)后,就重命名原日志文件进行归档,并生成新的日志文件用于log写入。如果还设置了一定时间内允许归档的日志文件的最大数量,将对过旧的日志文件进行删除操作。
RollingFile实现日志文件滚动更新,依赖于TriggeringPolicy和RolloverStrategy。
其中,TriggeringPolicy为触发策略,其决定了何时触发日志文件的rollover,即When。
RolloverStrategy为滚动更新策略,其决定了当触发了日志文件的rollover时,如何进行文件的rollover,即How。
RollingFile的触发rollover的策略有CronTriggeringPolicy(Cron表达式触发)、OnStartupTriggeringPolicy(JVM启动时触发)、SizeBasedTriggeringPolicy(基于文件大小)、TimeBasedTriggeringPolicy(基于时间)、CompositeTriggeringPolicy(多个触发策略的混合,如同时基于文件大小和时间)。上述使用的是基于文件大小来触发。
DefaultRolloverStrategy指定了当触发rollover时的默认策略。
DefaultRolloverStrategy是Log4j2提供的默认的rollover策略,即使在log4j2.xml中没有显式指明,也相当于为RollingFile配置下添加了如下语句。DefaultRolloverStrategy默认的max为7。
|
max参数指定了计数器的最大值。一旦计数器达到了最大值,过旧的文件将被删除。
max参数是与filePattern中的计数器%i配合起作用的,其具体作用方式与filePattern的配置密切相关。
1.如果filePattern中仅含有date/time pattern,每次rollover时,将用当前的日期和时间替换文件中的日期格式对文件进行重命名。max参数将不起作用。
如,filePattern="logs/app-%d{yyyy-MM-dd}.log"
2.如果filePattern中仅含有整数计数器(即%i),每次rollover时,文件重命名时的计数器将每次加1(初始值为1),若达到max的值,将删除旧的文件。
如,filePattern="logs/app-%i.log"
3.如果filePattern中既含有date/time pattern,又含有%i,每次rollover时,计数器将每次加1,若达到max的值,将删除旧的文件,直到data/time pattern不再符合,被替换为当前的日期和时间,计数器再从1开始。
如,filePattern="logs/app-%d{yyyy-MM-dd HH-mm}-%i.log"
特别注意该种场景,由于在使用过程中一般配置日志文件带有日志以及序号,该种情况下文件的滚动策略是日期和序号配合使用,假定日文文件格式为yyyy-mm-dd-i max=30 则表示每天日志文件最多有30个 而不表示所有文件共有30个,后面还需要设置日志定时清除策略,否则日志会积累越来越多。
修改max值 代码测试如下:
可知日志文件每分钟最多只会保存5个 ,仅表示每分钟最多5个。
删除日志文件
DefaultRolloverStrategy制定了默认的rollover策略,通过max参数可控制一定时间范围内归档的日志文件的最大个数。Log4j 2.5 引入了DeleteAction,使用户可以自己控制删除哪些文件,而不仅仅是通过DefaultRolloverStrategy的默认策略。
basePath指定了扫描开始路径,为baseDir文件夹。maxDepth指定了目录扫描深度,为2表示扫描baseDir文件夹及其子文件夹。IfFileName指定了文件名需满足的条件,IfLastModified指定了文件修改时间需要满足的条件。
再次测试发现程序只会保留1min之前的日志文件,其他日志都被清楚掉。
首先测试一下同步和异步的性能差异
同步日志方式
修改为异步方式
使用同步方式5.4s 异步方式2.9 可知异步方式性能优势明显。
同步日志,即当输出日志时,必须等待日志输出语句执行完毕后,才能执行后面的业务逻辑语句。
Log4j2中日志输出的详细过程如下:
1.首先使用全局Filter对日志事件进行过滤。
Log4j2中的日志Level分为8个级别,优先级从高到低依次为OFF、FATAL、ERROR、WARN、INFO、DEBUG、TRACE、 ALL。全局Filter的Level为ALL,表示允许输出所有级别的日志。logger.info()请求输出INFO级别的日志,通过。
2.使用Logger的Level对日志事件进行过滤。
Logger的Level为TRACE,表示允许输出TRACE级别及以上级别的日志。logger.info()请求输出INFO级别的日志,通过。
3.生成日志输出内容Message。
使用占位符的方式输出日志,输出语句为logger.info("increase {} from {} to {}", arg1, arg2, arg3)的形式,最终输出时{}占位符处的内容将用arg1,arg2,arg3的字符串填充。
log4j2用Object[]保存参数信息,在这一阶段会将Object[]转换为String[],生成含有输出模式串"increase {} from {} to {}"和参数数组String[]的Message,为后续日志格式化输出做准备。
4.生成LogEvent。
LogEvent中含有loggerName(日志的输出者),level(日志级别),timeMillis(日志的输出时间),message(日志输出内容),threadName(线程名称)等信息。
5.使用Logger配置的Filter对日志事件进行过滤。
Logger配置的Filter的Level为DEBUG,表示允许输出DEBUG及以上级别的日志。logger.info()请求输出INFO级别的日志,通过。
6.使用Logger对应的Appender配置的Filter对日志事件进行过滤。
Appender配置的Filter配置的INFO级别日志onMatch=ACCEPT,表示允许输出INFO级别的日志。logger.info()请求输出INFO级别的日志,通过。
7.判断是否需要触发rollover。
此步骤不是日志输出的必须步骤,如配置的Appender为无需进行rollover的Appender,则无此步骤
因为使用RollingFileAppender,且配置了基于文件大小的rollover触发策略,在此阶段会判断是否需要触发rollover。判断方式为当前的文件大小是否达到了指定的size,如果达到了,触发rollover操作。
8.PatternLayout对LogEvent进行格式化,生成可输出的字符串。
常见参数意义如下:
参数 |
意义 |
---|---|
%d |
日期格式,默认形式为2012-11-02 14:34:02,781 |
%p |
日志级别 |
%c{1.} |
%c表示Logger名字,{1.}表示精确度。若Logger名字为org.apache.commons.Foo,则输出o.a.c.Foo。 |
%t |
处理LogEvent的线程的名字 |
%m |
日志内容 |
%n |
行分隔符。"\n"或"\r\n"。 |
在此步骤,PatternLayout将根据Pattern的模式,利用各种Converter对LogEvent的相关信息进行转换,最终拼接成可输出的日志字符串。
如DatePatternConverter对LogEvent的日志输出时间进行格式化转换;LevelPatternConverter对LogEvent的日志级别信息进行格式化转换;LoggerPatternConverter对LogEvent的Logger的名字进行格式化转换;MessagePatternConverter对LogEvent的日志输出内容进行格式化转换等。
经各种Converter转换后,LogEvent的信息被格式化为指定格式的字符串。
9.使用OutputStream,将日志输出到文件。
将日志字符串序列化为字节数组,使用字节流OutoutStream将日志输出到文件中。如果配置了immediateFlush为true,打开app.log就可观察到输出的日志了。
使用异步日志进行输出时,日志输出语句与业务逻辑语句并不是在同一个线程中运行,而是有专门的线程用于进行日志输出操作,处理业务逻辑的主线程不用等待即可执行后续业务逻辑。
Log4j2中的异步日志实现方式有AsyncAppender和AsyncLogger两种。其中,AsyncAppender采用了ArrayBlockingQueue来保存需要异步输出的日志事件;AsyncLogger则使用了Disruptor框架来实现高吞吐。
每个Async Appender,内部维护了一个ArrayBlockingQueue,并将创建一个线程用于输出日志事件,如果配置了多个AppenderRef,将分别使用对应的Appender进行日志输出。
上述程序中,main线程作为生产者,EventProcessor线程作为消费者。
生产者生产消息
当运行到类似于logger.info、logger.debug的输出语句时,将生成的LogEvent放入RingBuffer中。
消费者消费消息
如果RingBuffer中有LogEvent需要处理,EventProcessor线程从RingBuffer中取出LogEvent,调用Logger相关联的Appender输出LogEvent(具体输出过程与同步过程相同,同样需要过滤器过滤、PatternLayout格式化等步骤)。
如果RingBuffer中没有LogEvent需要处理,EventProcessor线程将处于等待阻塞状态(默认策略)。
需要注意的是,虽然在log4j2.xml中配置了多个AsyncLogger,但是并不是每个AsyncLogger对应着一个处理线程,而是仅仅有一个EventProcessor线程进行日志的异步处理。
AsyncLogger的内部使用了Disruptor框架,大大提升了性能,后面可以研究一下。
启动任务 观察一下线程情况
参考链接
Log4j2中的同步日志与异步日志 - Ye_yang - 博客园
Log4j2中RollingFile的文件滚动更新机制 - Ye_yang - 博客园
黑马程序员java日志框架教程,全面深入学习多种java日志框架_哔哩哔哩_bilibili