私は人生で初めて、オープンソースになるJava APIを書いている立場にいます。うまくいけば、他の多くのプロジェクトに含まれることになります。
ロギングのために、私(そして実際に私が作業している人々)は常にJUL(java.util.logging)を使用しており、JULで問題が発生することはありません。しかし今、私は自分のAPI開発のために何をすべきかをより詳細に理解する必要があります。私はこれについていくつかの調査を行いましたが、得た情報を使用して、さらに混乱します。したがって、この投稿。
私はJUL出身なので、それに偏っています。残りについての私の知識はそれほど大きくありません。
私が行った調査から、人々がJULを嫌うこれらの理由を思いつきました。
「私は日が7月にリリースし、長い前に、Javaで開発を開始し、私がロギングフレームワーク-Xを続けることではなく、何か新しいことを学ぶことのためだけに簡単でした」。うーん。私は冗談ではありません、これは実際に人々が言うことです。この議論があれば、全員がCOBOLを実行できます。(私は確かにこれが怠惰な男であることと自分自身を関連付けることができます)
「JULのログレベルの名前は好きではありません。」真剣に、これは新しい依存関係を導入する理由には不十分です。
「JULからの出力の標準形式が気に入らない」。うーん。これは単なる設定です。コード的に何もする必要はありません。(確かに、昔は、正しいものにするために独自のFormatterクラスを作成する必要があったかもしれません)。
「logging-framework-Xも使用する他のライブラリを使用しているので、それを使用する方が簡単だと思いました」。これは循環論ですよね?「みんな」がJULではなくlogging-framework-Xを使用するのはなぜですか?
「他の誰もがlogging-framework-Xを使用しています」。これは、上記の特別なケースです。大多数が常に正しいとは限りません。
だから本当の大きな問題はなぜJULではないのですか?。私が見逃したものは何ですか?ロギングファサード(SLF4J、JCL)の存在理由は、複数のロギング実装が歴史的に存在しており、その理由は、私が見る限り、JULの前の時代に本当に戻っているためです。JULが完璧だったとしたら、ファサードのロギングは存在しなかったでしょうか。問題をさらに混乱させるのは、JUL自体がある程度ファサードであり、ハンドラー、フォーマッター、さらにはLogManagerさえもスワップできるようにすることです。
同じこと(ロギング)を行う複数の方法を採用するのではなく、そもそもなぜそれらが必要だったのか疑問に思わないでしょうか。(そして、それらの理由がまだ存在するかどうかを確認してください)
わかりました、これまでのところ私の研究により、JULの実際の問題であると思われるいくつかのことがわかりました。
パフォーマンス。SLF4Jのパフォーマンスは他より優れていると言う人もいます。これは時期尚早の最適化の場合のようです。1秒あたり数百メガバイトをログに記録する必要がある場合、とにかく正しいパスにいるとは思えません。JULも進化しており、Java 1.4で行ったテストは真実ではなくなっている可能性があります。あなたはそれについてここで読むことができ、この修正はそれをJava 7に取り入れました。多くの人はロギングメソッドでの文字列連結のオーバーヘッドについても話します。ただし、テンプレートベースのロギングはこのコストを回避し、JULにも存在します。個人的には、テンプレートベースのロギングを実際に書くことはありません。そのために怠惰です。たとえば、JULでこれを行う場合:
log.finest("Lookup request from username=" + username + ", valueX=" + valueX + ", valueY=" + valueY));
私のIDEは私に警告し、それを次のように変更するように許可を求めます。
log.log(Level.FINEST, "Lookup request from username={0}, valueX={1}, valueY={2}", new Object[]{username, valueX, valueY});
..もちろんそれを受け入れます。許可が与えられました!ご協力ありがとうございました。
そのため、私は実際にそのようなステートメントを自分で書くことはありません。それはIDEによって行われます。
パフォーマンスの問題について結論すると、JULのパフォーマンスが競合他社に比べて良くないことを示唆するものは何も見つかりませんでした。
クラスパスからの設定。デフォルトのJULは、クラスパスから構成ファイルをロードできません。数行のコード、それはそうするために。これが煩わしい理由はわかりますが、解決策は短くて簡単です。
出力ハンドラーの可用性。JULには、コンソール、ファイルストリーム、ソケット、メモリの5つの出力ハンドラーが付属しています。これらは拡張することも、新しいものを書くこともできます。これは、たとえば、UNIX / Linux SyslogおよびWindowsイベントログへの書き込みである可能性があります。私は個人的にこの要件があったことも、使用されたこともありませんでしたが、なぜそれが便利な機能であるのかについては確かに関係があります。たとえば、LogbackにはSyslog用のアペンダーが付属しています。それでも私はそれを主張します
- 出力宛先のニーズの99.5%は、JULに標準で含まれているものでカバーされます。
- 特別なニーズは、他の何かの上ではなく、JULの上にあるカスタムハンドラーによって満たされる可能性があります。JULのSyslog出力ハンドラーを作成するのに、別のロギングフレームワークの場合よりも時間がかかることを示唆するものは何もありません。
見落としていることがあるのではないかと本当に心配です。JUL以外のロギングファサードとロギング実装の使用は非常に広範囲に及んでいるので、理解できないのは私だという結論に達しなければなりません。それは初めてではないでしょう、私は恐れています。:-)
では、APIをどうすればよいですか?成功してほしい。もちろん、「流れに乗って」SLF4J(最近最も人気があるようです)を実装することもできますが、私自身のために、すべてのファズを正当化する今日のJULの何が間違っているのかを正確に理解する必要がありますか?自分のライブラリにJULを選択して、自分自身を妨害することはありますか?
テスト性能
(2012年7月7日のnolan600によって追加されたセクション)
SLF4Jのパラメーター化がJULの10倍以上であるというCekiからの以下の参照があります。だから私はいくつかの簡単なテストを始めました。一見すると主張は確かに正しいです。以下は予備的な結果です(ただし、読んでください!):
- 実行時間SLF4J、バックエンドログバック:1515
- 実行時間SLF4J、バックエンドJUL:12938
- 実行時間JUL:16911
上記の数値はミリ秒なので、少ないほど良いです。したがって、10倍のパフォーマンスの違いは、実際にはかなり近いです。私の最初の反応:それはたくさんです!
これがテストの中核です。ご覧のとおり、整数と文字列はループで構成されており、ログステートメントで使用されています。
for (int i = 0; i < noOfExecutions; i++) {
for (char x=32; x<88; x++) {
String someString = Character.toString(x);
// here we log
}
}
(私はログステートメントにプリミティブデータタイプ(この場合はint)とより複雑なデータタイプ(この場合はString)の両方を持たせたいと思っていました。
SLF4Jのログステートメント:
logger.info("Logging {} and {} ", i, someString);
JULのログステートメント:
logger.log(Level.INFO, "Logging {0} and {1}", new Object[]{i, someString});
JVMは、実際の測定が行われる前に一度実行された同じテストで「ウォームアップ」されました。Java 1.7.03はWindows 7で使用されました。最新バージョンのSLF4J(v1.6.6)とLogback(v1.0.6)が使用されました。stdoutとstderrがnullデバイスにリダイレクトされました。
ただし、注意してください。JULはgetSourceClassName()
デフォルトでソースクラス名を出力に出力しますが、Logbackは出力しないため、JULがほとんどの時間を費やしていることがわかります。リンゴとオレンジを比較しています。私はもう一度テストを行い、実際に同じものを出力するように、同様の方法でロギング実装を構成する必要があります。ただし、SLF4J + Logbackは引き続き上位に表示されると思われますが、上記の最初の数値とはかけ離れています。乞うご期待。
ところで、このテストは、SLF4JまたはLogbackを実際に使用したのが初めてでした。楽しい経験。JULは、あなたが始めているときは確かにずっと居心地が悪いです。
テストのパフォーマンス(パート2)
(2012年7月8日にnolan600によって追加されたセクション)
結局のところ、JULでパターンをどのように構成するか、つまり、ソース名が含まれているかどうかに関係なく、パフォーマンスにとってそれほど重要ではありません。私は非常に単純なパターンで試しました:
java.util.logging.SimpleFormatter.format="%4$s: %5$s [%1$tc]%n"
上記のタイミングはまったく変わりませんでした。私のプロファイラーは、getSourceClassName()
これが私のパターンの一部ではない場合でも、ロガーが呼び出しに多くの時間を費やしていることを明らかにしました。パターンは関係ありません。
したがって、少なくともテスト済みのテンプレートベースのログステートメントでは、JUL(低速)とSLF4J + Logback(高速)の実際のパフォーマンスの差はおよそ10倍になるように見えるというパフォーマンスの問題について結論付けています。Cekiが言ったように。
また、SLF4JのgetLogger()
呼び出しはJUL の同上よりもはるかに高価であるという別のことがわかります。(私のプロファイラーが正確である場合、95 ms vs 0.3 ms)。意味あり。SLF4Jは、基礎となるロギング実装のバインディングにしばらく時間をかける必要があります。これは私を怖がらせません。これらの呼び出しは、アプリケーションの存続期間中、いくらか珍しいはずです。堅牢性は実際のログ呼び出しにあるはずです。
最終結論
(2012年7月8日にnolan600によって追加されたセクション)
たくさんのご回答ありがとうございます。私が当初APIにSLF4Jを使用することに決めたと思っていたのとは対照的に。これは多くのこととあなたの入力に基づいています:
展開時にログの実装を柔軟に選択できます。
アプリケーションサーバー内で実行した場合のJUL設定の柔軟性の欠如に関する問題。
SLF4Jは、特にLogbackと組み合わせると、上記で詳しく説明したように、はるかに高速になります。これが大まかなテストであったとしても、JULよりもSLF4J + Logbackの最適化に多くの労力が費やされたと信じる理由があります。
ドキュメンテーション。SLF4Jのドキュメントは、はるかに包括的で正確です。
パターンの柔軟性。テストを行ったとき、JULにLogbackのデフォルトのパターンを模倣するようにしました。このパターンには、スレッドの名前が含まれています。JULはそのままではこれを実行できないことがわかりました。わかりました、今まで見逃していませんが、ログフレームワークでは見逃してはならないことだと思います。限目!
今日(または多くの)JavaプロジェクトはMavenを使用しているため、依存関係を追加することはそれほど大きな問題ではありません。これはSLF4Jに当てはまるようです。また、SLF4J jarとその仲間はサイズが小さいです。
奇妙なことに、SLF4Jで少し作業した後、JULにかなり腹を立てていました。JULでは、このようにする必要があることを今でも後悔しています。JULは完璧とはほど遠いですが、仕事をするのはちょっとです。十分に十分ではありません。Properties
例として同じことが言えますが、抽象化については考えていません。そのため、人々は独自の構成ライブラリをプラグインして、何を持っているのでしょう。その理由はProperties
、バーの真上にあるからだと思いますが、今日のJULにはその逆が当てはまります...過去には、存在しなかったためゼロになりました。
InternalLoggerFactory.java
。
java.lang.System.Logger
。これはインターフェースであり、そのフレームワークが追いつき、そのインターフェースの実装を提供している限り、必要な実際のロギングフレームワークにリダイレクトできます。モジュール化と組み合わせると、java.util.logging
別のフレームワークが必要な場合、を含まないバンドルされたJREを使用してアプリケーションをデプロイすることもできます。