Log4J徹底解説

概説&サンプル

目次


Log4J はロギングのためのクラス・ライブラリである。開発元は言わずと知れた Apache のサブプロジェクトの一つである。開発ホームページは http://logging.apache.org であり、そこからダウンロードできる。現在のバージョンは 1.2.8 であり、これを使って以下の解説は書いていく。

とはいえ、最新バージョンは 1.2.13 になった。これは 1.3 に向けた 1.2 系の最終リリースである、とアナウンスされている。なので、今回(2006/3)のこのテキストのバージョンアップでは、1.3-alpha-8 に従って、1.3 を先取りしちゃおう!というのが大きな修正になっている。実際、広く使われた 1.2.8 から 1.2.13 までのバージョンアップはバグ修正に近いものばかりである。それでも若干影響のあるものとしては、Category クラスを直接いじらないように、という方針の変更があり、すべて Logger からアクセスするように変更せよ、とのことだ。

しかしその後、1.2系の開発継続というかたちになって、2006年9月にいろいろとバグ対応したり、要望の多い Appender オプションが増えた 1.2.14 がリリースされ、2007年8月には同様に 1.2.15 がリリースされている。この開発一時ストップのウラには、それまでのメイン開発者であり、生みの親である Ceki Gülcü が提唱した 1.3 に向けてのいろいろな仕様が、「それまでのバージョンと互換性が取れない...」という理由で却下されて、Gülcüが Log4j についてヤル気をなくし、新たに自身のプロジェクトとして、Logback を始めちゃった...ということにある。だから、このページでいろいろ解説した 1.3 仕様は、今のところ公式に Apache の Log4j プロジェクトで採用される見込みはないものである(部分的に採り入れているものがないわけでもないが...)。いろいろややこしいな.....というわけで、筆者も腹を決めて、1.2系開発のその後の仕様追加と、Logback の解説とに、整理し直す。

