1. 程式人生 > >讀logback原始碼系列文章(八)——記錄日誌的實際工作類Encoder

讀logback原始碼系列文章(八)——記錄日誌的實際工作類Encoder

本系列的部落格從logback怎麼對接slf4j開始,逐步介紹了LoggerContext、Logger、ContextInitializer、Appender、Action等核心元件。跟讀logback的原始碼到這個程度,雖然不能說精通,不過至少日常的配置,和簡單的自定義擴充套件都不會有問題了。

這一篇是本系列部落格的最後一節,介紹一下實際記錄日誌的類Encoder。其實繼續深入下去,logback還是有很多值得研究的地方,比如Layout、Listener等。不過我個人感覺對logback框架已經比較熟悉了,所以就告一段落,有興趣的朋友可以自己再深入下去。下一步我準備讀一讀struts2的原始碼,因為個人感覺spring、tomcat、hibernate可能難了一點,最近的工作比較忙,不會有太多的時間鑽研技術,所以就挑選比較簡單的struts2來讀一讀。

前面的文章我們已經介紹過,logback記錄日誌的入口是Logger類,然後Logger類又是委託Appender來記錄日誌,但是實際上,Appender還不是實際工作的類,Appender往往還要委託Encoder來記錄日誌。可能會覺得有點繞,但是一直跟下來的朋友可能會跟我有一樣的感覺,就是logback框架的這種設計風格,雖然複雜了一點,但是在可擴充套件性上確實比較好

好了,老規矩,首先上圖:

bb23f429-4b17-391f-ba09-373e205b3b75.jpg


從圖中我們可以看到,Encoder是一個介面,定義了3個方法,其中最重要的方法是doEncode(ILoggingEvent event)方法

然後EncoderBase抽象類部分實現了Encoder介面,其實就是持有了一個OutputStream的引用,這是為了後面呼叫outputStream.write(byte[] b)方法,畢竟最終,日誌資訊是要寫到輸出流裡才可以的

EncoderBase又有若干個具體的實現類,但是實際上真正有意義的就是LayoutWrappingEncoder,這個實現類持有一個Layout欄位,委託Layout的doLayout(ILoggingEvent event)方法,來把一個日誌事件轉換成String。由於基本上只會使用PatternLayoutEncoder,所以在logback.xml裡這是無需配置的,如果使用了<encoder>標籤,不指定實現類,那麼就會預設使用PatternLayoutEncoder

上面大致介紹了一下結構,下面就來看具體的程式碼

public interface Encoder<E> extends ContextAware, LifeCycle {

  /**
   * This method is called when the owning appender starts or whenever output
   * needs to be directed to a new OutputStream, for instance as a result of a
   * rollover. Implementing encoders should at the very least remember the
   * OutputStream passed as argument and use it in future operations.
   * 
   * @param os
   * @throws IOException
   */
  void init(OutputStream os) throws IOException;

  /**
   * Encode and write an event to the appropriate {@link OutputStream}.
   * Implementations are free to differ writing out of the encoded event and
   * instead write in batches.
   * 
   * @param event
   * @throws IOException
   */
  void doEncode(E event) throws IOException;

  /**
   * This method is called prior to the closing of the underling
   * {@link OutputStream}. Implementations MUST not close the underlying
   * {@link OutputStream} which is the responsibility of the owning appender.
   * 
   * @throws IOException
   */
  void close() throws IOException;
}

這是Encoder的介面定義,大致上,只要關注doEncode()方法就可以了

abstract public class EncoderBase<E> extends ContextAwareBase implements Encoder<E> {

  protected boolean started;

  protected OutputStream outputStream;
  
  public void init(OutputStream os) throws IOException {
    this.outputStream = os;
  }
  
  public boolean isStarted() {
    return started;
  }
  
  public void start() {
    started = true;
  }

  public void stop() {
    started = false;
  }
}  

如前所述,EncoderBase主要是為了方便使用者的自定義擴充套件(在使用logback框架的時候,基本上只要是允許擴充套件的元件,都提供了一個Base抽象類,所以要自定義擴充套件,最簡單的辦法就是寫一個類繼承自XXXBase),這裡持有了一個OutputStream物件,目的是為了稍後呼叫outputStream.write()方法

