文 | 百川 on 资产管理

一、引言

Logback 是一个优秀的开源日志框架,我们很多项目都使用它来记录日志。实际使用时,通常仅需要一行语句即可记录相应的日志信息,如

logger.info("Hello world.");

那么,看似简单的语句背后都有哪些故事,打印日志时 Logback 内部都做了些什么?

本文以输出日志内容到文件为例,阐述 Logback 打印日志的工作流程。

二、Logback 设计

想要了解 Logback 打印日志的工作原理,首先需要清楚 Logback 所涉及的一些重要概念。

  • LoggingEvent

    LoggingEvent 表示日志事件的概念,其中包括了所有与打印日志请求相关的参数,如当前请求线程、当前时间、消息内容、请求级别等。整个日志打印流程都围绕这个类来展开。

  • Logger

    Logger 意为日志记录器,是打印日志的入口,打印日志时要先获取一个 Logger对象。每个 Logger 对象都有一个名字,且名字遵循层次化的命名规则。如 名为 "com.foo" 的 logger 是名为 "com.foo.Bar" 的 logger的父级。root logger 位于 logger 层级的最顶端,是所有 logger 的祖先。

  • Appender

    Logback 允许日志输出到多个目的地,Appender 表示目的地这个概念,常见的 appender 有控制台、文件、socket 服务器、数据库等。一个 logger 可以关联多个 appender。

  • Layout

    Layout 负责对日志消息进行格式化,用户可以自主设置日志输出的格式。

    如,采用转换模式 "%-4relative [%thread] %-5level %logger{32} - %msg%n" 输出的日志格式如下:

176[main]DEBUG manual.architecture.HelloWorld-Helloworld.

其中,第一列表示时间戳,第二列表示日志请求线程,第三列表示请求级别,第四列表示请求的 logger 的名字,"-"之后的内容为具体的日志文本。

Logback 核心类图如下:

三、打印日志流程

清楚了Logback 中的核心概念,下面开始介绍 Loback打印日志流程。

3.1 获取 Logger

调用 org.slf4j.LoggerFactory 类的 getLogger 方法,即可获取 Logger 对象,如图。

Logger x = LoggerFactory.getLogger("wombat");

Logger y = LoggerFactory.getLogger("wombat");

相同名字对应的 Logger 对象相同,即 x,y 指向同一个对象。

3.2 调用 logger 打印方法

下面介绍调用 logger 的 info() 方法时做了哪些事情。

3.2.1 取得过滤链(filter chain)的判定结果

此处使用的过滤器类为 TurboFilter,TurboFilter 与日志上下文绑定,在日志事件(LoggingEvent)创建之前被调用,会过滤所有的日志请求。如果过滤链的判断结果为 FilterReply.DENY,则记录请求被抛弃。如果结果是 FilterReply.NEUTRAL,则继续下一步,即进入2.2。如果结果是 FilterReply.ACCEPT,则忽略第二步, 直接进入第三步,即2.3。

3.2.2 检查日志级别

对 logger 的有效级别与日志请求级别进行比较。如果请求级别数值小于有效级别,本次请求将 被禁用,Logback 会直接抛弃请求,不做进一步处理;否则,继续下一步。

3.2.3 创建 LoggingEvent 对象

只有到了这一步,logback 才会创建 LoggingEvent 对象,该对象包含所有与请求相关的参数,如请求用的 logger、请求级别、消息、请求携带的异常、当前时间、当前线程等。

3.2.4 调用 appenders 的 doAppend() 方法

在创建 LoggingEvent 对象以后,logback 将调用所有可用 appender 的 doAppend()方法,进行日志输出。

doAppender()方法会调用 appender 关联的过滤器,此处使用的过滤器类为 Filter,执行过滤链判定, 返回结果同 TurboFilter 判定结果,都是一个 FilterReply 枚举值。如果当前过滤器返回结果是 FilterReply.DENY,则记录请求被抛弃;如果结果是 FilterReply.NEUTRAL,则将日志事件交由过滤链中下一个过滤器来处理;如果结果是 FilterReply.ACCEPT,日志事件被立即处理,不再经过剩余的过滤器。

3.2.5 格式化日志信息

appender 在输出日志信息前,需要对日志事件(LoggingEvent)进行格式化。以 OutputStreamAppender 为例,它使用 Encoder 将日志事件转换为字节数组并把字节数组写出到相应的输出流,在转换为字节数组前,Encoder 通过调用 Layout 的 doLayout() 方法将日志事件格式化为字符串。

3.2.6 输出日志信息

日志信息格式化完成以后,将会输出到各个 appender 对应的目的地。

Logback 打印日志整体流程如下图:

四、额外的工作

