Log4j2异步日志同步日志和混合日志的配置详解

Posted 达摩院的BLOG

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Log4j2异步日志同步日志和混合日志的配置详解相关的知识,希望对你有一定的参考价值。

    Log4j 2中记录日志的方式有同步日志和异步日志两种方式,其中异步日志又可分为使用AsyncAppender和使用AsyncLogger两种方式。

文章目录

  • 同步日志
  • 混合同步和异步日志
  • 异步日志(性能最好,推荐使用)

一、同步日志

    所谓同步日志,即当输出日志时,必须等待日志输出语句执行完毕后,才能执行后面的业务逻辑语句。下面给出小编在开发中的配置

<?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 %logger0 - %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,则日志会自动存入按年月日建立的文件夹下面并进行压缩,作为存档-->
        <RollingRandomAccessFile name="RollingRandomAccessFileInfo" fileName="$LOG_HOME/$LOG_MODULE_NAME-infoLog.log" filePattern="$LOG_ARCHIVE/$LOG_MODULE_NAME-infoLog-%dyyyy-MM-dd-%i.log.gz">
            <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-%dyyyy-MM-dd-%i.log.gz">
            <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">
            <AppenderRef ref="RollingRandomAccessFileInfo"/>
            <AppenderRef ref="RollingRandomAccessFileError"/>
        </Root>
    </Loggers>

</Configuration>

二、混合同步和异步日志

    Log4j-2.9及更高版本在类路径上需要 disruptor-3.3.4.jar 或更高版本。在Log4j-2.9之前,需要disruptor-3.0.0.jar或更高版本。无需将系统属性“Log4jContextSelector”设置为任何值。

可以在配置中组合同步和异步记录器。这为您提供了更大的灵活性,但代价是性能略有下降(与使所有记录器异步相比)。使用<asyncRoot><asyncLogger> 配置元素指定需要异步的记录器。配置只能包含一个根记录器(<root><asyncRoot>元素),但是可以组合异步和非异步记录器。例如,包含<asyncLogger>元素的配置文件也可以包含<root>和同步记录器的元素。

默认情况下,异步记录器不会将位置传递给I / O线程。如果您的某个布局或自定义过滤器需要位置信息,则需要在所有相关记录器的配置中设置“includeLocation = true”,包括根记录器。

    首先引入disruptor依赖

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

    混合异步记录器的配置可能如下所示:

<?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 %logger0 - %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-%dyyyy-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-%dyyyy-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>-->

        <!-- 测试,生产环境使用 -->
        <!-- 当使用<asyncLogger> or <asyncRoot>时,无需设置系统属性"Log4jContextSelector" -->
        <AsyncLogger name="com.jourwon" level="$LOG_INFO_LEVEL" additivity="false">
            <AppenderRef ref="RollingRandomAccessFileInfo"/>
            <AppenderRef ref="RollingRandomAccessFileError"/>
        </AsyncLogger>

        <Root level="$LOG_INFO_LEVEL">
            <AppenderRef ref="RollingRandomAccessFileInfo"/>
            <AppenderRef ref="RollingRandomAccessFileError"/>
        </Root>
    </Loggers>

</Configuration>

三、异步日志(性能最好,推荐使用)

    Log4j-2.9及更高版本在类路径上需要disruptor-3.3.4.jar或更高版本。在Log4j-2.9之前,需要disruptor-3.0.0.jar或更高版本。这是最简单的配置,并提供最佳性能。要使所有记录器异步,

请将disruptor jar添加到类路径,并将系统属性log4j2.contextSelector设置 为org.apache.logging.log4j.core.async.AsyncLoggerContextSelector。默认情况下,异步记录器不会将位置传递给I / O线程。

如果您的某个布局或自定义过滤器需要位置信息,则需要在所有相关记录器的配置中设置“includeLocation = true”,包括根记录器。

    首先引入disruptor依赖

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

    然后在src/java/resources目录添加log4j2.component.properties配置文件

# 设置异步日志系统属性
log4j2.contextSelector=org.apache.logging.log4j.core.async.AsyncLoggerContextSelector

   配置如下所示:

<?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 %logger0 - %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-%dyyyy-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-%dyyyy-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>

    当配置AsyncLoggerContextSelector作为异步日志时,请确保在配置中使用普通的 <root>和<logger>元素。AsyncLoggerContextSelector将确保所有记录器都是异步的,使用的机制与配置<asyncRoot> 或<asyncLogger>时的机制不同。

    通过log.info(“是否为异步日志:”, AsyncLoggerContextSelector.isSelected());可以查看是否为异步日志。

 

日志输出方式

sync

同步打印日志,日志输出与业务逻辑在同一线程内,当日志输出完毕,才能进行后续业务逻辑操作

Async Appender

异步打印日志,内部采用ArrayBlockingQueue,对每个AsyncAppender创建一个线程用于处理日志输出。

Async Logger

异步打印日志,采用了高性能并发框架Disruptor,创建一个线程用于处理日志输出。

