Logback日志框架配置详解+异步输出

1、配置文件logback-spring.xml

Spring Boot工程自带logback和slf4j的依赖,我们使用的时候重点只需放在编写配置文件上,需要引入什么依赖,日志依赖冲突只要不影响服务正常启动和访问的,统统都不需要我们管。

logback日志框架会默认加载classpath下命名为logback-spring.xml的配置文件。

将所有日志都存储在一个文件中,文件大小也随着应用的运行越来越大,并且不好排查问题。正确的做法应该是将各个级别的日志根据时间段记录存储在不同的日志文件中。但是由于咱们G4统一使用的是docker+k8s,所以暂时可不用按照日志级别进行区分。

<?xml version="1.0" encoding="UTF-8"?>
<!--日志级别优先级:OFF(最高等级,关闭所有日志输出) > FATAL > ERROR > WARN > INFO > DEBUG > TRACE > ALL(最低等级,打开所有日志输出)-->
<!--如果定义为INFO级别,则程序中DEBUG级别的日志将不会被打印出来(大于等于INFO级别的日志才会输出),所以设置的日志等级越高打印出来的日志越少-->
<!--
    scan="true": 配置文件如果发生改变,将会被重新加载。
    scanPeriod="600 seconds": 每隔600秒监测一次配置文件是否有修改,如果没有给出时间单位,默认单位是毫秒。当scan为true时,此属性生效。默认的时间间隔为1分钟。
    debug: 当此属性设置为true时,将打印出logback内部日志信息,实时查看logback运行状态。默认值为false。
-->
<configuration scan="true" scanPeriod="600 seconds" debug="false">
    <!-- 应用名称 -->
    <springProperty scope="context" name="app_name" source="spring.application.name"/>
    <springProperty scope="context" name="level" source="logging.level.logback"/>

    <!--日志文件的保存路径,首先查找系统属性-Dlog.dir,如果存在就使用其;否则,在当前目录下创建名为logs目录做日志存放的目录 -->
    <property name="log_home" value="${log.dir:-logs}/${app_name}"/>

    <!--日志输出格式:
        %d{HH:mm:ss.SSS}——日志输出时间
        %thread——输出日志的进程名字,这在Web应用以及异步任务处理中很有用
        %-5level——日志级别,并且使用5个字符靠左对齐
        %logger{36}——日志输出者的名字
        %msg——日志消息
        %n——平台的换行符
    -->
    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>ts=%d{yyyy-MM-dd HH:mm:ss.SSS} app=${app_name} th=[%thread] lv=%-5level class=%logger{5} msg=%line-%msg%n</pattern>
            <charset>utf-8</charset>
        </encoder>
    </appender>

    <!-- 日志信息异步输出配置 -->
    <appender name="ASYNC-STDOUT" class="ch.qos.logback.classic.AsyncAppender">
        <!--默认情况下,当blockingQueue的容量高于阈值时(80%),会丢弃ERROR以下级别的日志。如果不希望丢弃日志(既每次都全量保存),那可以设置为0-->
        <discardingThreshold>0</discardingThreshold>
        <!--默认情况下,队列的深度为256,不过该值首次建议设置大一些,后续根据自己业务的特点去调优。注意:该值会影响性能-->
        <queueSize>512</queueSize>
        <!--添加需要异步输出appender,只能添加一个-->
        <appender-ref ref="STDOUT"/>
    </appender>

    <!--表示:在com.taobao.diamond包下面,日志级别大于等于error的日志才会输出到控制台或日志文件-->
    <logger name="com.taobao.diamond" level="ERROR" additivity="false">
        <appender-ref ref="STDOUT"/>
    </logger>
    <logger name="org.springframework" level="ERROR" additivity="false">
        <appender-ref ref="STDOUT"/>
    </logger>
    <logger name="DiamondConfigDataLog" level="ERROR" additivity="false">
        <appender-ref ref="STDOUT"/>
    </logger>
    <logger name="com.dhgate.apsaras.util.ApsarasClientProperties" level="ERROR" additivity="false">
        <appender-ref ref="STDOUT"/>
    </logger>

    <!--多环境配置-->
    <!--测试环境-->
    <springProfile name="qa">
        <root level="${level}">
            <!--日志信息同步输出-->
            <!--<appender-ref ref="STDOUT"/>-->
            <!--日志信息异步输出-->
            <appender-ref ref="ASYNC-STDOUT"/>
        </root>
    </springProfile>
</configuration>

按照级别分开存储的详细的日志配置:

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