なんちゃって固い書き方で始めたが、元々は Apache とかには関係がなかったのだが、Jakarta に合流することになったプロジェクトのようだ(現在は厳密には Jakarta ではなく、Apache Logging プロジェクトに所属しており、ホームページは http://logging.apache.org にある)。面白いことに、これ自体結構いろいろと派生プロジェクトを持っているし、Jakarta には珍しく Java 以外の言語サポートがめちゃくちゃにある。要するに「汎用ロギングプロジェクト」という風に考えた方が良さそうだ。後で Log4cxx(C++版)Log4php(PHP版)ちょっと見るが、それ以外にも、

とまあ、呆れるくらいに意欲的である。

だから、Log4J とは、「ロギングのためのスキームである!」と言ってもいいんじゃないか、と思う。つまり、これらの言語の如何を問わず、ほぼ共通した設定ファイルによって、同じようにログが取れ、同じようにログを処理する...ということが可能になるのである。

こんな「良い」ものなのだが、一つだけ欠点がある。それはドキュメンテーションが本(「The complete Log4j manual」 by Ceki Gülcü QOS.ch ISBM:2970036908 。執筆時品切れである...多分バージョンアップに書籍がついていかなくなって、重版を止めてるんじゃないかな?)になっていて、有料なのである!(JBossと一緒だな)「ケチぃ..」と言うなかれ。それでも「すぐできるlog4j入門(Shot introduction to log4j)」(Ceki Gülcü)という簡単なマニュアルと、JavaDoc API は無料で付いて来る。だから筆者が漫談をしても許してもらえるのだ(苦笑)。

「The complete Log4j manual」は、どうも現在は、PDF出版のかたちで、著者の(現在 Apacheとは関係のない) Gülcü のサイトで販売する...という少しみっともないことになっているようだ。モチ英文だが、そこのレビューでは極めて評判がイイみたいだ(手前味噌の可能性もあるが、唯一の公式文献だしね....けど、どう考えても新しい仕様とか追加して書き直す可能性だけはゼロだ)。欲しい人は https://www.qos.ch/shop/products/log4j/log4j-Manual.jsp を覗きたまえ。お値段は 19.67ドルだ。

で、Logback の方だが、こっちのバージョンは 0.9.8 でまだβっぽいレビジョンが付いている。とはいえ、開発者が同じなんだから、勿論 Log4j 風のライブラリである。まあ、それだけじゃなくて、用途が明快なライブラリなんだから、誰がやっても似たような設計になるのは当然だな。大きく違うのは、

  1. Log4j は log4j.properties によるプロパティ形式の設定ファイルがあるが、Logback は logback.xml によるXML形式の設定ファイルしかない。時代遅れで階層構造を明示しずらいプロパティ形式設定ファイルは忌避されたようだ。
  2. Log4j は DOM を使った DOMCongirurator で XML形式設定ファイルを解析するが、Logback では JoranCongigurator という SAX ベースのやり方で設定ファイルを解析する。DOM だときっちり形式が DTD で決まるが、Joran の場合はそこらへんかなりいい加減にしてもいい。だから、エレメントの使い方とかずっと自由であるだけはなくて、別ファイルのプロパティを ant みたいに ${プロパティ名} で参照する機能とか、あるいは自分で勝手なエレメントを解析するハンドラを定義して、独自エレメントを使った設定とか(NewRuleという機能だ)、結構強烈なことができる。
  3. Log4j は自身が持つ org.apache.log4j.Logger から Logger インスタンスを取得して使うやり方と、commons-logging 経由で使うやり方と2通りあるが、Logback では commons-logging に相当する org.slf4j.LoggerFactory から org.slf4j.Logger を得るやり方しかない。きっちり分業しているわけだ。で、slf4j 自体実質 Gülcü: 自身が書いているものなので、特に設定しなければデフォルトで Logback を使うようになっている。commons-logging が Log4j を特別扱いしたのと同じような感覚だな。
  4. slf4j は、commons-logging のように、他ロガー実装に対するブリッジの役割りを果たす。少しインターフェイスが commons-logging より増えていて、「書式付きログ取りメソッド(java.textパッケージみたいに引数を展開する)」とか「MDC を扱うメソッドがここにある」とか追加されている。
  5. オールインワンのパッケージである Log4j に対して、Logback は logback-core-0.98.jar, logback-classic-0.9.8.jar, logback-access-0.9.8.jar と3つもパッケージがある。これは、一般的なスタンドアロンプログラムから使う場合と、Tomcat と Jetty の Webアプリコンテナの(デフォルトの)アクセスロガーとして使う場合に、それぞれ利用しやすいようにパッケージが分かれている。実際に違うのは classic パッケージが log4j 風の LoggingEvent をログ実体として使うのに対して、access パッケージは「HTTPアクセス情報」をオブジェクト化した AccessEvent をログ実体として使う。AcessEvent には、HTTPリクエストに対応したログ項目が用意されており、いちいちログメッセージにこれらを構築して入れる必要はなくて、コンテナが AccessEvent を HttpServletRequest, HttpServletResponse から生成し、それをログする用途で、それ用に Appender や Layout が用意されている。
  6. Filter機構は全面的に設計が見直されている。Log4j の Filter は直感的に分かりづらいものだが、Logback のインターフェイスはシンプルだ。それに加えて、Java まがいの判定ロジックを設定ファイルに書ける Evaluator が加わっている。
  7. まともな正規ドキュメントがなかった Log4j とは違い、正規ドキュメントがここらへんを説明していてくれている...(苦笑)さらに皮肉なことに、現在唯一マトモな公式Log4jマニュアルは、喧嘩別れした著者 Gülcü のサイトでPDF通販している...

などなど、Log4j 1.3 で予告されていた機能に加えて更に強烈な機能が新規に追加されている。

じゃ、もう少し具体的に見ていこう。Log4J にせよ Logback にせよ「カテゴリー階層ロガー」と呼ばれるものだ。つまり、ロガーを使う時に、「ロガーに名前を付けて」使うのである。この名前が「カテゴリー」なのだが、それが「階層」になっているのが大きな特徴である。

これは便利である。なぜなら、複数のソース(クラス)で一つのアプリケーションを作るのがフツーだが、その時に「チェックしたいソース(クラス)」だけをロギングするなんてことが出来るのである。とすれば、何かのサブモジュール(複数ファイル・クラス)だけをロギングできたらイイよな...と考えるのは自然で、ここらへんを「カテゴリー階層」化してうまくまとめることが出来るのである。継承モデルのようなもんである。

で、Log4J は「Java のため」の実装である。Java は言うまでもなく、OOPだ。というわけで、別に強制するわけではないが、この「カテゴリー」には、「そのクラスの完全修飾名を使え!」という重要なアドバイスがあるのである。要するに「ロギングのカテゴリー=クラス階層」でやるのがイイよ、と薦めていてくれているのである。なるほど頷けるご意見である。一応ここではそれでやっていこう。現実の開発でも、これが不便なケースって考えにくいしね(逆に言うと、これが「不便!」という時は、クラスの切り分けが間違ってない?)。

Log4J のもう一つの大きな特徴は、ログを出すだけではなくて、「出したログ」をどう処理するのか(ファイルに出すの?コンソールに出すの?etc...)を、設定ファイルで指定できるのである。勿論やろうと思えばこれを固定することも出来ないわけじゃないが、状況に応じてログの出力先を切り替えていく、という風にするのが大人というものだ。「でもぉ、スピードが遅くなるのはヤだから、デバッグコードは製品コードではプリプロセッサで消してるけど...」という方もご安心あれ。Log4J はスピード重視で、製品コードにデバッグコードを残しても、それほど負担にならないようにうまく書かれている。だから、製品コードではデバッグログを出さないようにログレベルを設定すればイイだけだ。

で、面白いことが何か、というと、この「出したログをどう処理するの?」のやり方がいろいろあるのである。この処理を任せるのが Appender 派生クラスである。Log4J の Appender はこんなに沢山ある。

org.apache.log4j.AsyncAppender
非同期出力
org.apache.log4j.ConsoleAppender
stdout,stderr などに出力
org.apache.log4j.DailyRollingFileAppender
ファイル保存。時間単位でログファイルをローテートする実装。
org.apache.log4j.varia.ExternallyRolledFileAppender
ファイル保存。ローテートのタイミングを外部アプリ(Roller)のトリガで行う実装。
org.apache.log4j.FileAppender
ファイル保存。
org.apache.log4j.jdbc.JDBCAppender
データベースに保存。「書き直す」と明言している。1.3 では org.apache.log4j.db.DBAppender というかたちになるようだ。
org.apache.log4j.net.JMSAppender
JMS でメッセージとして送る。
org.apache.log4j.lf5.LF5Appender
GUIのログビューアに出力。
org.apache.log4j.nt.NTEventLogAppender
Windows NTのイベントログに保存。
org.apache.log4j.varia.NullAppender
なにもしない実装。
org.apache.log4j.RollingFileAppender
ファイル保存。ファイルサイズに応じてローテートする。
org.apache.log4j.net.SMTPAppender
メールで送る。
org.apache.log4j.net.SocketAppender
Socket で外部サーバに送る。
org.apache.log4j.net.SocketHubAppender
Socketを使いサーバ動作する。
org.apache.log4j.net.SyslogAppender
外部 Syslogサーバに送る。
org.apache.log4j.net.TelnetAppender
リモートからログを取得するのに使える。

ふう、こんなにあるぞ...テンコ盛りってのはこのことだ。これらの Appender については後で解説するので、ちょっと待って欲しい。とはいえ、Log4J の「面白さ」とは、この多彩な Appender にある、ことは言うまでもないな。

Logback では次の Appender がある。

ch.qos.logback.core.ConsoleAppender
stdout,stderr などに出力
ch.qos.logback.core.read.CyclicBufferAppender
新登場。巡回バッファを使ったメモリAppender実装。
ch.qos.logback.classic.db.DBAppender
ch.qos.logback.access.db.DBAppender
データベースに保存。1.3 で予告されていたヘヴィな実装
ch.qos.logback.core.FileAppender
ファイル保存。
ch.qos.logback.classic.net.JMSQueueAppender
新登場。JMS でメッセージとして送るものだが、Queue を使う実装。
ch.qos.logback.classic.net.JMSTopicAppender
JMS でメッセージとして送る。
ch.qos.logback.core.read.ListAppender
新登場。メモリAppender で、ログを貯めて java.util.List で取得できるようにする。
ch.qos.logback.core.rolling.RollingFileAppender
ファイル保存。いろいろな条件でローテートする。
ch.qos.logback.classic.net.SMTPAppender
ch.qos.logback.access.net.SMTPAppender
メールで送る。
ch.qos.logback.classic.net.SocketAppender
ch.qos.logback.access.net.SocketAppender
Socket で外部サーバに送る。
ch.qos.logback.classic.net.SyslogAppender
外部 Syslogサーバに送る。
ch.qos.logback.core.net.TelnetAppender
リモートからログを取得するのに使える(0.9.8未実装)。

こう見てみると、機能的になくなった Appender は、

AsyncAppender
LF5Appender
NTEventAppender
SocketHubAppender

ということになる。その代わりメモリAppender系のものが増えているし、名前がなくなっていても代替のAppender があったり、分かれていたものが統合されたり、かなり変動がある。

サンプルパッケージ

このページで書かれた Java ソース及び設定ファイルの配布パッケージを作成した。適当にダウンロードして遊んでみてくれたまえ。ただし、ライブラリ類は入っていないので、各自自分で落して入れてくれ。ちなみに、状況の変化を受けて、1.2 版と 1.3 版は分けた。そのうち logback 版もお目見えするので、待っていてほしい。

まあ、筆者はこの手のものにあまり著作権とか主張するのは趣味ではないので、パブリックドメインで結構だ。「使えるぅ!」とか思ったら使ってくれ。また、こういうの自分で改造とかしなきゃ面白くないじゃん。勝手に改造して配布するのも全然OKだ。

で、完全にコンパイル&実行するためには、次のライブラリが必要だ。これらは解凍後の lib ディレクトリに仕込んでくれ。

  1. activation.jar --- もし SMTPAppender を使うのなら(1.2,1.3) →本家
  2. commons-logging.jar -- もし、commons-logging 経由で起動するなら(1.2,1.3) →本家
  3. jakarta-oro-*.jar -- もし、ExpressionRule を使うのなら(1.3) →本家
  4. jbossall-client.jarとか -- もし、JMSAppender を使うのなら(jp.or.nurs.sug.log4j.ejb.MDBLogger のコンパイルにも必要)(1.2,1.3) →本家
    あるいはactivemq-core-4.1.1.jarら ActiveMQ 版でも JMSAppenderは使える(これ以外に結構必要。詳細は「ActiveMQでやってみる」)。→本家
  5. mail.jar --- もし SMTPAppender を使うのなら(1.2,1.3) →本家
  6. rome-0.*.jar -- もし RSSLayout を使ってみるなら(1.2) →本家
  7. 適当な JDBC ドライバ -- もし JDBCAppender, DBAppender を使うのなら(1.2,1.3)
  8. 1.2系 log4j-*.jar(1.2) →本家
  9. 1.3系 log4j-*.jar(log4j-all-1.3alpha-8.jar)(1.3) →本家

配布パッケージの構成はこうだ。

+- build.xml
+- classes
+- commons-logging.property
+- conf --- 設定ファイル多数
+- *.sh (UNIX 用起動ファイル)
+- lib - log4js
+- src - sug - log4j -+- test -- テスト用ドライバ
                      +- ejb -- JMSAppender 関連
                      +- 他いろいろ

で、コンパイルは build.xml があるから、

% ant

で行くものだが、最低でもその前に log4j のライブラリを、lib/log4j.jar という名前でリンクを張っておくのがいい。こんな感じかな。

% cd lib/log4js
% cp /somewhere/log4j-1.2.*.jar .
% cd ..
% ln -sf log4js/log4j-1.2.?.jar log4j.jar

のようにして、lib/log4js にコピーの後、lib/log4j.jar に使うライブラリをリンクを張るようにしてくれ。現状での依存関係は、次の通り。

commons-logging.jar
jp.or.nurs.sug.log4j.test.TestCommon
jbossall-client.jar など JMS ライブラリを含むもの
jp.or.nurs.sug.log4j.client.TopicTester, jp.or.nurs.sug.log4j.ejb.MDBLogger
mail.jar(起動時には activation.jar が必要)
jp.or.nurs.sug.log4j.appender.SMTPAppender
rome-*.jar
jp.or.nurs.sug.log4j.layout.RSSLayout

なので、ライブラリが完全ではない場合には、build.xml を少しいじる必要がある。

<project name="log4j" default="compile" basedir=".">
   <!-- J2EE ライブラリ(JMS)を必要とするソース -->
   <property name="needJ2ee"
      value="sug/log4j/client/TopicTester.java,sug/log4j/ejb/MDBLogger.java" />
   <!-- jakarta-commons-logging ライブラリを必要とするソース -->
   <property name="needCommons"
      value="sug/log4j/test/TestCommons.java" />
   <!-- rome ライブラリを必要とするソース -->
   <property name="needRome"
      value="jp/or/nurs/sug/log4j/layout/RSSLayout.java"/>
   <!-- mail.jar ライブラリを必要とするソース -->
   <property name="needMail"
      value="jp/or/nurs/sug/log4j/appender/SMTPAppender.java"/>

   <!-- ****** 重要! ****** --> 
   <!-- あなたが持っているライブラリに合わせて、環境を設定すること! -->
   <!-- default: あなたの log4j が 1.2 系で、commons-logging も JMS 関連の
     ライブラリも持っていないのなら、セッティングはこうだ -->
   <property name="src.exclusive"
      value="${needJ2ee},${needCommons},${needRome},${needMail}" />

   <!-- もしあなたが commons-logging も JMS ライブラリも rome も 
     mail.jar も持っているのならば、セッティングはこうだ -->
       <property name="src.exclusive" value="" />
   -->

   <!-- で、あなたの log4j 以外のライブラリを lib ディレクトリにコピーし、
     log4j ライブラリは lib/log4js にコピーせよ。
     で、そのコピーしたライブラリに、lib/log4j.jar でリンクを張れ。
     UNIX のコマンドだとこうなる
       % pwd lib
       % ln -s log4js/your-log4j.jar log4j.jar
   -->

そうすれば、ant 一発でコンパイルが通るはずだ。

一番汎用の起動プログラム testLog4j.sh は次のようになっている。

#!/bin/sh

# ヘルプ表示
case "$1" in
    --help|-help|-h)
         echo "testLog4j.sh [conffile] [argments for Log4jTest]"
	 exit;;
