logback使用笔记
概述
异步输出日志
之前的日志配置方式是基于同步的,每次日志输出到文件都会进行一次磁盘IO。采用异步写日志的方式而不让此次写日志发生磁盘IO,阻塞线程从而造成不必要的性能损耗。添加一个基于异步写日志的appender,并指向原先配置的appender即可:
<!-- 异步输出 -->
<appender name="ASYNC-INFO" class="ch.qos.logback.classic.AsyncAppender">
<!-- 不丢失日志.默认的,如果队列的80%已满,则会丢弃TRACT、DEBUG、INFO级别的日志 -->
<discardingThreshold>0</discardingThreshold>
<!-- 更改默认的队列的深度,该值会影响性能.默认值为256 -->
<queueSize>256</queueSize>
<!-- 添加附加的appender,最多只能添加一个 -->
<appender-ref ref="INFO-LOG"/>
</appender>
<appender name="ASYNC-ERROR" class="ch.qos.logback.classic.AsyncAppender">
<!-- 不丢失日志.默认的,如果队列的80%已满,则会丢弃TRACT、DEBUG、INFO级别的日志 -->
<discardingThreshold>0</discardingThreshold>
<!-- 更改默认的队列的深度,该值会影响性能.默认值为256 -->
<queueSize>256</queueSize>
<!-- 添加附加的appender,最多只能添加一个 -->
<appender-ref ref="ERROR-LOG"/>
</appender>
原理浅析:
从log.info
开始,进入方法Logger.filterAndLog_0_Or3Plus()
,进入方法Logger.buildLoggingEventAndAppend()
,进入方法Logger.callAppenders()
,进入方法Logger.appendLoopOnAppenders()
,进入方法AppenderAttachableImpl.appendLoopOnAppenders()
,进入方法UnsynchronizedAppenderBase.doAppend()
,进入方法AsyncAppenderBase.append()
,即ch.qos.logback.classic.AsyncAppenderBase.append
方法:
protected void append(E eventObject) {
if (!this.isQueueBelowDiscardingThreshold() || !this.isDiscardable(eventObject)) {
this.preprocess(eventObject);
this.put(eventObject);
}
}
通过队列情况判断是否需要丢弃日志,不丢弃的话将它放到阻塞队列中,这个阻塞队列为ArrayBlockingQueueu,默认大小为256,可以通过配置文件进行修改。Logger.info(…)到append(…)就结束,只将日志塞入到阻塞队列的事,然后继续执行Logger.info(…)下面的语句。在AsyncAppenderBase类中定义一个Worker线程,run方法:
E e = parent.blockingQueue.take();
aai.appendLoopOnAppenders(e);
从阻塞队列中取出一个日志,并调用AppenderAttachableImpl类中的appendLoopOnAppenders方法维护一个Append列表。Worker线程中调用方法过程主要如下图:
最主要的两个方法就是encode和write,前一个法方会根据配置文件中encode指定的方式转化为字节码,后一个方法将转化成的字节码写入到文件中去。所以写文件是通过新起一个线程去完成的,主线程将日志扔到阻塞队列中,然后又去做其他事情。
配置错误日志发送到指定邮箱
<appender name="EMAIL" class="com.johnny.logclient.logback.MailAppender">
<evaluator class="com.johnny.logback.SMTPFrequencyEvaluator">
<maxCount>1000</maxCount>
<expiredTime>300</expiredTime>
</evaluator>
<cyclicBufferTracker class="ch.qos.logback.core.spi.CyclicBufferTracker">
<bufferSize>1</bufferSize>
</cyclicBufferTracker>
<smtpHost>smtp.awesome.com</smtpHost>
<smtpPort>25</smtpPort>
<username>ccc@awesome.com</username>
<password></password>
<to>aaa@awesome.com</to>
<from>bbbb@awesome.com</from>
<subject>【报错邮件】%X{ip}: %logger{20} - %m 累积错误数:%X{error_count}
</subject>
<layout class="ch.qos.logback.classic.html.HTMLLayout">
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS}%level%thread%logger{35}%message
</pattern>
</layout>
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>ERROR</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
</appender>
SMTPAppender为logback里面提供的API,该API的MailAppender源码如下:
public class MailAppender extends SMTPAppender {
public MailAppender() {
}
protected void append(ILoggingEvent leo) {
VariableLoggingEventVO eventVO = VariableLoggingEventVO.build(leo);
this.filter(eventVO);
super.append(eventVO);
}
private void filter(VariableLoggingEventVO eventVO) {
Map<String, String> newMdc = new HashMap(eventVO.getMdc());
eventVO.setMdcPropertyMap(newMdc);
eventVO.setFormattedMessage(Desensitiver.desensitive(eventVO.getFormattedMessage()));
}
}
public class Desensitiver {
public Desensitiver() {
}
public static String desensitive(String source) {
return PhoneDesensitiver.filterPhone(IdCardDesensitiver.filterIdCard(source));
}
}