第5章 エンコーダー

ACTION THIS DAY 彼らの要求するものを全て最優先にして、それが終わったら報告してくれ。

アラン・チューリングと彼の同僚の暗号解読者が署名した破格の予算請求書について、1941年10月、チャーチル首相がヘイスティングス・イスメイ将軍に伝えた言葉

エンコーダーとは何か

エンコーダーの役割は、ロギングイベントをバイト配列に変換するだけでなく、そのバイト配列をOutputStreamに書き込むことです。エンコーダーはlogback 0.9.19から導入されました。以前のバージョンでは、ほとんどのアペンダーはロギングイベントを文字列に変換してjava.io.Writerに書き込むのをレイアウトに任せていました。また、以前のバージョンでは、利用者はFileAppenderPatternLayoutをネストしていました。logback 0.9.19になってから、FileAppenderもその派生クラスも、エンコーダーを利用するようになり、レイアウトを使わなくなりました

どうしてこんな破壊的な変更をしたのか?

詳細は次章で説明しますが、レイアウトにできるのはロギングイベントを文字列に変換することだけです。また、レイアウトはロギングイベントを書き込む間何もできません。ロギングイベントをまとめてバッチ的に処理することができないのです。対照的にエンコーダーはバイト配列を書式化している間だけでなく、書式化されたバイト配列を書き込んでいる間も完全に主導権を握っています。

現時点では、利便性のあるエンコーダーはPatternLayoutEncoderだけです。単にPatternLayoutをラップしただけで、ほとんどの仕事は任せてしまいます。一見すると、エンコーダーは不必要な複雑さだけをもたらしているようにも見えます。しかし、私たちは新しく強力なエンコーダーが登場することによってこういった印象を上書きしてくれることに期待しています。

エンコーダーインターフェイス

エンコーダーの役割は、到着したロギングイベントをバイト配列に変換すること変換されたバイト配列を適切なOutputStreamに書き込むことです。前述したように、エンコーダーは親のアペンダーが管理しているOutputStreamに対して、いつ、どんなバイト配列を書き込むのか完全に制御することができます。エンコーダのインターフェイスを見てみましょう。

package ch.qos.logback.core.encoder;

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.
   */
  void init(OutputStream os) throws IOException;

  /**
   * Encode and write an event to the appropriate {@link OutputStream}.
   * Implementations are free to defer writing out of the encoded event and
   * instead write in batches.
   */
  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.
   */
  void close() throws IOException;
}

見ての通り、Encoderインターフェイスには少ししかメソッドが宣言されていません。ですが、これらのメソッドだけで驚くほどたくさんの仕事ができるのです。

LayoutWrappingEncoder

logback0.9.19より前は、ほとんどのアペンダーがログの出力形式の面倒をレイアウトにまかせていました。レイアウトインターフェイスを使用するコードが大量に残っているので、レイアウトとエンコーダーを仲介する方法が必要でした。それをするのがLayoutWrappingEncoderです。LayoutWrappingEncoder はエンコーダーインターフェイスを実装しています。そして、ラップしたレイアウトにロギングイベントを文字列に変換する仕事を委譲します。

LayoutWrappingEncoderのコードを一部抜粋して紹介します。レイアウトインスタンスにどうやって委譲しているのかがわかるでしょうか。。

package ch.qos.logback.core.encoder;

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

  protected Layout<E> layout;
  private Charset charset;
  private boolean immediateFlush = true;

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

  private byte[] convertToBytes(String s) {
    if (charset == null) {
      return s.getBytes();
    } else {
      return s.getBytes(charset);
    }
  }
}

doEncode()メソッドは、まず最初に受け取ったロギングイベントをラップしたレイアウトで文字列に変換します。そして変換結果の文字列を、使用者が指定した文字セットで符号化してバイト配列に変換します。次に、変換されたバイト配列を親アペンダーのOutputStreamに書き込みます。デフォルトではOutputStreamをすぐにフラッシュします。ただし、immediateFlushプロパティに明示的にfalseが指定されているときはフラッシュしません。immediateFlushプロパティにfalseを指定しておくと、ロギングのスループットが大幅に向上します。設定例については、次のPatternLayoutEncoderのところで紹介します。

PatternLayoutEncoder

PatternLayoutは最も広く使われているレイアウトです。この一般的なユースケースに対応するため、logbackはPatternLayoutEncoderを提供しています。これは、PatternLayoutのインスタンスだけをラップするようにしたLayoutWrappingEncoderの派生クラスです。

logback0.9.19の頃は、FileAppenderやその派生クラスの設定には必ずPatternLayoutが使われていました。今は代わりにPatternLayoutEncoderを使わなければなりません。これについては、logbackのエラーコードでも説明しています。

immediateFlushプロパティ

PatternLayoutEncoderLayoutWrappingEncoderの派生クラスなので、immediateFlushプロパティを設定することができます。immediateFlushのデフォルト値は"true"です。出力ストリームをすぐにフラッシュすることで、ロギングイベントがディスクに書き込まれること、アプリケーションが終了するときにちゃんとアペンダーを閉じなかったときでも、ロギングイベントが失われないことを保証することができます。一方、このプロパティに"false"を指定すると、(それが必要なのかどうかはわかりませんが)ロギングのスループットが5倍にまで向上する可能性があります。前述したとおり、immediateFlushにfalseを指定した場合、アプリケーションが終了するときにちゃんとアペンダーを閉じなかったとき、ディスクに書き込まれていないロギングイベントが失われる可能性があります。

FileAppenderPatternLayoutEncoderを指定した設定例を見てみましょう。immediateFlushプロパティにはfalseを指定しています。

<appender name="FILE" class="ch.qos.logback.core.FileAppender">
  <file>foo.log</file>
  <encoder>
    <pattern>%d %-5level [%thread] %logger{0}: %msg%n</pattern>
    <!-- this quadruples logging throughput -->
    <immediateFlush>false</immediateFlush>
  </encoder>
</appender>

ヘッダに出力形式を入れる

ログファイルの解析を容易にするため、logbackはログファイルの先頭にログの出力形式を出力することができます。この機能はデフォルトでは無効になっています。PatternLayoutEncoderoutputPatternAsHeaderプロパティにtrueを指定すれば、有効化することができます。以下に例を示します。

<appender name="FILE" class="ch.qos.logback.core.FileAppender">
  <file>foo.log</file>
  <encoder>
    <pattern>%d %-5level [%thread] %logger{0}: %msg%n</pattern>
    <outputPatternAsHeader>true</outputPatternAsHeader>
  </encoder>
</appender>

この設定を使うと次のように出力されます。

#logback.classic pattern: %d [%thread] %-5level %logger{36} - %msg%n
2012-04-26 14:54:38,461 [main] DEBUG com.foo.App - Hello world
2012-04-26 14:54:38,461 [main] DEBUG com.foo.App - Hi again
...

先頭行の"#logback.classic pattern"が出力形式として出力されたヘッダです。