1. 程式人生 > >logback-Appender 自定義詳解

logback-Appender 自定義詳解

實際上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程式碼  收藏程式碼
  1. public void doAppend(E eventObject) {  
  2.     // WARNING: The guard check MUST be the first statement in the  
  3.     // doAppend() method.  
  4.     // prevent re-entry.  
  5.     if (Boolean.TRUE.equals(guard.get())) {  
  6.       return;  
  7.     }  
  8.     try {  
  9.       guard.set(Boolean.TRUE);  
  10.       if (!this.started) {  
  11.         if (statusRepeatCount++ < ALLOWED_REPEATS) {  
  12.           addStatus(new WarnStatus(  
  13.               "Attempted to append to non started appender [" + name + "].",  
  14.               this));  
  15.         }  
  16.         return;  
  17.       }  
  18.       if (getFilterChainDecision(eventObject) == FilterReply.DENY) {  
  19.         return;  
  20.       }  
  21.       // ok, we now invoke derived class' implementation of append  
  22.       this.append(eventObject);  
  23.     } catch (Exception e) {  
  24.       if (exceptionCount++ < ALLOWED_REPEATS) {  
  25.         addError("Appender [" + name + "] failed to append.", e);  
  26.       }  
  27.     } finally {  
  28.       guard.set(Boolean.FALSE);  
  29.     }  
  30.   }  
  31.   abstract protected void append(E eventObject);  

上面的程式碼非常簡單,就不用說了,我們就直接看看實現類的append()方法是怎麼實現的,這裡我們選擇OutputStreamAppender實現類 
Java程式碼  收藏程式碼
  1. @Override  
  2.   protected void append(E eventObject) {  
  3.     if (!isStarted()) {  
  4.       return;  
  5.     }  
  6.     subAppend(eventObject);  
  7.   }  

首先檢查一下這個Appender是否已經啟動,如果沒啟動就直接返回,如果已經啟動,則又進入一個subAppend()方法 
Java程式碼  收藏程式碼
  1. /** 
  2.    * Actual writing occurs here. 
  3.    * <p> 
  4.    * Most subclasses of <code>WriterAppender</code> will need to override this 
  5.    * method. 
  6.    *  
  7.    * @since 0.9.0 
  8.    */  
  9.   protected void subAppend(E event) {  
  10.     if (!isStarted()) {  
  11.       return;  
  12.     }  
  13.     try {  
  14.       // this step avoids LBCLASSIC-139  
  15.       if (event instanceof DeferredProcessingAware) {  
  16.         ((DeferredProcessingAware) event).prepareForDeferredProcessing();  
  17.       }  
  18.       // the synchronization prevents the OutputStream from being closed while we  
  19.       // are writing. It also prevents multiple thread from entering the same  
  20.       // converter. Converters assume that they are in a synchronized block.  
  21.       synchronized (lock) {  
  22.         writeOut(event);  
  23.       }  
  24.     } catch (IOException ioe) {  
  25.       // as soon as an exception occurs, move to non-started state  
  26.       // and add a single ErrorStatus to the SM.  
  27.       this.started = false;  
  28.       addStatus(new ErrorStatus("IO failure in appender"this, ioe));  
  29.     }  
  30.   }  

這個方法居然什麼事也不幹。。做了一些檢查以後,又進入writeOut()方法。。。 
Java程式碼  收藏程式碼
  1. protected void writeOut(E event) throws IOException {  
  2.     this.encoder.doEncode(event);  
  3.   }  

writeOut()方法委託配置給它的Encoder元件來記錄 
Java程式碼  收藏程式碼
  1. public void doEncode(E event) throws IOException {  
  2.     String txt = layout.doLayout(event);  
  3.     outputStream.write(convertToBytes(txt));  
  4.     outputStream.flush();  
  5.   }  

到這裡,終於完了。Encoder元件又委託其Layout元件來將LoggingEvent進行格式化,返回一個String,然後通過OutputStream.write()方法,把格式化之後的日誌資訊寫到目的地 

耐心看到這裡的朋友,可能已經有點被繞暈了,怎麼Appender需要這麼麻煩嗎?其實我們這裡說的只是ConsoleAppender的doAppend()全流程,並不是所有Appender都這麼複雜的,當然也有一些更復雜的。。 

下面看一個簡單的Appender,就是我自己寫的MyAppender 
Java程式碼  收藏程式碼
  1. public class MyAppender extends AppenderBase<LoggingEvent> {  
  2.     @Override  
  3.     protected void append(LoggingEvent eventObject) {  
  4.         System.out.println(eventObject.getMessage());  
  5.     }  
  6. }  

好吧,非常簡單是不是,如果把這個Appender配置到logback.xml中,那麼當Logger.info()呼叫的時候,就會先走進AppenderBase類的doAppend()方法裡,進行Filter校驗等等,然後進入MyAppender的append()方法,不做其他的操作,直接把message給列印到Console上。當然,由於這個類是極度簡化的,沒有Encoder和Layout,也就沒辦法控制輸出日誌的時間,也沒有辦法對%thread等標記進行解析處理了。但是這個類可能可以很清晰地表達出,Appender元件是怎麼工作的:由AppenderBase類來呼叫Filter鏈,然後由Appender實現類來委託Encoder解析LoggingEvent,再輸出到目的地