esac

# log4j.xml があると指定設定ファイルをコピーしても動かない(log4j.xml優先)
if [ -e ./log4j.xml ]; then
    rm ./log4j.xml
fi


if [ $# -gt 0 ]; then
    case "$1" in
	*xml*)
	    cp $1 ./log4j.xml
	    shift;;
	*proper*)
	    cp $1 ./log4j.properties
	    shift;;
    esac
fi

# クラスパスのセット
classpath=".:classes"
for i in lib/*.jar
do classpath=${classpath}:${i}
   delim=":"
done

# 起動
java -classpath ${classpath} jp.or.nurs.sug.log4j.test.TestLog4j $*

で、設定ファイルの側だが、これはカレントディレクトリに log4j.properties か log4j.xml がある(コピーする)というやり方にした。何でこんなにややこしいやり方をしているのか、というと、要するに「log4j.propertiesがクラスパス中に複数存在したらどうなるか?」ということの結果である。その結果は「クラスパス順にすべて設定ファイルが有効で、結果を上書きする」という、この場合一番まずい結果なのである。

  1. 同一のシンボル(name属性)を使うエレメントがあれば、それはクラスパスで後のものが上書き。
  2. 別なシンボル(name属性)を使うエレメントは併合。

なので、このテストプログラムの場合は、カレントディレクトリに常に log4j.xml or log4j.properties だけがあることにした。これが一番間違いがない。だから、カレントディレクトリにある設定ファイルはテンポラリなものである。テストプログラムは平気でこれを上書きする。

このセッティングが終れば、あとは testLog4j.sh を使い倒していろいろ遊んでみればいい。

# conf/log4j.properties.simple を設定ファイルとする
% ./testLog4j.sh conf/log4j.properties.simple
# conf/log4j.properties.rolling1 を設定ファイルとする
% ./testLog4j.sh conf/log4j.properties.rolling1
# 引数指定をしなければ、前回起動の設定がそのまま
% ./testLog4j.sh

その他、いろいろな実験用インターフェイスが用意してあるので、スクリプトを見て使ってみてくれ。testLog4j.sh が一番汎用のものなので、後はそれに準じて使えばOKだ。



copyright by K.Sugiura, 1996-2006