行业知识
【Logback】Logback 中的 Appenders
���录
1、什么是 Appenders?
2、解说 AppenderBase.doAppend() 方法
3、logback-core 模块中的 Appenders
(1)OutputStreamAppender
(2)ConsoleAppender
(3)FileAppender
(4)RollingFileAppender
1、什么是 Appenders?
Appender 组件负责处理 Logback 日志写入的任务,该组件必须实现 ch.qos.logback.core.Appender 接口,该接口的主要方法总结如下://主要是解决将日志写到哪里去的问题
package ch.qos.logback.core;import ch.qos.logback.core.spi.ContextAware;import ch.qos.logback.core.spi.FilterAttachable;import ch.qos.logback.core.spi.LifeCycle;public interface Appender extends LifeCycle, ContextAware, FilterAttachable { String getName(); //核心方法 void doAppend(E var1) throws LogbackException; void setName(String var1);} Appender 接口中其实就一个最核心的方法,即 doAppend() 方法,且该方法只有一个参数,也就是类型为 E 的对象实例。泛型 E 的实例类型需要根据 logback 的具体模块进行决定,在 logback-classic 模块内,E 的类型为 ILoggingEvent(日志记录事件),在 logback-access 模块内,E 的类型为 AccessEvent。doAppend() 方法可以说是 logback 框架中最重要的方法,它负责将日志记录事件以合适的格式输出到合适的输出设备。//该方法框架的灵魂和各组件之间的桥梁
2、解说 AppenderBase.doAppend() 方法
抽象类 ch.qos.logback.core.AppenderBase 实现了 Appender 接口,该类是所有 Appender 组件的父类,它提供了所有 Appender 组件共享的基本功能,比如获取和设置 Appender 组件的名称,激活状态,layout 组件,过滤器等。在该类中有一个非常重要的方法实现,就是我们上边提到的 doAppend() 方法,AppenderBase 类的具体实现逻辑源码如下:
public synchronized void doAppend(E eventObject) { if (!this.guard) { try { //1、防止递归调用 this.guard = true; //2、判断Appender是否开启 if (!this.started) { //状态重复计数 if (this.statusRepeatCount++ 上边这段代码我在《Logback 日志框架的架构》这篇文章中也提到过,但也只是简要提及,这里将具体分析一下。
AppenderBase 类中的 doAppend() 方法是同步方法(synchronized修饰),所以不同的线程把日志记录到同一个 Appender 时都是线程安全的(独占访问)。
这里需要注意,同步有时候并不总是合适的,所以 logback 还提供了 doAppend() 的一般实现,即 ch.qos.logback.core.UnsynchronizedAppenderBase.doAppend(),该类中的实现除了不使用synchronized修饰方法外,其他逻辑与 AppenderBase 类中的实现几乎一样。
第一步,判断 guard 属性是否被设置为 true。如果为 true,那么程序将立即跳出此方法,如果没有设置 guard ,则在下一条语句中将其设置为 true。该 guard 属性确保了 doAppend() 方法不会递归调用自己。比如,如果在 append() 方法之外的某个地方想要调用 Appender 组件去记录一些内容,那么它可能会定向到刚刚调用它的同一个 Appender 组件,从而导致无限循环和堆栈溢出。//guard 属性防止程序递归调用
第二步,判断 started(开启状态) 是否为 true,如果为 false,doAppend() 将发送警告消息并返回。也就是说,一旦 Appender 已经关闭,就无法对其进行写入。
Appender 对象实现了 LifeCycle 接口,这意味着它们也实现了 start()、stop() 和 isStarted() 方法。当设置完 Appender 的所有属性后,logback 的配置框架(Joran)将调用 start() 方法来通知 Appender 激活这些属性。如果某些属性缺失或者属性之间相互干扰,那么 Appender 可能无法启动。一般情况下,根据 Appender 的类型,具体的启动的逻辑也会有所不同。//start()方法用来检查Appender配置是否完整,并激活Appender
如果 Appender 无法启动或已停止,则会通过 logback 的内部状态管理系统发出警告消息。为了避免系统被相同的警告消息淹没,经过几次尝试后(5次),doAppend() 将停止发出这些警告。
第三步,检查 Appender 过滤器决策结果。根据 Appender 过滤器链产生的决策结果,判断日志事件是否被拒绝或者被接受。在过滤器链没有做出决定的情况下,默认会接受日志事件。
然后,调用子类的 append() 的实现。 append() 方法负责将日志记录事件输出到指定的设备上。
最后,释放 guard ,以允许其他线程调用 doAppend() 方法。
3、logback-core 模块中的 Appenders
logback-core 模块为构建其他 logback 模块奠定了基础,因此在 logback-core 中,实现了一些最小化定制的组件,下边,我们将探讨几个开箱即用的 Appenders(追加器)。
(1)OutputStreamAppender
OutputStreamAppender 会使用 java.io.OutputStream 来处理日志事件。这个类会为其它 Appender 组件的构建提供基础服务。以下是该 Appender 的几个属性配置:
| 属性名称 | 类型 | 描述 |
| encoder | Encoder | 编码器配置 |
| immediateFlush | String | 默认值为“true”,立即刷新输出流可确保日志记录事件立即写出,防止日志记录丢失。 如果将此属性设置为“false”,可能会使记录吞吐量增加四倍左右(仅是测试值),不过,当应用程序退出,Appender未被正确关闭时,就可能使尚未写入磁盘的日志记录丢失。 |
用户通常不会直接实例化 OutputStreamAppender 对象,所以我们为什么还要提到它呢?
因为 OutputStreamAppender 是 ConsoleAppender、FileAppender、RollingFileAppender 这3 个非常重要的 Appender 对象的父类,所以重要性不言而喻,下图是 OutputStreamAppender 及其子类的类图。//都是基于OutputStream进行日志处理的Appender
(2)ConsoleAppender
顾名思义,ConsoleAppender 是将日志输出到控制台上,更准确的来说是输出到 System.out 或 System.err 上。ConsoleAppender 通过用户指定的 encoder 来格式化日志,因为 System.out 和 System.err 都是 java.io.PrintStream 类型,所以,它们都被包装在一个带有缓冲区的 OutputStreamWriter 中。
该 Appender 可以配置的属性如下:
| 属性名称 | 类型 | 描述 |
| encoder | Encoder | 编码器配置 |
| target | String | 输出字符串,目标为 System.out 或 System.err 。默认是 System.out。 |
| withJansi | boolean | withJansi 属性默认为 false,为 true 时会激活 Jansi 库,该库在 Windows 计算机上提供对 ANSI 颜色代码的支持。请注意,基于 Unix 的操作系统(例如 Linux 和 Mac OS X)默认支持 ANSI 颜色代码。//win系统颜色支持 |
以下是使用 ConsoleAppender 的示例配置:
%-4relative [%thread] %-5level %logger{35} -%kvp- %msg %n (3)FileAppender
前面提到过,FileAppender 也是 OutputStreamAppender 的子类,它的作用是把日志输出到指定的文件中。
该 Appender 可以配置的属性如下:
| 属性名称 | 类型 | 描述 |
| append | boolean | 默认设置为 true,即将日志追加到文件现有内容的末尾。如果为 false,则覆盖原有文件中的内容 |
| encoder | Encoder | 编码器配置 |
| file | String | 要写入的文件。如果该文件不存在,则创建该文件。该选项没有默认值,示例的配置值为:c:/temp/test.log 或 c:\temp\test.log 注意,如果文件的父目录不存在,FileAppender 也会自动的创建它。 |
| bufferSize | FileSize | 设置输出缓冲区的大小。默认值为 8192,即使在负载非常重且持续的情况下,256 KB 值一般也足够了。该属性在 OutputStreamAppender.immediateFlush 为 false 时可用。 该选项可以通过在数值后分别添加 KB、MB 和 GB 来指定字节、千字节、兆字节或千兆字节。例如,5000000、5000KB、5MB 和 2GB 都是有效值,其中前三个值是等效的。 |
| prudent(谨慎的) | boolean | 默认值为 false,如果设置为 true,即开启 Prudent 模式。在 prudent 模式下,即使存在多个不同主机(JVM)上运行的 FileAppender 实例,FileAppender 也会将日志安全地写入到指定文件。//安全模式 Prudent 模式依赖独占的文件锁,实验表明,使用文件锁定的成本大约是正常写入成本的三倍 (x3),因此在 Prudent 模式下,会降低日志写入的吞吐量。 Prudent 模式可以有效地将写入同一文件的所有 JVM 之间的 I/O 操作进行排序。因此,随着竞争访问文件的 JVM 数量增加,每个 I/O 操作产生的延迟也会增加。不过,只要 I/O 操作总数仅为每秒 20 个日志请求左右时,该模式对性能的影响就可以忽略不计。但是,如果是每秒生成 100 次或更多 I/O 操作的应用程序,那么该模式可能会对性能产生影响,因此,在这种情况下应该避免使用 Prudent 模式。 当日志文件位于网络文件系统上时,Prudent 模式的成本会更大。需要注意的是,网络文件系统上的文件锁有时可能存在强烈的偏差,使得当前拥有锁的进程在释放锁后可以立即重新获得锁。因此,当一个进程霸占日志文件的锁时,其他进程可能因为等待该锁而陷入***锁状态。 |
默认情况下,每个日志记录事件都被会立即刷新到底层的输出流。如果应用程序在没有正确关闭 Appender 的情况下退出,这时的日志记录也不会丢失,所以这种方式也更加的安全。
但是,为了显着提高日志记录的吞吐量,可能也会需要将 immediateFlush 属性设置为 false。
以下是使用 FileAppender 的示例配置:
testFile.log true true %-4relative [%thread] %-5level %logger{35} -%kvp- %msg%n 如果使用时间戳对写入文件进行唯一命名?
如果希望应用程序在每次启动时都自动创建一个新的日志文件,除了可以手动更改日志文件的名称以外,还有一种非常方便的方式,那就是使用时间戳,下边就是一个简单的例子:
log-${bySecond}.txt %logger{35} -%kvp- %msg%n 元素有两个强制的属性 key 和 datePattern 以及一个可选的 timeReference 属性。
- key 属性是键的名称,在该键 key 下时间戳将作为变量可供后续配置元素使用。
- datePattern 属性用于将当前时间(解析配置文件的时间)转换为字符串的日期模式。日期模式应遵循 SimpleDateFormat 中定义的约定。
- timeReference 属性表示时间戳的参考时间。该时间默认是配置文件的解析时间(即当前时间)。然而,在某些情况下,参考时间使用日志上下文创建的时间可能会更加的合适。为了实现这一目标,只要将 timeReference 属性设置为 “contextBirth” 就可以了。
示例的配置如下:
...
(4)RollingFileAppender
RollingFileAppender 对 FileAppender 的功能进行了扩展,它提供了滚动更新日志文件的功能。比如,使用 RollingFileAppender 将日志记录到名为 log.txt 的文件,当满足特定条件时,可以将其记录的目标更改为另一个文件。//日志文件滚动更新
实现 RollingFileAppender 的日志滚动功能需要配置两个重要的组件,第一个组件是 RollingPolicy(滚动策略),负责执行滚动所需的操作,第二个组件是 TriggeringPolicy(触发策略),用来定义什么时候进行滚动。//滚动策略+触发策略
一般来说,RollingFileAppender 必须同时设置 RollingPolicy 和 TriggeringPolicy。但是,如果一个 RollingPolicy 也实现了 TriggeringPolicy 接口,那么只需显式指定 RollingPolicy 即可。
以下是 RollingFileAppender 的可用属性:
属性名称 类型 描述 append boolean 默认设置为 true,即将日志追加到文件现有内容的末尾。如果为 false,则覆盖原有文件中的内容 encoder Encoder 编码器配置 file String 要写入的文件。
请注意,此处文件可以为空,在这种情况下,输出仅写入由 RollingPolicy 指定的目标。
rollingPolicy RollingPolicy 滚动执行策略,见下文详述 triggeringPolicy TriggeringPolicy 滚动触发策略,见下文详述 prudent(谨慎的) boolean Prudent 模式不支持 FixedWindowRollingPolicy(弃用策略)。
Prudent 模式只支持 TimeBasedRollingPolicy,但是有两个限制:
(1)在 Prudent 模式下,不支持也不允许文件压缩。 因为不能让一个 JVM 写入文件时,而另一个 JVM 正在压缩该文件。
(2)Appender 的 file 属性不能设置,且必须留空。因为大多数操作系统不允许在另一个进程打开文件时重命名该文件。
什么是滚动策略(RollingPolicy)?
RollingPolicy 用来定义文件滚动和重命名时所执行的操作,RollingPolicy 的接口如下所示:
package ch.qos.logback.core.rolling;import ch.qos.logback.core.FileAppender;import ch.qos.logback.core.rolling.helper.CompressionMode;import ch.qos.logback.core.spi.LifeCycle;public interface RollingPolicy extends LifeCycle { void rollover() throws RolloverFailure; String getActiveFileName(); CompressionMode getCompressionMode(); void setParent(FileAppender var1);}RollingPolicy 接口中一共有四个方法,其中 rollover() 方法用来执行滚动(归档)当前日志文件所涉及的工作,getActiveFileName() 用来方法来获取当前日志文件(写入实时日志的位置)的文件名,getCompressionMode() 用来获取日志的压缩模式,最后,setParent() 方法使 RollingPolicy 实例可以获得对其父类的引用。
基于时间的滚动策略:TimeBasedRollingPolicy
TimeBasedRollingPolicy 用来定义基于时间的滚动策略,比如按天或者按月进行滚动。
TimeBasedTriggeringPolicy 同时实现了 RollingPolicy 和 TriggeringPolicy 接口。它的可配置属性如下:
属性名称 类型 描述 fileNamePattern String 必须配置,该属性用来定义滚动(归档)日志文件。
该文件名称中一般会包含 %d 转换符,%d 转换符后边一般也还包含指定的日期和时间模式,比如:/test.%d{yyyy-MM-dd-HH}.log,表示按小时对日志文件进行滚动,默认的日期和时间模式为 yyyy-MM-dd
注意,当配置 RollingFileAppender 的 file 属性(父类中的属性)时,当前活动日志文件的名称就是 file 属性指定的文件名称,此时活动日志文件的名称不会随时间的改变而改变。因此,可以利用这个属性解耦活动日志文件的位置和归档日志文件的位置,这个在后边会举例。
多个 %d 转换符的说明:
fileNamePattern 的值可以包含多个 %d 转换符,但是其中只能有一个 %d 是推动日志文件滚动周期的主要转换符,其他 %d 必须使用 "aux" 参数进行标记,指明这是一个辅助性的转换符。
多个 %d 转换符有助于使用不同于滚动周期的文件夹结构来组织存档文件。比如,以下显示的文件名将按年和月来命名日志文件夹,但按天来更新日志文件:
/var/log/%d{yyyy/MM, aux}/myapplication.%d{yyyy-MM-dd}.log
注意,fileNamePattern 属性内,日期和时间模式内任意位置的正斜杠 "/" 或反斜杠 "\" 字符将被解释为目录分隔符。
maxHistory int 该属性用于控制要保留的归档文件的最长时间,超过该配置时间的旧文件会被异步删除。
比如,如果指定策略为每月滚动,并将 maxHistory 设置为 6,那么将只保留 6 个月内的存档文件。当旧的存档文件被删除时,为这些文件存档而创建的文件夹都会被删除。
maxHistory 的默认配置为零,即默认情况下不删除存档。
totalSizeCap int 该属性用于控制所有归档文件的总大小。当超过总大小上限时,最旧的存档将被异步删除。
此外,要使 totalSizeCap 属性生效,还需要设置 maxHistory 属性。框架 首先应用 maxHistory 属性限制,然后再应用 "总大小上限" 限制。//即首先删除超时的,然后再删除超大小的
totalSizeCap 属性的值可以通过在数值后分别添加 KB、MB 和 GB 来指定为字节、千字节、兆字节或千兆字节的单位。
默认情况下,totalSizeCap 设置为零,这意味着没有总大小上限。
cleanHistoryOnStart boolean 默认情况下,此属性设置为 false。如果设置为 true,则存档删除将在 Appender 启动时执行。
存档删除通常在日志回滚期间执行。但是,某些应用程序的生存时间可能不够长,无法触发滚动。因此,对于此类短暂的应用程序,存档删除可能永远没有机会执行。所以通过将 cleanHistoryOnStart 设置为 true,可以避免以上这种情况
以下是一些 fileNamePattern 的值及其效果的说明:
fileNamePattern表达式 回滚时间 示例 /wombat/foo.%d 按日滚动,由于 %d 转换符后省略了日期和时间匹配模式,所以采用的是默认的模式 yyyy-MM-dd
file 属性未设置:2023年11月23日的日志将输出到文件 /wombat/foo.2023-11-23。同样,24日期间的日志将输出到 /wombat/foo.2006-11-24。
file 属性设置为 /wombat/foo.txt 时:2023年11月23日的日志将输出到文件 /wombat/foo.txt。日志回滚时,foo.txt 将被重命名为 /wombat/foo.2023-11-23。此外,还将创建一个新的 /wombat/foo.txt 文件,用来记录24日的日志输出,此种情况下的活动日志文件始终是 /wombat/foo.txt。//file属性的作用
/wombat/%d{yyyy/MM}/foo.txt 按月滚动 file 属性未设置:在2023年10月期间,日志输出将转到 /wombat/2023/10/foo.txt。 11月期间,日志输出到 /wombat/2023/11/foo.txt。
file 属性设置为 /wombat/foo.txt:活动日志文件将始终为 /wombat/foo.txt。在2023年10月期间的日志将输出到 /wombat/foo.txt。 日志按月滚动后,/wombat/foo.txt 将更名为 /wombat/2023/10/foo.txt。此外,将创建一个新的 /wombat/foo.txt 文件,用来记录11月份的日志输出。 11月滚动时,/wombat/foo.txt 也将更名为 /wombat/2023/11/foo.txt。
/wombat/foo.%d{yyyy-ww}.log 按周滚动 示例与上边类似 /wombat/foo%d{yyyy-MM-dd_HH}.log 按小时滚动 示例与上边类似 /wombat/foo%d{yyyy-MM-dd_HH-mm}.log 按分钟滚动 示例与上边类似 /wombat/foo%d{yyyy-MM-dd_HH-mm, UTC}.log 按分钟滚动,并指定时区 在某些情况下,可能希望根据与主机时区不同的时钟来滚动日志文件。那么就可以在 %d 转换符的日期和时间匹配模式中传递时区参数。 /foo/%d{yyyy-MM,aux}/%d.log
//非常有用的匹配模式
按日滚动,按年和月来归档日志文件
在此示例中,第一个 %d 转换符被标记为辅助标记。然后,第二个 %d 转换符(省略了时间和日期匹配模式)被定为主要标记。因此,滚动将每天发生(默认为 %d),并且文件夹也将按照年份和月份命名。例如,在2023年11月期间,日志文件将全部放置在 /foo/2023-11/ 文件夹下,比如 /foo/2023-11/2023-11-14.log。 如何进行自动文件压缩?
TimeBasedRollingPolicy 支持自动文件压缩。如果 fileNamePattern 选项的值以 .gz 或 .zip 结尾,则自动启用文件压缩功能。
fileNamePattern表达式 回滚时间 示例 /wombat/foo.%d.gz 按日滚动,自动对存档文件进行 GZIP 压缩
file 属性未设置:2023年11月23日期间的日志将输出到文件 /wombat/foo.2023-11-23。但是,当日日志滚动时,该文件将被压缩为 /wombat/foo.2023-11-23.gz。 11月24日,日志记录将输出到 /wombat/folder/foo.2023-11-24,同样到第二天开始滚动时进行压缩。
file 属性设置为 /wombat/foo.txt:2023年11月23日期间的日志输出到文件 /wombat/foo.txt。滚动时该文件将被压缩并重命名为 /wombat/foo.2023-11-23.gz。此外,将创建一个新的 /wombat/foo.txt 文件,用来记录11月24日期间的日志输出。 11月24日日志滚动时,/wombat/foo.txt 将被压缩并重命名为 /wombat/foo.2023-11-24.gz。
总结上边的配置信息,我们可以发现 fileNamePattern 属性具有有双重用途,首先,通过定义的日期和时间匹配模式,logback 可以获得请求的滚动周期。此外,它还可用于定义每个存档文件的名称。
这里需要提一下,日期和时间匹配模式 yyyy-MM 和 yyyy@MM 都可以指定日志按月滚动,只不过它们生成的存档文件将带有不同的名称,比如:foo.2023-11.log,foo.2023@11.log。
示例的配置文件内容如下:
logFile.log logFile.%d{yyyy-MM-dd}.log 30 3GB %-4relative [%thread] %-5level %logger{35} -%kvp- %msg%n基于文件大小和时间的滚动策略:SizeAndTimeBasedRollingPolicy
有时后,你可能既希望按日期对日志文件进行归档,但同时又希望限制每个日志文件的大小。特别是当后期处理工具对日志文件大小有限制要求时(很多文件读取工具都有文件大小限制),这种需求会非常的迫切。
为了满足上述这些要求,logback 也附带了基于文件大小和时间的滚动策略 SizeAndTimeBasedRollingPolicy。
除了 "%d" 转换符之外,基于大小的滚动策略还依赖于 "%i" 转换符。在该策略下,%i 和 %d 两个转换符都是必需的。"%i" 转换符的作用,就是当前日志文件在滚动时间到达之前,如果达到 maxFileSize 时,都会从 0 开始递增索引并对其进行归档。
下表列出了适用于 SizeAndTimeBasedRollingPolicy 的属性:
属性名称 类型 描述 maxFileSize FileSize 当前日志文件在滚动时间到达之前,如果达到 maxFileSize 时,都会从 0 开始递增索引并对其进行归档。
配置值后可以添加 KB、MB 和 GB 等单位来指定字节、千字节、兆字节或千兆字节。
checkIncrement Duration 检查文件大小是一项成本相对较高的操作,所以默认情况下每 60 秒执行一次。但是,你也可以设置不同的检查时间增量作为持续时间。 需要注意的是,这些属性都只是对适用于 TimeBasedRollingPolicy 的属性的补充,也就是说,适用于 TimeBasedRollingPolicy 的属性也同样适用于 SizeAndTimeBasedRollingPolicy。
示例的配置文件内容如下:
mylog.txt mylog-%d{yyyy-MM-dd}.%i.txt 100MB 60 20GB %msg%n什么是触发策略(TriggeringPolicy)?
TriggeringPolicy 用来指示 RollingFileAppender 何时滚动,TriggeringPolicy 接口仅包含一个方法。
package ch.qos.logback.core.rolling;import ch.qos.logback.core.spi.LifeCycle;import java.io.File;public interface TriggeringPolicy extends LifeCycle { boolean isTriggeringEvent(File var1, E var2);}isTriggeringEvent() 方法将活动文件和当前正在处理的日志记录事件作为参数。具体实现需要根据这些参数来确定是否触发日志滚动。
用得最多的触发策略,就是 TimeBasedRollingPolicy,它同时也实现了 RollingPolicy 接口,所以也兼作滚动策略。上边我们对 TimeBasedRollingPolicy 已经做了详细的介绍,此处不再赘述。
至此,logback-core 模块中的 Appenders 就已经介绍完了,除了 logback-core 模块外,logback-classic 和 logback-access 模块中也有附带了很多的 Appenders,比如,SocketAppender(明文输送到远程目标)、SSLSocketAppender(加密输送到远程目标)、SMTPAppender(输送到电子邮件)、DBAppender(输送到数据库)、SyslogAppender(输送到远程日志系统)、SiftingAppender(筛选日志记录)、AsyncAppender(异步处理日志记录) 等。此外还可以自定义 Appender,因为篇幅限制,不再详细叙述,感兴趣的可以去查阅相关文档。
至此,全文结束。