简介

image.png

异步Appender

ref: https://juejin.cn/post/7197778998439706682

介绍

使用AsyncAppender来异步打印日志时,每个AsyncAppender持有一个ArrayBlockingQueue,并且每个AsyncAppender还会启动一个后台线程来消费阻塞队列中的待打印日志,后台线程每消费一条待打印日志,就会遍历当前AsyncAppender持有的Appender来完成日志打印。

image.png

使用

测试程序中的logger会继承使用根日志打印器的LoggerConfig,也就持有了nameMyAsync的异步Appender

<?xml version="1.0" encoding="UTF-8"?>

<Configuration status="INFO">
<Appenders>
<!-- 配置两个普通Appender -->
<Console name="MyConsole" target="SYSTEM_OUT">
<ThresholdFilter level="INFO" onMatch="ACCEPT" onMismatch="DENY"/>
<PatternLayout pattern="%msg%n"/>
</Console>
<RollingFile name="MyFile" fileName="mylog.log"
filePattern="mylog.log.%i">
<ThresholdFilter level="INFO" onMatch="ACCEPT" onMismatch="DENY" />
<PatternLayout pattern="%msg%n"/>
<SizeBasedTriggeringPolicy size="20M"/>
</RollingFile>

<!-- 让异步Appender引用普通Appender -->
<Async name="MyAsync">
<AppenderRef ref="MyConsole"/>
<AppenderRef ref="MyFile"/>
</Async>
</Appenders>

<Loggers>
<!-- 让根日志打印器引用异步Appender -->
<Root level="INFO">
<Appender-ref ref="MyAsync"/>
</Root>
</Loggers>
</Configuration>

异步Logger

ref: https://juejin.cn/post/7198126784764182586

介绍

使用AsyncAppender来异步打印日志时,每个AsyncAppender持有一个ArrayBlockingQueue,并且每个AsyncAppender还会启动一个后台线程来消费阻塞队列中的待打印日志,后台线程每消费一条待打印日志,就会遍历当前AsyncAppender持有的Appender来完成日志打印。

image.png

使用

Log4j2的配置文件中使用<AsyncLogger**>标签配置一个异步Logger,并为这个异步Logger配置普通Appender**

<?xml version="1.0" encoding="UTF-8"?>

<Configuration status="INFO">
<Appenders>
<!-- 配置两个普通Appender -->
<Console name="MyConsole" target="SYSTEM_OUT">
<ThresholdFilter level="INFO" onMatch="ACCEPT" onMismatch="DENY"/>
<PatternLayout pattern="%msg%n"/>
</Console>
<RollingFile name="MyFile" fileName="mylog.log"
filePattern="mylog.log.%i">
<ThresholdFilter level="INFO" onMatch="ACCEPT" onMismatch="DENY" />
<PatternLayout pattern="%msg%n"/>
<SizeBasedTriggeringPolicy size="20M"/>
</RollingFile>
</Appenders>

<Loggers>
<!-- 为根Logger配置普通Appender -->
<Root level="INFO">
<Appender-ref ref="MyConsole"/>
<Appender-ref ref="MyFile"/>
</Root>
<!-- 定义一个异步Logger并为其配置普通Appender -->
<AsyncLogger name="com.lee.learn.log4j2.asynclogger.LearnLog4j2Async" level="INFO" additivity="false">
<appender-ref ref="MyConsole"/>
</AsyncLogger>
</Loggers>
</Configuration>

全量异步

ref: https://juejin.cn/post/7199620944339337274

介绍

异步日志打印,实际就是将打印日志时最终涉及到的I/O操作放在一个单独的后台线程中执行,从而让业务线程能够快速的从Logger.log() 方法返回。

  • Log4j2初始化时,会创建并启动AsyncLoggerContext作为上下文,启动AsyncLoggerContext时就会创建并启动Disruptor队列,打印日志的后台线程也是在这时完成启动。

  • 业务线程中打印日志时,默认不会带方法位置信息,除非配置日志打印器的includeLocation属性为true,并且日志打印器的Appender中的PatternLayout有%l等格式符。

使用

日志配置

同步日志打印时的一份简单配置,并未使用到异步标签例如<**AsyncLogger**>,<**Async**>等,而要开启全量异步日志打印

<?xml version="1.0" encoding="UTF-8"?>

<Configuration>
<Appenders>
<Console name="MyConsole" target="SYSTEM_OUT">
<ThresholdFilter level="DEBUG" onMatch="ACCEPT" onMismatch="DENY"/>
<PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} %p [%l] %m%n"/>
</Console>
</Appenders>

<Loggers>
<Root level="INFO" includeLocation="true">
<Appender-ref ref="MyConsole"/>
</Root>
</Loggers>
</Configuration>

增加依赖

<dependency>
<groupId>com.lmax</groupId>
<artifactId>disruptor</artifactId>
<version>3.4.2</version>
</dependency>

配置开启全量

  1. JVM参数实现: -DLog4jContextSelector=org.apache.logging.log4j.core.async.AsyncLoggerContextSelecto

  2. properties配置文件: log4j2.component.properties

