前面我們看到 最終logger輸出是委托給了appender 如果沒有配置appender是不會輸出的
示例配置
<?xml version="1.0" encoding="UTF-8"?> <configuration> <property name="CHARSET" value="UTF-8"/> <!--為了防止進程退出時,內存中的數據丟失,請加上此選項--> <shutdownHook class="ch.qos.logback.core.hook.DelayingShutdownHook"/> <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> <Encoder> <pattern><pattern>|%p|%d{yyyy-MM-dd HH:mm:ss.SSS}|%t|%logger{10}:%line%n %m%n%n</pattern></pattern> <charset>${CHARSET}</charset> </Encoder> </appender> <appender name="ASYNC_STDOUT" class="ch.qos.logback.classic.AsyncAppender"> <filter class="ch.qos.logback.classic.filter.ThresholdFilter"> <level>info</level> </filter> <appender-ref ref="STDOUT"/><!--最終異步委托給consoleAppender--> <includeCallerData>true</includeCallerData> </appender> <!--給root關聯appender 默認我們所有logger都沒有appender 前面看到是遞歸網上找父類輸出 都是父類統一輸出--> <root level="info"> <appender-ref ref="ASYNC_STDOUT"/> </root> </configuration>
默認的appender實現
我們可以根據需求選擇以下默認的實現 如果沒有合適的需要擴展再參考下面的相關類擴展
下面我們舉例看其中 一個如果我們有定制化需求可以參考實現定制
AsyncAppender
類圖
我們需要自定義appender只需要繼承UnsynchronizedAppenderBase就行了
start
ch.qos.logback.core.AsyncAppenderBase#start
調用時機在解析標簽處 分別調用start 和end
private AsyncAppenderBase<E>.Worker worker = new AsyncAppenderBase.Worker();
/** * 啟動worker */ public void start() { //防止重復啟用 if (!this.isStarted()) { //AsyncAppenderBase.AppenderAttachableImpl的數量如果美而有保存 if (this.appenderCount == 0) { this.addError("No attached appenders found."); } else if (this.queueSize < 1) { this.addError("Invalid queue size [" + this.queueSize + "]"); } else { //初始化一個線程安全的數組阻塞隊列 ququeSize默認為256 this.blockingQueue = new ArrayBlockingQueue(this.queueSize); if (this.discardingThreshold == -1) { this.discardingThreshold = this.queueSize / 5; } this.addInfo("Setting discardingThreshold to " + this.discardingThreshold); this.worker.setDaemon(true); this.worker.setName("AsyncAppender-Worker-" + this.getName()); //重寫了父類的start 所以保證不破壞父類邏輯 所以調用父類start super.start(); //<1>啟動一個worker AsyncAppenderBase<E>.Worker worker = new AsyncAppenderBase.Worker();內部類 消費隊列數據 委托給當前類的 AppenderAttachableImpl<E> aai = new AppenderAttachableImpl(); this.worker.start();//這里是調用線程的start } } }
start調用點在解析完append end標簽之后 后面會講
ch.qos.logback.core.joran.action.AppenderAction#end
public void end(InterpretationContext ec, String name) { if (!this.inError) { //如果實現了LifeCycle 接口則調用start方法 if (this.appender instanceof LifeCycle) { this.appender.start(); } Object o = ec.peekObject(); if (o != this.appender) { this.addWarn("The object at the of the stack is not the appender named [" + this.appender.getName() + "] pushed earlier."); } else { ec.popObject(); } } }
<1>
class Worker extends Thread { Worker() { } public void run() { AsyncAppenderBase<E> parent = AsyncAppenderBase.this; AppenderAttachableImpl aai = parent.aai; //判斷主類對象是否started while (parent.isStarted()) { try { //循環消費 E e = parent.blockingQueue.take(); //<3>這里是委托給前面 <appender-ref ref="STDOUT"/> 配置最終還是Console 這里我們可以配置的appender 上面用例配置的STDOUT aai.appendLoopOnAppenders(e); } catch (InterruptedException var5) { break; } } AsyncAppenderBase.this.addInfo("Worker thread will flush remaining events before exiting. "); //以下是當started關閉 不接受消息 但是還是要消費完 Iterator i$ = parent.blockingQueue.iterator(); while (i$.hasNext()) { E ex = i$.next(); aai.appendLoopOnAppenders(ex); parent.blockingQueue.remove(ex); } //相關委托的appender started也標識為false aai.detachAndStopAllAppenders(); } }
可以看到aynycAppender最終沒有做實際的事情最終還是委托給了 AppenderAttachableImpl 對應上面配置內部封裝的就是ConsoleAppender
AppenderAttachableImpl
<3>appendLoopOnAppenders
ch.qos.logback.core.spi.AppenderAttachableImpl#appendLoopOnAppenders
public int appendLoopOnAppenders(E e) { int size = 0; //獲得容器內的所有appender Appender<E>[] appenderArray = (Appender[])this.appenderList.asTypedArray(); int len = appenderArray.length; for(int i = 0; i < len; ++i) { //<4>逐個調用doAppend appenderArray[i].doAppend(e); ++size; } return size; }
UnsynchronizedAppenderBase
<4>doAppend
ch.qos.logback.core.spi.AppenderAttachableImpl#appendLoopOnAppenders
->
ch.qos.logback.core.UnsynchronizedAppenderBase#doAppend
private ThreadLocal<Boolean> guard = new ThreadLocal(); public void doAppend(E eventObject) { //防止一個線程同時進入多個吧。。。 if (!Boolean.TRUE.equals(this.guard.get())) { try { this.guard.set(Boolean.TRUE); //必須為started狀態 if (this.started) { //appender的過濾器 參考ThresholdFilter 實現 可以對消息搜集做過濾 if (this.getFilterChainDecision(eventObject) == FilterReply.DENY) { return; } //<5>模板模式化 由子類實現 this.append(eventObject); return; } if (this.statusRepeatCount++ < 3) { this.addStatus(new WarnStatus("Attempted to append to non started appender [" + this.name + "].", this)); } } catch (Exception var6) { if (this.exceptionCount++ < 3) { this.addError("Appender [" + this.name + "] failed to append.", var6); } return; } finally { this.guard.set(Boolean.FALSE); } } } protected abstract void append(E var1);
OutputStreamAppender
<5>append
ch.qos.logback.core.spi.AppenderAttachableImpl#appendLoopOnAppenders
->
ch.qos.logback.core.UnsynchronizedAppenderBase#doAppend
->
ch.qos.logback.core.OutputStreamAppender#append
protected void append(E eventObject) { if (this.isStarted()) { this.subAppend(eventObject); } }
ch.qos.logback.core.OutputStreamAppender#subAppend
protected void subAppend(E event) { if (this.isStarted()) { try { if (event instanceof DeferredProcessingAware) { ((DeferredProcessingAware)event).prepareForDeferredProcessing(); }
//encoder具體查看https://www.cnblogs.com/LQBlog/p/12164918.html#autoid-2-0-0 byte[] byteArray = this.encoder.encode(event); this.writeBytes(byteArray); } catch (IOException var3) { this.started = false; this.addStatus(new ErrorStatus("IO failure in appender", this, var3)); } } }
private void writeBytes(byte[] byteArray) throws IOException { if (byteArray != null && byteArray.length != 0) { this.lock.lock(); try { //System.out進行輸出 this.outputStream.write(byteArray); if (this.immediateFlush) { this.outputStream.flush(); } } finally { this.lock.unlock(); } } }
總結
1.Logger必須和appender關聯才可以產生作用
2.我們可以為Appender配置Filter做定制的過濾