第3章 logbackの設定

物事の本質を正確に表現するときは、記号を使うのが最も適している。また、記号によって表現されたものがあれば、思考に費やす労力が驚くほど軽減されるのだ。

—GOTTFRIED WILHELM LEIBNIZ

設定スクリプトの例を示しながら、logback の設定方法を説明していきます。logback は Joran という設定フレームワークを利用しています。Joran については後の章で紹介します。

logback の設定

アプリケーションコードにロギング要求を埋め込むには、かなりの計画と作業が必要です。調査したところ、だいたいコード全体の4%ほどがロギングのために使われていました。したがって、そこそこの規模のアプリケーションであっても、数千行のロギング行が含まれることになるのです。その数を考えれば、私たちにロギング式を管理するためのツールが必要となる理由が理解できるのではないでしょうか。

logback はプログラム的に設定することもできるし、XML や Groovy で記述された設定スクリプトで設定することもできます。なお、log4jユーザーのために、log4j.propertiesファイルlogback.xmlに変換するPropertiesTranslatorを用意しています。

さて、logback が設定するところを順番に見ていきましょう。

  1. logback はクラスパス上でlogback.groovyというファイルを探します。

  2. 見つからなかったら、今度はクラスパス上でlogback-test.xmlというファイルを探します。

  3. 見つからなかったら、今度はクラスパス上でlogback.xmlというファイルを探します。

  4. 何も見つからなかったら、自動的にBasicConfiguratorを使って設定します。ロギング出力は直接コンソールに出力されるようになります。

四番目のステップは、設定ファイルが見つからなかった場合デフォルトの(ごく基本的な)設定をする、ということです。

もし Maven を使用しているなら、src/test/resources フォルダの下に logback-test.xmlを置いている場合でも、それがアーティファクトに入り込まないことは Maven によって保証されます。したがって、テスト用と実際の環境用で、logback-test.xmllogback.xmlを使い分けることができます。Antでも原則的に同じことができます。

logback の自動設定

logback を設定する一番簡単な方法は、デフォルト設定を使わせることです。仮にMyApp1というアプリケーションがあるつもりで、これがどのように行われるのか詳しく見てみましょう。

