この記事では、log4j APIについての素晴らしい機能と設計理論について 記述しています。log4jは、オープンソースプロジェクトを元に多数の 著者によって作業が行われています。 ログ文を任意の粒度によって出力させるかどうかの制御を開発者に提供します。 それは、実行時に外部の設定ファイルを使用して完全に変更することができます。 何よりも、log4jはゆるやかな学習曲線を描きます。注意してください: ユーザー・フィードバックから判断して、それはまた、全く中毒性です。
ほとんどのとても大きなアプリケーションでは、それ独自のログ記録または トレースAPIを含んでいます。E.U. SEMPER プロジェクトでは、 独自のトレースAPI書くことを決定しました。それは1996年の初めのことでした。 数え切れないほどの強化と、いくつかの具体化と多くの作業の後で そのAPIは、log4j(Javaのための人気があるログ記録パッケージ)になるために 発展しました。 パッケージはApache Public Licenseの下で 配布されています。 そして、完全なオープン・ソース・ライセンスは open sourceイニシアティブによって認定されています。 最新のlog4jバージョン、全ソースコード、クラスファイル、ドキュメントについては http://jakarta.apache.org/log4j/ で見つけることができます。
独立した作者のIgor Poteryaevは、log4jをPython言語に移植しました。 Bastiaan Bakker は、C++に移植を開始しました。 かれらのプロジェクトは、意外ではないかもしれませんが、 log4p と log4cpp と呼ばれます。
ログ文をコードに挿入することは、コードをデバッグするためのローテクな 方法です。デバッガが必ずしも利用できなかったり、適用できない場合には、 それはまた、唯一の方法であるかもしれません。これは、通常マルチスレッド 化されたアプリケーションや一般に配布されたアプリケーションの場合に あてはまります。
経験によれば、ログ記録は開発サイクルの重要な構成要素であることが 明らかになります。それはいくつかの利点を提供しました。 それは、アプリケーションの実行時の正確な状況を提供することができます。 いったんコードに挿入されれば、出力を記録するための生成は人間の干渉を 必要としません。 さらに、ログ出力は、あとで調査するために永続性のある媒体に 保存することができます。 開発サイクルでの使用に加えて、十分に豊富なログキング パッケージは、また、検査ツールとして見ることができました。
ブライアン・カーニハンと、ロブ・パイクは、かれらの本当に素晴らしい著書 "The Practice of Programming"でこう述べています。
個人的な選択としては、我々は、スタックトレースをしたり1個か2個の変数の 値を取得したりする以上のことはデバッガでは使用しない傾向にあります。ひ とつの理由は、複雑なデータ構造と制御フローの詳細で簡単に見失ってしまう からです。プログラムを通してステップ実行することは、一生懸命に考えて、 出力文を追加し、重要な場所に自己チェックコードを入れたりするよりも有意 義ではないと感じています。文をクリックしながらでは、慎重にある場所から 表示された出力を調べるよりも時間がかかります。その場所を知っていたと仮 定しても、コードの重要な場所にシングルステップすることよりも、出力文を 決定する方が短時間でできます。さらに重要なのは、デバッグ文はプログラム に残り、デバッグセッションは一時的です。
ログ記録は、その欠点があります。 それは、アプリケーションを遅くすることです。 あまりに冗長ならば、それは目に見えないスクロールを引き起こします。 これらの不安を軽減するために、log4jは速く、柔軟にできています。 ログ記録をアプリケーションの主な目的ではなくするために、 log4j APIは簡単で分かりやすく使いやすくなるように務めています。
Log4j は、3つのメイン構成要素があります。 categories, appenders, layoutsです。 これら構成要素の3つのタイプが協調して動作することによって 開発者は、ログメッセージをどの優先度で、 これらのメッセージをどのようにフォーマットして、どこに報告するか の指定を実行時に制御することを可能にします。
単純なSystem.out.println
と比較してログ記録APIの
一番のそして最大の利点は、特定のログ文だけを使用不能にして出力せずに
その他は通常に出力するというものです。この機能は
ログ記録空間(すなわちすべての可能なログ記録文の空間)が
いつくかの開発者による基準によって分類されると想定します。
この所見は、我々にカテゴリーをパッケージの中心概念に選ばせました。
カテゴリー概念は、
org.apache.log4j.Category
クラスで具体化されます。カテゴリーは、エンティティ(実在)という名前を付けられます。
カテゴリー名は、大文字と小文字の区別があります。それらは、階層的な名前付け
規則に従います。
|
例えば、"com.foo"
と名づけられたカテゴリーは、
"com.foo.Bar"
と名づけられたカテゴリーの親になります。
同様に、"java"
は、"java.util"
の親であり、
"java.util.Vector"
の祖先です。この名付方法は、
Java開発者には、おなじみのものでしょう。
rootカテゴリーは、カテゴリー階層の最上位に属します。 それは、2つの事柄において例外的です:
rootカテゴリを取り出すためには、 static Category.getRoot メソッドを呼び出します。その他の全てのカテゴリーは、 static Category.getInstance メソッドで取り出して、インスタンス化されます。 このメソッドは、パラメータとして要求されたカテゴリーの名前をとります。 カテゴリー・クラスの中の基本のメソッドの一部を、以下に示します。
package org.apache.log4j; public Category class { // Creation & retrieval methods: public static Category getRoot(); public static Category getInstance(String name); // printing methods: public void debug(Object message); public void info(Object message); public void warn(Object message); public void error(Object message); // generic printing method: public void log(Priority p, Object message); }
カテゴリーは、優先度が割り当てられるかもしれません。
設定可能な優先度は、
DEBUG,
INFO,
WARN,
ERROR,
FATAL
で、 org.apache.log4j.Priority
クラスで定義されています。この表面上制限されたセットに隠された理論は、
優先度の静的(たとえ大きいとしても)セットよりむしろより
柔軟なカテゴリー階層の使用を促進することです。
しかしPriority
クラスをサブクラス化することによって
独自の優先度を定義するかもしれません。
与えられたカテゴリーが優先度を割り当てられないとき、 それは優先度が割り当てられた最も近い先祖から、優先度を 継承します。より正式に言えば:
|
全てのカテゴリーが優先度の継承を確実にするために、 rootカテゴリーは、常に割り当てられた優先度を持ちます。
以下の4つの表は、さまざまに割り当てられた優先度値と、 上記の規則によって優先度を継承した結果です。
|
| ||||||||||||||||||||||||||||||
|
|
ログ記録の要求は、カテゴリー・インスタンスの出力メソッドのうちの
1つを呼ぶことによってなされます。出力メソッドは
debug
,
info
,
warn
,
error
,
fatal
,
log
です。
定義により、printingメソッドは、ログ記録要求の優先度を決定します。
例えば、c
がカテゴリー・インスタンスのとき、
c.info("..")
文は、優先度 INFOのログ記録要求です。
ログ記録要求は、その優先度が、そのカテゴリーの優先度以上 であれば、有効であると言われます。 そうでなければ、要求は無効であると言われます。 割り当てられた優先度のないカテゴリーは、階層から継承します。 この規則は、以下に要約されます。
|
このルールは、優先度は以下の順番と仮定しています。
DEBUG < INFO < WARN < ERROR < FATAL
。
ここにこのルールの例があります。
// "com.foo" という名前のカテゴリー・インスタンスを得る Category cat = Category.getInstance("com.foo"); // その優先度を INFO に設定する cat.setPriority(Priority.INFO); Category barcat = Category.getInstance("com.foo.Bar"); // この要求は有効、なぜなら WARN >= INFOだから。 cat.warn("Low fuel level."); // この要求は無効、なぜなら DEBUG < INFOだから。 cat.debug("Starting search for nearest gas station."); // "com.foo.Bar"という名前のカテゴリー・インスタンス barcatは // "com.foo"という名前のカテゴリーから優先度を継承するでしょう。 // つまり、以下の要求は有効、なぜなら // INFO >= INFOだから。 barcat.info("Located nearest gas station."); // この要求は無効、なぜならDEBUG < INFOだから。 barcat.debug("Exiting gas station search");
同じ名前でgetInstance
メソッドを呼ぶことは、
常に正確に同じカテゴリー・オブジェクトの参照を返します。
つまりカテゴリーの設定と同一インスタンスを取り出すために
コード中に参照を持って回る必要がないのです。
カテゴリーは、どんな順序でも設定し構築することができます。
特に、カテゴリーは、たとえそれが後で、インスタンス化されたとしても、
その子孫をみつけて、つながります。
log4j環境の設定は、一般的にアプリケーション初期化で行われます。 好ましい方法は、設定ファイルを読むことです。 この方法は、簡単に検討できるでしょう。
Log4jは、ソフトウェアコンポーネントによってカテゴリーに 名前をつけることが簡単にできます。 これは、クラスの完全限定名と等しいカテゴリー名で、 各クラスでカテゴリーを静的にインスタンス化することによって実現することができます。 これは、カテゴリーを定義するのに役に立つ直接の方法です。 ログ出力時にそれを生成したカテゴリーの名前が付くので、 この名付方法は、ログ・メッセージの出所が簡単に確認できます。 しかし、これはカテゴリー名を付けるための方法のひとつの例にすぎません。 Log4jは、設定可能なカテゴリーの集合を制限しません。 開発者は、必要であれば自由にカテゴリー名をつけて構いません。
それにもかかわらず、彼らが定義されるクラス名をカテゴリー名とすることは、 これまで知られている最良の方法であるようです。
カテゴリーに基づいてログ要求を選択的に有効にしたり無効にしたりする能力は 全体のほんの一部です。 log4jでは、複数の宛先にログを出力することができます。 log4jでは、これは、appenderと呼ばれています。 現在appenderは、 コンソール, ファイル, GUI コンポーネント, リモートソケット サーバ, NT イベントログ, リモート・UNIX Syslog デーモンがあります。それはまた、非同期に 記録することが可能です。.
カテゴリーは複数のappenderを参照することができます。addAppender
メソッドは、カテゴリーにappenderを追加します。
与えられたカテゴリーのそれぞる有効なログ要求は、上位の階層においてappenders
と同様にそのカテゴリーにおいて、全てのappendersに転送されます。
言い換えると、appendersはカテゴリー階層から加算的に受け継がれます。
例えば、コンソールappenderがrootカテゴリーに加えられるならば、
全ての有効なログ記要求はコンソールの上で少なくとも出力します。
それに加えて、ファイルappenderがCカテゴリーに加えられるならば、Cと
Cの子供のログ要求は、ファイルとコンソールに出力されます。
appenderの累積がもはや付加されないように、
このデフォルトの挙動を上書きするには、
additivityフラグ設定
をfalse
にすることで可能です。
appender addivility管理ルールついて以下にまとめます。
|
以下の表は例を示します:
カテゴリー名 | Appendersの追加 | Additivity フラグ | 出力先 | コメント |
---|---|---|---|---|
root | A1 | 無指定 | A1 | root カテゴリーは、anonymousだが、Category.getRoot()メソッドからアクセスできる |
x | A-x1, A-x2 | true | A1, A-x1, A-x2 | rootのAppenders が"x"のアダプターに追加される。 |
x.y | なし | true | A1, A-x1, A-x2 | rootと"x"のAppenders。 |
x.y.z | A-xyz1 | true | A1, A-x1, A-x2, A-xyz1 | rootと "x" と "x.y.z"のAppenders。 |
security | A-sec | false | A-sec | additivityフラグがfalse なので、
appenderの追加はされません。
|
security.access | なし | true | A-sec |
"security"の addivilityフラグが false なので
"security"の appenderだけになります。
|
たいてい、ユーザーは出力宛先だけでなく出力フォーマットもカスタマイズと思っています。
これは、appenderとlayoutが連携することによって実現しています。
レイアウトはログ記録要求をユーザー独自のフォーマットに変換することに対して責任があり、
appenderは、その宛先にフォーマットされた出力を送ります。
PatternLayout
(標準のlog4j配布の一部)は、
ユーザーにC言語のprintf
関数に類似した変換パターンによって、
出力フォーマットを指定させます。
例えば、変換パターン"%r [%t] %-5p %c - %m%n" を PatternLayoutに適用すると、 このような出力となります:
176 [main] INFO org.foo.Bar - Located nearest gas station.
最初のフィールドは、プログラムが開始してからの経過時間を ミリ秒単位で表します。 2番目のフィールドは、ログ要求をしているスレッドです。 3番目のフィールドは、ログ文の優先度です。 4番目のフィールドは、ログ要求に関連するカテゴリー名です。 『-』の後のテキストは、ログ文のメッセージです。
重要なのは、log4jは、ログメッセージをユーザ指定の基準の内容
によって変換します。
たとえば、あなたの現在のプロジェクトで、
しばしば使用されるオブジェクトタイプのOranges
ログが必要
とした場合、
orangeをログに記録する必要があるときにはいつでも
OrangeRenderer
を呼び出して登録することができます。
Object renderers は、 ObjectRenderer インターフェースを実装しなければなりません。
ログ要求をアプリケーション・コードに挿入することは、計画と労力のかなりの量を必要とします。 計測によると、およそ4パーセントのコードがログを記録するために専用であることを示します。 従って、中程度の大きさのアプリケーションさえ、ログ記録の何千ものステートメントを 彼らのコードの範囲内ではめ込んでおきます。 彼らの番号を与えられて、手でそれらを修正する必要なしでこれらのログ文を管理することは、 避けられなくなります。
log4j環境は、プログラム的に完全に設定できます。 しかし、設定ファイルを使用してlog4jを設定するほうが、はるかに柔軟性があります。 現在、設定ファイルは、XMLまたはJavaプロパティ(key=value)形式で書く ことができます。
架空のアプリケーションMyApp
を用いて
log4jを使う方法を示します。
import com.foo.Bar; // log4j クラスをインポートします. import org.apache.log4j.Category; import org.apache.log4j.BasicConfigurator; public class MyApp { // "MyApp"という名前のCategory のインスタンスの参照をするために // static カテゴリー変数を定義します。 static Category cat = Category.getInstance(MyApp.class.getName()); public static void main(String[] args) { // コンソールにログを出力する簡単な設定をします BasicConfigurator.configure(); cat.info("Entering application."); Bar bar = new Bar(); bar.doIt(); cat.info("Exiting application."); } }
MyApp
は、log4jに関連したクラスをインポートすることことから始めます。
それからクラスの完全限定名になるMyApp
名で静的カテゴリー変数を定義します。
MyApp
は、com.foo
パッケージで定義される
Bar
クラスで使用されます。
package com.foo; import org.apache.log4j.Category; public class Bar { static Category cat = Category.getInstance(Bar.class.getName()); public void doIt() { cat.debug("Did it again!"); } }
BasicConfigurator.configure メソッドの呼び出しで、かなり単純なlog4jの用意します。 このメソッドは、コンソール上に出力するConsoleAppenderをrootカテゴリーに 加えようにあらかじめ組み込まれています。 出力は、"%-4r [%t] %-5p %c %x - %m%n"パターンにセットされる PatternLayoutを使ってフォーマットされます。
注意: デフォルトでは、rootカテゴリーの優先度は、
Priority.DEBUG
に割り当てられています。
MyApp の出力は以下のようになります。
0 [main] INFO MyApp - Entering application. 36 [main] DEBUG com.foo.Bar - Did it again! 51 [main] INFO MyApp - Exiting application.
以下の図は、BasicConfigurator.configure
メソッドが
呼ばれた直後のMyApp
のオブジェクト・ダイヤグラムを
示しています。
MyApp
クラスは、BasicConfigurator.configure
メソッドを呼び出すことによってlog4jを設定します。
他のクラスは、org.apache.log4j.Category
クラスをインポートして、
彼らが使いたいカテゴリーを取り出して、せっせとログ記録する必要があるだけです。
前の例は、常に同じログ情報を出力します。
幸いにも、ログ出力を実行時に制御することができるように、
MyApp
を修正することは、簡単です。
わずかに修正されたバージョンは、ここにあります。
import com.foo.Bar; import org.apache.log4j.Category; import org.apache.log4j.PropertyConfigurator; public class MyApp { static Category cat = Category.getInstance(MyApp.class.getName()); public static void main(String[] args) { // BasicConfigurator は、 PropertyConfigurator に置き換えられました。 PropertyConfigurator.configure(args[0]); cat.info("Entering application."); Bar bar = new Bar(); bar.doIt(); cat.info("Exiting application."); } }
MyApp
のこのバージョンは設定ファイルを解析して
それに応じてログ記録を設定するために
PropertyConfigurator
を実行します。
前のBasicConfigurator
の例を元にして、
結果として、正確に同じ出力となる設定ファイルのサンプルは、これです。
# rootカテゴリーの優先度をDEBUGにして、appender A1を加えます。 log4j.rootCategory=DEBUG, A1 # A1 にConsoleAppenderをセットします log4j.appender.A1=org.apache.log4j.ConsoleAppender # A1 は、PatternLayout を使用します。 log4j.appender.A1.layout=org.apache.log4j.PatternLayout log4j.appender.A1.layout.ConversionPattern=%-4r [%t] %-5p %c %x - %m%n
あなたが例をコピー&ペーストするならば、その結果、若干の行で 行末に空白を含むかもしれないことに注意してください。 これらの後ろの空白は、きれいに整えられなくて、 PropertyConfiguratorによって解釈されません。 あなたがこの記事を読む頃には、問題は訂正されなければなりません。
com.foo
パッケージに属しているコンポーネントの出力を見ることに、
もはや興味がないと想定します。
以下の設定ファイルは、これを達成するための1つの方法を示します。
log4j.rootCategory=DEBUG, A1 log4j.appender.A1=org.apache.log4j.ConsoleAppender log4j.appender.A1.layout=org.apache.log4j.PatternLayout # ISO 8601形式の日付を出力します log4j.appender.A1.layout.ConversionPattern=%d [%t] %-5p %c - %m%n # パッケージ com.foo上で、優先度WARNのメッセージだけ出力します。 log4j.category.com.foo=WARN
The output of MyApp
configured with this file is shown below.
このファイルによって設定されたMyApp
の出力は以下のようになります。
2000-09-07 14:07:41,508 [main] INFO MyApp - Entering application. 2000-09-07 14:07:41,529 [main] INFO MyApp - Exiting application.
カテゴリーcom.foo.Bar
には割り当てられた優先度がないので、
com.foo
から優先度を継承します。
そして、それは設定ファイルにおいてWARNと設定されていました。
Bar.doIt
メソッドからのログ文は優先度DEBUGを持ちます、
これはカテゴリー優先度 WARNより低いです。
従って、doIt
のログ要求は、抑制されます。
ここに複数appenderを使用した他の設定ファイル例があります。
log4j.rootCategory=debug, stdout, R log4j.appender.stdout=org.apache.log4j.ConsoleAppender log4j.appender.stdout.layout=org.apache.log4j.PatternLayout # 呼び出したファイル名と行番号をパターンへ出力します log4j.appender.stdout.layout.ConversionPattern=%5p [%t] (%F:%L) - %m%n log4j.appender.R=org.apache.log4j.RollingFileAppender log4j.appender.R.File=example.log log4j.appender.R.MaxFileSize=100KB # バックアップファイルを1つ保存します log4j.appender.R.MaxBackupIndex=1 log4j.appender.R.layout=org.apache.log4j.PatternLayout log4j.appender.R.layout.ConversionPattern=%p %t %c - %m%n
この設定ファイルで拡張されたMyAppが呼び出されるとコンソールへの 出力は以下のようになります。
INFO [main] (MyApp2.java:12) - Entering application. DEBUG [main] (Bar.java:8) - Doing it again! INFO [main] (MyApp2.java:15) - Exiting application.
さらに、rootカテゴリーには、2個めのappenderが割り当てられているので、
出力はexample.log
ファイルにも向けられます。
そのファイルのサイズが100KBに達するとき、このファイルは切り替えられます。
切り替えが起こると、example.log
の古いバージョンは
example.log.1
に自動的に移動されます。
これらの異なるログ記録の挙動を得るために、我々がコードの再コンパイルを
必要としなかったということに注意してください。
また、これにより以下のことが簡単にできるようになります。
UNIX Syslogデーモンにログを記録
し、全てのcom.foo
出力をNTイベントログへ送り、
またはログ・イベントを第二のlog4jサーバーに転送することによって
例えばローカル・サーバー方針によってログ記録するリモートlog4j
サーバーにイベントを記録するなど。
log4jライブラリは、その環境について何の仮定もしません。
特に、デフォルトのlog4j appendersは、ありません。
しかし特定のよく定義された状況の下で、
デフォルトの初期化は、アプリケーションへの正確なエントリ点が
実行時環境に依存するような環境において、非常に役立ちます。
例えば、同じアプリケーションが、アプレットとして、
独立型アプリケーションとしてまたはWebサーバの管理下のservlet
として使うことができます。
正確なデフォルトの初期化アルゴリズムは、次のように定義されます:
Loader.getResouce()
に場所を検索するためのリストについてのもうすこし複雑なことが記述していあるので
参照しくてださい。クラスローダーを使用するクラスパスから URLフォーマットは重要です。そのreference部分は、
設定のクラス名として使用されます。たとえば、以下のコマンドラインで
あなたのアプリケーションを実行したとすると
URLに参照部分が無い場合には、PropertyConfigurator
がURLを解析します。しかし、URLの最後が".xml"拡張子の場合には、
DOMConfigurator
がURLを解析するために使用されます。
たいていの、現実のシステムは、同時に複数のクライアントを扱わなければ
なりません。
そのようなシステムの典型的なマルチスレッド化された実装では、
異なるスレッドは、異なるクライアントを取り扱います。
ロギングは、特に複雑に配布されたアプリケーションをたどって、
デバッグするのにかなり適しています。
他のものから1台のクライアントのログ記録の出力に差異を認める共通の方法は、
各々のクライアントのために新しい別々のカテゴリーをインスタンス化することです。
これは、カテゴリーの増殖を促進して、ログの管理オーバーヘッドを増加します。 より簡単な方法は、同じクライアント相互作用から
始められる各ログ要求対して、独自の印をつけることです。
R. Martin, D. Riehle, F. Buschmann により編集された
Pattern Languages of Program Design 3(Addison-Wesley, 1997)本で
ニール・ハリソンは、「Patterns for Logging Diagnostic Messages」で
このメソッドを説明しました。 各要求に独自の印を付けるために、
ユーザーは文脈上の情報をNDC(Nested Diagnostic Contextの省略形)に
押し込みます。
NDCクラスは、下記に示します。
NDCは、文脈上の情報のスタックとしてスレッド毎に管理されます。
この点を示すために、多数のクライアントに内容を提供する
servletの例をとらせてください。
servletは、他のコードを実行する前に要求のまさしくその始めで、NDCを構築することができます。
文脈上の情報は、クライアントのホスト名や、その他の要求に固有なものとなります。
そして、情報は一般的にクッキーに含まれます。
それゆえに、たとえservletが同時に提供している複数のクライアントがあるとしても、
各クライアント要求は異なるNDCスタックを持つので、すなわち同じカテゴリーに属して、
同じコードによって始められるログは、まだ識別することができます。
これをクライアントの要求の間、実行する全てのコードに、新たにインスタンス化
されたカテゴリーを持ちまわる複雑さと比較してみてください。 それにもかかわらず、複雑なアプリケーション(例えば仮想ホスティングWebサーバー
のような)場合には、発行された要求とソフトウェア構成要素に依存しつつ
仮想ホストに依存した異なるログを記録しなればなりません。
最近のlog4jリリースは、複数の階層木をサポートします。
この拡張は、カテゴリー階層のそれ自身のコピーを所有するために
各々の仮想ホストを許します。 ログ記録に対するたびたび引用された議論のうちの1つは、その計算コストです。
適度に大きさを設定されたアプリケーションさえ何千ものログ要求を
引き起こすことができるので、これは正当な関心です。
多くの努力は、ログのパフォーマンスとチューニングと計測に費やされました。
log4jは、速度と柔軟性を主張しており、速度が第一で柔軟性が第二です。 ユーザは以下のパフォーマンス問題について注意すべきです。デフォルト初期化手続き
Category
クラスの
static イニシャライザは、自動的にlog4jを構成しようとすることになります。
Java言語は、クラスをメモリにロードときにクラスの静的初期化が
一度だけ呼ばることを保証します。
(異なるクラスローダーは、同じクラスのコピーをまた別にロードするかもしれません。)
resource
文字列変数にセットします。もしシステム・プロパティ
が定義されていなければ、resource
は、
"log4j.properties"にセットされます。
resource
変数をURLに変換しようとします。
MalformedURLException
が発生するなどして、
resource
変数が、URLに変換できない場合には、
org.apache.log4j.helpers.Loader.getResource(resource, Category.class)
を呼び出すことによってクラスパスからresource
を検索します。
"log4j.properties"は、URLでは無いのでこの場合は失敗します。
resource
を検索した
結果成功すれば、well-formed URLとなります。
java -Dlog4j.configuration=file:/temp/myconfig.xyz#com.myCompany.myConfigurator
file:/tmp/myconfig.xyz
によって参照されるファイルを
解釈してcom.myCompany.myConfigurator
の新しいインスタンス
によってlog4jは、設定されます。あなたが指定したコンフィギュレータは、Configurator
インタフェースを実装しなければなりません。
ネスト化診断コンテキスト
public class NDC {
// 診断を出力するときに使用する
public static String get();
// NDCからコンテキストの一番上を削除
public static String pop();
// 現在のスレッドに診断コンテキストを追加
public static void push(String message);
// このスレッドから診断コンテキストを削除
public static void remove();
}
org.apache.log4j.NDC
クラスの全てのメソッドがstaticであることに注意してください。
NDCの出力がオンにされると、ログ要求がなされるたびに、
適当なlog4j構成要素はログ出力で現在のスレッドの全ての NDCスタック
が含まれます。
これはユーザーの干渉なしでされます。
そして、その人はコードにおいてよく定義された2、3の点でpush
と
pop
メソッドを使用することのそばにNDCで正しい情報を置くことだけに対して責任があります。
対照的に、クライアント毎のカテゴリー方法は、コードにおいて大規模な変更を
必要とします。パフォーマンス
ログの 全体をオフにしたり、優先度の設定 をしたときには、ログ要求のコストは、メソッド起動および整数比較からなります。 233 MHz Pentium IIマシンでは、このコストはだいたい5〜50ナノ秒の範囲です。
しかし、メソッドの起動は、パラメータ構築の「隠された」コストを含みます。
例えば、cat
のいくつかのカテゴリーで、このように書くと
cat.debug("Entry number: " + i + " is " + String.valueOf(entry[i]));
メッセージ・パラメータを構築することのコストを負います。すなわち
メッセージが記録されるかどうかにもかかわらず、
整数i
とentry[i]
を文字列に変換して、
中間文字列を連結します。
パラメータ構築のこのコストは全く高くつきます、
そして、それは関係しているパラメータのサイズに依存します。
To avoid the parameter construction cost write:
パラメータ構築のコストを避けるためには、このように書いてください:
if(cat.isDebugEnabled() { cat.debug("Entry number: " + i + " is " + String.valueOf(entry[i])); }
debuggingが無効であるならば、これはパラメータ構築のコストがかかりません。
一方、カテゴリーのdebugが有効にされるならば、
それはカテゴリーが有効かどうか評価するコストが二回かかります:
debugEnabled
で1回とdebug
で1回です。
カテゴリーを評価することはそれが実際にログにかかる時間のおよそ1%なので、
これは微々たるオーバーヘッドです。
log4jでは、ログ要求は、Category クラスのインスタンスで行われます。 Categoryは、クラスでありインタフェースではありません。 これは、いくらかの柔軟性を犠牲にしても計測可能なほどにメソッド起動のコストを下げます
とあるユーザーは、すべてのログ文を外してコンパイルするために コンパイル時や、プリプロセスの手段を使います。 これは、ロギングに対して完全にパフォーマンス効率が上がります。 しかし、結果としてアプリケーションバイナリは、何もログ文を含まないので、 そのバイナリでは、二度とログ記録をオンにすることができません。 私の意見では、これは小さいパフォーマンス利益と引きかえに支払う代償は あまりにも大きいです。
これは、基本的にカテゴリー階層を歩くパフォーマンスです。 ログ記録がオンにされるとき、log4jはまだ要求カテゴリーの優先度でログ要求の優先度を比較する必要があります。 しかし、カテゴリーには割り当てられた優先度がないかもしれません; 彼らは、カテゴリー階層からそれらを継承することができます。 このように、優先度を継承する前に、カテゴリーはその先祖を捜す必要があるかもしれません。
できるだけ速くこの階層散歩を行う重大な努力が行われました。
例えば、子供カテゴリーは、彼らの現存の先祖だけとつながります。
以前に示されるBasicConfigurator
例では、
com.foo.Bar
という名前をつけられるカテゴリーは直接rootカテゴリーに結ばれます。
そして、それによって存在しないcom
またはcom.foo
カテゴリーを回避します。
特に「まばらな」階層において、これはかなり散歩の速度を改善します。
階層を歩くことの典型的なコストは、再び233MHzのPentium IIマシンでは、 5〜15マイクロ秒の範囲です。
これは、出力されるログをフォーマットして、それを目標の宛先に送ることのコストです。 ここでは再び、重大な努力は、レイアウト(フォーマッタ)を、できるだけ速く実行させることでした。 同じことは、appendersにあてはまります。 実際にログ記録することの典型的なコストは、およそ100〜300マイクロ秒です。 実際の説明は、org.apache.log4.performance.Logging を参照してください。
log4jは多くの機能を持ちますが、その最初の設計目標は速度でした。
いくつかのlog4j構成要素は、パフォーマンスを向上させるために何度も書き直されました。
それにもかかわらず、貢献者はしばしば新しい最適化を考え出します。
SimpleLayout で
設定されたパフォーマンス・テストでlog4jがSystem.out.println
と同じくらい速くログ記録することを示したということを、あなたはそれを知っていて
うれしくなる筈です。
Sunは、Javaのためにログ記録APIを定義するためにコミュニティ・プロセス(JSR47)を開始しました。 このAPIは、JDK version 1.4で要求されています。 JSR47仕様は 最近、公開レビューの準備ができるようになりました。
JSR47 APIとlog4jは、アーキテクチャのレベルで全く類似しています。 JSR47 APIは、階層的な名前空間(log4jの中心機能のうちの1つ)をサポートします。 その一方で、log4jはJSR47で欠けている多くの便利な機能があります。 たとえば、log4jはappenderの継承をサポートしていますが、JSR47は サポートしていません。また、log4jではカテゴリーの定義順序は自由ですが、 JSR47では子を定義するよりも前にまず親から定義しなければなりません。
ときどき、ユーザは、log4jは "標準準拠"かどうか聞いてきます。 log4jは、プロトコルやpluggable-APIとして準拠することはできますが、 実装として準拠することは不可能です。 これは実装かそうでないかの違いです。 JSR47は、ログAPIの骨組みの実装なのです。 それは、単なるインタフェースや通信プロトコルではありません。 JSR47 API以外のAPIは、JSR47 APIに準拠ることにはなりません。
私の党派の意見において、log4jの後に勢いを与えられて、 JSR47がリリースされる頃には、廃止されていそうです。 log4jは、委員会以外の、毎日に使う人々によって、書かれています。
Log4jは、Javaで記述される人気があるログ記録パッケージです。 その特徴的な機能のうちの1つは、カテゴリーの中の継承の概念です。 カテゴリー階層を使用することで、どのログ文が任意の粒度で出力するか制御 することを可能にします。 これは、ログ出力の量を減らして、ログのコストを最小にするのに役立ちます。
log4j APIの利点のうちの1つは、その管理能力です。 一旦ログ文がコードに挿入されれば、それらは設定ファイルで制御することができます。 それらは、選択的に有効/無効にすることができ、ユーザーに選ばれたフォーマットで 異なる複数の出力目標に向けることができます。 log4jパッケージは、出荷されたコードに重いパフォーマンスコストを負わせることなく ログ文を残すことができるように設計されています。
記事のレビューに関してN. Asokanに多大な感謝をします。 彼は、また、カテゴリー概念の提唱者のうちのひとりです。 私は、私がこの記事を書くのを奨励するためにPopularPower のNelson Minarに感謝します。 彼は、また、この記事に多くの役に立つ提案と訂正をしてくれました。
Log4jは、努力の集大成です。 プロジェクトに貢献した全ての作者へ特に感謝します。 例外なく、パッケージの中の最高の機能は、全てをユーザー・コミュニティで 開始されました。
[訳注: これは熊坂祐二が翻訳しました。 日本語訳に対するコメントは、jajakarta-report@nekoyanagi.com宛に送って下さい。]