logback性能优化详解

本文涉及的产品
日志服务 SLS,月写入数据量 50GB 1个月
简介: logback性能优化详解

前言


不正确的日志打印不但会降低程序运行性能,还会占用大量IO资源和硬盘存储空间。

本文主要总结一些能提高日志打印性能的手段。


一、通过AsyncAppender异步输出日志


我们通常使用的ConsoleAppender 和 RollingFileAppender都是同步输出日志,会阻塞程序运行。只有当日志打印完毕程序才会继续执行。


而通过AsyncAppender实现异步日志输出,会启用单独日志线程去记录日志,并且不会阻塞程序运行,可以极大的增加日志打印的吞吐量。


具体实现可以查看:logback异步输出日志详解

107.png

配置示例:

添加一个基于异步写日志的 appender,并指向原先配置的 appender即可。

<configuration>
  <!-- 同步输出 -->
  <appender name="FILE" class="ch.qos.logback.core.FileAppender">
    <file>myapp.log</file>
    <encoder>
      <pattern>%logger{35} - %msg%n</pattern>
    </encoder>
  </appender>
  <!-- 异步输出 -->
  <appender name="ASYNC" class="ch.qos.logback.classic.AsyncAppender">
    <appender-ref ref="FILE" />
  </appender>
  <root level="DEBUG">
    <appender-ref ref="ASYNC" />
  </root>
</configuration>


二、禁用即时刷新immediateFlush=false


默认情况下,每个日志事件都会立即刷新到基础输出流。这种默认方法更安全,因为如果应用程序退出时没有正确关闭附加程序,那么日志事件就不会丢失。但是,为了显著提高日志吞吐量,我们可以将 immediateFlush 属性设置为 false。

<configuration>
  <appender name="FILE" class="ch.qos.logback.core.FileAppender">
    <file>testFile.log</file>
    <append>true</append>
    <!-- 禁用即时刷新提高日志吞吐量 -->
    <immediateFlush>false</immediateFlush>
    <encoder>
      <pattern>%-4relative [%thread] %-5level %logger{35} - %msg%n</pattern>
    </encoder>
  </appender>
  <root level="DEBUG">
    <appender-ref ref="FILE" />
  </root>
</configuration>


原理分析:

无论是ConsoleAppender还是 FileAppender,都是继承了OutputStreamAppender。

所有的日志输出本质都是I/O写入操作。

通过禁用即时刷新可以充分利用IO缓冲区,提高IO性能。

106.png


三、规范日志输出格式pattern


<encoder>
        <!-- 日志默认输出格式 -->
        <pattern>%-4relative [%thread] %-5level %logger{35} - %msg %n</pattern>
        <!-- 采用utf8字符格式输出,避免出现乱码 -->
        <charset>utf8</charset>
</encoder>


日志布局介绍:https://logback.qos.ch/manual/layouts.html


主要是需要禁止输出下面这些信息:


1、禁止输出文件名

参数

说明

F/file

输出发出日志记录请求的 Java 源文件的文件名。

生成文件名信息并不是特别快。因此,应该避免使用它,除非执行速度不是问题。

输出发出日志记录请求的 Java 源文件的文件名。

生成文件信息并不是特别快。因此,应该避免使用它,除非执行速度不是问题。


2、禁止输出调用方类名

参数

说明

C{length}

class{length}

输出发出日志记录请求的调用方的完全限定类名。生成调用方类信息并不是特别快。因此,应该避免使用它,除非执行速度不是问题。

推荐采用%logger{26}输出日志记录器的名称,而不要输出类名。


3、禁止输出行号

参数

说明

L / line

输出发出日志记录请求的地方的行号。
生成行号信息并不是特别快。因此,应该避免使用它,除非执行速度不是问题。

4、禁止输出调用方类名

参数

说明

M / method

输出发出日志记录请求的方法名。

生成方法名并不是特别快。因此,应该避免使用它,除非执行速度不是问题。

输出发出日志记录请求的方法名,速度比较慢,尽量避免。


注意⚠️:

如果你的项目输出日志非常缓慢,赶紧检测下日志输出格式中是否包含上面这些内容。


四、提高生产环境的日志输出级别


可以根据项目运行的不同环境来限制日志输出的默认级别。

比如开发环境dev由于需要查看更详细的日志信息便于程序调试,可以将root的日志级别设置为DEBUG。

而测试环境test、生产环境prod不需要关注调试级别的日志信息,只需要将root日志级别设置为INFO。


logback 日志级别排序: TRACE < DEBUG < INFO < WARN < ERROR


