人生で初めて、オープンソース化されるJava APIを書いている立場にいることに気付きました。他の多くのプロジェクトに含まれることを願っています。
ロギングのために、私(そして実際に私が働いている人々)は常にJUL(Java.util.logging)を使用しており、それに関して何の問題もありませんでした。ただし、API開発のために何をすべきかをより詳細に理解する必要があります。私はこれについていくつかの調査を行いましたが、入手した情報を使用すると、さらに混乱します。したがって、この投稿。
私はJULから来たので、私はそれに偏っています。残りの私の知識はそれほど大きくありません。
私が行った研究から、私は人々がJULを好きではないこれらの理由を思いつきました:
「SunがJULをリリースするずっと前にJavaで開発を始めました。何か新しいことを学ぶよりもlogging-framework-Xを続けるほうが簡単でした」。うーん私は冗談ではありません、これは実際に人々が言うことです。この引数を使用すると、すべてCOBOLを実行できます。 (しかし、私はこれが怠け者であることと確かに関連することができます)
"JULのログレベルの名前が気に入らない"。 OK、まじめな話、これは新しい依存関係を導入するだけの理由ではありません。
"JULからの出力の標準形式が気に入らない"。うーんこれは単なる構成です。コードごとに何もする必要はありません。 (本当のことですが、昔は正しくフォーマットするために独自のFormatterクラスを作成しなければならなかったかもしれません)。
"logging-framework-Xも使用する他のライブラリを使用しているため、そのライブラリを使用する方が簡単だと思った"。これは循環引数ですよね? 「全員」がJULではなくlogging-framework-Xを使用するのはなぜですか?
"他の誰もがlogging-framework-Xを使用しています"。これは、上記の特別な場合にすぎません。大半が常に正しいとは限りません。
本当の大きな問題は、なぜJULではないのですか?私が逃したことは何ですか?ロギングファサード(SLF4J、JCL)の存在理由は、複数のロギングインプリメンテーションが歴史的に存在しており、その理由が実際にJULの前の時代に戻っていることです。 JULが完璧だった場合、ロギングファサードは存在しませんか?問題をさらに混乱させるために、JULはある程度それ自体がファサードであり、ハンドラー、フォーマッター、さらにはLogManagerさえも交換できるようにします。
同じことを複数の方法で行う(ロギング)のではなく、そもそもなぜそれらが必要だったのか疑問に思うべきではないでしょうか? (およびそれらの理由がまだ存在するかどうかを確認します)
わかりました、これまでの私の研究は、JULの本当の問題である可能性のあるいくつかの事柄を導きました:
パフォーマンス。 SLF4Jのパフォーマンスは他のものより優れていると言う人もいます。これは私にとっては時期尚早な最適化の場合のようです。毎秒数百メガバイトのログを記録する必要がある場合は、とにかく正しい道を進んでいるかどうかわかりません。 JULも進化しており、Java 1.4で行ったテストは正しくない可能性があります。それについて読むことができます here そして、この修正により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のアペンダーが付属しています。それでも私はそれを主張します
見落としていることがあるのではないかと心配しています。 JUL以外のロギングファサードとロギング実装の使用は非常に広まっているので、私が理解できないのは私であるという結論に達する必要があります。それは初めてではないだろう、私は恐れている。 :-)
それでは、APIで何をすべきでしょうか?成功してほしい。もちろん、「フローに合わせて」SLF4J(最近最も人気のあるようです)を実装できますが、私自身のために、すべてのファズを正当化する今日のJULの何が間違っているのかを正確に理解する必要がありますか?ライブラリにJULを選択することで自分自身を妨害しますか?
(2012年7月7日にnolan600によって追加されたセクション)
SLF4Jのパラメーター化がJULの10倍以上高速であるというCekiからの参照があります。だから私はいくつかの簡単なテストを始めました。一見、主張は確かに正しいです。予備的な結果は次のとおりです(ただし、続きを読んでください!)。
上記の数値はミリ秒ですので、少ないほど良いです。したがって、最初はパフォーマンスの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は、実際の測定が行われる前に同じテストを1回実行して「ウォームアップ」されました。 Java 1.7.03はWindows 7で使用されました。最新バージョンのSLF4J(v1.6.6)およびLogback(v1.0.6)が使用されました。 stdoutとstderrはnullデバイスにリダイレクトされました。
ただし、JULはデフォルトでgetSourceClassName()
にほとんどの時間を費やしていることがわかります。これは、JULがデフォルトで出力にソースクラス名を出力するのに対し、Logbackは出力しないためです。そこで、リンゴとオレンジを比較しています。実際に同じものを出力するように、再度テストを実行し、同様の方法でロギング実装を構成する必要があります。ただし、SLF4J + Logbackは引き続き上に表示されると思いますが、上記の初期値からはほど遠いです。乞うご期待。
Btw:このテストは、SLF4JまたはLogbackを実際に使用したのは初めてでした。楽しい経験。あなたが始めているとき、JULは確かにずっと歓迎されません。
(2012年7月8日にnolan600によって追加されたセクション)
結局のところ、JULでパターンを構成する方法、つまりソース名が含まれているかどうかは、パフォーマンスにとって重要ではありません。私は非常に単純なパターンで試しました:
Java.util.logging.SimpleFormatter.format="%4$s: %5$s [%1$tc]%n"
そして、それは上記のタイミングをまったく変えませんでした。私のプロファイラーは、たとえパターンが私のパターンの一部でなくても、ロガーがgetSourceClassName()
の呼び出しに多くの時間を費やしていることを明らかにしました。パターンは関係ありません。
したがって、少なくともテスト済みのテンプレートベースのログステートメントでは、JUL(低速)とSLF4J + Logback(高速)の実際のパフォーマンスの差はおよそ10倍になるとパフォーマンスの問題で結論付けています。チェキが言ったように。
また、SLF4JのgetLogger()
呼び出しは、JULの2つよりもはるかに高価であるということもわかります。 (プロファイラーが正確な場合は、95ミリ秒と0.3ミリ秒)。意味あり。 SLF4Jは、基礎となるロギング実装のバインディングにある程度の時間をかけなければなりません。これは私を怖がらせません。これらの呼び出しは、アプリケーションの存続期間中に多少なります。堅牢性は、実際のログ呼び出しにあるはずです。
(2012年7月8日にnolan600によって追加されたセクション)
答えてくれてありがとう。 APIにSLF4Jを使用することにしました。これは、多くのことと入力に基づいています。
展開時にログ実装を柔軟に選択できます。
アプリケーションサーバー内で実行した場合、JULの設定の柔軟性が不足する問題。
SLF4Jは、特にLogbackと組み合わせると、上記で詳述したように確かにずっと高速です。これが単なる大まかなテストであったとしても、JULよりもSLF4J + Logbackの最適化に多くの努力が注がれたと信じる理由があります。
ドキュメンテーション。 SLF4Jのドキュメントは、はるかに包括的で正確です。
パターンの柔軟性。テストを行ったとき、JULがLogbackのデフォルトパターンを模倣するように設定しました。このパターンには、スレッドの名前が含まれます。 JULはそのままではこれができないことがわかりました。わかりました、今まで見逃していませんが、ログフレームワークから見逃すべきものではないと思います。期間!
ほとんどの(または多くの)Javaプロジェクトは現在Mavenを使用しているため、特に依存関係が安定している場合、つまりAPIを絶えず変更しない場合、依存関係の追加はそれほど重要ではありません。これはSLF4Jに当てはまるようです。また、SLF4J jarおよびその友人はサイズが小さくなっています。
そのため、SLF4Jを少し使った後、実際にJULにかなり怒ったという奇妙なことが起こりました。 JULでこのようにしなければならないことを今でも後悔しています。 JULは完璧とはほど遠いですが、それでも仕事はできます。ただ十分ではありません。例としてProperties
についても同じことが言えますが、それを抽象化することは考えていません。そうすれば、人々は自分の構成ライブラリをプラグインして、何を持っているかを知ることができます。理由は、Properties
がバーのすぐ上にあるのに対し、今日のJULには逆のことが当てはまるからだと思います...そして過去には、それが存在しなかったためにゼロになりました。
免責事項:私はlog4j、SLF4Jおよびlogbackプロジェクトの創設者です。
SLF4Jを好む客観的な理由があります。 1つは、SLF4Jにより、エンドユーザーは自由に基本的なロギングフレームワークを選択できます。さらに、賢明なユーザーは log4jを超える機能を提供するlogback を好む傾向があり、j.u.lは遅れをとっています。機能面ではj.u.lは一部のユーザーには十分かもしれませんが、他の多くのユーザーにはそれだけでは不十分です。簡単に言えば、ロギングが重要な場合、基礎となる実装としてSLF4Jとlogbackを使用する必要があります。ロギングが重要でない場合、j.u.lで問題ありません。
ただし、oss開発者は、自分だけでなくユーザーの好みを考慮する必要があります。 youがSLF4Jがjulより優れていると確信しているからではなく、現在(2012年7月)ほとんどのJava開発者がSLF4Jを好んでいるため、SLF4Jを採用する必要があります。ロギングAPI。最終的に一般的な意見を気にしないことに決めた場合は、次の事実を考慮してください。
したがって、世論よりも「堅い事実」を保持することは、一見勇敢なように見えますが、この場合は論理的な誤りです。
それでも納得できない場合、 JB Nizet は追加の強力な引数を作成します。
エンドユーザーが、自分のコード、またはlog4jまたはlogbackを使用する別のライブラリに対してこのカスタマイズを既に行っていた場合を除きます。 j.u.lは拡張可能ですが、logback、j.u.l、log4jを拡張する必要があるのは、4つの異なるロギングフレームワークを使用する4つのライブラリを使用しているため、他のどのロギングフレームワークしかわからないからです。 SLF4Jを使用すると、選択したものではなく、必要なロギングフレームワークを構成できます。 典型的なプロジェクトでは、あなたのものだけでなく、無数のライブラリを使用していることに注意してください。
何らかの理由でSLF4J APIが嫌いで、それを使用することで仕事の面白さがなくなる場合は、ぜひj.u.lにアクセスしてください。結局のところ、 j.u.lをSLF4Jにリダイレクト する手段があります。
ちなみに、j.u.lのパラメーター化はSLF4Jの少なくとも10倍遅いため、結果として顕著な違いが生じます。
Java.util.logging
はJava 1.4で導入されました。その前にロギングの用途がありました。そのため、他の多くのロギングAPIが存在します。これらのAPIはJava 1.4よりも前に頻繁に使用されていたため、1.4がリリースされたときに0に落ちなかった大きな市場シェアを占めていました。
JULは、1.4でさらに悪化し、1.5でのみ良くなった(そして私も6では推測しますが、あまりよくわかりません)とあなたが述べた多くの素晴らしいことから始めませんでした。
JULは、同じJVM内の異なる構成の複数のアプリケーションにはあまり適していません(相互作用しない複数のWebアプリケーションを考えてください)。 Tomcatは、それを機能させるためにいくつかのフープをジャンプする必要があります(正しく理解できれば、JULを効果的に再実装します)。
ライブラリが使用するロギングフレームワークに常に影響を与えることはできません。そのため、SLF4J(実際には他のライブラリよりも非常に薄いAPIレイヤーです)を使用すると、ロギングワールド全体のやや一貫した全体像を維持するのに役立ちます(そのため、同じシステムでライブラリロギングを行いながら、基礎となるロギングフレームワークを決定できます)。
ライブラリは簡単に変更できません。以前のバージョンのライブラリがlogging-library-Xを使用していた場合、logging-library-Y(たとえばJUL)に簡単に切り替えることはできません。新しいロギングフレームワークを使用し、(少なくとも)ロギングを再構成します。特にほとんどの人に明らかな利益をもたらさない場合、それは大きなノーノーです。
JULは少なくともだと思いますが、最近は他のロギングフレームワークの有効な代替手段です。
私見、slf4jのようなロギングファサードを使用する主な利点は、エンドユーザーに選択を課すのではなく、ライブラリのエンドユーザーが希望する具体的なロギング実装を選択できることです。
たぶん彼はLog4jまたはLogBack(特別なフォーマッター、アペンダーなど)に時間とお金を投資しており、julを構成するよりもLog4jまたはLogBackを使い続けることを好みます。問題ありません:slf4jはそれを許可します。 7月よりもLog4jを使用するのが賢明な選択ですか?多分そうでないかもしれません。しかし、あなたは気にしません。エンドユーザーに好みのものを選択させます。
JULを使い始めたのは、私が疑っているように、JULをすぐに使用するのが最も簡単だったからです。しかし、長年にわたって、私は選択にもう少し時間を費やしたかったのにと思うようになりました。
私の今の主な問題は、多くのアプリケーションで使用されるかなりの量の「ライブラリ」コードがあり、すべてがJULを使用していることです。これらのツールをWebサービスタイプのアプリで使用するたびに、ログが消えるか、予測不能または奇妙な場所に移動します。
私たちの解決策は、ライブラリログコールは変更されず、使用可能なロギングメカニズムに動的にリダイレクトされることを意味するライブラリコードにファサードを追加することでした。 POJOツールに含まれている場合、それらはJULに向けられますが、Webアプリとしてデプロイされる場合、LogBackにリダイレクトされます。
残念ながら、ライブラリコードではパラメーター化されたログを使用していませんが、必要に応じて後から変更できるようになりました。
Slf4jを使用してファサードを構築しました。
Logback-1.1.7でslf4j-1.7.21に対してjulを実行し、SSDに出力しました。Java 1.8、Win64
julは1Mループで48449ミリ秒、ログバック27185ミリ秒を実行しました。
それでも、私にとっては、もう少し速度が速く、APIが少し優れていても、3つのライブラリと800Kの価値はありません。
package log;
import Java.util.logging.Level;
import Java.util.logging.Logger;
public class LogJUL
{
final static Logger logger = Logger.getLogger(LogJUL.class.getSimpleName());
public static void main(String[] args)
{
int N = 1024*1024;
long l = System.currentTimeMillis();
for (int i = 0; i < N; i++)
{
Long lc = System.currentTimeMillis();
Object[] o = { lc };
logger.log(Level.INFO,"Epoch time {0}", o);
}
l = System.currentTimeMillis() - l;
System.out.printf("time (ms) %d%n", l);
}
}
そして
package log;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class LogSLF
{
static Logger logger = LoggerFactory.getLogger(LogSLF.class);
public static void main(String[] args)
{
int N = 1024*1024;
long l = System.currentTimeMillis();
for (int i = 0; i < N; i++)
{
Long lc = System.currentTimeMillis();
logger.info("Epoch time {}", lc);
}
l = System.currentTimeMillis() - l;
System.out.printf("time (ms) %d%n", l);
}
}