日志输出到文件以后,会引出一些新的问题。如单个文件太大能否进行切割,能否对文件进行归档,超出一定时间的历史日志能否自动删除等。Logback 内部,RollingFileAppender 担此重任,用来处理这类问题。

RollingFileAppender 继承自 FileAppender,能够滚动记录文件,其内部依赖两个重要组件。一个是 RollingPolicy,负责滚动处理(rollover)。另一个是 TriggeringPolicy,决定是否滚动以及何时进行滚动处理。

项目中名为 “ERROR” 的 appender 配置如下,应用通过该 appender,将 ERROR 级别的日志输出到 error.log 文件中,同时,使用基于大小和时间的滚动策略来对 error.log 文件进行处理。

name="ERROR" class="ch.qos.logback.core.rolling.RollingFileAppender">

${LOG_PATH}/error.log

class="ch.qos.logback.classic.filter.ThresholdFilter">

ERROR

class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">

${LOG_PATH}/%d{yyyy-MM-dd}/error-%d{yyyy-MM-dd}-%i.log.gz

100MB

60

3GB

%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %caller{1} - %msg%n

utf8

这里用到了 SizeAndTimeBasedRollingPolicy,它同时实现了 RollingPolicy 和 TriggeringPolicy,该类类图如下:

RollingFileAppender 在输出日志信息时,会调用一个 subAppend()方法,代码如下:

@Override

protected void subAppend(E event) {

synchronized (triggeringPolicy) {

if (triggeringPolicy.isTriggeringEvent(currentlyActiveFile, event)) {

rollover();

}

}

super.subAppend(event);

}

在同步代码块中,首先判断是否触发翻转操作,如果是,则对日志文件进行翻转处理。可见,日志翻转发生在打印日志时。这里翻转操作有两种情况。

4.1 基于文件大小滚动

如果当前日志文件大小超过了设置的 maxFileSize,会触发一次翻转操作,将当前文件归档,同时创建一个新的活动文件,用来输出日志。

4.2 基于时间滚动

Logback 在初始化时会设置一个滚动触发时间点,当到达该时间点以后,会触发一次翻转操作,同样将当前文件归档,同时创建一个新的活动文件,用来输出日志;此外,会按照 maxHistory 的设置对归档日志文件进行删除,若设置了 totalSizeCap,还会判断归档日志文件大小是否超过了该值,超过则会进行清理。清理工作由异步线程来进行,关键代码如下:

public class ArhiveRemoverRunnable implements Runnable {

Date now;

ArhiveRemoverRunnable(Date now) {

this.now = now;

}

@Override

public void run() {

clean(now);

if (totalSizeCap != UNBOUNDED_TOTAL_SIZE_CAP && totalSizeCap > 0) {

capTotalSize(now);

}

}

}

五、尾声

至此,Logback 打印日志到文件的工作流程就介绍完了。

翻阅 Logback资料时,在官方 jira 上看到过一个问题:有个桌面应用使用 Logback 记录日志,该应用每天运行几次,但基本不会在凌晨使用;问题现象是应用配置了 maxHistory ,历史日志文件并没有被删除。经分析,是因为 Logback 滚动触发时间是在每次应用启动的时候进行设置,按天滚动就设置为第二天凌晨记录日志时触发,由于该应用只在白天运行,且每次启动都会重新设置触发时间,导致滚动策略永远不会触发,所以历史日志文件不会被删除。

在后面的版本中,Logback 在 TimeBasedRollingPolicy 中增加了 cleanHistoryOnStart 属性,配置为 true 以后,可以在应用启动时执行历史日志删除工作,解决了上述问题。

由此可见,任何优秀的系统都不是一蹴而就的,而是在满足业务场景的基础上不断迭代演化而来的。

推荐阅读:

  1. 记一次故障引发的线程池使用的思考

  2. 营销系统在预付卡场景下的演进

  3. CAP一致性协议及应用解析

-The End-

Vol.201

有赞技术团队

为 442 万商家,150 个行业,330 亿电商交易额

提供技术支持

微商城|零售|美业 | 教育

微信公众号:有赞coder    微博:@有赞技术

技术博客:tech.youzan.com

The bigger the dream,

the more important the team.

