logback-Appender 自定義詳解
阿新 • • 發佈:2018-12-30
實際上Appender可能是logback框架中最重要的元件之一,雖然Logger是記錄日誌的介面,但是如果一個Logger沒有關聯到任何Appender的話,那麼這個Logger就無法記錄任何資訊。此外雖然logback提供了很多擴充套件點,但是在應用中,我們可能很少會擴充套件filter,很少擴充套件layout和encoder,但是我們擴充套件Appender的機會卻是很多的
老規矩,首先上圖,看一下Appender的大圖景,這裡要說明的是,實現Appender介面有2個base類,一個是AppenderBase,另一個是UnsynchronizedAppenderBase,這2個類非常接近,80%以上的程式碼都是相同的。如果我們自己要自定義Appender的話,只要寫一個類繼承自這2個base類就好
首先是有一個Appender介面,然後如上文所說,UnsynchronizedAppenderBase類實現了這個介面,但是它本身是一個抽象類,需要繼承它才能得到真正的實現類。Appender介面繼承了FilterAttachable介面,而UnsynchronizedAppenderBase類持有一個FilterAttachableImpl類,委託這個類來實現FilterAttachable接口裡定義的方法
然後OutputStreamAppender是繼承自UnsynchronizedAppenderBase的Appender實現類,雖然它已經不是抽象類了,但是實際也是不能直接使用的,它的實現類就是最常見的ConsoleAppender和FileAppender
只要自己使用過logback的朋友都知道,appender元素下面還需要配置encoder元素,這裡的Encoder介面就是對應這個encoder元素的,因為其實Appender元件還不是最終實際記錄日誌資訊的元件,它要委託encoder元件來完成LoggingEvent的格式化和記錄
介紹完了大體的結構,我們接下來就看看,從Appender介面的doAppend()方法,是怎麼一步步地最終記錄日誌的
首先是UnsynchronizedAppenderBase裡面的doAppend()方法,它主要是記錄了Status狀態,然後檢查Appender上的Filter是否滿足過濾條件,最後再呼叫實現子類的appender()方法。很眼熟是嗎,這裡用到了一個設計模式——模板方法
Java程式碼
上面的程式碼非常簡單,就不用說了,我們就直接看看實現類的append()方法是怎麼實現的,這裡我們選擇OutputStreamAppender實現類
Java程式碼
首先檢查一下這個Appender是否已經啟動,如果沒啟動就直接返回,如果已經啟動,則又進入一個subAppend()方法
Java程式碼
這個方法居然什麼事也不幹。。做了一些檢查以後,又進入writeOut()方法。。。
Java程式碼
writeOut()方法委託配置給它的Encoder元件來記錄
Java程式碼
到這裡,終於完了。Encoder元件又委託其Layout元件來將LoggingEvent進行格式化,返回一個String,然後通過OutputStream.write()方法,把格式化之後的日誌資訊寫到目的地
耐心看到這裡的朋友,可能已經有點被繞暈了,怎麼Appender需要這麼麻煩嗎?其實我們這裡說的只是ConsoleAppender的doAppend()全流程,並不是所有Appender都這麼複雜的,當然也有一些更復雜的。。
下面看一個簡單的Appender,就是我自己寫的MyAppender
Java程式碼
好吧,非常簡單是不是,如果把這個Appender配置到logback.xml中,那麼當Logger.info()呼叫的時候,就會先走進AppenderBase類的doAppend()方法裡,進行Filter校驗等等,然後進入MyAppender的append()方法,不做其他的操作,直接把message給列印到Console上。當然,由於這個類是極度簡化的,沒有Encoder和Layout,也就沒辦法控制輸出日誌的時間,也沒有辦法對%thread等標記進行解析處理了。但是這個類可能可以很清晰地表達出,Appender元件是怎麼工作的:由AppenderBase類來呼叫Filter鏈,然後由Appender實現類來委託Encoder解析LoggingEvent,再輸出到目的地
老規矩,首先上圖,看一下Appender的大圖景,這裡要說明的是,實現Appender介面有2個base類,一個是AppenderBase,另一個是UnsynchronizedAppenderBase,這2個類非常接近,80%以上的程式碼都是相同的。如果我們自己要自定義Appender的話,只要寫一個類繼承自這2個base類就好
首先是有一個Appender介面,然後如上文所說,UnsynchronizedAppenderBase類實現了這個介面,但是它本身是一個抽象類,需要繼承它才能得到真正的實現類。Appender介面繼承了FilterAttachable介面,而UnsynchronizedAppenderBase類持有一個FilterAttachableImpl類,委託這個類來實現FilterAttachable接口裡定義的方法
然後OutputStreamAppender是繼承自UnsynchronizedAppenderBase的Appender實現類,雖然它已經不是抽象類了,但是實際也是不能直接使用的,它的實現類就是最常見的ConsoleAppender和FileAppender
只要自己使用過logback的朋友都知道,appender元素下面還需要配置encoder元素,這裡的Encoder介面就是對應這個encoder元素的,因為其實Appender元件還不是最終實際記錄日誌資訊的元件,它要委託encoder元件來完成LoggingEvent的格式化和記錄
介紹完了大體的結構,我們接下來就看看,從Appender介面的doAppend()方法,是怎麼一步步地最終記錄日誌的
首先是UnsynchronizedAppenderBase裡面的doAppend()方法,它主要是記錄了Status狀態,然後檢查Appender上的Filter是否滿足過濾條件,最後再呼叫實現子類的appender()方法。很眼熟是嗎,這裡用到了一個設計模式——模板方法
Java程式碼
- 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);
- }
- }
- abstract protected void append(E eventObject);
上面的程式碼非常簡單,就不用說了,我們就直接看看實現類的append()方法是怎麼實現的,這裡我們選擇OutputStreamAppender實現類
Java程式碼
- @Override
- protected void append(E eventObject) {
- if (!isStarted()) {
- return;
- }
- subAppend(eventObject);
- }
首先檢查一下這個Appender是否已經啟動,如果沒啟動就直接返回,如果已經啟動,則又進入一個subAppend()方法
Java程式碼
- /**
- * Actual writing occurs here.
- * <p>
- * Most subclasses of <code>WriterAppender</code> will need to override this
- * method.
- *
- * @since 0.9.0
- */
- protected void subAppend(E event) {
- if (!isStarted()) {
- return;
- }
- try {
- // this step avoids LBCLASSIC-139
- if (event instanceof DeferredProcessingAware) {
- ((DeferredProcessingAware) event).prepareForDeferredProcessing();
- }
- // the synchronization prevents the OutputStream from being closed while we
- // are writing. It also prevents multiple thread from entering the same
- // converter. Converters assume that they are in a synchronized block.
- synchronized (lock) {
- writeOut(event);
- }
- } catch (IOException ioe) {
- // as soon as an exception occurs, move to non-started state
- // and add a single ErrorStatus to the SM.
- this.started = false;
- addStatus(new ErrorStatus("IO failure in appender", this, ioe));
- }
- }
這個方法居然什麼事也不幹。。做了一些檢查以後,又進入writeOut()方法。。。
Java程式碼
- protected void writeOut(E event) throws IOException {
- this.encoder.doEncode(event);
- }
writeOut()方法委託配置給它的Encoder元件來記錄
Java程式碼
- public void doEncode(E event) throws IOException {
- String txt = layout.doLayout(event);
- outputStream.write(convertToBytes(txt));
- outputStream.flush();
- }
到這裡,終於完了。Encoder元件又委託其Layout元件來將LoggingEvent進行格式化,返回一個String,然後通過OutputStream.write()方法,把格式化之後的日誌資訊寫到目的地
耐心看到這裡的朋友,可能已經有點被繞暈了,怎麼Appender需要這麼麻煩嗎?其實我們這裡說的只是ConsoleAppender的doAppend()全流程,並不是所有Appender都這麼複雜的,當然也有一些更復雜的。。
下面看一個簡單的Appender,就是我自己寫的MyAppender
Java程式碼
- public class MyAppender extends AppenderBase<LoggingEvent> {
- @Override
- protected void append(LoggingEvent eventObject) {
- System.out.println(eventObject.getMessage());
- }
- }
好吧,非常簡單是不是,如果把這個Appender配置到logback.xml中,那麼當Logger.info()呼叫的時候,就會先走進AppenderBase類的doAppend()方法裡,進行Filter校驗等等,然後進入MyAppender的append()方法,不做其他的操作,直接把message給列印到Console上。當然,由於這個類是極度簡化的,沒有Encoder和Layout,也就沒辦法控制輸出日誌的時間,也沒有辦法對%thread等標記進行解析處理了。但是這個類可能可以很清晰地表達出,Appender元件是怎麼工作的:由AppenderBase類來呼叫Filter鏈,然後由Appender實現類來委託Encoder解析LoggingEvent,再輸出到目的地