#参考ref: https://logging.apache.ac.cn/log4j/2.x/manual/async.html#SysPropsAllAsync
#https://www.cnblogs.com/damoblog/p/17352615.html#:~:text=RINGBUFFER
# 设置异步日志系统属性
log4j2.contextSelector=org.apache.logging.log4j.core.async.AsyncLoggerContextSelector
# 日志抛弃策略
log4j2.discardThreshold=ERROR
log4j2.asyncQueueFullPolicy=Discard
log4j2.asyncLoggerConfigWaitStrategy=Sleep
log4j2.asyncLoggerWaitStrategy=Sleep
# 按照高峰1/s 500条每条200byte计算 500 * 200 = 100000 高峰持续60s 100000 * 60 byte 扩张1.5倍= 9000000 ~ 8.6MB 必须为1024倍数设置为8MB
log4j2.asyncLoggerRingBufferSize=8388608
log4j2.asyncLoggerTimeout=10
# 异步日志等待策略为Sleep时,线程睡眠时间(单位:ns)
log4j2.asyncLoggerSleepTimeNs=100
# 异步日志等待策略为Sleep时,自旋重试次数
log4j2.asyncLoggerRetries=200

参考配置

<?xml version="1.0" encoding="UTF-8"?>
<Configuration>

<Properties>
<!-- 日志输出级别 -->
<Property name="LOG_INFO_LEVEL" value="info"/>
<!-- error级别日志 -->
<Property name="LOG_ERROR_LEVEL" value="error"/>
<!-- 在当前目录下创建名为log目录做日志存放的目录 -->
<Property name="LOG_HOME" value="./log"/>
<!-- 档案日志存放目录 -->
<Property name="LOG_ARCHIVE" value="./log/archive"/>
<!-- 模块名称, 影响日志配置名,日志文件名,根据自己项目进行配置 -->
<Property name="LOG_MODULE_NAME" value="spring-boot"/>
<!-- 日志文件大小,超过这个大小将被压缩 -->
<Property name="LOG_MAX_SIZE" value="100 MB"/>
<!-- 保留多少天以内的日志 -->
<Property name="LOG_DAYS" value="15"/>
<!--输出日志的格式:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度, %msg:日志消息,%n是换行符 -->
<Property name="LOG_PATTERN" value="%d [%t] %-5level %logger{0} - %msg%n"/>
<!--interval属性用来指定多久滚动一次-->
<Property name="TIME_BASED_INTERVAL" value="1"/>
</Properties>

<Appenders>
<!-- 控制台输出 -->
<Console name="STDOUT" target="SYSTEM_OUT">
<!--输出日志的格式-->
<PatternLayout pattern="${LOG_PATTERN}"/>
<!--控制台只输出level及其以上级别的信息(onMatch),其他的直接拒绝(onMismatch)-->
<ThresholdFilter level="${LOG_INFO_LEVEL}" onMatch="ACCEPT" onMismatch="DENY"/>
</Console>

<!-- 这个会打印出所有的info级别以上,error级别一下的日志,每次大小超过size或者满足TimeBasedTriggeringPolicy,则日志会自动存入按年月日建立的文件夹下面并进行压缩,作为存档-->
<!--异步日志会自动批量刷新,所以将immediateFlush属性设置为false-->
<RollingRandomAccessFile name="RollingRandomAccessFileInfo" fileName="${LOG_HOME}/${LOG_MODULE_NAME}-infoLog.log" filePattern="${LOG_ARCHIVE}/${LOG_MODULE_NAME}-infoLog-%d{yyyy-MM-dd}-%i.log.gz" immediateFlush="false">
<Filters>
<!--如果是error级别拒绝,设置 onMismatch="NEUTRAL" 可以让日志经过后续的过滤器-->
<ThresholdFilter level="${LOG_ERROR_LEVEL}" onMatch="DENY" onMismatch="NEUTRAL"/>
<!--如果是info\warn输出-->
<ThresholdFilter level="${LOG_INFO_LEVEL}" onMatch="ACCEPT" onMismatch="DENY"/>
</Filters>
<PatternLayout pattern="${LOG_PATTERN}"/>
<Policies>
<!--interval属性用来指定多久滚动一次,根据当前filePattern设置是1天滚动一次-->
<TimeBasedTriggeringPolicy interval="${TIME_BASED_INTERVAL}"/>
<SizeBasedTriggeringPolicy size="${LOG_MAX_SIZE}"/>
</Policies>
<!-- DefaultRolloverStrategy属性如不设置,则默认同一文件夹下最多保存7个文件-->
<DefaultRolloverStrategy max="${LOG_DAYS}"/>
</RollingRandomAccessFile>

<!--只记录error级别以上的日志,与info级别的日志分不同的文件保存-->
<RollingRandomAccessFile name="RollingRandomAccessFileError" fileName="${LOG_HOME}/${LOG_MODULE_NAME}-errorLog.log" filePattern="${LOG_ARCHIVE}/${LOG_MODULE_NAME}-errorLog-%d{yyyy-MM-dd}-%i.log.gz" immediateFlush="false">
<Filters>
<ThresholdFilter level="${LOG_ERROR_LEVEL}" onMatch="ACCEPT" onMismatch="DENY"/>
</Filters>
<PatternLayout pattern="${LOG_PATTERN}"/>
<Policies>
<TimeBasedTriggeringPolicy interval="${TIME_BASED_INTERVAL}"/>
<SizeBasedTriggeringPolicy size="${LOG_MAX_SIZE}"/>
</Policies>
<DefaultRolloverStrategy max="${LOG_DAYS}"/>
</RollingRandomAccessFile>

</Appenders>

<Loggers>
<!-- 开发环境使用 -->
<!--<Root level="${LOG_INFO_LEVEL}"> <AppenderRef ref="STDOUT"/> </Root>-->

<!-- 测试,生产环境使用 -->
<Root level="${LOG_INFO_LEVEL}" includeLocation="false">
<AppenderRef ref="RollingRandomAccessFileInfo"/>
<AppenderRef ref="RollingRandomAccessFileError"/>
</Root>
</Loggers>

</Configuration>