appender是日志框架核心对象之一,它的知识点包含两个,原理,类结构。
1、原理
它本质就是处理ILoggingEvent对象,并将结果传递给Layout对象进行格式化。它的入参是Event对象,出参是字符串。
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创建失败,开启失败。
第三步,运行过滤器,Appender对象也包含过滤器,它与流程中第一步中的过滤器类型是不同的。
第四步,将logging request请求封装为ILogging Event对象,并作为参数传入append方法。
2、类结构
父接口:
- LifeCycle:它用于管理Appender的生命周期,包含三个方法start,stop,isStarted。
- ContextAware:它用于将Appender对象与LoggerContext对象进行绑定。
- FilterAttachable:它用于管理过滤器,包含四个方法,添加过滤器addFilter,清空所有过滤器clearAllFilters,获取所有过滤器getCopyOfAttachedFilterList,获取过滤器执行结果getFilterChainDecision,返回FilterReply,它是一个枚举类,有三个值,DENY(请求失败),NEUTRAL(继续下一个过滤器),ACCEPT(忽略剩余过滤器,请求成功)。
接口实现类:
- UnsynchronizeAppenderBase:所有Appender的父类,它是一个抽象类,只有doAppend方法,这个方法的流程在之前已提过。
- OutputStreamAppender:ConsoleAppender与FileAppender的父类,它需要一个核心对象OutputStream将message写入到Appender中。
- Filter:过滤器,在第八章中将详细介绍。
- Encoder:格式化日志信息,在第五章中将详细介绍。
- ConsoleAppender:Appender种类的其中一种,代表输出控制台。
- FileAppender:Appender种类的其中一种,代表日志信息将记录到文件中
- RollingFileAppender:特殊的FileAppender,代表FileAppender会根据RollingPolicy,TriggerPolicy按照某种模式生成新的日志文件。
- TriggerPolicy:它的作用是定义何时会触发新日志文件的生成。
- RollingPolicy:它的作用是定义新日志文件的目录,名称,压缩格式等规则。
2.1 outputStreamAppender
ConsoleAppender与FileAppender的父类。它包含了两个最基本的属性encoder,immediateFlush。
属性:
encoder:appender依赖的Encoder,负责格式化日志信息。
immediateFlush: 是否将日志信息刷新到Console或File。默认情况下会刷新到缓存区域,通常设置为false,等待缓存区满或者达到一定比例时,刷新。
2.2 consoleAppender
ConsoleAppender对应程序的输出控制台,在Java中对应System.out或System.err,也可以是任意一种PrintStream。它是OutputStreamAppender的子类
属性:
target:输出控制台对应的输出流对象,默认值为System.out。
withJansi:不同日志级别设置不同的背景颜色。不常用,略。
示例:
<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>
2.3 fileAppender
FileAppender对应文件系统,Java语言是跨平台的,它的输出终端是File。此时File不能是目录。
属性:
append:新日志信息是否追加到文件的末尾,默认值为true。
encoder:公共内容。略。
file: 文件的绝对路径或相对路径,不能是目录。
示例:
<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>
2.4 rollingFileAppender
RollingFileAppender是FileAppender的增强版,可以动态的生成新的日志文件。动态包含两项内容,
第一,何时生成,即when,通常是日志文件达到一定大小,或者是根据日期。
第二,新日志文件的命名规则。
属性:
triggeringPolicy:指定何时生成日志文件。
rollingPolicy: 定义新日志的命名规则,这里的命名包含日志文件的目录结构和日志文件名。
类结构:
- FixedWindowRollingPolicy只实现了RollingPolicy接口
- SizeBasedTriggeringPolicy只实现了TriggeringPolicy接口。
- TimeBasedRollingPolicy同时实现了RollingPolicy接口和TriggeringPolicy,SizeAndTimeBasedRollingPolicy是它的子类。
2.4.1 FixedWindowRollingPolicy
它是一种新日志文件名称的生成规则。
步骤如下:
第一步,重命名日志文件为name + index。
第二步,创建新日志文件,名称为name。
示例:假设name为log.txt
第一次,将log.txt重命名为log1.txt,创建log.txt。
第二次,log1.txt重命名为log2.txt,log重命名为log1.txt,创建log.txt。
Log.txt始终重命名为log1.txt,而log1.txt会导致index(当前) -1 次重命名,所以index不应该过大。
属性:
minIndex:index的最小值,默认为1,通常不修改。
maxIndex:index的最大值,当设置超过20时,会被设置为20。
fileNamePattern:日志文件名称的格式,默认值为name + index,可以任意设置,其中i%表示index变量。例如上述示例指定name_%i,则日志文件名称为log_1.txt。
2.4.2 SizeBasedTriggeringPolicy
它定义当日志文件超出一定大小时,触发生成新日志。
属性:
maxFileSize:单个日志文件的最大值
示例:
<triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy"> <maxFileSize>3MB</maxFileSize> </triggeringPolicy>
2.4.3 TimeBasedRollingPolicy
前面两个类有很明显的短板,它们只能定义单个日志大小的条件,而且只能触发新日志文件的生成,无法触发删除旧日志。TimeBasedRollingPolicy可以全部实现,但是它也同样有短板,就是只能定义时间条件作为触发机制。
属性:
maxHistory: 日志文件保留的最长时间。它由fileNamePattern中的最小单位决定。
totalSizeCap:日志文件的总大小,当超过这个数值时,会触发旧日志的删除。
clearHistoryOnStart:触发删除旧日志的时机,当此值为true时,Appender在启动时便会触发。当此值为false时,会在rollOver阶段触发。 本质是调用appender.start方法时触发还是RollingPolicy接口的rollOver时触发。
fileNamePattern:日志的文件名称格式。必须包含%d{dateFormat}。
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。
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,当前的日志信息都记录在此文件中。
示例:
<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>
2.4.4 SizeAndTimeBasedRollingPolicy
在TimeBasedRollingPolicy基础上添加了大小的条件,这个属性是maxFileSize,在生成新日志文件时,添加了%i格式。其他属性同TimeBasedRollingPolicy。
属性:
maxFileSize:单个日志文件的最大值。
fileNamePattern:新日志文件的命名规则。名称中包含%d{dateFormat}或%i。
示例:
<!-- 设置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>
综上所述:
FixedWindowRollingPolicy与SizeBasedTriggeringPolicy不能单独使用,而且不支持删除历史日志的功能,只支持生成新日志,新日志的名称只支持%i变量,触发的条件也只能选择大小。
TimeBasedRollingPolicy同时支持新日志的生成和历史日志的删除,但是在新日志的名称只支持%d变量,触发的条件也只能选择时间。
SizeAndTimeBasedRollingPolicy功能最全面,同时支持新日志的生成和历史日志的删除,新日志的名称同时支持%d和%i变量,触发的条件也最全面,同时支持时间,单个日志大小,总日志大小。但同时配置也最繁琐。
2.5 dBAppender
DBAppender是将日志的信息记录到数据库中,需要配置数据源的信息。
配置DBAppender的步骤如下:
第一步,在ch/qos/logback/classic/db/script包下找到对应数据库的脚本。在数据库中执行脚本。
第二步,配置数据源,如果数据源指定为DriverManagerConnectionSource,它使用JDBC的方式,没有任何数据连接池的概念,效率较低。如果数据源指定为DataSourceConnectionSource,可以配置dataSource,可以使用C3P0,Apache的dataSource或者阿里的durid数据源。如果数据源指定为JNDIConnectionSource,则配置JNDI的数据源,一般这种方式很少使用。
第三步,将数据库的信息提取到properties文件当中,使用property标签引入相应的properties文件。
注:logback相关表的主键需要由数据库来生成主键,logback在插入时不会生成主键。会导致插入语句失败。
2.5.1 表结构
2.5.2 属性
当connectionSource为DriverManagerConnectionSource, 属性为driverClass,url,user,password。
当connectionSource为DataSourceConnectionSource, 属性为任意的一种数据库连接池技术,例如C3P0。
当connectionSource为JNDIConnectionSource时,略。
2.5.3 配置
<appender name="DB" class="ch.qos.logback.classic.db.DBAppender"> <!-- 连接信息 --> <connectionSource class="ch.qos.logback.core.db.DataSourceConnectionSource"> <dataSource class="com.mchange.v2.c3p0.ComboPooledDataSource"> <!-- 驱动类 --> <driverClass>${logback.driver}</driverClass> <!-- 数据库地址 --> <jdbcUrl>${logback.url}</jdbcUrl> <!-- 用户名 --> <user>${logback.userName}</user> <!-- 密码 --> <password>${logback.password}</password> </dataSource> </connectionSource> </appender>
2.6 SMTPAppender
SMTPAppender是将日志发送到邮件当中,它默认的发送方式是异步的。底层的实现方式是java.mail.api,所以需要引入相应的jar包。
使用步骤如下:
第一步,填写java.mail.api所需要的必要属性,例如发件的服务器地址,端口,发件人,收件人,主题,用户名,密码等等信息。
第二步,配置SMTPAppender。
2.6.1 属性
邮箱服务器信息:
smtpHost:发件服务器的地址。例如126邮箱的为smtp.126.com。
smtpPort:服务器的端口号,默认情况下时25,开始SSL时为465.
to:收件人的邮箱地址,多个使用逗号分隔或者使用多个to标签。
from:发件人的邮箱地址。
subject:邮件的主题,默认是%logger{20} - %m
username:用户名
password:密码
SSL:是否开启SSL,默认为false。
STARTTLS:没有理解。
CharsetEncoding:默认的字符编码集。
Evaluator:
默认情况下只有error级别的日志才会触发发送邮件。可以通过配置Evaluator来覆盖默认的触发行为。
第一种,编写自定义的Evaluator。
- 编写自定义XXEvaluator继承ContextAwareBase并且实现EventEvaluator接口。
- 配置evaluator,class属性指定为类的全名。
第二种,使用内置的OnMarkerEvaluator。
- 使用MarkerFactory.getMarker(“key”)来获取marker对象。
- 将marker对象作为参数传入到打印日志的方法中。
- 在evaluator下配置marker子标签,每一个子标签对应一个value值。
第三种,使用内置的JaninoEventEvaluator。
- 同onMarkerEvaluator,只不过在evaluator下面配置expression子标签。类似于marker!=null 或者marker.contains(“key”)等这样的表达式。
第四种,使用内置的GEventEvaluator。
- 同JaninoEventEvaluator,只不过表达式的写法不同。
其他:
cyclicBufferTracker:配置缓存日志请求的数量,默认为64。
localhost:主机的地址,未理解。
asynchronousSending:是否异步发送,默认为true。
includeCallerData:未理解。
sessionViaJNDI:忽略。
jndiLocation:忽略。
2.6.2 配置
<appender name="email" class="ch.qos.logback.classic.net.SMTPAppender"> <!-- 邮箱地址 --> <smtpHost>${logback.smtpHost}</smtpHost> <!-- 使用SSL --> <SSL>true</SSL> <!-- 收件人 --> <to>${logback.to}</to> <!-- 发送人 --> <from>${logback.from}</from> <!-- 主题 --> <subject>TESTING:%logger{20} - %m{5}</subject> <!-- 编码方式 --> <charsetEncoding>${logback.charsetEncoding}</charsetEncoding> <!-- 用户名 --> <username>${logback.username}</username> <!-- 密码 --> <password>${logback.passWord}</password> <!-- 布局 --> <layout class="ch.qos.logback.classic.html.HTMLLayout" /> <!-- 缓存 --> <cyclicBufferTracker class="ch.qos.logback.core.spi.CyclicBufferTracker"> <!-- send just one log entry per email --> <bufferSize>1</bufferSize> </cyclicBufferTracker> <!-- evaluator,相当于何时触发发送邮件的事件 --> <!-- <evaluator class="com.module.logback.evaluator.SelfEvaluator" /> --> <!-- marker --> <evaluator class="ch.qos.logback.classic.boolex.OnMarkerEvaluator"> <marker>notify</marker> </evaluator> </appender>