根据spring环境打印不同级别的日志:

<!-- 开发环境日志级别为DEBUG -->
<springProfile name="dev">
    <root level="DEBUG">
        <appender-ref ref="STDOUT"/>
        <appender-ref ref="FILE"/>
    </root>
</springProfile>
<!-- 测试环境日志级别为INFO -->
<springProfile name="test">
    <root level="INFO">
        <appender-ref ref="STDOUT"/>
        <appender-ref ref="FILE"/>
    </root>
<!-- 生产环境日志级别为INFO -->
<springProfile name="prod">
    <root level="INFO">
        <appender-ref ref="STDOUT"/>
        <appender-ref ref="FILE"/>
    </root>
</springProfile>


另外spring boot下,还可以针对项目中的特定类指定不同的日志输出级别:

比如开发环境dev,将mapper目录下的日志设置为trace级别,这样就可以将数据库操作的sql、入参以及返回都打印出来,方便调试。

但是针对测试和生产环境,则一定要禁止。

application-dev.properties

logging.level.com.laowan.test.demo.mapper=trace

application-prod.properties

#可以注释掉该行,使其继承root的日志级别,也可以主动设置为info
logging.level.com.laowan.test.demo.mapper=info


五、非控制台输出严禁彩色打印


在上篇文章logback控制台彩色日志输出中提到过,彩色日志只有在控制台输出才能展示彩色效果,如果是文件类型的日志输出,会输出大量彩色渲染的识别码,不利于日志查看,同时也会增加日志输出的压力。


彩色日志的文件输出:

105.png


六、根据日志的输出级别保存到不同文件


虽然采用多日志文件输出可以在一定程度避免IO进程对单文件的竞争,但是日志输出文件频繁切换也可能会对日志输出性能造成一定影响,单日志文件的顺序写入有可能性能更高。


由于该方式的没有经过具体的性能测试,具体是否能起到优化效果暂未可知,所以只推荐在希望分离出error级别日志,方便问题定位的时候使用。


通过配置LevelFilter控制日志输出级别:

<appender name="errorlog" class="ch.qos.logback.core.rolling.RollingFileAppender">  
     <file>${BUILD_FOLDER:-logs}/error/error.log</file>  
     <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">  
            <FileNamePattern>${BUILD_FOLDER:-logs}/error/error-%d{yyyy-MM-dd}.log</FileNamePattern>  
            <MaxHistory>10</MaxHistory>  
      </rollingPolicy>  
     <encoder>  
        <pattern>[%-5level] %d{HH:mm:ss.SSS} [%thread] %logger{36} - %msg%n</pattern>  
        <charset>utf8</charset>  
    </encoder>  
    <filter class="ch.qos.logback.classic.filter.LevelFilter">  
        <level>error</level>  
        <onMatch>ACCEPT</onMatch>  
        <onMismatch>DENY</onMismatch>  
    </filter>  
</appender>

七、根据日志具体作用设置对应的日志输出级别


只有在程序开发的过程中,在打印日志的时候设置了合适的日志级别,这样我们在logback中根据各个环境控制的不同日志级别才能达到更好的效果。

不然调试级别的日志输出采用info级别,异常日志输出又采用info级别,就完全失去了日志级别控制的意义。


log.trace("trace级别日志,一般用于调试跟踪,常见的比如mapper层日志打印输出sql执行返回");
log.debug("调试级别日志,一般用于调试跟踪");
log.info("info级别日志输出,一般用于输出一些关键信息,比如请求入参,方法耗时等");
log.warn("告警级别日志输出");
log.error("异常级别日志输出");


典型的错误用法:


1、胡乱采用error级别输出关键日志信息

public void order(OrderReq req) {
    log.error("请求入参为:{}",req);
    //具体业务处理
}

2、异常日志采用info级别输出

try {
   //逻辑处理
}catch (Exception ex){
    log.info("请求失败,异常原因",ex);
}

八、减少不必要的日志输出


分析日志是否有输出的必要,是否可以对输出的日志降级。

有时候减少一些不必要的日志输出,可以极大的减少日志输出的IO压力,提高程序性能。


九、其他


1、字符串连接输出

反例:采用+号进行字符串连接

public void add(User user) {
    log.info("新增用户ID:"+user.getId()",年纪:"+user.getAge());
    //具体业务处理
}


正例:采用{}替换符连接

public void add(User user) {
    log.info("新增用户ID:{},年纪:{}",user.getId(),user.getAge());
    //具体业务处理
}

2、异常日志输出