public class EchoEncoder<E> extends EncoderBase<E> {

  String fileHeader;
  String fileFooter;

  public EchoEncoder() {
  }

  public void doEncode(E event) throws IOException {
    String val = event + CoreConstants.LINE_SEPARATOR;
    outputStream.write(val.getBytes());
  }

  public void close() throws IOException {
    if (fileFooter == null) {
      return;
    }
    outputStream.write(fileFooter.getBytes());
  }

  public void init(OutputStream os) throws IOException {
    super.init(os);
    if (fileHeader == null) {
      return;
    }
    outputStream.write(fileHeader.getBytes());
  }
}

這是EchoEncoder,基本上,這個類只是起一個示例的作用,在實際應用中,不能配置Layout的Encoder沒有太大的意義。該類的doEncode()方法就是對日誌事件加上一個分行符,然後通過outputStream輸出,也就是說,使用這個encoder的時候,不需要配置layout。下面看一下關鍵性的LayoutWrappingEncoder,由於持有了Layout元件,所以這個Encoder有了自定義輸出格式的能力,所以也是目前基本上唯一有實用性的Encoder元件

public void init(OutputStream os) throws IOException {
    super.init(os);
    writeHeader();
  }

  void writeHeader() throws IOException {
    if (layout != null && (outputStream != null)) {
      StringBuilder sb = new StringBuilder();
      appendIfNotNull(sb, layout.getFileHeader());
      appendIfNotNull(sb, layout.getPresentationHeader());
      if (sb.length() > 0) {
        sb.append(CoreConstants.LINE_SEPARATOR);
        // If at least one of file header or presentation header were not
        // null, then append a line separator.
        // This should be useful in most cases and should not hurt.
        outputStream.write(convertToBytes(sb.toString()));
        outputStream.flush();
      }
    }
  }

這個類在初始化的時候,會把檔案頭首先輸出,這主要是在HTMLLayoutBase中的,如果不準備以HTML格式輸出日誌,這2個方法是可以忽略的

public void doEncode(E event) throws IOException {
    String txt = layout.doLayout(event);
    outputStream.write(convertToBytes(txt));
    outputStream.flush();
  }

然後看一下最最關鍵的doEncode()方法,它委託layout元件將日誌事件轉換成String字串,然後通過outputStream輸出。。。

好吧,所以到這裡其實還沒有完,接下來就要看看Layout元件是怎麼把ILoggingEvent轉換成String的了。不過這已經不是本篇的主題,本人也不準備繼續了,只能告訴大家,Layout元件是委託Converter元件,按照配置檔案裡定義的格式,得到格式化之後的字串的,有興趣的朋友可以繼續研究研究

本系列部落格就到此結束了,個人感覺從中收穫最大的程式碼有以下幾個地方:
1、logback框架用JoranConfigurator來實現配置的設計思路是非常值得借鑑的,這部分內容記錄在ContextInitializer那篇部落格裡
2、logback通過Logger-->Appender-->Encoder層層委託,來實現日誌記錄的思路也很精彩,通過這種層層委託的方式,使整個框架的靈活性和擴充套件性都很強
3、用StaticLoggerBinder類來實現具體日誌框架和slf4j門面的對接,這部分的設計思路和程式碼也相當不錯
4、ContextSelectorStaticBinder類,解決了在JNDI等環境下,不同應用使用不同的LoggerContext的問題,解決問題的思路也很值得學習
5、用XXXBase類來提供自定義元件擴充套件點的方法,在設計系統時也可以借鑑

總的來說,logback是一個很優秀的開源日誌框架,閱讀其原始碼除了可以加深對其理解,更好地使用它之外,還可以通過其中優秀的設計思想和程式碼實現,提高自己的程式設計水平。我認為如果想閱讀原始碼的話,logback框架是一個不錯的開始

本系列部落格就到這裡了,後面我準備讀一讀struts2框架
  • bb23f429-4b17-391f-ba09-373e205b3b75-thumb.jpg
  • 大小: 107.5 KB