fastreport调用frf文件直接打印_打印日志时 Logback 内部都做了些什么相关推荐

  1. fastreport调用frf文件直接打印_来吧~~ 这里提供自助打印~

    照片打印:支持5寸.6寸.7寸.A4多尺寸介质打印 文档打印:支持word.pdf.ppt.wps等文档格式A4幅面打印 证件照:已有证件照拼版打印功能:无电子照可在线制作打印,支持蓝底.红底.白底证 ...

  2. go 调用其他文件函数_一文读懂Go中软件包概念

    Go编程语言的软件包管理和部署的完整概述 如果您熟悉Java或NodeJS之类的语言,那么您可能非常熟悉软件包. 包不过是带有一些代码文件的目录,该目录从单个引用点公开了不同的变量(功能). 让我解释 ...

  3. 详解printf重定向到文件中,打印日志的实现

    printf是将信息打印到终端,但是有时当我们需要打印的信息比较多时,终端无法将所有信息都能够保留在屏幕上,这样我们就不能在终端获取我们想要的信息了,重定向很好的帮我们解决了这个问题,下面我就通过重定 ...

  4. 怎么修改file文件框的无文件提示_使用LativeLink时,DO文件编制步骤

    大侠好,欢迎来到FPGA技术江湖,江湖偌大,相见即是缘分.大侠可以关注FPGA技术江湖,在"闯荡江湖"."行侠仗义"栏里获取其他感兴趣的资源,或者一起煮酒言欢. ...

  5. python中mod是什么意思_【python中,mod_python到底做了些什么呢?】mod python 教程

    python 编程小白 ,不会用doctest 请大神指教怎么用!! >>> >>> def is_between(v, lower, higher): ...   ...

  6. go 调用其他文件函数_一篇文章让你了解Go语言中方法Methods的使用内幕

    概述 Go语言中的方法只不过是一个带有接收器的函数.接收器是某个特定类型(如struct)的实例,或是任何其他自定义类型的实例.所以基本上,当你把函数附加到类型上时,该函数就成为这个类型的一个方法.方 ...

  7. go 调用其他文件函数_【C# 调用 Go 语言】0x1 Hello Golang

    C# 和 Golang 都不是世界上最好的语言.在不同的业务场景下,C# 和 Golang 各有所长.如果要编写一个网络程序,显然 Golang 更擅长,而如果要编写一个桌面 GUI 程序,那么 C# ...

  8. 如何用python新建文件夹_用Python编写一个每天都在系统下新建一个文件夹的脚本...

    这个程序的功能非常的简单,就是每天在系统中新建一个文件夹.文件夹即当前的时间.此代码是在同事那边看到的,为了锻炼下自己薄弱的Python能力,所以花时间重新写了一个.具体代码如下: import ti ...

  9. 上古卷轴5json文件修改_【白夜谈】我做了一款失败的《社长卷轴》Mod

    文/Kong 题图 / CaesarZX 白夜谈是编辑的自留地,聊一些比较个人的内容. 但是对于我来说,这个栏目总是一个特别的苦差.因为我实在没有太多表达观点的欲望,所以我和负责白夜谈的跳跳老师商量, ...

最新文章

  1. 中国互联网+光伏发电行业商业模式创新与投资机会深度研究报告
  2. 高颜值免费在线SCI绘图工具增加上传功能
  3. 标准出现问题,人工智能正在走向错误的方向
  4. asp.net用Zxing库实现条形码输出
  5. 使用VMware进行基于Ubuntu16.04LTS的Spark集群搭建
  6. 【现代程序设计】【homework-07】
  7. php新特性:trait 关键字使用
  8. 山东大学 2020级数据库系统 实验八、九
  9. 笔记 | 《机器学习》手推笔记更新集成学习(Boosting和随机森林)
  10. java 蓝桥杯算法训练 连续正整数的和(题解)
  11. 学习Maven之Maven Surefire Plugin(JUnit篇)
  12. Case:update中把in改写成join性能提高数倍
  13. 如何在计算机管理路由器,如何在电脑上管理自家的wifi ? | 192路由网
  14. 体外诊断(IVD)行业系列四:快速崛起的独立医学实验室
  15. 暗影精灵3 PLUS 安装黑苹果
  16. 来信, 创业 和 移动互联网
  17. 证件照换底色,快速简单!(附去水印宝藏工具)
  18. oracle agile培训资料,敏捷基础知识培训.pdf
  19. 如何多人共同编辑_微信编辑器可以多人协作排版吗?
  20. 推荐一款牛逼的Windows神器!功能很强大!

热门文章

  1. absolute 必须 relative_想搞BLDC电机控制,就必须要懂的角度传感器
  2. Android BufferQueue生产消费原理(八)
  3. 解决Tensorflow使用CPU报错
  4. Mac OS X 10.8.3反编译Android apk
  5. VALSE学习(十六): Visual Question Generation and Answering-视觉问题生成和视觉问题
  6. linux服务器知识学习:了解Linux系统的启动过程
  7. linux服务器知识学习:linux简介与linux的发行版
  8. c语言字符括号怎么表示什么意思,请问C语言中的bioskey(0)是啥意思,括号里的值又有啥用?...
  9. python reduce函数filter_Python filter()及reduce()函数使用方法解析
  10. windows系统通过git上传代码