<!--日志级别优先级:OFF(最高等级,关闭所有日志输出) > FATAL > ERROR > WARN > INFO > DEBUG > TRACE > ALL(最低等级,打开所有日志输出)-->
<!--如果定义为INFO级别,则程序中DEBUG级别的日志将不会被打印出来(大于等于INFO级别的日志才会输出),所以设置的日志等级越高打印出来的日志越少-->
<!--
    scan="true": 配置文件如果发生改变,将会被重新加载。
    scanPeriod="600 seconds": 每隔600秒监测一次配置文件是否有修改,如果没有给出时间单位,默认单位是毫秒。当scan为true时,此属性生效。默认的时间间隔为1分钟。
    debug: 当此属性设置为true时,将打印出logback内部日志信息,实时查看logback运行状态。默认值为false。
-->
<configuration scan="true" scanPeriod="600 seconds" debug="false">
    <!-- 应用名称 -->
    <springProperty scope="context" name="app_name" source="spring.application.name"/>
    <springProperty scope="context" name="level" source="logging.level.logback"/>

    <!--日志文件的保存路径,首先查找系统属性-Dlog.dir,如果存在就使用;否则,在当前目录下创建名为logs目录做日志存放的目录 -->
    <property name="log_home" value="${log.dir:-logs}/${app_name}"/>
    <!--日志输出格式:
        %d{HH: mm:ss.SSS}——日志输出时间
        %thread——输出日志的进程名字,这在Web应用以及异步任务处理中很有用
        %-5level——日志级别,并且使用5个字符靠左对齐
        %logger{36}——日志输出者的名字
        %msg——日志消息
        %n——平台的换行符
    -->
    <property name="basePattern" value="ts=%d{yyyy-MM-dd HH:mm:ss:SSS} app=${app_name} th=[%thread] lv=%-5level class=%logger msg=%line-%msg%n">
    </property>

    <!--info级别日志输出和存档设置:按天切分 %d{yyyy-MM-dd}-->
    <property name="rollInfoName" value="${log_home}/feignClient_web-%d{yyyy-MM-dd}.%i.log"/>
    <!--warn级别日志输出和存档设置:按天切分 %d{yyyy-MM-dd}-->
    <property name="rollWarnName" value="${log_home}/feignClient_web_warn-%d{yyyy-MM-dd}.%i.log"/>
    <!--error级别日志输出和存档设置:按天切分 %d{yyyy-MM-dd}-->
    <property name="rollErrorName" value="${log_home}/feignClient_web_error-%d{yyyy-MM-dd}.%i.log"/>

    <!-- 控制台日志输出设置 -->
    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <pattern>${basePattern}</pattern>
            <!--设置字符集,解决乱码问题-->
            <charset>UTF-8</charset>
        </encoder>
    </appender>

    <!-- INFO级别日志输出设置 -->
    <appender name="rollingFileInfo" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <!-- 日志记录器的滚动策略:基于大小和时间 -->
        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
            <!--会将当天的日志记录在<file>${log_home}/buyer_dispute_web.log</file>,然后将昨天的日志归档到下面的文件中-->
            <fileNamePattern>${rollInfoName}</fileNamePattern>
            <!--单个日志文件最大1024M,到了这个值,就会再创建一个日志文件,日志文件的名字最后+1-->
            <maxFileSize>1024MB</maxFileSize>
            <!--日志文件保留天数:保存最近30天的日志-->
            <maxHistory>30</maxHistory>
            <!--所有的日志文件最大30G,超过就会删除旧的日志-->
            <totalSizeCap>30GB</totalSizeCap>
        </rollingPolicy>
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <!--日志输出格式-->
            <pattern>${basePattern}</pattern>
            <!--设置字符集,解决乱码问题-->
            <charset>UTF-8</charset>
        </encoder>
        <!-- ThresholdFilter:临界值过滤器,过滤掉低于指定临界值的日志。当日志级别等于或高于临界值时,过滤器返回NEUTRAL;当日志级别低于临界值时,日志会被拒绝。 -->
        <!--LevelFilter:级别过滤器,根据日志级别进行过滤。如果日志级别等于配置级别,过滤器会根据onMath 和 onMismatch接收或拒绝日志。
            有以下子节点:
            <level>:设置过滤级别
            <onMatch>:用于配置符合过滤条件的操作
            <onMismatch>:用于配置不符合过滤条件的操作
            onMatch 和 onMismatch说明:
                onMatch="ACCEPT" 表示匹配该级别及以上
                onMatch="DENY" 表示不匹配该级别及以上
                onMatch="NEUTRAL" 表示该级别及以上的,由下一个filter处理,如果当前是最后一个,则表示匹配该级别及以上
                onMismatch="ACCEPT" 表示匹配该级别以下
                onMismatch="DENY" 表示不匹配该级别以下
                onMismatch="NEUTRAL" 表示该级别及以下的,由下一个filter处理,如果当前是最后一个,则不匹配该级别以下的
        -->
        <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
            <!-- 接收INFO及以上级别的日志信息(onMatch="ACCEPT")输出到这个路径中,不匹配debug级别以下日志信息 -->
            <level>INFO</level><!--过滤掉所有低于INFO级别的日志-->
        </filter>
    </appender>

    <!-- warn级别日志输出设置 -->
    <appender name="rollingFileWarn" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
            <fileNamePattern>${rollWarnName}</fileNamePattern>
            <maxFileSize>1024MB</maxFileSize>
            <maxHistory>30</maxHistory>
            <totalSizeCap>30GB</totalSizeCap>
        </rollingPolicy>
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <pattern>${basePattern}</pattern>
            <charset>UTF-8</charset>
        </encoder>
        <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
            <level>WARN</level>
        </filter>
    </appender>

    <!-- error级别日志输出设置 -->
    <appender name="rollingFileError" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
            <fileNamePattern>${rollErrorName}</fileNamePattern>
            <maxFileSize>1024MB</maxFileSize>
            <maxHistory>30</maxHistory>
            <totalSizeCap>30GB</totalSizeCap>
        </rollingPolicy>
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <pattern>${basePattern}</pattern>
            <charset>UTF-8</charset>
        </encoder>
        <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
            <level>ERROR</level>
        </filter>
    </appender>

    <!--性能检测日志-->
    <appender name="rollingFileAccess" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>${log_home}/access-%d{yyyy-MM-dd}.log</fileNamePattern>
            <maxHistory>30</maxHistory>
        </rollingPolicy>
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <pattern>%msg%n</pattern>
        </encoder>
    </appender>

    <!-- 日志信息异步输出配置 start -->
    <appender name="ASYNC-STDOUT" class="ch.qos.logback.classic.AsyncAppender">
        <!--默认情况下,当blockingQueue的容量高于阈值时(80%),会丢弃ERROR以下级别的日志。如果不希望丢弃日志(既每次都全量保存),那可以设置为0-->
        <discardingThreshold>0</discardingThreshold>
        <!--默认情况下,队列的深度为256,不过该值首次建议设置大一些,后续根据自己业务的特点去调优。注意:该值会影响性能-->
        <queueSize>128</queueSize>
        <!--添加需要异步输出appender,只能添加一个-->
        <appender-ref ref="STDOUT"/>
    </appender>

    <appender name="ASYNC-INFO" class="ch.qos.logback.classic.AsyncAppender">
        <!--默认情况下,当blockingQueue的容量高于阈值时(80%),会丢弃ERROR以下级别的日志。如果不希望丢弃日志(既每次都全量保存),那可以设置为0-->
        <discardingThreshold>0</discardingThreshold>
        <!--默认情况下,队列的深度为256,不过该值首次建议设置大一些,后续根据自己业务的特点去调优。注意:该值会影响性能-->
        <queueSize>512</queueSize>
        <!--添加需要异步输出appender,只能添加一个-->
        <appender-ref ref="rollingFileInfo"/>
    </appender>

    <appender name="ASYNC-WARN" class="ch.qos.logback.classic.AsyncAppender">
        <!--默认情况下,当blockingQueue的容量高于阈值时(80%),会丢弃ERROR以下级别的日志。如果不希望丢弃日志(既每次都全量保存),那可以设置为0-->
        <discardingThreshold>0</discardingThreshold>
        <!--默认情况下,队列的深度为256,不过该值首次建议设置大一些,后续根据自己业务的特点去调优。注意:该值会影响性能-->
        <queueSize>256</queueSize>
        <!--添加需要异步输出appender,只能添加一个-->
        <appender-ref ref="rollingFileWarn"/>
    </appender>

    <appender name="ASYNC-ERROR" class="ch.qos.logback.classic.AsyncAppender">
        <!--默认情况下,当blockingQueue的容量高于阈值时(80%),会丢弃ERROR以下级别的日志。如果不希望丢弃日志(既每次都全量保存),那可以设置为0-->
        <discardingThreshold>0</discardingThreshold>
        <!--默认情况下,队列的深度为256,不过该值首次建议设置大一些,后续根据自己业务的特点去调优。注意:该值会影响性能-->
        <queueSize>256</queueSize>
        <!--添加需要异步输出appender,只能添加一个-->
        <appender-ref ref="rollingFileError"/>
    </appender>

    <appender name="ASYNC-ACCESS" class="ch.qos.logback.classic.AsyncAppender">
        <!--默认情况下,当blockingQueue的容量高于阈值时(80%),会丢弃ERROR以下级别的日志。如果不希望丢弃日志(既每次都全量保存),那可以设置为0-->
        <discardingThreshold>0</discardingThreshold>
        <!--默认情况下,队列的深度为256,不过该值首次建议设置大一些,后续根据自己业务的特点去调优。注意:该值会影响性能-->
        <queueSize>256</queueSize>
        <!--添加需要异步输出appender,只能添加一个-->
        <appender-ref ref="rollingFileAccess"/>
    </appender>
    <!-- 日志信息异步输出配置 end -->

    <!--日志输出过滤 start-->
    <!--additivity设为false后,则这个logger不会将日志流反馈到root中,只会输出到指定的日志文件输出,达到过滤日志的目的-->
    <logger name="com.taobao.diamond.utils" additivity="false">
        <level value="${level}"/>
        <!--此时该包或类里面,日志级别>=${level}的日志,只会在对应ref指定的地方输出-->
        <appender-ref ref="rollingFileInfo"/>
        <appender-ref ref="rollingFileWarn"/>
    </logger>
    <!--表示:在com.taobao.diamond.client包下面,日志级别大于等于error的日志只会在到控制台里打印-->
    <logger name="com.taobao.diamond.client" level="ERROR" additivity="false">
        <appender-ref ref="STDOUT"/>
    </logger>
    <!--表示:在com.alibaba.nacos.client包下面,日志级别大于等于warn的日志才会输出到控制台和info日志文件-->
    <logger name="com.alibaba.nacos.client" level="WARN" additivity="false">
        <appender-ref ref="STDOUT"/>
        <appender-ref ref="rollingFileInfo"/>
    </logger>
    <logger name="org.springframework" additivity="false">
        <level value="${level}"/>
        <appender-ref ref="rollingFileWarn"/>
    </logger>
    <!--日志输出过滤 end-->

    <!--多环境日志输出配置,可以在bootstrap.yml中配置选择哪个 profiles:spring.profiles.active=qa-->
    <springProfile name="qa">
        <root level="${level}">
            <!--同步输出-->
            <!--<appender-ref ref="STDOUT"/>
            <appender-ref ref="rollingFileInfo"/>
            <appender-ref ref="rollingFileWarn"/>
            <appender-ref ref="rollingFileError"/>
            <appender-ref ref="rollingFileAccess"/>-->
            <!--异步输出-->
            <appender-ref ref="ASYNC-STDOUT"/>
            <appender-ref ref="ASYNC-INFO"/>
            <appender-ref ref="ASYNC-WARN"/>
            <appender-ref ref="ASYNC-ERROR"/>
            <appender-ref ref="ASYNC-ACCESS"/>
        </root>
    </springProfile>