例:BasicConfiguratorの使用例(logback-examples/src/main/java/chapters/configuration/MyApp1.java

package manual.configuration;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class MyApp1 {
  final static Logger logger = LoggerFactory.getLogger(MyApp1.class);

  public static void main(String[] args) {
    logger.info("Entering application.");

    Foo foo = new Foo();
    foo.doIt();
    logger.info("Exiting application.");
  }
}

このクラスでは、ロガーを静的クラスメンバ変数として定義しています。その後、Fooクラスのオブジェクトを生成します。Fooクラスの定義は次のとおりです。

例:ロギングするクラスの例(logback-examples/src/main/java/chapters/configuration/Foo.java

package chapters.configuration;
  
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
   
public class Foo {
  static final Logger logger = LoggerFactory.getLogger(Foo.class);
  
  public void doIt() {
    logger.debug("Did it again!");
  }
}

この章で紹介しているサンプルプログラムを実行するには、クラスパスに所定のjarファイルを置いておかなければなりません。詳しくはセットアップの説明をを参照してください。

logback-test.xmllogback.xmlも無い場合、logback はBasicConfiguratorによって最小限の設定を行います。最小限の設定としてやることは、ルートロガーにConsoleAppenderを割り当てることだけです。出力はPatternLayoutEncoderによって書式化されます。PatternLayoutEncoderには、%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%nという書式化パターンが設定されています。また、ルートロガーにはデフォルトでDEBUGレベルが割り当てられます。

java chapters.configuration.MyApp1を実行すると次のような出力が得られます。

16:06:09.031 [main] INFO chapters.configuration.MyApp1 - Entering application. 16:06:09.046 [main] DEBUG chapters.configuration.Foo - Did it again! 16:06:09.046 [main] INFO chapters.configuration.MyApp1 - Exiting application.

(あるとしたら)logback を設定するコード以外に、クライアントコードは一切logbackに依存しません。ロギングフレームワークとしてlogbackを使用するアプリケーションがコンパイル時に依存するのはSLF4Jであって、logbackではありません。

MyApp1アプリケーションとlogbackのリンクは、アプリケーションが呼び出したorg.slf4j.LoggerFactoryクラスとorg.slf4j.Loggerクラスによって行われます。これらのクラスは、使用するロガーを(クラスローダーから?)取得し、つなぎあわせます。Fooクラスからlogbackへの依存は、org.slf4j.LoggerFactoryorg.slf4j.Loggerのimportを介した間接的なものであることに注意しましょう。SLF4Jはあらゆるロギングフレームワークから利用するための抽象層を備えています。おかで、ほとんどのコードをあるロギングフレームワークから別のロギングフレームワークに移行するのがとても簡単になっています。

logback-test.xmlまたはlogback.xmlによる自動設定

前述したとおり、logback はクラスパス上でlogback-test.xmllogback.xmlを探して自分を設定しようとします。次に示す設定ファイルは、BasicConfiguratorによって行われるのとまったく同じ内容の設定です。

例:基本的な設定ファイル(logback-examples/src/main/java/chapters/configuration/sample0.xml

Groovyで見る
<configuration>

  <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
    <!-- encoders are assigned the type
         ch.qos.logback.classic.encoder.PatternLayoutEncoder by default -->
    <encoder>
      <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
    </encoder>
  </appender>

  <root level="debug">
    <appender-ref ref="STDOUT" />
  </root>
</configuration>

ファイル名をsample0.xmlからlogback.xml(またはlogback-test.xml)に変更して、クラスパスに含まれるフォルダに置きましょう。そしてMyApp1アプリケーションを実行すると、前の実行例と同じ出力結果が得られるはずです。

警告やエラーが発生した際、ステータスメッセージを自動的に出力する

設定ファイルを解析している間に警告やエラーが発生した場合、logback は内部状態を表すメッセージを自動的にコンソールに出力します。

出力が重複することを避けるため、利用者が明示的にステータスリスナーを登録していた場合(後で例を示します)ステータスの自動出力は無効になります。

警告やエラーが起きていなくても logback の内部状態を出力させたければ、StatusPrinterクラスのprint()メソッドを使えばよいです。次のMyApp2アプリケーションは、内部状態を出力するためのコードが二行追加されていることを除いてMyApp1とまったく同じです。

例:logbackの内部状態を出力する(logback-examples/src/main/java/chapters/configuration/MyApp2.java

  public static void main(String[] args) {
    // SLF4J が logback を使うようになっていると想定
    LoggerContext lc = (LoggerContext) LoggerFactory.getILoggerFactory();
    // logback の内部状態を出力
    StatusPrinter.print(lc);
    ...
  }

何も問題が無ければ、コンソールに次のような出力が表示されます。

17:44:58,578 |-INFO in ch.qos.logback.classic.LoggerContext[default] - Found resource [logback-test.xml]
17:44:58,671 |-INFO in ch.qos.logback.classic.joran.action.ConfigurationAction - debug attribute not set
17:44:58,671 |-INFO in ch.qos.logback.core.joran.action.AppenderAction - About to instantiate appender of type [ch.qos.logback.core.ConsoleAppender]
17:44:58,687 |-INFO in ch.qos.logback.core.joran.action.AppenderAction - Naming appender as [STDOUT]
17:44:58,812 |-INFO in ch.qos.logback.core.joran.action.AppenderAction - Popping appender named [STDOUT] from the object stack
17:44:58,812 |-INFO in ch.qos.logback.classic.joran.action.LevelAction - root level set to DEBUG
17:44:58,812 |-INFO in ch.qos.logback.core.joran.action.AppenderRefAction - Attaching appender named [STDOUT] to Logger[root]

17:44:58.828 [main] INFO  chapters.configuration.MyApp2 - Entering application.
17:44:58.828 [main] DEBUG chapters.configuration.Foo - Did it again!
17:44:58.828 [main] INFO  chapters.configuration.MyApp2 - Exiting application.

出力結果の最後の方に、前の例で出力されたものと同じ行があることが分かるでしょう。logback の内部メッセージには気をつけるようにしてください。Statusオブジェクトを使うと内部状態に簡単にアクセスできます。

コード中でStatusPrinterをプログラム的に呼び出す代わりに、設定ファイルで内部ステータスを出力するように指定することができます。警告やエラーが起きなくてもです。そのためにはconfiguration要素のdebug属性を設定します。この要素はXML形式の設定ファイルにおける最上位要素です。後で例を示します。debug属性はステータス情報にだけしか影響しないことは覚えておいてください。繰り返しますが、logback の他の設定には影響しません。ロガーレベルについても同様です。(たとえそうなっていて欲しくても、ルートロガーのログレベルはDEBUGなりません。)

例:基本的な設定ファイルをデバッグモードにする(logbackexamples/src/main/java/chapters/configuration/sample1.xml

Groovyで見る
<configuration debug="true"> 

  <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> 
    <!-- encoders are  by default assigned the type
         ch.qos.logback.classic.encoder.PatternLayoutEncoder -->
    <encoder>
      <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
    </encoder>
  </appender>

  <root level="debug">
    <appender-ref ref="STDOUT" />
  </root>
</configuration>

configuration 要素のdebug属性が設定されていると、次のような内部ステータスが出力されます。

  1. 設定ファイルが見つかったかどうか
  2. 設定ファイルのXML文法が適格かどうか

これらのいずれかの条件が満たされなかった場合、設定ファイルを読み込むことができないので、Joran にはdebug属性を理解することができません。見つかった設定ファイルがXMLとして不適格な場合、logback はエラーと判断し、自動的に内部状態をコンソールに出力します。ですが、設定ファイルが見つからなかった場合、logback は内部状態を自動的に出力することはありません。それだけではエラーと見做すには不十分だからです。MyApp2アプリケーションの例で示したように、StatusPrinter.print()をプログラム的に呼び出していれば、どんな場合でも内部ステータス情報を確実に出力するようになります。

ステータスメッセージが無くても強制的に内部ステータス情報を出力させていると、不正なlogback.xmlがどこにあるのか突き止めるのが難しくなります。アプリケーションのソースコードを簡単に変更できない商用環境においては特にそうです。不正な設定ファイルの場所を見つけやすくするには、システムプロパティの"logback.statusListenerClass"(すぐ後で説明します)にStatusListenerを指定するとよいでしょう。そうすれば、強制的にステータスメッセージを出力させることができます。"logback.statusListenerClass" システムプロパティを使うと、エラーが発生したときに自動的に出力されるメッセージを抑止することもできます。

システムプロパティでデフォルトの設定ファイルの場所を指定する

システムプロパティの"logback.configurationFile"を使えば、デフォルトの設定ファイルの場所を指定することができます。このプロパティにはURL形式の値を指定します。クラスパス上のリソースやアプリケーションの外部のファイルを指定することができます。

java -Dlogback.configurationFile=/path/to/config.xml chapters.configuration.MyApp1

ファイルの拡張子は ".xml" か ".groovy" でなければならないので注意してください。他の拡張子の場合は無視します。ステータスリスナーを明示的に登録しておくと、設定ファイルの場所を突き止めるのに役立つでしょう。

設定ファイルが変更されたら自動的に再読み込みする

logback-classic は設定ファイルの変更を監視することができます。そして、なんらか変更があった場合は自動的に再読み込みすることができます。

configuration要素のscan属性を設定すると、設定ファイルの監視と自動的な再読み込みができるようになります。

例:設定ファイルの変更の監視と自動的な再読み込み(logback-examples/src/main/java/chapters/configuration/scan1.xml

Groovyで見る
<configuration scan="true"> 
  ... 
</configuration> 

デフォルトでは、設定ファイルを一分毎に監視します。configuration要素のscanPeriod属性に、監視周期を設定することができます。設定値はミリ秒、秒、分または時間単位で指定することができます。以下に例を示します。

例:デフォルトとは異なる監視周期を指定する(logback-examples/src/main/java/chapters/configuration/scan2.xml

Groovyで見る
<configuration scan="true" scanPeriod="30 seconds" > 
  ...
</configuration> 

時間の単位を指定しなかった場合ミリ秒として扱われますが、たいていの場合不適切だと思いますので注意してください。時間単位を指定することを忘れないでください。

舞台裏で起きていることを説明しましょう。scan 属性に true を指定すると、ReconfigureOnChangeFilterというTurboFilterの派生クラスがインストールされます。TurboFiltersについては後の章 で説明します。設定ファイルのスキャンは「内部スレッド」で行います。ロガーの出力メソッドが呼び出されるときは常にスキャンされるということです。(訳注:正確には、出力メソッドが呼び出されるたびにフィルタが評価されるので、その都度 logback のコンテキストに指定された ThreadExecutor にファイルをスキャンするタスクが投入されます。そして、ThreadExecutor の管理するスレッドによって実行されます。)例えば、scan 属性に true が設定されている場合、myLoggerというロガーを使った「myLoger.debug("hello");」というロギング式があったら、このロギング式が実行されるたびにReconfigureOnChangeFilterが呼び出されることになります。さらに言うと、myLoggerでDEBUGレベルが無効になっているとしてもフィルターはその都度呼び出されることになります。

設定ファイルを変更すると、何回かロギング処理を実行した後で、かつ、監視周期に基いて決定した遅延時間が経過したら、自動的に再読み込みします。

どんなロガーであれ、ロガーレベルがどうであれ、出力メソッドを呼び出すとReconfigureOnChangeFilterも呼び出すことになるので、間違いなく致命的な性能影響があります。したがって、監視周期が経過していようともいまいとも、とてもコストの高い処理になります。性能を改善するため、実際にReconfigureOnChangeFilterが有効になるのは、N回のロギングごとに一回だけです。あなたのアプリケーションがどれくらいの頻度でロギングするのかにもよるのですが、logback はNの値を実行している環境に合わせて調整します。Nのデフォルト値は16です。CPUを占有するアプリケーションの場合最大で2^16(=65536)にまで増えます。

簡単に言うとこうなります。設定ファイルを変更すると、何回かロギング処理を実行した後で、かつ、監視周期に基いて決定した遅延時間が経過したら、自動的に再読み込みします。

JoranConfiguratorを直接呼び出す

logback は、logbac-core に含まれる Joran という設定用ライブラリを利用しています。logback のデフォルトの設定の仕組みでは、JoranConfiguratorを呼び出して、クラスパス上で見つけたデフォルトの設定ファイルを渡すようになっています。何か理由があってデフォルトの設定の仕組みを上書きしたい場合、JoranConfiguratorを直接呼び出すことができます。次に示すMyApp3アプリケーションでは、設定ファイルを引数としてJoranConfiguratorを呼び出しています。

例:JoranConfiguratorを直接呼び出す(logback-examples/src/mian/java/chapters/configuration/MyApp3.java

package chapters.configuration;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import ch.qos.logback.classic.LoggerContext;
import ch.qos.logback.classic.joran.JoranConfigurator;
import ch.qos.logback.core.joran.spi.JoranException;
import ch.qos.logback.core.util.StatusPrinter;

public class MyApp3 {
  final static Logger logger = LoggerFactory.getLogger(MyApp3.class);

  public static void main(String[] args) {
    // SLF4J が logback を使うようになっていると想定
    LoggerContext context = (LoggerContext) LoggerFactory.getILoggerFactory();
    
    try {
      JoranConfigurator configurator = new JoranConfigurator();
      configurator.setContext(context);
      // デフォルト設定を取り消すために context.reset() を呼び出す
      // 複数の設定を積み重ねる場合は呼ばないようにする
      context.reset(); 
      configurator.doConfigure(args[0]);
    } catch (JoranException je) {
      // StatusPrinter will handle this
    }
    StatusPrinter.printInCaseOfErrorsOrWarnings(context);

    logger.info("Entering application.");

    Foo foo = new Foo();
    foo.doIt();
    logger.info("Exiting application.");
  }
}

アプリケーションは、新しく生成したJoranConfiguratorを、有効なLoggerContextに設定してから、ロガーコンテキストを初期化します。そして、アプリケーションの引数として渡された設定ファイルを使ってconfiguratorに設定させています。警告やエラーが発生した場合、内部ステータスが出力されます。複数の設定を積み重ねる場合は、context.reset()を呼び出さないようにしなければなりません。

ステータスメッセージの内容

logback は StatusManagerオブジェクトに内部ステータス情報を蓄積しています。このオブジェクトはLoggerContextからアクセスすることができます。

StatusManagerがあれば、logbackのコンテキストに関連付けられた全てのステータス情報にアクセスすることができます。メモリ使用量を妥当なレベルで抑えるため、StatusManagerのデフォルト実装ではステータスメッセージを先頭と末尾に分けて格納しています。先頭には最初のH個のステータスメッセージを格納し、末尾には最後のT個のステータスメッセージを格納します。今のところHTも150になっていますが、将来的にこの値は変更されるかもしれません。

logback-classic の配布物には、ViewStatusMessagesServlet というサーブレットが含まれています。このサーブレットは、LoggerContextに関連付けられたStatusManagerの持つステータスメッセージを、HTML の表で描画します。出力サンプルは次のようになります。

クリックすると拡大します

Webアプリケーションにこのサーブレットを追加するには、WEB-INF/web.xmlに次の行を追加します。

  <servlet>
    <servlet-name>ViewStatusMessages</servlet-name>
    <servlet-class>ch.qos.logback.classic.ViewStatusMessagesServlet</servlet-class>
  </servlet>

  <servlet-mapping>
    <servlet-name>ViewStatusMessages</servlet-name>
    <url-pattern>/lbClassicStatus</url-pattern>
  </servlet-mapping>

ViewStatusMessagesサーブレットにアクセスするためのURLは次のようになります。 http://host/yourWebapp/lbClassicStatus

ステータスメッセージの待ち受け

StatusManagerStatusListenerを割り当てて、ステータスメッセージを受け取った応答として直ちに何らかの処理を実行させることができます。ただし、ステータスメッセージを受け取ることができるのは logback の設定が完了した後になります。ステータスリスナーを登録しておくと、人間が介入しなくてもlogbackの内部状態を監視できるようになるので便利です。

logback の配布物にはStatusListenerを実装したOnConsoleStatusListenerというクラスが含まれています。名前が表すとおり、到着したステータスメッセージを全てコンソールに出力します。

これは、StatusManager にOnConsoleStatusListenerを登録するサンプルコードです。

   LoggerContext lc = (LoggerContext) LoggerFactory.getILoggerFactory(); 
   StatusManager statusManager = lc.getStatusManager();
   OnConsoleStatusListener onConsoleListener = new OnConsoleStatusListener();
   statusManager.add(onConsoleListener);

ステータスリスナーは、登録された後に発生したステータスメッセージだけしか受け取らないので気をつけてください。それより前のステータスメッセージは受け取りません。ですので、ステータスリスナーの登録を設定ファイルの一番最初の方に置いて、他のディレクティブよりも先に評価されるようにするとよいでしょう。

また、これは一つ以上のステータスリスナーを登録できるということでもあります。例を示します。

例:ステータスリスナーの登録(logback-examples/src/main/java/chapters/configuration/onConsoleStatusListener.xml

Groovyで見る
<configuration>
  <statusListener class="ch.qos.logback.core.status.OnConsoleStatusListener" />  

  ... the rest of the configuration file  
</configuration>

システムプロパティ "logback.statusListenerClass"

ステータスリスナーを登録するには、システムプロパティの "logback.statusListenerClass" に、リスナークラス名を設定する方法もあります。例を示します。

java -Dlogback.statusListenerClass=ch.qos.logback.core.status.OnConsoleStatusListener ...

logback の配布物にはいくつかのステータスリスナー実装が含まれています。OnConsoleStatusListener は、受け取ったステータスメッセージを、コンソール(例えばSystem.out)に出力します。OnErrorConsoleStatusListener は、受け取ったステータスメッセージをSystem.errに出力します。NopStatusListenerは、受け取ったステータスメッセージをそのまま捨ててしまいます。

設定の途中でステータスリスナーを登録したり、システムプロパティの"logback.statusListenerClass" でステータスリスナーを指定した場合は、メッセージステータスの自動出力(エラー発生時)が無効になるので気をつけてください。つまり、NopStatusListenerを使うように設定すると、内部ステータスの出力を完全に止めることが出来るのです。

java -Dlogback.statusListenerClass=ch.qos.logback.core.status.NopStatusListener ...

logback-classic を止める

logback-classic が使用しているリソースを解放するには、どんな場合でもlogbackコンテキストを停止することをお勧めします。コンテキストを停止すると、コンテキストに定義されたロガーに割り当てられている全てのアペンダーを閉じて、すべてのアクティブなスレッドを停止します。

import org.sflf4j.LoggerFactory;
import ch.qos.logback.classic.LoggerContext;
...

// SLF4J が logback を使うようになっていると想定
LoggerContext loggerContext = (LoggerContext) LoggerFactory.getILoggerFactory();
loggerContext.stop();

Webアプリケーションの場合、logback-classic を停止してリソースを開放するために、ServletContextListenercontextDestroyedメソッドから上記のようなコードを実行します。

設定ファイルの構文

ここまでに、たくさんのわかりやすい例を見てきてお分かりのとおり、logback はコードを再コンパイルすること無く、ロギングの振る舞いを変えることができます。アプリケーションの特定の部分でロギングを無効にする、UNIX Syslog デーモンに直接出力する、データベースに出力する、ログの可視化ツールに出力する、別の logback サーバにロギングイベントを転送する、こういった色んな設定が実に簡単にできるようになっています。ロギングイベントの転送について補足すると、ログの転送はローカルサーバーのポリシーに従って行われます。例えば、ロギングイベントを二つ目のリモートlogbackサーバーに転送する、というようなものです。

このセクションの残りの部分では、設定ファイルの構文を紹介します。

すでに何度か読んできたとおり、logback の設定ファイル構文はとても柔軟になっています。そのため、DTD や XML スキーマによって定義することができません。そうは言っても設定ファイルの基本的な構成要素について説明しないと始まらないでしょう。次のように定義されています。configuration要素は、0以上のappender要素、0以上のlogger要素、1つのroot要素によって構成されています。次の図は、この基本構造を図示したものです。

基本的な構文

タグ名に大文字と小文字のどちらを使うべきなのかわからないときは、キャメルケース規約に従ってもらえれば、ほとんどの場合は合っていると思います。

タグ名の大文字小文字の区別

logback 0.9.17 以降のリリースから、タグ名の大文字小文字は区別しないと明記されるようになりました。例えば、<logger><Logger><LOGGER>はどれもconfiguration要素の子要素として正しく、同じように解釈されます。XMLとしての適格性ルールは有効なままなので、開きタグを<xyz>として書いたら、閉じタグは</xyz>でなければなりません。</XyZ>ではダメです。暗黙的なルールとして、タグ名については最初の一文字以外は大文字小文字を区別するようになっています。したがって、<xyz><Xyz>は同じものとみなされますが、<xYz>はダメです。暗黙のルールとは言っても大体はキャメルケースに従っています。Javaの世界では一般的な規則です。タグが明示的なアクションと暗黙的なアクションのどちらにも関連付けられていると、それを説明するのは決して簡単なことではありません。ですので、XML タグ名の最初の一文字が大文字小文字を区別するかどうかについて言及しておくことはとても重要なのです。

ロガーの設定について、あるいは、logger要素について

ここでは、少なくともレベルの継承基本的な選択ルールについてある程度理解しておかなければなりません。そうでなければ、あなたがエジブト語の学者でも無い限り、logback の設定ファイルはヒエログラフよりも意味の無いものでしかないでしょう。

ロガーはlogger要素で設定します。logger要素には少なくとも一つのname属性が必要です。level属性、そしてtrueまたはfalseを指定できるadditivity属性は任意です。level属性にはTRACE、DEBUG、INFO、WARN、ERROR、ALLまたはOFFのいずれかの文字列を指定できます。大文字小文字は区別しません。大文字小文字を区別しない特別な値としてINHERITEDまたはその同義語としてNULLを指定すると、祖先から継承したロガーのレベルを強制的に使用することができます。ロガーのレベルを設定した後で、祖先ロガーのレベルを継承するべきだったことがわかった場合に便利です。

log4jとは違って、logback-classic はロガーを設定した時に割り当てたどんなアペンダーもくクローズしないし削除もしないので注意してください。

logger要素には任意個のappender-ref要素を含めることができます。参照しているアペンダーがロガーに割り当てられます。log4jとは違って、logback-classic はロガーを設定した時に割り当てたどんなアペンダーもくクローズしないし削除もしないので注意してください。

ルートロガー、あるいは、root要素について

ルートロガーはroot要素で設定します。たった一つ、level属性だけを指定できます。ルートロガーにはadditivity属性などの他の属性を指定することはできません。さらに、ルートロガーの名前は予め"ROOT"になっているので、name属性も指定することはできません。level属性にはTRACE、DEBUG、INFO、WARN、ERROR、ALLまたはOFFのいずれかの文字列を指定できます。大文字小文字は区別しません。ルートロガーにはINHERITEDまたはその同義語であるNULLを指定することはできません。

log4jとは違って、logback-classic はルートロガーを設定した時に割り当てたどんなアペンダーもくクローズしないし削除もしないので注意してください。

logger要素と同じく、root要素には任意個のappender-ref要素を含めることができます。参照しているアペンダーがロガーに割り当てられます。log4jとは違って、logback-classic はルートロガーを設定した時に割り当てたどんなアペンダーもくクローズしないし削除もしないので注意してください。

ロガーあるいはルートロガーにレベルを設定するのは非常に簡単です。例を示します。"chapters.configuration" パッケージのコンポーネントから出力されていたDEBUGメッセージが不要になったとしましょう。次の設定ファイルはそれを実現するための例です。

例:ロガーのレベルを設定する(logback-examples/src/main/java/chapters/configuration/sample2.xml

Groovyで見る
<configuration>

  <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
    <!-- encoders are assigned the type
         ch.qos.logback.classic.encoder.PatternLayoutEncoder by default -->
    <encoder>
      <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
    </encoder>
  </appender>

  <logger name="chapters.configuration" level="INFO"/>

  <!-- 厳密にはここでルートロガーのlevel要素を設定する必要はありません -->
  <!-- デフォルトでDEBUGが指定されるからです       -->
  <root level="DEBUG">		
    <appender-ref ref="STDOUT" />
  </root>  
  
</configuration>

この設定ファイルをMyApp3アプリケーションの引数として渡すと、次のような出力が得られます。

17:34:07.578 [main] INFO  chapters.configuration.MyApp3 - Entering application.
17:34:07.578 [main] INFO  chapters.configuration.MyApp3 - Exiting application.

"chapters.configuration.Foo"のロガーが出力していたDEBUGメッセージが抑止されていることに気づきましたか。気になるならFooクラスを見なおしてください。

あらゆるロガーのレベルを思った通りに設定することができます。次の設定ファイルでは、chapters.configurationロガーのレベルをINFOに設定していますが、同時にchapters.configuration.FooのレベルをDEBUGに設定しています。

例:複数のロガーのレベルを設定する(logback-examples/src/main/java/chapters/configuration/sample3.xml

Groovyで見る
<configuration>

  <appender name="STDOUT"
    class="ch.qos.logback.core.ConsoleAppender">
    <encoder>
      <pattern>
        %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n
     </pattern>
    </encoder>
  </appender>

  <logger name="chapters.configuration" level="INFO" />
  <logger name="chapters.configuration.Foo" level="DEBUG" />

  <root level="DEBUG">
    <appender-ref ref="STDOUT" />
  </root>

</configuration>

MyApp3アプリケーションの引数にこの設定ファイルを指定して実行すると、コンソールには次のように出力されます。

17:39:27.593 [main] INFO chapters.configuration.MyApp3 - Entering application. 17:39:27.593 [main] DEBUG chapters.configuration.Foo - Did it again! 17:39:27.593 [main] INFO chapters.configuration.MyApp3 - Exiting application.

JoranConfigurationsample3.xmlを設定した後のロガーとそのレベルを表にまとめました。

ロガー名 割り当てられたレベル 有効レベル
root DEBUG DEBUG
chapters.configuration INFO INFO
chapters.configuration.MyApp3 null INFO
chapters.configuration.Foo DEBUG DEBUG

これは、MyApp3クラスのINFOレベルのロギング式2つと、FooクラスのdoIt()メソッドにあるDEBUGレベルのロギング式がいずれも有効であるということです。ルートロガーのレベルは常に非null値が指定されること、デフォルトではDEBUGが指定されていることに注意しましょう。

基本的な選択ルールについても考えてみましょう。ロギング要求が呼び出されるかどうかは、アペンダーを割り当てられたロガーに指定されたレベルそのものではなく、ロガーの有効レベルによって決定されるというものです。logback はまず最初にロギング式が有効かどうかを判定します。有効な場合は、ロガーのレベルに依らず、ロガー階層で見つかったアペンダーを呼び出します。設定ファイルsample4.xmlでその点を確認しましょう。

例:ロガーレベルのサンプル(logback-examples/src/main/java/chapters/configuration/sample4.xml

Groovyで見る
<configuration>

  <appender name="STDOUT"
   class="ch.qos.logback.core.ConsoleAppender">
   <encoder>
     <pattern>
        %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n
      </pattern>
    </encoder>
  </appender>

  <logger name="chapters.configuration" level="INFO" />

  <!-- turn OFF all logging (children can override) -->
  <root level="OFF">
    <appender-ref ref="STDOUT" />
  </root>

</configuration>

sample4.xmlを設定した後のロガーとそのレベルを表にまとめました。

ロガー名 割り当てられたレベル 有効レベル
root OFF OFF
chapters.configuration INFO INFO
chapters.configuration.MyApp3 null INFO
chapters.configuration.Foo null INFO

sample4.xmlでは、ConsoleAppenderにSTDOUTという名前を付けて、ルートロガーに割り当てています。ルートロガーのレベルはOFFです。しかし、MyApp3の引数としてsample4.xmlを指定して実行すると次のような出力になってしまいます。

17:52:23.609 [main] INFO chapters.configuration.MyApp3 - Entering application.
17:52:23.609 [main] INFO chapters.configuration.MyApp3 - Exiting application.

つまり、INFOレベルが有効レベルとなっているロガーであるchapters.configuration.MyApp3chapters.configuration.Fooのどちらも、ルートロガーのレベルによる影響を受けていないのです。蛇足ですが、Java実装の中ではchapters.configurationロガーを直接参照しているところはありませんが、実際に存在しています。設定ファイルで宣言されいるからです。

アペンダーの設定

アペンダーの設定はappender要素で行います。この要素にはname属性class属性という二つの必須属性があります。name属性にはアペンダーの名前を、class属性にはアペンダーのクラスの完全名を指定します。appender要素には任意個のlayout要素encoder要素filter要素を含めることができます。appender要素には、これらの一般的な要素とは別に、アペンダークラスのJavaBean規約に従ったプロパティを任意の数だけ含めることができます。logbackのコンポーネントのどんなプロパティでも違和感無く設定できるのは、Joran設定フレームワークの主な長所です。詳しくは後の章で説明します。一般的な構造を図示したのが次の図です。JavaBeanプロパティをサポートしているようには見えないことがわかるでしょう。

アペンダの構文

layout要素にはclass属性が必須です。レイアウトクラスの完全名を指定します。appender要素と同じく、layout要素にはレイアウトクラスのプロパティを含めることができます。レイアウトクラスとしてPatternLayoutを指定するのが一般的なので、その場合class属性は省略できるようになっています。これはデフォルトのクラスマッピングルールで定義されています。

encoder要素にはclass属性が必須です。エンコーダクラスの完全名を指定します。エンコーダクラスとしてPaternLayoutEncoderを指定するのが一般的なので、その場合class属性を省略できるようになっています。これはデフォルトのクラスマッピングルールで定義されています。

複数のアペンダーにログを出力するのも、さまざまなアペンダーを定義してロガーから参照するのも簡単です。設定ファイルの例を示しましょう。

例:複数のロガー(logback-examples/src/main/java/chapters/configuration/multiple.xml

Groovyで見る
<configuration>

  <appender name="FILE" class="ch.qos.logback.core.FileAppender">
    <file>myApp.log</file>

    <encoder>
      <pattern>%date %level [%thread] %logger{10} [%file:%line] %msg%n</pattern>
    </encoder>
  </appender>

  <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
    <encoder>
      <pattern>%msg%n</pattern>
    </encoder>
  </appender>

  <root level="debug">
    <appender-ref ref="FILE" />
    <appender-ref ref="STDOUT" />
  </root>
</configuration>

この設定スクリプトでは、FILESTDOUTというアペンダーを定義しています。FILEmyApp.logというファイルへログを出力するアペンダーです。このアペンダーのエンコーダーはPatternLayoutEncoderで、日付、ログレベル、スレッド名、ロガー名、ソースコードファイル名、ロギング式の書かれたソースコードファイル中の行番号、メッセージ、改行文字を出力します。二つ目のアペンダーはSTDOUT[/0}という名前です。これはコンソールにログを出力するアペンダーです。このアペンダーのエンコーダーは、メッセージと改行文字だけを出力します。

二つのアペンダーはルートロガーに割り当てられています。それぞれappender-ref要素から名前で参照されています。それぞれのアペンダーにエンコーダーを指定していることに気づきましたか。普通エンコーダーは複数のアペンダーから共有できるように設計されていません。レイアウトにも同じことが言えます。このように、logbackの設定ファイルは、エンコーダーやレイアウトを共有するためのいかなる構文上の手段も提供しません。

アペンダーの積み重ね

デフォルトではアペンダーは積み重ねられていきます。つまり、ロガーに割り当てられたアペンダーにログを出力するのとまったく同様に、祖先ロガーに割り当てられたアペンダーにもログが出力されます。従って、同じアペンダーを複数のロガーに割り当てると、ログ出力が重複することになります。

例:アペンダーの重複(logback-examples/src/main/java/chapters/configuration/duplicate.xml

Groovyで見る
<configuration>

  <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
    <encoder>
      <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
    </encoder>
  </appender>

  <logger name="chapters.configuration">
    <appender-ref ref="STDOUT" />
  </logger>

  <root level="debug">
    <appender-ref ref="STDOUT" />
  </root>
</configuration>

MyApp3アプリケーションの引数にduplicate.xmlを指定して実行すると、次のような出力が得られます。

14:25:36.343 [main] INFO chapters.configuration.MyApp3 - Entering application. 14:25:36.343 [main] INFO chapters.configuration.MyApp3 - Entering application. 14:25:36.359 [main] DEBUG chapters.configuration.Foo - Did it again! 14:25:36.359 [main] DEBUG chapters.configuration.Foo - Did it again! 14:25:36.359 [main] INFO chapters.configuration.MyApp3 - Exiting application. 14:25:36.359 [main] INFO chapters.configuration.MyApp3 - Exiting application.

ログ出力の重複には気をつけましょう。STDOUTという名前のアペンダーは、ルートロガーとchapters.configurationロガーに割り当てられています。ルートロガーは全てのロガーの祖先ロガーです。chapters.configurationロガーはchapters.configuration.MyApp3ロガーとchapters.configuration.Fooロガーの親ロガーです。子孫にあたるこれら二つのロガーによるロギング要求は二重に出力されます。なぜなら、STDOUTアペンダーが、chapters.configurationロガーとルートロガーの両方に割り当てられているからです。

アペンダーが積み重ねられるようになっているのは、決して新しい利用者を罠にかけるためではありません。これは非常に便利なフィーチャなのです。例えば、システム中の全てのロガーによるメッセージをコンソールに出力させつつ、特定のロガーのメッセージだけを特別なアペンダーに出力させるように設定することができるのです。

例:複数のアペンダー(logback-examples/src/main/java/chapters/configuration/restricted.xml

Groovyで見る
<configuration>

  <appender name="FILE" class="ch.qos.logback.core.FileAppender">
    <file>myApp.log</file>
    <encoder>
      <pattern>%date %level [%thread] %logger{10} [%file:%line] %msg%n</pattern>
    </encoder>
  </appender>

  <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
    <encoder>
      <pattern>%msg%n</pattern>
    </encoder>
  </appender>

  <logger name="chapters.configuration">
    <appender-ref ref="FILE" />
  </logger>

  <root level="debug">
    <appender-ref ref="STDOUT" />
  </root>
</configuration>

この例では、システム中の全てのロガーのメッセージはコンソールアペンダーに出力されます。そして、chapters.configurationロガーとその子孫ロガーからのロギング要求はmyApp.logファイルにも出力されます。

デフォルトの積み重ねを止める

デフォルトの積み重ねがニーズに合わない場合は、aditivity フラグに false を設定して止めることができます。そうすれば、ロガーツリーのある枝から下の部分では、ツリーの他の部分と違って、ロガーに割り当てられたアペンダーにだけ出力するようになります。

例:aditivity フラグ(logback-examples/src/main/java/chapters/configuration/aditivityFlag.xml

Groovyで見る
<configuration>

  <appender name="FILE" class="ch.qos.logback.core.FileAppender">
    <file>foo.log</file>
    <encoder>
      <pattern>%date %level [%thread] %logger{10} [%file : %line] %msg%n</pattern>
    </encoder>
  </appender>

  <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
    <encoder>
      <pattern>%msg%n</pattern>
    </encoder>
  </appender>

  <logger name="chapters.configuration.Foo" additivity="false">
    <appender-ref ref="FILE" />
  </logger>

  <root level="debug">
    <appender-ref ref="STDOUT" />
  </root>
</configuration>

この例ではchapters.configuration.FooロガーにFILEというアペンダーが割り当てられています。また、chapters.configuration.Fooロガーのaditivityフラグにfalseが指定されているので、ログはFILEアペンダーにだけ出力されて、ログ階層の先祖に割り当てられているアペンダーには出力されません。他のロガーがchapters.configuration.Fooロガーのaditivityフラグの設定に気づくことはありません。MyApp3アプリケーションをaditivityFlag.xmlを引数として実行すると、コンソールにchapters.configuration.MyApp3ロガーからの出力が表示されます。しかし、chapters.configuration.Fooロガーの出力がfoo.log以外に表れることはありません。

コンテキスト名の設定

前の章で述べたように、全てのロガーはロガーコンテキストに割り当てられます。ロガーコンテキストのデフォルトの名前は「default」です。しかし、contextNameディレクティブを使えば別の名前を付けることが出来ます。ロガーコンテキストの名前は一度設定したら変更できないので注意してください。複数のアプリケーションが同じ対象にロギングする場合、アプリケーションを区別しやすくするには、コンテキストに名前を付けるのが簡単でわかりやすい方法です。

例:コンテキスト名を設定して表示する(logback-examples/src/main/java/chapters/configuration/contextName.xml

Groovyで見る
<configuration>
  <contextName>myAppName</contextName>
  <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
    <encoder>
      <pattern>%d %contextName [%t] %level %logger{36} - %msg%n</pattern>
    </encoder>
  </appender>

  <root level="debug">
    <appender-ref ref="STDOUT" />
  </root>
</configuration>

これはロガーコンテキストに名前を付ける例です。レイアウトのパターンに変換指示語としてcontextNameを指定すると、コンテキスト名が出力されます。

変数の置換

このドキュメントの以前のバージョンでは、「変数の置換」ではなく「プロパティの置換」と呼んでいました。どちらも相互に置き換えられるくらい同じ意味の言葉ですが、前者のほうが明確に意味を表していると思います。

よくあるスクリプト言語のように、logbackの設定ファイルは変数を定義して、値と置き換えられるようになっています。変数は設定ファイルの中で定義することもできるし、外部のファイルで定義することもできます。そして外部リソースでも定義することもできるし、何らか計算することさえできます。また、実行時に定義することもできます。

値を指定できるところなら設定ファイル中のどこでも変数を置換することができます。

値を指定できるところなら設定ファイル中のどこでも変数を置換することができます。 変数の置換をする構文はUnixシェルとよく似ています。${から始まり}で終わる文字列は、プロパティの値への参照と見做されます。aNameというプロパティの場合文字列の"${aName}"は、その値に置き換えられます。

変数にはスコープがあります(後で説明します)。

HOSTNAME変数とCONTEXT_NAME変数はよく使われるので、コンテキストスコープを持つ変数として自動的に定義されます。

変数の定義

変数は、設定ファイルや外部のプロパティファイル、あるいは外部リソースを読み込むとき、それぞれで一度だけ定義することができます。歴史的な理由により、propertyXML要素を使って変数を定義することになっていますが、logback 1.0.7以降ではvariable要素を使うことも出来ます。これらの要素は完全に互換性があります。

設定ファイルの先頭で変数を定義する例を見てみましょう。定義された変数は、設定ファイルの後半のほうでログ出力用ファイルの位置を指定するために使われています。

例:変数の置換(logback-examples/src/main/java/chapters/configuration/variableSubstitution1.xml

Groovyで見る
<configuration>

  <property name="USER_HOME" value="/home/sebastien" />

  <appender name="FILE" class="ch.qos.logback.core.FileAppender">
    <file>${USER_HOME}/myApp.log</file>
    <encoder>
      <pattern>%msg%n</pattern>
    </encoder>
  </appender>

  <root level="debug">
    <appender-ref ref="FILE" />
  </root>
</configuration>

次は、システムプロパティを使って同じことをしてみましょう。設定ファイル中にproperty要素が宣言されていないので、logback はそれをシステムプロパティから探します。Javaのシステムプロパティはコマンドラインで次のように指定します。

java -DUSER_HOME="/home/sebastien" MyApp2

例:システム変数の置換(logback-examples/src/main/java/chapters/configuration/variableSubstitution2.xml

Groovyで見る
<configuration>

  <appender name="FILE" class="ch.qos.logback.core.FileAppender">
    <file>${USER_HOME}/myApp.log</file>
    <encoder>
      <pattern>%msg%n</pattern>
    </encoder>
  </appender>

  <root level="debug">
    <appender-ref ref="FILE" />
  </root>
</configuration>

複数の変数が必要なときは、変数の定義だけを含むファイルを別に用意したほうが便利な場合もあります。構成例を示します。

例:別ファイルを使用する変数の置換(logback-examples/src/main/java/chapters/configuration/variableSubstitution3.xml

Groovyで見る
<configuration>

  <property file="src/main/java/chapters/configuration/variables1.properties" />

  <appender name="FILE" class="ch.qos.logback.core.FileAppender">
     <file>${USER_HOME}/myApp.log</file>
     <encoder>
       <pattern>%msg%n</pattern>
     </encoder>
   </appender>

   <root level="debug">
     <appender-ref ref="FILE" />
   </root>
</configuration>

この設定ファイルではvariables1.propertiesという名前のファイルを参照しています。指定されたファイルで定義された変数がローカルスコープに定義されます。variable.propertiesの内容は次のようなものです。

例:変数定義ファイル(logback-examples/src/main/java/chapters/configuration/variables1.properties)
USER_HOME=/home/sebastien

ファイルの代わりにクラスパス上のリソースを指定することもできます。

<configuration>

  <property resource="resource1.properties" />

  <appender name="FILE" class="ch.qos.logback.core.FileAppender">
     <file>${USER_HOME}/myApp.log</file>
     <encoder>
       <pattern>%msg%n</pattern>
     </encoder>
   </appender>

   <root level="debug">
     <appender-ref ref="FILE" />
   </root>
</configuration>

スコープ

ローカルスコープコンテキストスコープシステムスコープのそれぞれで変数を定義することができます。デフォルトのスコープはローカルスコープです。OSの提供する環境変数の読み込みはできますが、書き込みは出来ません。

ローカルスコープ。ローカルスコープで定義された変数は、その変数が定義されている設定ファイルの解釈、実行が終了するまで有効です。当然ながら、設定ファイルを解釈、実行するたびに、ローカルスコープの変数は新しく定義されることになります。

コンテキストスコープ。コンテキストスコープで定義された変数は、コンテキストに登録されます。コンテキストが破棄されるまで、あるいは、コンテキストが初期化されるまで有効です。つまり、一度コンテキストスコープで定義された変数はコンテキストの一部となるのです。ですので、全てのロギングイベントから利用できますし、そのイベントをシリアライズして送信した先のリモートホストでも利用できます。

システムスコープ。システムスコープで定義された変数は、JVMのシステムプロパティに登録されます。JVMが停止するか、初期化されるまで有効です。

最初にローカルスコープで変数を検索します。そしてコンテキストスコープ、システムスコープの順に検索し、最後にOSの環境変数を検索します。

変数を置換する際、最初にローカルスコープで変数を検索します。そしてコンテキストスコープ、システムスコープの順に検索し、最後にOSの環境変数を検索します。

変数のスコープは、property要素define要素insertFromJNDI要素scope属性で指定します。scope属性に指定できるのは、"local"、"context"、"system" のいずれかの文字列です。scope属性を指定しなかった場合、スコープは常に"local"と見做されます。

例:変数をコンテキストスコープで定義する(logback-examples/src/main/java/chapters/configuration/contextScopedVariable.xml

Groovyで見る
<configuration>

  <property scope="context" name="nodeId" value="firstNode" />

  <appender name="FILE" class="ch.qos.logback.core.FileAppender">
    <file>/opt/${nodeId}/myApp.log</file>
    <encoder>
      <pattern>%msg%n</pattern>
    </encoder>
  </appender>

  <root level="debug">
    <appender-ref ref="FILE" />
  </root>
</configuration>

この例ではnodeId変数をコンテキストスコープで定義しています。この変数は全てのロギングイベントで有効であり、シリアライズ化して送信した先のリモートホストでも利用できます。

変数のデフォルト値

特定の状況では、変数が宣言されていなかったり、値がnullの場合、デフォルト値が使えるようになっているほうが望ましいことがあります。Bashと同じく、":-"演算子を使ってデフォルト値を指定することができます。例えば、 aNameという名前の変数が定義されていない場合、"${aName :-golden }"という文字列を変数置換した結果は"goleden"になります。

変数のネスト

変数はネストすることができます。変数の名前として、デフォルト値として、値として、他の変数を指定することができます。

値のネスト

変数の値を定義するとき、他の変数を指定することができます。例えば、宛先のディレクトリだけでなく、ファイル名も変数で指定したい場合、それぞれの変数をまとめた第三の変数"destination"を定義して、それを利用できるのです。プロパティファイルの例を示します。

例:変数のネスト(logback-examples/src/main/java/chapters/configuration/variables2.properties

USER_HOME=/home/sebastien
fileName=myApp.log
destination=${USER_HOME}/${fileName}

"destination"変数の定義で、"USER_HOME"変数と"fileName"変数を組み合わせていることが分かりますか。

例:別のファイルを使用した変数の置換(logback-examples/src/main/java/chapters/configuration/variableSubstitution4.xml)
<configuration>

  <property file="variables2.properties" />

  <appender name="FILE" class="ch.qos.logback.core.FileAppender">
    <file>${destination}</file>
    <encoder>
      <pattern>%msg%n</pattern>
    </encoder>
  </appender>

  <root level="debug">
    <appender-ref ref="FILE" />
  </root>
</configuration>

名前のネスト

変数を参照するとき、変数の名前として、他の変数を指定することができます。たとえば、"uesrid"変数に"alice"という文字列が指定されていることにしましょう。そうすると、"${${userid}.password}"という文字列は、"alice.password"変数を参照することになるのです。

デフォルト値のネスト

変数のデフォルト値に他の変数を指定することができます。たとえば、'id'変数に値が割り当てられておらず、'userid'変数には"alice"という文字列が指定されていることにしましょう。そうすると、"${id:-${userid}}" という文字列は"alice"という文字列になるのです。

HOSTNAME変数

あると便利なので、HOSTNAME変数は自動的にコンテキストスコープに定義されるようになっています。

CONTEXT_NAME変数

変数名のとおり、CONTEXT_NAME変数には、現在のロギングコンテキストの名前が設定されています。

タイムスタンプを設定する

timestamp要素を使うと、現在の日付と時刻に応じた値を持つプロパティを定義することができます。timestamp要素については後続の章で説明します。

実行時に変数を定義する

define要素を使うと動的に変数を定義できます。define要素には二つの必須属性name属性class属性があります。name属性には変数の名前を指定します。class属性にはPropertyDefinerインターフェイスを実装したクラスの完全名を指定します。PropertyDefinergetPropertyValue()メソッドの返す値が、変数の値になります。scope属性スコープを指定することもできます。

以下に例を示します。

<configuration>

  <define name="rootLevel" class="a.class.implementing.PropertyDefiner">
    <shape>round</shape>
    <color>brown</color>
    <size>24</size>
  </define>
 
  <root level="${rootLevel}"/>
</configuration>

この例では、"a.class.implementing.PropertyDefiner"クラスのプロパティとして、shape、color、sizeを指定しています。指定したプロパティのセッターメソッドがPropertyDefinerクラスに定義されていれば、logback は設定ファイルで指定されたプロパティの値をインスタンスに設定することができます。

logback の配布物には現在のところ非常に単純な二つのPropertyDefiner実装クラスが含まれてます。

実装クラス名 説明
FileExistsPropertyDefiner pathプロパティに指定したファイルが存在していれば"true"を、そうでなければ"false"を返します。
ResourceExistsPropertyDefiner クラスパス上に指定されたリソースが存在すれば"true"を、そうでなければ"false"を返します。

設定ファイル内の条件分岐

開発環境、テスト環境、本番環境のような環境ごとに用意されたlogback設定ファイルと格闘しなければならないことがよくあります。これらの設定ファイルの内容はほとんど同じですが、少しだけ異なる箇所があります。同じような設定ファイルの重複を避けるため、logback は設定ファイル中で条件分岐できるようになっています。if要素then要素else要素を使えば、さまざまな環境用のファイルを一つにまとめることができるのです。条件分岐できるようにするため、Jainoライブラリを使用しています。

条件分岐式の一般的な形式を次に示します。

   <!-- if-then form -->
   <if condition="some conditional expression">
    <then>
      ...
    </then>
  </if>
  
  <!-- if-then-else form -->
  <if condition="some conditional expression">
    <then>
      ...
    </then>
    <else>
      ...
    </else>    
  </if>

condition属性に指定するのはJavaの条件式です。コンテキストスコープとシステムスコープの変数が利用できます。property()メソッドか、その省略形であるp()メソッドの引数としてヘンス名を渡すと、その値を文字列として返します。たとえば、"k"という値の変数の値にアクセスするには、property("k")あるいはp("k")という書き方をします。変数"k"が未定義ならproperty()メソッドは空文字列を返します。nullではありません。つまり、nullチェックは不要です。

isDefined()メソッドを使うと、変数が定義されているかどうかを確かめることが出来ます。たとえば、変数"k"が定義されているかどうかをチェックするにはisDefined("k")と書けばよいです。他にも、nullチェックをするためのisNull()メソッドが用意されています。 例: isNull("k")

<configuration debug="true">

  <if condition='property("HOSTNAME").contains("torino")'>
    <then>
      <appender name="CON" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
          <pattern>%d %-5level %logger{35} - %msg %n</pattern>
        </encoder>
      </appender>
      <root>
        <appender-ref ref="CON" />
      </root>
    </then>
  </if>

  <appender name="FILE" class="ch.qos.logback.core.FileAppender">
    <file>${randomOutputDir}/conditional.log</file>
    <encoder>
      <pattern>%d %-5level %logger{35} - %msg %n</pattern>
   </encoder>
  </appender>

  <root level="ERROR">
     <appender-ref ref="FILE" />
  </root>
</configuration>

configuratoin要素の中ならどこにでも条件分岐を置くことが出来ます。if-then-else式をネストすることもできます。しかし、XML文法は非常に面倒ですし、汎用プログラミング言語の基盤には不適切です。したがって、条件分岐が多すぎると、設定ファイルはあっという間に他の人には理解できないものになってしまいます。ましてや、後で自分が読み返しても理解できないでしょう。

JNDIから変数を取得する

特定の状況下では、JNDIに格納されたenvの内容を参照したいこともあるでしょう。insertFromJNDIディレクティブを使うと、JNDIに格納されたenvを取得して、ローカルスコープの変数として取り込むことができます。他の変数と同じく、scope属性に指定した別のスコープに新しい変数を登録することができます。

例:JNDI経由で取得したenvを変数として登録する(logback-examples/src/main/java/chapters/configuration/insertFromJNDI.xml

<configuration>
  <insertFromJNDI env-entry-name="java:comp/env/appName" as="appName" />
  <contextName>${appName}</contextName>

  <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
    <encoder>
      <pattern>%d ${CONTEXT_NAME} %level %msg %logger{50}%n</pattern>
    </encoder>
  </appender>

  <root level="DEBUG">
    <appender-ref ref="CONSOLE" />
  </root>
</configuration>

この例は、env の "java:comp/env/appName"をappNameという名前の変数として登録するものです。contextNameディレクティブで指定しているコンテキスト名には、その前に定義されているinsertFromJNDIディレクティブで登録した変数を使用していることがわかりますか。

ファイルの取り込み

Joranは、設定ファイルの一部として別のファイルを読み込むことができます。そのためには、include要素として宣言します。

例:ファイルの取り込み(logback-examples/src/main/java/chapters/configuration/containingConfig.xml

<configuration>
  <include file="src/main/java/chapters/configuration/includedConfig.xml"/>

  <root level="DEBUG">
    <appender-ref ref="includedConsole" />
  </root>

</configuration>

取り込まれるファイルでは、全ての要素がincluded要素の中に入っていなければなりません。ConsoleAppenderを宣言する例を示します。

例:ファイルの取り込み(logback-examples/src/main/java/chapters/configuration/includedConfig.xml

<included>
  <appender name="includedConsole" class="ch.qos.logback.core.ConsoleAppender">
    <encoder>
      <pattern>"%d - %m%n"</pattern>
    </encoder>
  </appender>
</included>

繰り返しになりますが、取り込まれるファイルでは、included要素が必須です。

include する対象として指定するのは、外部ファイルだけではなく、クラスパス上のリソースでも、URLでもよいです。

指定されたファイルが存在しなかった場合、logback はステータスメッセージを出力してそのことを報告します。取り込もうとしているファイルが任意のものである場合、include要素のoptional属性にtrueを指定しておけば、エラーメッセージを抑止することができます。

<include optional="true" ..../>

コンテキストリスナーを追加する

LoggerContextListenerインターフェイスのインスタンスは、ロガーコンテキストのライフサイクルに関連するイベントを待ち受けます。

JMXConfiguratorLoggerContextListenerインターフェイスの実装の一つです。詳しくは後の章で説明します。

LevelChangePropagator

バージョン0.9.25から、logback-classic の配布物にはLoggerContextListenerインターフェイスの実装であるLevelChangePropagatorが含まれるようになりました。これは、logback-classic のあらゆるロガーのレベルの変更を捉えて、java.util.logging フレームワークに伝播します。ログレベルの変化を伝播するのはコストが高いので、ロギング式を無効にすることで改善されたはずの性能が台無しになってしまいます。LogRecordのインスタンスはロギング式が有効な場合にだけ、SLF4Jを介してlogbackに渡されます。したがって、実際のアプリケーションではjul-to-slf4jブリッジを使うのが合理的です。

contextListener要素にLevelChangePropagatorを登録するには次のようにします。

<configuration debug="true">
  <contextListener class="ch.qos.logback.classic.jul.LevelChangePropagator"/>
  .... 
</configuration>

LevelChangePropagatorのプロパティresetJULを指定すると、j.u.l.loggers に指定されていたレベルがすべて初期化されます。ただし、ハンドラーはそのまま残ります。

<configuration debug="true">
  <contextListener class="ch.qos.logback.classic.jul.LevelChangePropagator">
    <resetJUL>true</resetJUL>
  </contextListener>
  ....
</configuration>