1、配置文件logback-spring.xml
Spring Boot工程自带logback和slf4j的依赖,我们使用的时候重点只需放在编写配置文件上,需要引入什么依赖,日志依赖冲突只要不影响服务正常启动和访问的,统统都不需要我们管。
logback日志框架会默认加载classpath下命名为logback-spring.xml的配置文件。
将所有日志都存储在一个文件中,文件大小也随着应用的运行越来越大,并且不好排查问题。正确的做法应该是将各个级别的日志根据时间段记录存储在不同的日志文件中。但是由于咱们G4统一使用的是docker+k8s,所以暂时可不用按照日志级别进行区分。
<?xml version="1.0" encoding="UTF-8"?>
<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"/>
<property name="log_home" value="${log.dir:-logs}/${app_name}"/>
<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">
<discardingThreshold>0</discardingThreshold>
<queueSize>512</queueSize>
<appender-ref ref="STDOUT"/>
</appender>
<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="ASYNC-STDOUT"/>
</root>
</springProfile>
</configuration>
按照级别分开存储的详细的日志配置:
<?xml version="1.0" encoding="UTF-8"?>
<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"/>
<property name="log_home" value="${log.dir:-logs}/${app_name}"/>
<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>
<property name="rollInfoName" value="${log_home}/feignClient_web-%d{yyyy-MM-dd}.%i.log"/>
<property name="rollWarnName" value="${log_home}/feignClient_web_warn-%d{yyyy-MM-dd}.%i.log"/>
<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>
<appender name="rollingFileInfo" class="ch.qos.logback.core.rolling.RollingFileAppender">
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<fileNamePattern>${rollInfoName}</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>INFO</level>
</filter>
</appender>
<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>
<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>
<appender name="ASYNC-STDOUT" class="ch.qos.logback.classic.AsyncAppender">
<discardingThreshold>0</discardingThreshold>
<queueSize>128</queueSize>
<appender-ref ref="STDOUT"/>
</appender>
<appender name="ASYNC-INFO" class="ch.qos.logback.classic.AsyncAppender">
<discardingThreshold>0</discardingThreshold>
<queueSize>512</queueSize>
<appender-ref ref="rollingFileInfo"/>
</appender>
<appender name="ASYNC-WARN" class="ch.qos.logback.classic.AsyncAppender">
<discardingThreshold>0</discardingThreshold>
<queueSize>256</queueSize>
<appender-ref ref="rollingFileWarn"/>
</appender>
<appender name="ASYNC-ERROR" class="ch.qos.logback.classic.AsyncAppender">
<discardingThreshold>0</discardingThreshold>
<queueSize>256</queueSize>
<appender-ref ref="rollingFileError"/>
</appender>
<appender name="ASYNC-ACCESS" class="ch.qos.logback.classic.AsyncAppender">
<discardingThreshold>0</discardingThreshold>
<queueSize>256</queueSize>
<appender-ref ref="rollingFileAccess"/>
</appender>
<logger name="com.taobao.diamond.utils" additivity="false">
<level value="${level}"/>
<appender-ref ref="rollingFileInfo"/>
<appender-ref ref="rollingFileWarn"/>
</logger>
<logger name="com.taobao.diamond.client" level="ERROR" additivity="false">
<appender-ref ref="STDOUT"/>
</logger>
<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>
<springProfile name="qa">
<root level="${level}">
<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">
<discardingThreshold>0</discardingThreshold>
<queueSize>512</queueSize>
<appender-ref ref="STDOUT"/>
</appender>
step3、在springProfile多环境配置里,为root标签指定日志异步输出的appender对应的ref值
<springProfile name="qa">
<root level="${level}">
<appender-ref ref="STDOUT"/>
</root>
</springProfile>
3、同步、异步输出日志,性能对比测试
既然能提高性能的话,必须进行一次测试比对,同步和异步输出日志性能到底能提升多少倍?
服务器硬件和配置相同的前提下,用Apache Jmeter测试工具分别压。
3.1、200个线程跑10分钟。
3.2、服务接口代码(包含远程调用)
@RequestMapping("/feignClient/logbackAsyncAppender")
public String testLogbackAsyncAppender(String timeout) throws InterruptedException {
System.out.println("FeignClientController.logbackAsyncAppender--->测试日志同步和异步打印的性能 start");
long startTime = System.currentTimeMillis();
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) + "毫秒。");
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
|