</configuration>

2、logback 高级特性:异步输出日志

目前所有的日志记录方式采用的都是同步的方式,即直接将日志写入文件。每次日志输出到文件都会进行一次磁盘IO,在多应用的时候这种效果会导致一定的线程运行延迟,所以可以采用异步的方式处理。

采用异步写日志的方式,通过不让主线程去写日志文件而减少磁盘IO,避免并发下造成线程阻塞,从而减少不必要的性能损耗。

异步输出日志的方式很简单,添加一个基于异步写日志的appender,并指向原先配置的appender即可:

step1、原先的appender

<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
    <encoder>
        <pattern>ts=%d{yyyy-MM-dd HH:mm:ss.SSS} app=${app_name} th=[%thread] lv=%-5level class=%logger{5} msg=%line-%msg%n</pattern>
        <charset>utf-8</charset>
    </encoder>
</appender>

step2、异步的appender

<!-- 日志信息异步输出配置 -->
<appender name="ASYNC-STDOUT" class="ch.qos.logback.classic.AsyncAppender">
    <!--默认情况下,当blockingQueue的容量高于阈值时(80%),会丢弃ERROR以下级别的日志。如果不希望丢弃日志(既每次都全量保存),那可以设置为0-->
    <discardingThreshold>0</discardingThreshold>
    <!--默认情况下,队列的深度为256,不过该值首次建议设置大一些,后续根据自己业务的特点去调优。注意:该值会影响性能-->
    <queueSize>512</queueSize>
    <!--添加需要异步输出appender,只能添加一个,这里指向原先配置的appender即可-->
 <appender-ref ref="STDOUT"/>
