第四章节介绍核心对象Appender。它的主要内容如下:
- What is Appender? Appender对象的职责,以及它的关系。
- Appender的类体系结构。
- Appender的种类,在本章中介绍了很多Appender,最常见的有ConsoleAppender,FileAppender,DBAppender,SMTPAppender。其他的Appender之后补充。
1、 Appender介绍
本小节从三个方面介绍Appender,
- Appender对象的职责
- Appender对象与其他核心对象之间的关系
- 分析Appender对象的核心方法doAppend
在第二章节中描述Appender对象是一种终端的抽象。它的职责是记录日志信息。
它与Logger,Encoder,Layout之间的关系如下:
- Logger是发送logging request的源头,假设logging request通过了过滤器,通过了日志级别的比较,就会创建IloggingEvent对象,并将该对象作为参数传给Appender的doAppend方法。
- Appender对象将日志信息格式化的职责委托给Encoder对象。
- 在学习完第五章,第六章之后,会发现Layout是Encoder接口的升级版。
在处理logging request的核心流程中,Appender的主要功能抽象表现为doAppend方法。以下是它的源码
public void doAppend(E eventObject) { // WARNING: The guard check MUST be the first statement in the // doAppend() method. // prevent re-entry. if (Boolean.TRUE.equals(guard.get())) { return; } try { guard.set(Boolean.TRUE); if (!this.started) { if (statusRepeatCount++ < ALLOWED_REPEATS) { addStatus(new WarnStatus("Attempted to append to non started appender [" + name + "].", this)); } return; } if (getFilterChainDecision(eventObject) == FilterReply.DENY) { return; } // ok, we now invoke derived class' implementation of append this.append(eventObject); } catch (Exception e) { if (exceptionCount++ < ALLOWED_REPEATS) { addError("Appender [" + name + "] failed to append.", e); } } finally { guard.set(Boolean.FALSE); } }
- 第一步,判断guard变量,这样做的目的是为了防止多线程情况下重复进入doAppend方法。
- 第二步,判断appender是否已经开启,如果没有开启,抛出异常。例如在linux上设置FileAppender时,若没有创建文件夹的权限,会导致FileAppender创建失败,开启失败。
- 第三步,运行过滤器,判断过滤器的返回值,对应核心流程的第一步。
- 第四步,将logging request请求封装为ILogging Event对象,并作为参数传入append方法。
2、类结构
2.1 父接口
- LifeCycle:它用于管理Appender的生命周期,包含三个方法start,stop,isStarted。
- ContextAware:它用于将Appender对象与LoggerContext对象进行绑定。
- FilterAttachable:它用于管理过滤器,包含四个方法,添加过滤器addFilter,清空所有过滤器clearAllFilters,获取所有过滤器getCopyOfAttachedFilterList,获取过滤器执行结果getFilterChainDecision,返回FilterReply,它是一个枚举类,有三个值,DENY(请求失败),NEUTRAL(继续下一个过滤器),ACCEPT(忽略剩余过滤器,请求成功)。
2.2 接口实现类
- UnsynchronizeAppenderBase:所有Appender的父类,它是一个抽象类,只有doAppend方法,这个方法的流程在之前已提过。
- OutputStreamAppender:ConsoleAppender与FileAppender的父类,它需要一个核心对象OutputStream将message写入到Appender中。
- Filter:过滤器,在第八章中将详细介绍。
- Encoder:格式化日志信息,在第五章中将详细介绍。
- ConsoleAppender:Appender种类的其中一种,代表输出控制台。
- FileAppender:Appender种类的其中一种,代表日志信息将记录到文件中
- RollingFileAppender:特殊的FileAppender,代表FileAppender会根据RollingPolicy,TriggerPolicy按照某种模式生成新的日志文件,删除历史日志文件。
- TriggerPolicy:它的作用是定义何时会触发新日志文件的生成或删除历史日志文件。
- RollingPolicy:它的作用是定义新日志文件的目录,名称,压缩格式等规则。
3、appender种类
Appender的种类非常多,在本小节中介绍使用频率极高的ConsoleAppender和FileAppender。
3.1 OutputStreamAppender
描述 |
ConsoleAppender与FileAppender的父类。它包含了两个最基本的属性encoder,immediateFlush。 |
属性 |
属性:encoder 说明:appender依赖的Encoder,负责格式化日志信息。 值:默认值为PatternLayoutEncoder,它也拥有默认的格式。 是否必填:是 |
|
属性:immediateFlush 说明:是否将日志信息立马写入到Console或File。当此值设置为true时,日志信息不会由于项目的异常中断而丢失。当此时设置为false时,可以提高日志的吞吐量。 值:布尔值。 是否必填:是,默认值为true。 |
注意事项 |
在配置文件中不能配置OutputStreamAppender。 |
3.2 ConsoleAppender
描述 |
ConsoleAppender对应程序的输出控制台,在Java中对应System.out或System.err,也可以是任意一种PrintStream。它是OutputStreamAppender的子类 |
属性 |
属性:encoder 与OutputStreamAppender的含义相同。 |
|
属性:target 说明:输出控制台对应的输出流对象,默认值为System.out。 值:System.out或者System.err。 是否必填:是。 |
|
属性:withJansi 说明:这个属性的功能是给不同级别的日志设置不同的背景颜色。若要启用此功能,需要引入jansi的jar包。 值:布尔值。 是否必填:是,默认值为false。 |
注意事项 |
withJansi属性的功能完全可以使用插件替代,例如可以在Eclipse安装logViewer插件实现相同的功能。 |
示例如下:
<!-- 定义输出控制台 ConsoleAppender --> <appender name="console" class="ch.qos.logback.core.ConsoleAppender"> <!-- 包含1个encoder --> <encoder> <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n </pattern> </encoder> <!-- 包含1个target,System.out 或者是System.err,默认为System.out --> <target>System.out</target> <!-- 包含1个 withJansi, 是否对不同级别的日志用颜色来区分 --> <withJansi>false</withJansi> </appender>
3.3 FileAppender
描述 |
FileAppender对应操作系统的文件系统,因为Java语言是跨平台的,所以它的输出终端是File,此时File不能是目录。它是OutputStreamAppender的子类 |
属性 |
属性:append 说明:新日志信息是否追加到文件的末尾,默认值为true。 值:布尔值 是否必填:是 |
属性:encoder 继承自OutputStreamAppender。 |
|
属性:file 说明:文件的绝对路径或相对路径。此时文件需要满足
值:字符串。 是否必填:是。 |
|
其他 |
对于prudent属性,我没有完全理解,只是读出了两个关键点,第一个是多JVM,多线程上保证安全的写入,第二个是会降低性能。所以该属性设置为默认值false即可。 |
示例如下:
<!-- 定义输出到文件 FileAppender --> <appender name="FILE" class="ch.qos.logback.core.FileAppender"> <!-- 设置日志文件的名称,default_file_name是一个自定义变量 --> <file>${default_file_name}</file> <!-- 设置是否追加在日志文件,该值默认为true,无需配置 --> <append>true</append> <!-- 设置immediateFlush,该值默认为true,无需配置 --> <immediateFlush>true</immediateFlush> <!-- 设置一个或者多个encoder --> <encoder> <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern> </encoder> </appender>
3.4 RollingFileAppender
RollingFileAppender与FileAppender的输出终端都是文件系统的文件,唯一的区别,前者是动态的,后者是静态的。
动态性体现在会根据某些特定条件自动生成新的日志文件和删除旧日志文件。它的特定条件有两个,时间和日志文件的大小,也可以是二者的混合。
在FileAppender的基础上,它有两个特殊的属性,triggeringPolicy和rollingPolicy。其中
- triggeringPolicy定义生成新日志或删除旧日志的时机,即”when”。例如当日志文件大小超过1M时生成新文件,例如每隔一天生成一份新文件等等。
- rollingPolicy用于定义新日志的命名规则,这里的命名包含日志文件的目录结构和日志文件名。
在开始之前,首先需要了解它的类结构
3.4.1 类结构
- FixedWindowRollingPolicy只实现了RollingPolicy接口
- SizeBasedTriggeringPolicy只实现了TriggeringPolicy接口。
- TimeBasedRollingPolicy同时实现了RollingPolicy接口和TriggeringPolicy,SizeAndTimeBasedRollingPolicy是它的子类。
3.4.2 FixedWindowRollingPolicy
描述 |
它只实现了RollingPolicy接口,主要的作用是定义日志文件的命名规则,它的命名规则为name + index,其中index作为变量,值位于minIndex-maxIndex之间。 |
属性 |
属性:minIndex 说明:index的最小值,默认值为1。 值:数字 是否必填:必填 |
属性:maxIndex 说明:index的最大值,默认值为3。当设置超过20时,将其值设置为20。 值:数字,minIndex-21之间的整数,不包含21 是否必填:是 |
|
属性:fileNamePattern 说明:使用%i表示index变量。 值:字符串,必须包含%i。 是否必填:是。 |
|
其他 |
假设存在log.txt作为日志文件, 当第一次触发时,会将log.txt重命名为log+minIndex.txt,若minIndex为1,则文件名为log1.txt。并新创建log.txt文件。 当第二次触发时,会将log1.txt重命名为log2.txt,log.txt重命名为log1.txt,新创建log.txt文件。 以此类推。可以看到当触发次数越多时,需要重命名的操作也越多,所以maxIndex不宜过大 |
3.4.3 SizeBasedTriggeringPolicy
描述 |
它只实现了TriggeringPolicy,它只定义如何触发生成新日志的时机,从名字中可以看到该类只提供日志文件大小的条件,即当日志文件超过一定大小时,触发新日志的生成。 |
属性 |
属性:maxFileSize 说明:单个日志文件的最大值。 值:数字+单位,数字任意正数,单位为KB,MB,GB等 是否必填:必填 |
RollingPolicy必须与TriggeringPolicy一起组合使用。示例如下:
<!-- 定义输出到文件 ,FixedWindowPolicy,它只实现了RollingPolicy接口,所以还需要指定triggeringPolicy --> <appender name="fixSizeRollingFile" class="ch.qos.logback.core.rolling.RollingFileAppender"> <!-- 设置日志文件的名称 --> <file>${file_fix_size_name}</file> <!-- 设置rollingPolicy --> <rollingPolicy class="ch.qos.logback.core.rolling.FixedWindowRollingPolicy"> <!-- 指定fileNamePattern,只能指定i --> <fileNamePattern>${file_dir}/${byYear}/application_%i.zip</fileNamePattern> <!-- 最小索引值 --> <minIndex>1</minIndex> <!-- 最大索引值,最大为20,超出20时,会设置为20 --> <maxIndex>5</maxIndex> </rollingPolicy> <!-- 设置triggeringPolicy --> <triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy"> <maxFileSize>3MB</maxFileSize> </triggeringPolicy> <!-- 设置一个或者多个encoder --> <encoder> <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern> </encoder> </appender>
3.4.4 TimeBasedRollingPolicy
描述 |
前面两个类有很明显的短板,它们只能定义单个日志大小的条件,而且只能触发新日志文件的生成,无法触发删除旧日志。TimeBasedRollingPolicy可以全部实现,但是它也同样有短板,就是只能定义时间条件作为触发机制。 |
属性 |
属性:maxHistory 说明:日志文件保留的最长时间。它的单位由fileNamePattern中的最小单位决定。它是触发删除旧日志的主要属性。 值:数字 是否必填:否 |
属性:totalSizeCap 说明:日志文件的总大小,当超过这个数值时,会触发旧日志的删除。它是触发删除旧日志的主要属性 值:数字 + 单位,数字为整数,单位为KB,MB,GB等 是否必填:否 |
|
属性:cleanHistoryOnStart 说明:触发删除旧日志的时机,当此值为true时,Appender在启动时便会触发。当此值为false时,会在rollOver阶段触发。本质是调用appender.start方法时触发还是RollingPolicy接口的rollOver时触发。 值:布尔值。 是否必填:默认为false。表示在调用RollingPolicy接口的rollOver时触发 |
|
属性:fileNamePattern 说明:它主要用于定义新日志文件的命名规则。包括目录的路径和日志文件名。在定义时使用%d{dateFormat},它会转换为日期字符串。 值:字符串,必须包含%d。 是否必填:是。 |
除上述的属性外,需要介绍dateFormat日期格式,file属性与fileNamePattern属性的关系。
3.4.4.1 dateFormat日期格式
dateFormat格式与Java的日期格式相同。
yyyy,MM,ww,dd,HH,mm,ss分别代表年,月,周,天,时(24小时制),分,秒。分,秒基本上用不到,不可能短时间就生成新日志。
- 当fileNamePattern中只出现一个%d时,此时%d{yyyy-MM-dd}会以每天生成新的日志文件
- 当fileNamePattern中出现多个%d时,其他的时间日期需要添加aux,表示不以该日期格式的最小单位作为间隔。例如 %d{yyyy-MM,aux}/log-%d{yyyy-MM-dd}.zip。
- 当fileNamePattern中出现”/” 时,它定义了多层目录结构,例如%d{yyyy/MM}会生成年/月的目录结构
- 当fileNamePattern中需要选择不同时区时,第二个参数指定时区,例如%d{yyyy-MM,UTC},该日期格式的时区为UTC。
3.4.4.2 file与fileNamePattern
当同时指定file属性和fileNamePattern属性时,file属性会作为当前日志文件,fileNamePattern会作为历史日志存放目录。它们可以指定为不同的目录。
假设fileNamePattern设置为%d{yyyy/MM/dd}/log.txt,file设置为applicationLog.txt,此时触发一次新日志生成的步骤如下:
- applicationLog.txt重命名为log.txt,并移动到yyyy/MM/dd的目录下,
- 在file属性的目录下重新生成applicationLog.txt,当前的日志信息都记录在此文件中。
示例如下:
<!-- 定义输出到文件 RollingFileAppender,每周一次 --> <appender name="weeklyRollingFile" class="ch.qos.logback.core.rolling.RollingFileAppender"> <!-- 设置日志文件的名称 --> <file>${file_daily_name}</file> <!-- 设置是否追加在日志文件,该值默认为true --> <!-- 设置rollingPolicy --> <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"> <!-- 指定fileNamePattern,定义日志文件或者是压缩包存放的位置,如果存在file属性,日志输出到file文件中,fileNamePattern存放日志压缩文件的路径 --> <fileNamePattern>${file_dir}/%d{yyyy/MM,aux}/%d{yyyy-MM-dd}_log.zip</fileNamePattern> <!-- 指定历史日志的最长存放时间,它的单位为fileNamePattern中的最小单位,此示例中为天,所以会删除10天以上的历史日志 --> <maxHistory>10</maxHistory> <!-- 最大的大小,100MB --> <totalSizeCap>100MB</totalSizeCap> </rollingPolicy> <!-- 设置triggeringPolicy --> <!-- 设置一个或者多个encoder --> <encoder> <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern> </encoder> </appender>
3.4.5 SizeAndTimeBasedRollingPolicy
描述 |
它在TimeBasedRollingPolicy基础上添加了大小的条件,这个属性是maxFileSize,在生成新日志文件时,添加了%i格式。其他属性同TimeBasedRollingPolicy |
属性 |
属性:maxFileSize 说明:单个日志文件的最大值,超过时,会触发新日志文件的生成。 值:数字 + 单位,单位为KB,MB,GB等 是否必填:否 |
属性:fileNamePattern 说明:它主要用于定义新日志文件的命名规则。包括目录的路径和日志文件名。在定义时使用%d{dateFormat},它会转换为日期字符串。使用%i,它会转换为index值,该值的默认值为0,每次由大小条件触发的新日志生成都会使index值加1。 值:字符串,必须包含%d和%i。 是否必填:是。 |
示例如下:
<!-- 定义输出到文件,每个文件大小到5M时,重新生成一个日志文件,区别在于rollingPolicy指定的Class对象不同 --> <appender name="sizeAndWeeklyRollingFile" class="ch.qos.logback.core.rolling.RollingFileAppender"> <!-- 设置日志文件的名称 --> <file>${file_daily_size_name}</file> <!-- 设置是否追加在日志文件,该值默认为true --> <append>true</append> <!-- 设置immediateFlush --> <immediateFlush>true</immediateFlush> <!-- 设置rollingPolicy --> <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy"> <!-- 指定fileNamePattern,定义日志文件或者是压缩包存放的位置,如果存在file属性,日志输出到file文件中,fileNamePattern存放日志压缩文件的路径 --> <fileNamePattern>${file_dir}/%d{yyyy/MM,aux}/%d{yyyy-MM-dd}_log.zip</fileNamePattern> <!-- 每个文件的大小 --> <maxFileSize>5MB</maxFileSize> <!-- 指定最大的历史,10分钟,对应fileNamePattern中的日期格式 --> <maxHistory>10</maxHistory> <!-- 最大的大小,100MB --> <totalSizeCap>100MB</totalSizeCap> </rollingPolicy> <!-- 设置triggeringPolicy --> <!-- 设置一个或者多个encoder --> <encoder> <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern> </encoder> </appender>
综上所述:
FixedWindowRollingPolicy与SizeBasedTriggeringPolicy不能单独使用,而且不支持删除历史日志的功能,只支持生成新日志,新日志的名称只支持%i变量,触发的条件也只能选择大小。
TimeBasedRollingPolicy同时支持新日志的生成和历史日志的删除,但是在新日志的名称只支持%d变量,触发的条件也只能选择时间。
SizeAndTimeBasedRollingPolicy功能最全面,同时支持新日志的生成和历史日志的删除,新日志的名称同时支持%d和%i变量,触发的条件也最全面,同时支持时间,单个日志大小,总日志大小。但同时配置也最繁琐。
按需选择即可。
3.5 DBAppender
待完善
3.6 SMTPAppender
待完善
至此本篇内容结束,原著链接为:http://logback.qos.ch/manual/appenders.html