|
2008年01月22日
Logging API vs Log4j
結局のところ Logging API と Log4j のどちらを選べばよいのでしょうか?
主観ですが双方のメリット・デメリットをざっくり比較すると以下のように
なります。
|
Logging API |
Log4j |
|
| JDK標準 |
○ |
× |
Log4jは追加JAR必須 |
| パフォーマンス |
○ |
◎ |
実用レベル |
| 設計の柔軟性 |
○ |
○ |
実用レベル |
| 標準機能 |
△ |
◎ |
LoggingAPIは簡易実装並 |
| 設定ファイル |
△ |
○ |
LoggingAPIは詳細設定不十分 |
| 動作実績 |
○ |
◎ |
実用レベル |
| 下位互換 |
JDK 1.4 |
JDK 1.1 |
1.3以前ならLog4j |
| セキュリティ |
○ |
× |
LoggingAPIはアクセス制御の管理下 |
DB保存 Winイベントログ Unix Syslog メール通知等 |
△ |
○ |
LoggingAPIは要実装 |
◎…優位 (だがオーバースペック気味)、
○…十分に実用レベル
△…使用に難あり、
×…必須要件なら選択不可
使う側にしてみれば必要な実装が一通り揃っておりパフォーマンス的にも有利な
Log4j を選択したいところです。特に大規模なシステム開発では、チームごとに
自分たちのドメインの設定ファイルを作成したり、設定ファイル上でパッケージ
ごとの出力先を分けられる必要が出てきます。また運用要件で syslog
などを使用する必要が出てくるかも知れません。
しかし必要な要件をどちらも満たしているのであれば、実質的にこの選択は JAR
の追加配備に問題があるかどうかで決まります。
- Log4j 推奨
-
JAR の追加配備が容易な場合。具体的にはサーブレットや EJB などのサーバサイド開発。
あるいはイントラネットで Java Web Start
を使用したり、インストーラが整っているリッチクライアント。
技術者であればどちらを選択しても実質的なパフォーマンスはあまり変わらないと
理解できるでしょう。しかしログに関するパフォーマンスは技術者でない立場の人間
でも気になってしまうところです。後々、パフォーマンスに関して痛くもない腹を
探られる心配をするくらいなら (逆にそれを一蹴できる理由がないなら) Log4j
が無難といえます。
- Logging API 推奨
-
JAR の追加配備が難しい場合。不特定多数のブラウザで動くアプレットなどのように
ネットワークダウンロードを伴う場合や、小回りが重視される小さなユーティリティ
ツール、バッチなど。またライセンスやライブラリ管理などの理由でプロジェクトで
不必要なライブラリは極力使わない方針の場合。
また Logging API は Java 標準レベルのアクセス制御が考慮されており、信用の
置けないクラスライブラリが勝手にログハンドラを差し替えてシステムのログを
盗めないような構成もできます (そもそもそんなライブラリを、とも思いますが)。
実際問題、実測的な裏づけもなしに Logging API から Log4j へ差し替えるチューニング
行為は徒労に終わるでしょう。これは余程手を尽くした後の悲劇的なショットガン対処
なのか、あるいは分析者が手を抜いているかのどちらかであり、一般的には使用頻度の高い
SQL や XML 解析処理の一つ二つを再考した方がはるかに効果的です。
とはいえまともな技術者なら根拠もなしに問題ないだろうと結論付けるのも問題ですので、
実質的なパフォーマンスでどの程度の差が出るかを以下簡単にレポートしてみました。
目的
Java 1.4 から標準機能となった Logging API と Jakarta Log4j 双方のロギング機能に
対して、一般的なシステムを想定した場合のパフォーマンス検討ができる程度の
実効値を取得します。なお、これはログ出力のパフォーマンス向上を検討するもの
ではありません。
実験方法
Logging API、Log4j それぞれ 10 秒間で何回のログ出力を実行できるかを計測。
これを交互にそれぞれ 10 回実行して平均をとり 1 回の呼び出しあたりの実行時間
を求める。
双方共に同じ書式のログを出力させるためには Logging API で
Formatter
を実装しなければならないが、その実装のちょっとした差が結果に大きく影響した
ため、同じ実装の
Layout
も作成してフォーマット処理の時間差は考慮から除外することにした。フォーマット
されたログの出力内容は以下の通り。
出力内容
[2008/01/23 06:01:11] - hello, world
[2008/01/23 06:01:11] - hello, world
...
実行は実質的にシングルスレッドで行っているため、並列処理に対する効率は結果に反映されない。
| OS |
Linux 2.6.18.2-34 |
| CPU |
Intel Xeon 2.67GHz SMP |
| HDD |
ATA/100 7,200rpm (論理ボリューム ext3) |
| JVM |
Java SE 6 Sun JRE 1.6.0_04-b12 HotSpot Server VM mixed mode |
| Log4j |
Apache Log4j 1.2.15 |
ここに記載している結果の信頼性や、別の環境での追従試験を行う場合は、以下の
ZIP ファイルに格納されているソースファイルを参照のこと。
結果と考察
まず、ログ出力時に I/O ブロッキングを伴う
FileHandler 、
FileAppender
で実行する。続いてディスク I/O を伴わない NullHandler (自作)、
NullAppender
で実行し、ディスク I/O の影響がどれほどかを算出する。
ディスク I/O あり
torao@sapphire:~/java> java -classpath "log4j-1.2.15.jar:logpfm.jar" biz.moy
o.lab.experiment.logging.Performance --count 10
java version "1.6.0_04"
Java(TM) SE Runtime Environment (build 1.6.0_04-b12)
Java HotSpot(TM) Server VM (build 10.0-b19, mixed mode)
Linux 2.6.18.2-34-default (unknown)
------------
Java Logging API: Java 1.6.0_04 (Sun Microsystems Inc.)
Jakarta Log4j : log4j 1.2.15 ("Apache Software Foundation")
------------
file:Jakarta Log4j 781,047/10.003 = 78,081.276[logs/sec]
file:Java Logging API 719,255/10.005 = 71,889.555[logs/sec]
file:Jakarta Log4j 900,323/10.005 = 89,987.306[logs/sec]
file:Java Logging API 711,957/10.005 = 71,160.120[logs/sec]
file:Jakarta Log4j 869,048/10.005 = 86,861.369[logs/sec]
file:Java Logging API 729,445/10.005 = 72,908.046[logs/sec]
file:Jakarta Log4j 926,571/10.002 = 92,638.572[logs/sec]
file:Java Logging API 707,967/10.014 = 70,697.723[logs/sec]
file:Jakarta Log4j 906,108/10.002 = 90,592.681[logs/sec]
file:Java Logging API 729,666/10.002 = 72,952.010[logs/sec]
file:Jakarta Log4j 912,063/10.004 = 91,169.832[logs/sec]
file:Java Logging API 691,792/10.002 = 69,165.367[logs/sec]
file:Jakarta Log4j 885,245/10.002 = 88,506.799[logs/sec]
file:Java Logging API 681,406/10.001 = 68,133.787[logs/sec]
file:Jakarta Log4j 921,638/10.003 = 92,136.159[logs/sec]
file:Java Logging API 688,199/10.002 = 68,806.139[logs/sec]
file:Jakarta Log4j 921,573/10.001 = 92,148.085[logs/sec]
file:Java Logging API 652,643/10.002 = 65,251.250[logs/sec]
file:Jakarta Log4j 950,436/10.009 = 94,958.138[logs/sec]
file:Java Logging API 652,620/10.002 = 65,248.950[logs/sec]
ディスク I/O なし
torao@sapphire:~/java> java -classpath "log4j-1.2.15.jar:logpfm.jar" biz.moy
o.lab.experiment.logging.Performance --count 10 --null
java version "1.6.0_04"
Java(TM) SE Runtime Environment (build 1.6.0_04-b12)
Java HotSpot(TM) Server VM (build 10.0-b19, mixed mode)
Linux 2.6.18.2-34-default (unknown)
------------
Java Logging API: Java 1.6.0_04 (Sun Microsystems Inc.)
Jakarta Log4j : log4j 1.2.15 ("Apache Software Foundation")
------------
null:Jakarta Log4j 10,686,665/10.005 = 1,068,132.434[logs/sec]
null:Java Logging API 5,232,437/10.013 = 522,564.366[logs/sec]
null:Jakarta Log4j 10,746,895/10.021 = 1,072,437.381[logs/sec]
null:Java Logging API 3,804,484/10.009 = 380,106.304[logs/sec]
null:Jakarta Log4j 11,097,833/10.006 = 1,109,117.829[logs/sec]
null:Java Logging API 3,835,283/10.006 = 383,298.321[logs/sec]
null:Jakarta Log4j 10,976,611/10.006 = 1,097,002.898[logs/sec]
null:Java Logging API 3,805,548/10.005 = 380,364.618[logs/sec]
null:Jakarta Log4j 10,852,740/10.003 = 1,084,948.515[logs/sec]
null:Java Logging API 3,879,895/10.005 = 387,795.602[logs/sec]
null:Jakarta Log4j 11,069,250/10.002 = 1,106,703.659[logs/sec]
null:Java Logging API 3,821,842/10.002 = 382,107.778[logs/sec]
null:Jakarta Log4j 11,009,490/10.001 = 1,100,838.916[logs/sec]
null:Java Logging API 3,883,152/10.002 = 388,237.552[logs/sec]
null:Jakarta Log4j 10,894,213/10.001 = 1,089,312.369[logs/sec]
null:Java Logging API 3,831,653/10.001 = 383,126.987[logs/sec]
null:Jakarta Log4j 11,091,203/10.001 = 1,109,009.399[logs/sec]
null:Java Logging API 3,902,513/10.003 = 390,134.260[logs/sec]
null:Jakarta Log4j 11,128,411/10.005 = 1,112,284.958[logs/sec]
null:Java Logging API 3,856,284/10.002 = 385,551.290[logs/sec]
実行結果から単位秒あたりの実行回数平均と 1 回の呼び出しに要する平均時間は以下の通り。
| |
|
Logging API |
Log4j |
|
Logging API |
Log4j |
|
[call/sec] |
[call/sec] |
|
[μsec/call] |
[μsec/call] |
| |
| |
| ディスク I/O あり |
69,622 |
89,708 |
|
14.363 |
11.147 |
| ディスク I/O なし |
398,340 |
1,094,975 |
|
2.510 |
0.913 |
| |
| ディスク I/O 影響 |
|
|
|
11.853 |
10.234 |
| |
単位時間あたりの実行回数から I/O の影響を受ける状況で 28.9%、全く受けない
状況なら 174.9% ほど Log4j の方が高い
パフォーマンスであることが窺えます。
また、ディスク I/O の割合に対してログ機能の
実行時間は 1~2 割程度でしかないことが分かります。これはパフォーマンス
的な観点でログの選定を思案するより、非同期ログ (Log4j で言うところの
AsyncAppender )
の使用やディスク性能、I/O バッファ調整による効果が大きく出ると予想されます。
どちらが速いかと問われれば今のところ Log4j の優位性は揺るぎありません。
PatternLayout
にしても、
MessageFormat
や
String#format()
はもちろんの事、普通のプログラマが普通に組んだ固定フォーマット用
Layout クラスの方が遅くても私はあまり驚かないでしょう。
しかし、これは所詮μ秒
オーダーの差でしかないということも事実です。具体例を挙げれば、
秒 1,000 回のログを出力する負荷の高いサーバで全体で数ミリ秒、
並列処理の同期化を考慮してもせいぜい数十ミリ秒程度の差にしかならない
でしょう。
一般的には、どちらを使用してもロギング機能自体のパフォーマンスが
システムに与えるインパクトはないと言えるでしょう。むしろログを
出力するために文字列を組み立てる処理
(Object#toString()
など) や、自作の Formatter クラス実装、ファイルシステムの
I/O ブロックといったライブラリ以外の要因の方がはるかに大きいと予想します。
|
|