经典日志系统异步打印配置清单

一、前言

在高并发高流量响应延迟要求比较小的系统中同步打日志已经满足不了需求了,同步打日志会阻塞调用打日志的线程,而打日志本身是需要写磁盘的,所以会造成rt增加。异步日志就是为了解决这个问题,本文我们探讨场景的几种日志系统的异步log配置方案。

二、日志打印模型

  • 同步日志模型

如上图,多个业务线程打印日志时候要等把内容写入磁盘后才会返回,所以打日志的rt就是写入磁盘的耗时。

  • 异步日志模型

如上图多个业务线程打印日志时候是把打印任务放入内存队列后就直接返回了,而具体打印日志是有日志系统的一个日志线程去队列里面获取然后执行,可见这种打印rt就是写入内存队列的耗时。其实是一个多生产者单消费者模型。

三、LogBack异步配置

 
   
   
 
  1. <!--1.同步appender -->

  2. <appender name="syncAppender" class="ch.qos.logback.core.rolling.RollingFileAppender">

  3. ...

  4. </appender>


  5. <!--2.异步appender -->

  6. <appender name="ASYNC-LOG" class="ch.qos.logback.classic.AsyncAppender">

  7. <!--内部实现是一个有界ArrayBlockingQueue,queueSize是队列大小。该值会影响性能.默认值为256-->

  8. <queueSize>512</queueSize>

  9. <!--当队列的剩余容量小于这个阈值并且当前日志level TRACE, DEBUG or INFO,则丢弃这些日志。默认为queueSize大小的20%。-->

  10. <discardingThreshold>102</discardingThreshold>

  11. <!--neverBlock=true则写日志队列时候会调用阻塞队列的offer方法而不是put,如果队列满则直接返回,而不是阻塞,即日志被丢弃。-->

  12. <neverBlock>true</neverBlock>

  13. <!--实际负责写日志的appender,最多只能添加一个-->

  14. <appender-ref ref="syncAppender" />

  15. </appender>


  16. <!--3.log对象 -->

  17. <logger name="errorLog" additivity="false">

  18. <appender-ref ref="ASYNC-LOG"/>

  19. ...

  20. </logger>


  • 代码1是我们正常的同步syncAppender的配置



  • 代码2创建一个异步appender,然后使用appender-ref引用同步的syncAppender。注意,这里把neverBlock=true则写日志队列时候会调用阻塞队列的offer方法而不是put,如果队列满则直接返回,业务调用线程不会被阻塞,即日志被丢弃。并且异步队列大小为512。



  • 代码3设置异步appender到log对象。


四、Log4j异步配置

 
   
   
 
  1. <appender name="ASYNC-ERROR"class="org.apache.log4j.AsyncAppender">

  2. <!--内部实现是一个列表,BufferSize是列表大小。该值会影响性能.默认值为128-->

  3. <param name="BufferSize" value="1024" />

  4. <!--Blocking=false则写日志队列满时候不会阻塞调用线程。默认是true-->

  5. <param name="Blocking" value="false" />

  6. <appender-ref ref="ERROR" />

  7. </appender>

其中BufferSize为队列的大小,默认128;Blocking=false则写日志队列满时候不会阻塞调用线程。默认是true,标识队列满时候会阻塞调用线程。

五、Log4j2异步配置

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

  2. <Configuration status="warn" name="MyApp" packages="">

  3. <Appenders>

  4. <File name="MyFile" fileName="logs/app.log">

  5. <PatternLayout>

  6. <Pattern>%d %p %c{1.} [%t] %m%n</Pattern>

  7. </PatternLayout>

  8. </File>

  9. <!--4.1 设置异步appender-->

  10. <Async name="Async">

  11. <AppenderRef ref="MyFile"/>

  12. <bufferSize>2048</bufferSize>

  13. <blocking>false</blocking>

  14. </Async>

  15. </Appenders>

  16. <Loggers>

  17. <Root level="error">

  18. <AppenderRef ref="Async"/>

  19. </Root>

  20. </Loggers>

  21. </Configuration>

  • 如上代码4.1设置异步appender.bufferSize:队列的大小为默认1024 blocking,默认为true,如果队列满了当前线程会被阻塞等待队列有空间为止 , false则如果队列满了则丢弃。

六、总结

本文简单介绍了异步日志打印原理,可知三种主流日志系统都是使用队列来实现异步解耦,让业务线程及时返回,但是需要注意当队列满时候设置的策略是丢弃还是阻塞调用线程,另外讲解了如何配置异步appender,从而启动异步日志打印。


以上是关于Log4j2异步日志同步日志和混合日志的配置详解的主要内容,如果未能解决你的问题,请参考以下文章

log4j2 异步日志原理及配置

Log4j2中的同步日志与异步日志

Log4j2简介和异步日志梳理

【日志】Log4j2配置

Log4j2异步日志背后的数字

日志框架,选择Logback Or Log4j2?