Windows2000 + Tomcat + Eclipse 連携 Log4j メモ(2003/09/02) 
 

実行した環境

OS

Windows2000

J2SE

1.4.1.01

Tomcat

4.1.12

Eclipse

2.1.1

Log4j ってば?

アプリケーションを作成する場合、必ずと言っていいほど(いや必ずか)トレース情報をファイルやコンソールに出してデバッグやチェックする方法をとります。このさいファイルに出力するのであれば、ファイルI/Oのクラスを使用したりしますが自作するのが面倒であったり、思った通りのパフォーマンスがでないなど。。。。っていうときに Jakartaプロジェクトの Log4j を使用すると非常に便利かつ高パフォーマンスを 期待することができます。

また Log4j は、その機能の多さからファイルに出力するだけではなく、syslog / smtp / tcp / smtp 等などの、考えられる通知系すべてのインタフェースを持ちます。

本家 : Apache Software Foundation Log4j

参考 : すぐできるlog4j入門

まずは、Log4j をインストールしましょう。参考サイトは、Googleで検索すると山のようにでてきますので、そちらを参考にしてください。また Log4j を使用するには、当然ながら JDK / Tomcat のインストールも終わっている事が前提です。

Log4j との連携(Eclipse + Tomcat + α)

Log4j とは直接関係ないですが、世の中の流行(はやり)でサーブレットなんぞを使うって前提で Eclipse に Tomcat プラグインをいれましょう。こっちも参考サイトは、山のようにあるので Googleで検索してインストールしてみましょう。

参考 : Eclipse Plugin

参考 : Log4Jのインストール

参考 : プラグイン

ここで Eclipse + Tomcat + Log4j を使うときに微妙にハマルのが、Tomcat プラグインによって作成する Tomcat プロジェクト(実際プログラムを運用するのに最適なディレクトリ構成)で作成した場合、Log4j のパッケージを正しい位置に置かないとエラーが発生することです。ちなみに以下の感じ。(1〜4 は、一度のみで OK)

1.JakartaプロジェクトのWebサイト等よりLog4Jのライブラリを入手します。

2.ダウンロードした Log4j パッケージを解凍し適当なディレクトリに保存します。例えば "C:\java\jakarta-log4j-1.2.8"と置いてみる。

3.環境変数 CLASSPATH  に Log4j のパッケージ位置を追加します。上記の場合だと "C:\java\jakarta-log4j-1.2.8\dist\lib" です。

4.log4j-1.2.8.jar を "<Tomcatインストールディレクトリ>\common\lib" にコピーします。

5.JARファイルへのパスを通すために、Eclipse を起動して該当プロジェクトを選択し右クリック→「プロパティ」→「Javaのビルド・パス」→「ライブラリ」→「外部JARの追加」で Log4J のJARファイルを追加します。

6.各プロジェクトの "\WEB-INF\lib"以下にも、log4j-1.2.8.jar をコピーします。

Log4j を使ってみる(複数アペンダーの使用)

こちらも参考サイトは、山のようにあるので Googleで検索して勉強してみましょう。

ってアッサリしてますが Log4j は、使用自体は超簡単ですがその特性を良く理解しないと痛い目に合います。それはどういう事かというと。

1.他サーブレットで、ルートカテゴリを共有し使用する場合

2.同一のアペンダーを使用した場合

例えば良くあるサンプルでは、下記の様に Category クラスをインスタンス化しその実体を持ちまわる事によって、複数の実体毎に出力のタイミングを操作するという方法が考えつきますが実際は、上記両方の問題を含む事になるのです。

Category Cat = Category.getInstance(this.getClass());

問題となる判り易いサンプルを作ってみました。

・Category AA と EE は、別々のインスタンスと作成する。

それぞれ AA と EE に対し設定ファイルを読み込み、ルートカテゴリDEBUG出力先を変える。

・this.getClass() は、"Log4j_sample" というクラス名を返すとする。

・AA と EE の debug メソッドに対して処理を行う。

Category AA = Category.getInstance(this.getClass());
PropertyConfigurator.configure("AA.conf");

Category EE = Category.getInstance(this.getClass());
PropertyConfigurator.configure("EE.conf");

AA.debug("AA output");
EE.debug("EE output");

上記のプログラムを動作させると一見問題なさそうに思えますが、複数のスレッドを起動して挙動をみると別々の出力先であるにも関わらず、出力結果が混ざってしまいます。これは何故でしょう。

ここで Log4j の特性をおさらいすると RootCategory は階層構造をもつクラス構成となっています。

上記のプログムラではインスタンスを別々に作成していますが、一番初めに Category クラスのインスタンスが作成された時点で RootCategory 階層構造が作成され、2回目以降の Category クラスのインスタンス作成時には、一番初めに作成された階層構造を元として内部的に複写(常に同一の階層構造)されます。

このため AA と EE のインスタンスで別々の動作を行う様に見える処理も階層構造の DEBUG は、同じ出力先を見ているため log4j の内部ロックが正しく行われない場合に、その制御が正しく行われず、同一階層構造の DEBUG に出力されてしまう結果となってしまうのです。

参考 : 複数のCategory(Logger)を使う

また、もうひとつのハマリとして Log4j は、プログラム単位で RootCategory 階層構造を作らず、動作している Webサーバに1つの階層構造をもつと言うことです。ですので、別のプログラムで同じアペンダーを使用しても平気でしょ?っというのは安易な考えで上記と同じ現象となってしまいます。一番いいのは、異なったアペンダーをそのプログラム単位で持つということです。

解決方法:

いままでの話しで察してもらって判る通り、プログラム単位に異なった Category名を付けることで解決します。これは、自分で Category名を指定し RootCategory 階層構造に追加することによって、唯一それ自身とする方法をとるからです。(←わかりずらいっすよね?)

下記は問題となったサンプルの修正版です。

プログラム

Category AA = Category.getInstance("SAMPLE_AA");
PropertyConfigurator.configure("AA.conf");

Category EE = Category.getInstance("
SAMPLE_EE");
PropertyConfigurator.configure("EE.conf");

AA.debug("AA output");
EE.debug("EE output");

設定ファイル(AA.conf)

# AA.conf
log4j.category.SAMPLE_AA=DEBUG,AA

log4j.appender.AA=org.apache.log4j.RollingFileAppender
log4j.appender.AA.File=c:\\temp\\Sample_AA.log
log4j.appender.AA.MaxFileSize=1000KB
log4j.appender.AA.MaxBackupIndex=3
log4j.appender.AA.layout=org.apache.log4j.PatternLayout
log4j.appender.AA.layout.ConversionPattern=%d [%t] %-5p (%F:%L) - %m%n

設定ファイル(EE.conf)

# AA.conf
log4j.category.SAMPLE_EE=DEBUG,EE

log4j.appender.EE=org.apache.log4j.RollingFileAppender
log4j.appender.EE.File=c:\\temp\\Sample_EE.log
log4j.appender.EE.MaxFileSize=1000KB
log4j.appender.EE.MaxBackupIndex=3
log4j.appender.EE.layout=org.apache.log4j.PatternLayout
log4j.appender.EE.layout.ConversionPattern=%d [%t] %-5p (%F:%L) - %m%n