第13章 log4jからの移行

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

—ALPHONSE KARR, Les Guêpes

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

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

広い観点から見ると、log4jとlogback-classicは密接に関連し合っています。ロガーやアペンダー、レイアウトといった基本的なコンポーネントはどちらのフレームワークにもありますし、目的も同じです。同様に、どちらのフレームワークにも一番重要な内部データ構造としてLoggingEventがありますが、実装まで完全に同じわけではありません。一番の違いは、logback-classic のLoggingEventILoggingEventインターフェイスを実装しているところです。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()メソッドの実装があるので、今回のように単純なアペンダーならそれで十分でしょう。