# 第13章 log4jからの移行

多くのものが変化し、多くのものがそのままである。

—ALPHONSE KARR, Les Guêpes

本章ではカスタマイズされたlog4jのコンポーネントのアペンダーやレイアウトをlogback-classicに移行する方法について議論します。

log4j のクライアントAPIとして`org.apache.log4j`パッケージの`Logger`や`Category`を使っているだけなら、SLF4JのSLF4J移行ツールで自動的に移行することができます。log4j.propertiesから同じ内容のlogback.xmlを作るには、log4j.properties トランスレーターが使えます。

広い観点から見ると、log4jとlogback-classicは密接に関連し合っています。ロガーやアペンダー、レイアウトといった基本的なコンポーネントはどちらのフレームワークにもありますし、目的も同じです。同様に、どちらのフレームワークにも一番重要な内部データ構造として`LoggingEvent`がありますが、実装まで完全に同じわけではありません。一番の違いは、logback-classic の`LoggingEvent`は`ILoggingEvent`インターフェイスを実装しているところです。log4jのコンポーネントをlogback-classicに移行するために必要な変更のほとんどは、`LoggingEvent`の実装の差異を埋めることに費やされます。差異があるといってもその範囲は限られているので安心してください。出来る限りの努力をしたにも関わらず移行がうまくいかなかったら、logback-dev メーリングリストで質問してください。logbackの開発者たちがアドバイスしてくれるはずです。

### log4jのレイアウトを移行する

さあ移行してみましょう。対象はlog4j の単純なレイアウトTrivialLog4jLayoutということにします。これはロギングイベントのメッセージを指定された書式で書式化した文字列を返すものです。コードを見てください。

    package chapters.migrationFromLog4j;

    import org.apache.log4j.Layout;
    import org.apache.log4j.spi.LoggingEvent;

    public class TrivialLog4jLayout extends Layout {

      public void activateOptions() {
        // there are no options to activate
      }

      public String format(LoggingEvent loggingEvent) {
        return loggingEvent.getRenderedMessage();
      }

      public boolean ignoresThrowable() {
        return true;
      }
    }

logback-classicで等価なレイアウトTrivialLogbackLayoutは次のようになります。

    package chapters.migrationFromLog4j;

    import ch.qos.logback.classic.spi.ILoggingEvent;
    import ch.qos.logback.core.LayoutBase;

    public class TrivialLogbackLayout extends LayoutBase<ILoggingEvent> {

      public String doLayout(ILoggingEvent loggingEvent) {
        return loggingEvent.getMessage();
      }
    }

ご覧のように、文字列を書式化するメソッドはlog4jでは`format()`でしたが、logback-classic では`doLayout()`なのです。また、logback-classic には`ignoresThrowable()`に相当するメソッドはありません。logback-classicのレイアウトは`LayoutBase<ILoggingEvent>`を継承しなければならないので気をつけてください。

`activateOptions()`メソッドの効果については大いに議論の余地があります。log4jでは、log4j configurator（`PropertyConfigurator`あるいは`DOMConfiguration`） から、レイアウトの全てのオプションが設定された後に`activateOptions()`メソッドが呼ばれるようになっています。したがって、レイアウトには自分に指定されたオプションの整合性をチェックするタイミングが無いので、全て自分で賄わなければなりません。

logback-classic では、レイアウトはLifeCycleインターフェイスを実装しなければなりません。このインターフェイスには`start()`メソッドがあります。この`start()`メソッドは、log4jの`activateOptions()`メソッドとほぼ同様の役割を果たします。

### log4jのアペンダーを移行する

アペンダーの移行は、レイアウトの移行とほとんど同じです。単純なアペンダーTrivialLog4jAppenderを移行することを考えてみましょう。このアペンダーはレイアウトの返す文字列をコンソールに出力するだけのものです。

    package chapters.migrationFromLog4j;

    import org.apache.log4j.AppenderSkeleton;
    import org.apache.log4j.spi.LoggingEvent;

    public class TrivialLog4jAppender extends AppenderSkeleton {

      protected void append(LoggingEvent loggingevent) {
        String s = this.layout.format(loggingevent);
        System.out.println(s);
      }

      public void close() {
        // nothing to do
      }

      public boolean requiresLayout() {
        return true;
      }
    }

logback-classicで等価なアペンダーTrivialLogbackAppenderは次のようになります。

    package chapters.migrationFromLog4j;

    import ch.qos.logback.classic.spi.ILoggingEvent;
    import ch.qos.logback.core.AppenderBase;

    public class TrivialLogbackAppender extends AppenderBase<ILoggingEvent> {

      @Override
      public void start() {
        if (this.layout == null) {
          addError("No layout set for the appender named [" + name + "].");
          return;
        }
        super.start();
      }

      @Override
      protected void append(ILoggingEvent loggingevent) {
        // note that AppenderBase.doAppend will invoke this method only if
        // this appender was successfully started.

        String s = this.layout.doLayout(loggingevent);
        System.out.println(s);
      }
    }

コードを比べてみると分かるのですが、`append()`メソッドはそのままです。logbackでは`requiresLayout()`メソッドを使用しないので、これは削除できます。logbackの`stop()`メソッドは、log4jの`close()`メソッドと同じ意味です。logback-classicの`AppenderBase`には何もしない`stop()`メソッドの実装があるので、今回のように単純なアペンダーならそれで十分でしょう。