</appender>

step3、在springProfile多环境配置里,为root标签指定日志异步输出的appender对应的ref值

<springProfile name="qa">
    <root level="${level}">
        <!--日志信息同步输出-->
        <appender-ref ref="STDOUT"/>
        <!--日志信息异步输出-->
        <!--<appender-ref ref="ASYNC-STDOUT"/>-->
    </root>
</springProfile>

3、同步、异步输出日志,性能对比测试

既然能提高性能的话,必须进行一次测试比对,同步和异步输出日志性能到底能提升多少倍?

服务器硬件和配置相同的前提下,用Apache Jmeter测试工具分别压。

3.1、200个线程跑10分钟。

在这里插入图片描述

3.2、服务接口代码(包含远程调用)

/**
 * 日志同步、异步输出性能对比测试接口
 *
 * @param timeout
 * @return
 */
// http://localhost:7997/feignClient/logbackAsyncAppender?timeout=4
@RequestMapping("/feignClient/logbackAsyncAppender")
public String testLogbackAsyncAppender(String timeout) throws InterruptedException {

    System.out.println("FeignClientController.logbackAsyncAppender--->测试日志同步和异步打印的性能 start");

    long startTime = System.currentTimeMillis();
    // 日志级别优先级:ERROR > WARN > INFO > DEBUG > TRACE
    for (int i = 0; i < 500; i++) {
        log.trace("测试日志同步和异步打印的性能trace,timeout=" + Integer.valueOf(timeout) * (75 + 25) + (int) (1 + Math.random() * (10000 - 1 + 1)));
        log.debug("测试日志同步和异步打印的性能debug,timeout=" + Integer.valueOf(timeout) * (75 + 25) + (int) (1 + Math.random() * (10000 - 1 + 1)));
        log.info("测试日志同步和异步打印的性能info,timeout=" + Integer.valueOf(timeout) * (75 + 25) + (int) (1 + Math.random() * (10000 - 1 + 1)));
        log.warn("测试日志同步和异步打印的性能warn,timeout=" + Integer.valueOf(timeout) * (75 + 25) + (int) (1 + Math.random() * (10000 - 1 + 1)));
        log.error("测试日志同步和异步打印的性能error,timeout=" + Integer.valueOf(timeout) * (75 + 25) + (int) (1 + Math.random() * (10000 - 1 + 1)));
    }

    long endTime = System.currentTimeMillis();

    System.out.println("FeignClientController.logbackAsyncAppender--->测试日志同步和异步打印的性能 end,耗时:" + (endTime - startTime) + "毫秒。");// 此for循环输出日志的耗时。同步模式下,平均耗时158ms;异步模式下,平均耗时:27ms

    return clientService.feignClientLogbackAsyncAppender(timeout);
}

