打印性能是日志打印框架的核心关注点之一,从logback的日志打印流程看看其性能如何?

本文基于logback1.2.3版本,以下配置(1.2.0版本后,logback提供了 SizeAndTimeBasedRollingPolicy 策略,可同时基于时间+空间两个维度控制日志文件的滚动)

形如下面这句debug日志的打印,调用栈是这样的

LOGGER.debug("msg {}", msg);

Appender接口&实现类

Appender接口作为logback的核心三大件(Logger、Appender、Layout)之一,所有的日志打印Appender类都必须遵循Appender接口的规范。

关于性能的代码

根据上文的调用栈,可以看到Logger.debug()最终调用了OutputStreamAppender.subAppend(),其中核心代码就两句:encodewriteBytes

/*** Actual writing occurs here.* <p>* Most subclasses of <code>WriterAppender</code> will need to override this* method.* * @since 0.9.0*/
protected void subAppend(E event) {if (!isStarted()) {return;}try {// this step avoids LBCLASSIC-139if (event instanceof DeferredProcessingAware) {((DeferredProcessingAware) event).prepareForDeferredProcessing();}// the synchronization prevents the OutputStream from being closed while we// are writing. It also prevents multiple threads from entering the same// converter. Converters assume that they are in a synchronized block.// lock.lock();byte[] byteArray = this.encoder.encode(event);writeBytes(byteArray);  // 写入encode的日志内容} catch (IOException ioe) {// as soon as an exception occurs, move to non-started state// and add a single ErrorStatus to the SM.this.started = false;addStatus(new ErrorStatus("IO failure in appender", this, ioe));}
}

官方注释:OutputStream的输出需要同步,同时为了防止多个线程同时使用,converter对event对象向输出流数据的转换需要得到串行化保障。

再看下writeBytes()的实现,可以看到所有的日志打印线程到这个方法中需要阻塞、同步执行write方法。

// OutputStreamAppender line: 54,默认立即刷新=true
boolean immediateFlush = true;/*** All synchronization in this class is done via the lock object.*/
protected final ReentrantLock lock = new ReentrantLock(false);private void writeBytes(byte[] byteArray) throws IOException {if(byteArray == null || byteArray.length == 0)return;lock.lock();  // 重入锁try {this.outputStream.write(byteArray);if (immediateFlush) { // 是否立即刷新缓冲内容this.outputStream.flush();}} finally {lock.unlock();}
}

其次需要关注immediateFlush变量,因为我们没有配置,默认是开启立即刷新。
官方注释:1.2.0版本后,支持在logback.xml的<appender>节点设置immediateFlush属性,如果不设置,默认值是true,即每次日志输出流缓冲队列都将被刷新。

同时提醒,开启立即刷新,虽然比较“安全”,但将降低系统日志输出的整体性能

/*** Sets the immediateFlush option. The default value for immediateFlush is 'true'. If set to true,* the doEncode() method will immediately flush the underlying OutputStream. Although immediate flushing* is safer, it also significantly degrades logging throughput.** @since 1.0.3*/
public void setImmediateFlush(boolean immediateFlush) {addWarn("As of version 1.2.0 \"immediateFlush\" property should be set within the enclosing Appender.");addWarn("Please move \"immediateFlush\" property into the enclosing appender.");this.immediateFlush = immediateFlush;
}

为了确认缓冲是真实存在的,就要看看这里this.outputStream的具体实现类:ch.qos.logback.core.recovery.ResilientFileOutputStream,其中组合了一个os(BufferedOutputStream)对象

// FileAppender中定义了缓冲区大小,默认是 8192 字节(8kb)
public static final long DEFAULT_BUFFER_SIZE = 8192;
private FileSize bufferSize = new FileSize(DEFAULT_BUFFER_SIZE);// 构造方法,组合BufferedOutputStream
public ResilientFileOutputStream(File file, boolean append, long bufferSize) throws FileNotFoundException {this.file = file;fos = new FileOutputStream(file, append);this.os = new BufferedOutputStream(fos, (int) bufferSize);this.presumedClean = true;
}// 抽象父类默认方法,调用os.write
public void write(byte b[], int off, int len) {if (isPresumedInError()) {if (!recoveryCoordinator.isTooSoon()) {attemptRecovery();}return; // return regardless of the success of the recovery attempt}try {os.write(b, off, len);postSuccessfulWrite();} catch (IOException e) {postIOFailure(e);}
}

至此我们已经知道 RollingFileAppender 的日志打印最终会调用BufferedOutpuStream.write方法,那么说明日志打印存在缓冲,且缓冲队列的默认是8kb

FileAppenderbufferSize字段,找到了其setter方法,没有其他类调用,猜测是可以通过反射配置的。

public void setBufferSize(FileSize bufferSize) {addInfo("Setting bufferSize to ["+bufferSize.toString()+"]");this.bufferSize = bufferSize;
}

那我们能显示的设置FileAppender#bufferSize大小吗?我做了以下尝试:
1.Logback官网查找Appender目录中的bufferSize设置,无果
2.项目中直接配置 <bufferSize>1MB</bufferSize> 没有起作用
3.stackoverflow搜索无果

总结

1.对于同步的RollingFileAppender而言,多线程的log appender打印是有意义的,因为只会在调用BufferedOutpuStream.write方法时加锁同步,encode等方法也是比较耗时的,多线程可以增加系统吞吐量;对于 AsyncAppender而言,增加日志消费线程则意义不大, 这应该也是AsyncAppender中只提供了一个worker守护线程的原因。

2.使用 RollingFileAppender 时,logback提供给我们<ImmediateFlush>标签使用,设置为false时可获得更高的logging吞吐量。

3.FileAppender中的bufferSize字段无法从外部设置,默认大小是8kb,在ImmediateFlush=false的情况下满8kb刷新一次缓冲区。

logback-RollingFileAppender源码分析(关于缓冲和性能)相关推荐

  1. 【Android 性能优化】应用启动优化 ( 阶段总结 | Trace 文件分析及解决方案 | 源码分析梳理 | 设置主题的方案总结 ) ★

    文章目录 一. 常用的耗时方法优化方案 ( 重要 ) 二. 源码分析梳理 1. 应用启动时间计算相关源码分析 2. Launcher 应用中启动 Android 应用流程 三. 启动白屏解决方案 An ...

  2. Python3.5源码分析-List概述

    Python3源码分析 本文环境python3.5.2. 参考书籍<<Python源码剖析>> python官网 Python3的List对象 list对象是一个变长对象,在运 ...

  3. java bufferedwrite_Java BufferedWriter BufferedReader 源码分析

    一:BufferedWriter 1.类功能简介: BufferedWriter.缓存字符输出流.他的功能是为传入的底层字符输出流提供缓存功能.同样当使用底层字符输出流向目的地中写入字符或者字符数组时 ...

  4. Spring Security 源码分析:Spring Security 授权过程

    Spring Security是一个能够为基于Spring的企业应用系统提供声明式的安全访问控制解决方案的安全框架.它提供了一组可以在Spring应用上下文中配置的Bean,充分利用了Spring I ...

  5. Spring Security源码分析八:Spring Security 退出

    为什么80%的码农都做不了架构师?>>> Spring Security是一个能够为基于Spring的企业应用系统提供声明式的安全访问控制解决方案的安全框架.它提供了一组可以在Spr ...

  6. Linux驱动修炼之道-SPI驱动框架源码分析(上)

    Linux驱动修炼之道-SPI驱动框架源码分析(上)   SPI协议是一种同步的串行数据连接标准,由摩托罗拉公司命名,可工作于全双工模式.相关通讯设备可工作于m/s模式.主设备发起数据帧,允许多个从设 ...

  7. java io源码解读_Java IO源码分析(五)——CharArrayReader 和 CharArrayWriter

    简介 CharArrayReader 是字符数组的输入流,它和我们之前讲的ByteArrayInputStream十分类似,顾名思义,区别在于一个用于字符数组,一个用于字节数组,在Java中字符是16 ...

  8. FATFS文件系统框架及源码分析

    FATFS是一个为小型嵌入式系统设计的通用FAT(File Allocation Table)文件系统模块.FatFs 的编写遵循ANSI C,并且完全与磁盘I/O层分开.因此,它独立(不依赖)于硬件 ...

  9. Zookeeper源码分析(二) ----- zookeeper日志

    zookeeper源码分析系列文章: Zookeeper源码分析(一) ----- 源码运行环境搭建 原创博客,纯手敲,转载请注明出处,谢谢! 既然我们是要学习源码,那么如何高效地学习源代码呢?答案就 ...

  10. FatFsVersion0.01源码分析

    FatFsVersion0.01源码分析 目录 一.API的函数功能简述 二.FATFS主要数据结构 1.FAT32文件系统的结构 2.FATFS主要数据结构 ①   FATFS ②   DIR ③  ...

最新文章

  1. Kotlin问题解决
  2. 1. 批量梯度下降法BGD 2. 随机梯度下降法SGD 3. 小批量梯度下降法MBGD
  3. C语言经典例35-字符串反转
  4. EndNote(二)之英文引文导入方式
  5. 怎么知道网站是用什么程序做的
  6. 如何安装Bit-Z IOS版APP
  7. js判断只能输入数字或小数点
  8. Spring实战 MethodInvokingJobDetailFactoryBean使用与分析
  9. python龟图_python学习turtle(龟图标状态)
  10. 船舶和计算机结合论文格式,近海船舶监控系统中航迹关联算法的计算机研究与实现...
  11. 非使用FindControl方法找到深层嵌套的控件
  12. python打印日历代码_带tkinter的日历(打印所选日期)
  13. 创建SQL Server索引的好工具
  14. Android 发送邮件信息,附带附件
  15. Tensorflow官方文档学习理解 (五)-卷积MNIST
  16. 双亲委派模型与 Flink 的类加载策略
  17. Android HttpClient用法
  18. bs结构管理系统 服务器多少钱,购买BS或CS架构的进销存软件哪个更划算
  19. Secure CRT 配色方案
  20. 英语背单词有用吗_学英语千万不要背单词 背单词有效吗

热门文章

  1. RabbitMq中的mandatory
  2. Qt上位机同时使用ZLG-USBCan2卡与USBCan2C
  3. 阿里云字体图标的引用
  4. pymol作图-氢键
  5. 学IT,是不是穷人家孩子获得高收入的唯一出路?
  6. 2020牛客暑期多校训练营(第八场)I.Interesting Computer Game(并查集)
  7. LYTRO图像文件数据包
  8. 水生植物的Java莫斯
  9. P1423 小玉在游泳 NOIP python题解
  10. vue项目中通过百度地图API获取当前位置定位