反例:采用e.printStackTrace()打印日志堆栈


try {
   //逻辑处理
}catch (Exception e){
    e.printStackTrace();
}


正例:采用log.error(“异常描述”,e)打印日志堆栈

try {
   //逻辑处理
}catch (Exception e){
    log.error("请求失败,异常原因",e);
}

总结


本文主要是对logback日志输出的相关优化手段进行了总结说明。

1、通过AsyncAppender进行异步输出日志和禁止即时刷新是比较直接有效的手段,但需要注意两者都要日志丢失的风险,在需要进行日志收集归档的情况的要谨慎使用。


2、设置日志输出级别,对性能优化至关重要。


3、注意日志的打印格式pattern,严禁日志输出中包含文件名,类名,方法名以及行号。


4、减少不必要的日志输出。

目录
打赏
0
1
0
0
24
分享
相关文章
如何保证分布式文件系统的数据一致性
分布式文件系统需要向上层应用提供透明的客户端缓存,从而缓解网络延时现象,更好地支持客户端性能水平扩展,同时也降低对文件服务器的访问压力。当考虑客户端缓存的时候,由于在客户端上引入了多个本地数据副本(Replica),就相应地需要提供客户端对数据访问的全局数据一致性。
31853 78
如何保证分布式文件系统的数据一致性
HTML5+CSS3前端入门教程---从0开始通过一个商城实例手把手教你学习PC端和移动端页面开发第8章FlexBox布局(上)
HTML5+CSS3前端入门教程---从0开始通过一个商城实例手把手教你学习PC端和移动端页面开发第8章FlexBox布局
17655 18
灵骏可预期网络:Built for AI Infrastructure
通用人工智能离我们越来越近,全世界的关注和投入正在带来日新“周”异的变化。回顾人工智能的诞生和发展历程,人类计算能力的进步几乎牵动了每一次的重大技术突破,当前的大模型热潮更是如此,只是动辄千万亿参数级的模型体量,所需计算资源远超单颗芯片的上限,超大规模的计算集群成为支撑技术发展和应用创新的关键基础设施。面向智能:云基础设施网络技术面临新挑战如何突破单个芯片、单个服务器节点的算力上限,在超大规模情况
31193 10
灵骏可预期网络:Built for AI Infrastructure
设计模式(C++版)
看懂UML类图和时序图30分钟学会UML类图设计原则单一职责原则定义:单一职责原则,所谓职责是指类变化的原因。如果一个类有多于一个的动机被改变,那么这个类就具有多于一个的职责。而单一职责原则就是指一个类或者模块应该有且只有一个改变的原因。bad case:IPhone类承担了协议管理(Dial、HangUp)、数据传送(Chat)。good case:里式替换原则定义:里氏代换原则(Liskov 
36193 19
设计模式(C++版)
带你简单了解Chatgpt背后的秘密:大语言模型所需要条件(数据算法算力)以及其当前阶段的缺点局限性
带你简单了解Chatgpt背后的秘密:大语言模型所需要条件(数据算法算力)以及其当前阶段的缺点局限性
24468 14
重生之---我测阿里云U1实例(通用算力型)
阿里云产品全线降价的一力作,2023年4月阿里云推出新款通用算力型ECS云服务器Universal实例,该款服务器的真实表现如何?让我先测为敬!
36515 15
重生之---我测阿里云U1实例(通用算力型)
为笔记本更换固态硬盘的方法
本文介绍为笔记本电脑拆机、更换固态硬盘的具体方法~
18011 41
为笔记本更换固态硬盘的方法
Redis性能高30%,阿里云倚天ECS性能摸底和迁移实践
Redis在倚天ECS环境下与同规格的基于 x86 的 ECS 实例相比,Redis 部署在基于 Yitian 710 的 ECS 上可获得高达 30% 的吞吐量优势。成本方面基于倚天710的G8y实例售价比G7实例低23%,总性价比提高50%;按照相同算法,相对G8a,性价比为1.4倍左右。
【分布式技术专题】「分布式技术架构」手把手教你如何开发一个属于自己的限流器RateLimiter功能服务
随着互联网的快速发展,越来越多的应用程序需要处理大量的请求。如果没有限制,这些请求可能会导致应用程序崩溃或变得不可用。因此,限流器是一种非常重要的技术,可以帮助应用程序控制请求的数量和速率,以保持稳定和可靠的运行。
29747 52
AI助理

你好,我是AI助理

可以解答问题、推荐解决方案等

登录插画

登录以查看您的控制台资源

管理云资源
状态一览
快捷访问