3.3、结果对比

1)同步输出日志:
在这里插入图片描述

2)异步输出日志:
在这里插入图片描述

重点关注指标【TPS】吞吐量:系统在单位时间内处理请求的数量。在同步输出日志中TPS为15.3/sec,异步TPS为19.9/sec,可以明显看到性能有所提升!

实际线上的服务处理时间导致的暂停时间会远远大于模拟时间,因此异步的优势应该更大。

4、异步日志输出原理

异步输出日志中最关键的就是配置文件中ch.qos.logback.classic包下AsyncAppenderBase类中的append方法,感兴趣的可以查看一下源码了解下。

可以配置的项为queueSize,discardingThreshold。

discardingThreshold:通过队列情况判断是否需要丢弃日志,不丢弃的话将它放到阻塞队列中。默认情况下,当blockingQueue的容量高于阈值时(80%),会丢弃ERROR以下级别日志,如果不希望丢弃日志(既每次都全量保存),那可以设置为0。 如正常日志可以丢弃,那可以极大的提升性能,并保存关键的ERROR日志。
queueSize:这个阻塞队列为ArrayBlockingQueueu,默认大小为256,可以通过配置文件进行修改。

核心思想:写文件是通过新起一个线程去完成的,主线程将日志扔到阻塞队列中,然后继续做其他事情。

5、对比测试工程源码

https://download.csdn.net/download/want_you_gogo/84377177

  • 0
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值