Java Logging:A Nightmare Story

エントリー


ログファイルに行を書き込む正しい方法への、Javaプラットフォームの厄介で曲がりくねった道。 Javaでのロギングの歴史は、企業や個々のプログラマーとの相互作用など、オープンソースの機能を研究するという点で非常に有益です。 Javaロギングの歴史、それがどこに来たのか、どのように生きるのかについて、できる限り説明します。 私の状況の分析は、伐採が常に好みの問題であり、私の好みが私自身の大人を形成した理由について非常に主観的です。 動物園全体のロギングフレームワークの技術的特徴という点ではなく、オープンソースモデルの開発者のポリシーと心理学の観点からは有益だと思います。

開始する


ロギングライブラリは、少なくともコンソール/ログファイルへの行の出力を許可する必要があることは明らかです。

もちろん、最初はSystem.err.println 。 さらに、サーブレットAPIの最初のバージョンにはlog機能が含まれていました(ただし、非常に原始的)。

1999年のより高度なソリューションのオプションの1つは、DIサービスに加えてLogEnabledインターフェイスを提供するAvalonプロジェクト(およびExcaliburおよびFortressと呼ばれるサブプロジェクト)でした。 LogEnabledを宣言したオブジェクトが挿入されました(DIとの接続を強調するために「挿入」の代わりにこの単語を使用します)。タイプLoggerのオブジェクトで、a)行b)例外を記述できます。 当時のこのアプローチは新鮮で革新的なように見えましたが、現代の観点から見ると、それは純粋な愚かさと過剰なエンジニアリングです。 ロギングにDIを使用しても意味がありません。このLoggerの静的インスタンスは誰にでも適しています。 Avalonでは、このひどいLoggerを保存する場所と、クラスがDIを使用していない場合(つまり、コンテナによって制御されていない場合)にどうすればよいかを考える必要があり、本当にログインしたいと思います。

1999年頃、新世代ライブラリlog4jが登場します。 ライブラリのプロトタイプはIBMによって開発され(当時、青い巨人がJavaをOS / 2に詰め込もうとしていた時代に)、ASFがバトンを手に入れました。 この製品はすでに、実際のニーズに基づいてより多くの検討とテストが行​​われました。 一般に、当時のJavaのサーバーアプリケーションは1年ほど前のものであり、サーバーでは常にログが要求されていたと言わざるを得ません。 この間、Javaコミュニティは、彼らが何をどのように必要としているかを徐々に理解し始めました。

log4jは、ロガーまたはカテゴリ (つまり、ログに書き込みたいアプリケーションの領域)の概念、いわゆるappendersによって実行される実際のログエントリ、およびレコードのフォーマット( レイアウト )を分割しました。 log4j構成は、どのアペンダーがどのカテゴリーにアタッチされるかを決定し、 ログレベルメッセージは各アペンダーに分類されます。

log4jの基礎は、カテゴリの階層です。 たとえば、 org.hibernateからのすべてのメッセージを記録し、 org.hibernateからすべてをミュートできます。 しばらくして、事実上、アプリケーションのカテゴリ階層とパッケージ階層を一致させる慣行が確立されました。

カテゴリの階層により、不要なメッセージを非常に効果的に遮断できるため、log4jは非常に賢く機能しました。 ちなみに、ロガーの基本的なことは、不要な(通常は90%を超える不要な)フィルター処理とフォーマットの記録速度ほど記録速度ではありません。

log4jで規定されている原則は、log4cxx、log4net(および新しいcub-log4php)など、他の言語に非常にうまく移植されています。 Python 2.xの標準ロギングパッケージは、log4jを再設計したものです(他のライブラリを少し追加しました)。

それで、要約します。 成功したアーキテクチャ、明確な構成スキーム、 フェイルセーフの原則-プラットフォームにそのような素晴らしいライブラリを含めてみませんか?

Java Logging API


実際、すべてが奇妙になりました。 IBMは、log4jが発生した深さで、新しいJSR47(Java Logging API)の形成を疑問視するのに非常に迅速であることが判明しました。 特に、JSR47の責任者である同志Graham Hamiltonは、log4jを基礎としてではなく、 元のIBMロギングツールキットを採用することを決定しました。 さらに、ロギングツールキットは最大限に使用されました。すべてのメインクラスの名前が一致しただけでなく、それらの実装も一致しました。 プラットフォームの次のリリースをキャッチするために、彼らはコードをできる限り少なくしようとしました。 ただし、概念的にはlog4jと非常によく似ていて、アペンダーの代わりにハンドラーと呼ばれ、レイアウトの代わりにフォーマッターがありました。

JSR47の主な目的は実装はなくAPIを定義することであったため、 使用可能な(プラットフォームのデフォルトの)出力ツールは4つ(log4jには10以上)しかなく、フォーマッターは非常に貧弱であり、すぐにフォーマッターを作成する必要がありました十分ではありません。 JSR47は、 .propertiesの形式で構成を使用することを提案し、ファイル内にすべてを記述できるわけではないことを括弧内に記載しました。 したがって、構成が複雑な場合、プログラマーは予期せず、コードを記述する必要があることがわかりました。 .propertiesの形式では.propertiesその構成は実現できません。

これは、JSR47のパフォーマンスが低下したということではありません。 いくつかの場所で、彼はメモリ内に彼の設定の特別な表現を保持することでlog4jを追い越しました(偶然にもこの設定を複雑にしました)。 しかし、判明したように、JSR47はいわゆる発信者情報、つまり「このメッセージはどこから来たのか」を強制的に収集しました。 発信者情報の取得は、かなり高価な操作であり、ネイティブコードを使用して続行します。 log4jの経験豊富な叔父はこれを知っていたので、この機会に「それをオンにしない方がよい」という条件を提供しました。

log4jの開発者はオープンな請願書を作成し、「アセンブリラインからJSR47を削除する」ことを要求しましたが、まだプラットフォームの一部ではありませんでした。 請願書は100人以上の人々によって署名されました...しかし、それは遅すぎました。 JDKの次のリリースが承認され、プラットフォームは初歩的なjava.util.logging 、または略してJULで未来に広まりました。 新しいロギングは非常に未開発で不便だったため、いくつかのアプリケーションサーバー(ResinとJettyの中で)でのみ使用することにしました。 しかし、Sunは請願に応じ、元のJSR47の主要な問題のほとんどは徐々に解消されました。 それにもかかわらず、これらの操作は、木製の橋に支柱を取り付けるようなもので、この橋は鉄筋コンクリートではありません。 log4j開発者はSunに向かって縮小しましたが、JULの曲率はまだかなり高いことに注意してください。 特に、JDK 1.4ライセンスでは、 log4j JUL実装として使用できませんでした 。 log4jの最終列車はなくなりました。

多数のログライター(つまりハンドラー)をサポートできないため、JULは信じられないほど多くのログレベルを定義することで自慢しました。 たとえば、メッセージのデバッグには、FINE、FINER、FINESTの3つのレベルがすでにありました。 これをすべて見ると、開発者はしばしば、3つのレベルのうちどれを使用すべきかをまったく理解していませんでした。

Javaコミュニティは、人気のある安定した開発中のlog4jと並行した「標準」ロギングの出現により完全に混乱しました。 2つのうちどちらがテナントであるかは誰にもわかりませんでした。 プロジェクトで複数のライブラリがアセンブルされ、それぞれが独自のロギングと独自の設定を使用して、ログファイルをまったく異なる方法で記録する状況がしばしばありました。

もちろん、コミュニティはこの問題を修正しようとしました。 ラッピングの流行が始まった。 または、私はパンデミックとさえ言うでしょう。

ラッパー地獄


複数のライブラリを接続し、それらのログを1つの全体に結合しようとすると(およびコードは変更できません)、これはアダプターと呼ばれます。 JULアダプターはlog4jで作成され、その逆も同様です。 残念ながら、機能アダプターは「最小公倍数」です。 コンテキストサポート(NDCおよびMDC)がlog4jに登場した場合でも、JULの輸血中に失われました。 さらに悪いことに、JULはJDK 1.4以降でしか機能しませんでしたが、エンタープライズアプリケーションの数は依然として1.3のままでした。 その結果、コミュニティは、誰もが一緒に使用し、いつでもどこでも機能する「事実上の共通基準」を作成するという考えに取り付かれました。

2002年頃、commons-logging(JCL = Jakarta Commons Logging)と呼ばれるプロジェクトがJakartaグループから登場しました。 実際、その時点で存在していたすべてのロギングツールのラッパーでした。 ラッパー( Logと呼ばれるインターフェース)を使用するようにアプリケーションを作成し、「適切な」ロギングシステムを選択してそれ自体に接続することが提案されました。 ラッパーは機能的に貧弱で、既存のログツールに追加しませんでした。

適切なロギングシステムはどのように自動的に選択されましたか? しかし、これは最も興味深いです。 まず、CLASSPATHのどこかに特別なcommons-logging.propertiesファイルを配置することで、明示的に設定できます。 第二に、システムプロパティを介して(明らかに、誰もしません)。 第三に、log4jがCLASSPATHのどこかで検出された場合、自動的にアクティブ化されました。 他のすべてのライブラリの実装も同じ方法で検索され、最初のライブラリは常に接続されていました。

いいね! まあ、つまり、世界中のすべてのソフトウェアがcommons-loggingを使用しているいいでしょう 。 その後、JARを安全に収集してアプリケーションサーバーに配置すると、JCLがこのアプリケーションサーバーのログを取得します。

実際、判明したように、多くのソフトウェアは通常「開発者のお気に入りのログ」を使用します。 これは、完全に任意のライブラリが依存関係の形式で、たとえばlog4jをプルアップできることを意味します。したがって、CLASSPATHに分類され、log4jを使用するようにJCLが予期せず切り替えられます。 commons-logging.propertiesさらに悪化しcommons-logging.properties 。 活動家が自分のJARにそれを押し込むことを考えていた場合、このJARを接続すると、あなた自身が理解し、書き込みが失われます。 状況の特別な不正行為は、それがどのJARから感染したのかが完全に理解できないという事実によって与えられました。 時々、すべてのJARをアルファベット順にソートするのに役立ちました。 時にはタンバリン。

ロギングの選択が完全に予測不能であることが、JCLの主要で非常に楽しい機能であることが判明しました。 log4jグループは、commons-logging APIを採用する前に、怒っているThink again記事で爆発しました。これは、流行を止め、既存のソリューションlog4jの改良に集中することを提案しました。

残念ながら、手遅れでした。 数百、数千の図書館がジャカルタからコモンズロギングに移されました。 その中には、Hibernate、Spring、Tomcatがありました。 その後、これらのライブラリの多くのユーザーは、一般的にClassLoader hellと呼ばれる問題の波に流されました。 アプリケーションサーバーは、かなり複雑なClassLoaderの階層を使用しますが、多くの場合、J2EE標準から大きく逸脱しています。 これらの条件下では、 JCLが2回初期化され、誤って初期化され、完全に神秘的なスタックトレースが発生し、ログラッパーに問題があると疑うことさえできなくなります。

実際、なぜオープンソースがこのような奇妙な方法で機能し、この倒錯を生み出したのでしょうか? なぜ開発者は、もう1つの成熟した人気のあるオープンソース製品であるlog4jを使用するだけではありませんか? ここでのポイントは、おそらく、ASF(およびこの悪夢を生成したジャカルタグループがASFの一部である)またはSunを追跡するために使用されるコミュニティの特定の慣性です。 JCLを使用したプロジェクトのクリティカルマスが形成されるとすぐに、他の全員(最も愚かな人々ではなく、Gavin King?)がJCLの使用を開始します(Apacheは素晴らしいからです!)。 これは一般的に、ApacheやSunなどのブランドが何百万人もの開発者が急いでいる低圧力の領域を作成できるブラウン運動を連想させます。 JCLの場合、「サクセスストーリー」は2003年にRod Waldhoff (いわゆるジャカルタコモンズの開発者の1人)のブログで説明されました。

新たな進展


したがって、2004年のどこかでキットに含まれています。
  1. 安定して機能的に開発されたlog4j
  2. 鈍いjava.util.logging
  3. 問題コモンズ-ロギング
  4. 言及する価値のない少数の小さなロガー

現時点では、log4jプロジェクトでは保守的な雰囲気が優勢であることに注意してください。 古いJDKとの互換性に特に注意が払われました。 新しいブランチlog4j-1.3.xの開発のようです。 このバージョンは、一種の妥協ソリューションです。はい、新しい機能が必要です。はい、下位互換性を維持したいです。はい、私たちとあなたの両方を満足させようとします。 その間、可変引数、JMX拡張機能、その他のギフトを備えたJDK 1.5の途中で。 log4jチームは脳波を開始しました。 2.xブランチは切り離されています。メインの1.2.xブランチとは互換性がなく、JDK 1.5専用に作成されています。 Javaコミュニティは短気です。 それは何かとして起こっているようです。 しかし、正確に理解してはならないのは、log4j 2.0はまだ達成できないアルファであり、log4j 1.3は非常にバグが多く、ドロップインの互換性が約束されていないことです。 ブランチ1.2のみがまだ安定しており、数年でジャンプします -注意! -バージョン1.2.6から1.2.12。

2006年のどこかで、log4jの創設者の1人であるCekiGülcüは、急速に衰退したチームを去ることに決めました。 そのため、SLF4J(Java用のシンプルロギングファサード)と呼ばれる次の「すべてのラッパー」が誕生しました。 これは、log4j、JUL、commons-logging、およびlogbackと呼ばれる新しいロガーのラッパーです。 ご覧のとおり、進捗状況はすぐに「ラッパーの周りのラッパー」の段階に達しました。 同じスキームによれば、ラップされたライブラリの数が階乗として増加することを予測するのは簡単です。 ただし、SLF4Jには他の工夫もあります。 これらは、log4jからSLF4J、commons-loggingからSLF4Jなどへの特別なバイナリアダプタです。 このようなアダプターは、ソースが利用できないコード用に作られています。 ただし、ログライブラリの元のJARを置き換える必要があります。 これがどんなおridgeになるのか想像できませんが、本当にしたいのならできます。

私のラッパーに対する憎しみすべてにとって、正直なところ、SLF4Jはよくできた製品です。 前任者のすべての欠点が考慮されました。 たとえば、CLASSPATHでクラス検索を行うシャーマニズムダンスの代わりに、より信頼性の高いスキームが考案されました。 これで、ラッパー全体が2つの部分に分割されますslf4j-log4j12.jar (アプリケーションで使用される)と、ロギングの各タイプの個別のJARファイルで表される実装(たとえば、 slf4j-log4j12.jarslf4j-jdk14.jarなど)。 必要な実装ファイルをプロジェクトに接続するだけで十分です。その後、おっと! 使用されるすべてのプロジェクトコードとすべてのライブラリ(SLF4J APIにアクセスする場合)は、正しい方向にログインします。

機能的には、SLF4JはNDCやMDCなどの最新の機能をすべてサポートしていました。 呼び出しを実際にラップすることに加えて、SLF4Jは、文字列をフォーマットするときに、小さいながらも便利なボーナスを提供しました。 ここでのボーナスは次のとおりです。 コードでは、多くの場合、フォームの構造を印刷する必要があります。

 log.debug("User " + user + " connected from " + request.getRemoteAddr()); 

実際に文字列を出力することに加えて、ここではuser.toString()変換の後に文字列の連結が暗黙的に続きます。 すべては何もないでしょう。 デバッグモードでは、実行速度はそれほど気になりません。 ただし、たとえばINFOでレベルを設定した場合でも、文字列の構築は引き続き行われることがわかります。 奇跡はありませんlog.debug呼び出す前に行が構築されるため、 log.debugはこれを何らかの方法で制御する方法がありません。 このlog.debugいくつかの重要な内部ループに配置されているlog.debug想像すると...一般的に、あなたはそのように生きることはできません。 log4jの開発者は、次のようなデバッグコードのフレーミングを提案しました。

 if (log.isDebugEnabled()) { log.debug("User " + user + " connected from " + request.getRemoteAddr()); } 

良くない 理論的には、これらの問題はすべてロギングライブラリ自体で処理する必要があります。 この問題は、単にlog4jのアキレス腱でした。 開発者はキックに緩慢に反応し、オブジェクトをロギングコール(正確には1つ!)に追加できるようになり、 ObjectRendererインターフェイスを使用してこのオブジェクトがログに書き込まれる方法についても説明しました。 概して、これらはすべて言い訳と半分の措置でした。

SLF4Jは、古いバージョンのJDKおよびAPIとの互換性のフレームワークによって圧迫されていなかったため、すぐによりエレガントなソリューションを提案しました。
  log.debug("User {} connected from {}", user, request.getRemoteAddr()); 

一般的に、すべては簡単です。 この行の{}は、個別に渡されるパラメーターへのリンクです。 パラメーターを文字列に変換し、ログレコードの最終フォーマットは、DEBUGレベルが設定されている場合にのみ行われます。 多くのパラメーターを渡すことができます。 うまくいく! フレーミングを記述する必要はありません。

括弧内で、GStringの概念があるGroovy言語によって、この機能も完全に予期せず実装されたことに注意する必要があります。 文字列を表示
 "User ${user} connected from ${request.getRemoteAddr()}" 
、いくつかのコンテキスト変数(ここではuserrequest )に暗黙的に関連付けられており、文字列の計算は延期されます 。 これは、log4jなどのログライブラリにとって非常に便利です。GString入力に入力し、計算せずに破棄するか、通常の(静的)文字列-文字列に変換できます。

要するに、SLF4Jは将来に備えて有能に作られました。 これにより、コミュニティでの人気が大幅に高まりました。現在、SLF4Jは、Jetty、Hibernate、Mina、Geronimo、Mule、Wicket、Nexusなどの重要なプロジェクトで使用されています...一般的に、かつてcommons-loggingにかかっていたほとんどすべての敗者はSLF4Jに切り替えました。 何年も前に、コモンズのログが目的の状態に改善されなかったのはなぜでしょうか? しかし、これらはオープンソースの現実です。その中でのソフトウェアの開発は、進化よりも革命的です。

SLF4Jと同時に、まったく新しいロガーがテーブルに追加されました-Logback。 それは伐採で犬を食べた男性によって作られ、実際には本当に良い製品であることが判明しました。 logbackはもともとJDK 1.5+の下で研ぎ澄まされ、log4jプロジェクトに内在するすべての老人性下位互換性疾患を一気に取り除いた。 そしてそれは、可変引数、 java.util.concurrentその他の喜びを意味します。 たとえば、組み込みのランタイムフィルタリングシステムにより、ユーザーセッションに応じてログレベルを変更したり、ユーザーを別のログファイルに分散したりすることができます。

著者が描いた田園にマスタードを投げます。 これらの機能のほとんどは、log4jの追加アペンダーとして実装できます。 構成を曲げてファイルすることはより困難ですが、これのために新しいロガーに切り替える必要はないという事実です。 したがって、Logbackでアドバタイズされたすべてのチップは便利ですが、一意ではありません。

コミュニティに関しては、Logbackを慎重に扱います。 まず、数年のうちに彼はバージョン0.9.xに到達しましたが、これは一部のプログラマーを怖がらせます。 第二に、LogbackはApacheの傘下にもSunのスコープ内にもありません。 それはsc帳面な人々を悩ます。 第三に、作者は食べる必要があるため、Logbackとサポートの一部のアドオンにはお金が必要です。 これは時々生徒を怖がらせます。 とりわけ、Logbackにはかなり複雑なデュアルライセンス(LGPL / EPL)がありますが、log4jにはユニバーサルApacheライセンスがあります。 ライブラリおよび一般的に再配布可能なソフトウェアの場合、ライセンスは非常に微妙なポイントです。

概して、今日のLogbackは進化の頂点です。Logbackに加えて、10個の新しいロギングライブラリが登場しましたが、どれも生き残れない可能性が高いです。要約すると、現時点での状況は次のとおりです。

オープンソースコミュニティは「重心」に群がる傾向があるとすでに述べました。現在、そのような重心は、その「普遍性」のため、むしろSLF4Jです。SLF4Jの相対的な人気は、ある程度新しいラッパーの出現を保証します。SLF4Jを使用するプロジェクトの数は、すでに「クリティカルマス」を蓄積するのに十分です。ログバック(同じ著者、気にしないでください)には、そのようなクリティカルマスはありません。(ちなみに、log4jは今でも黄金の山とバージョン2.0を約束しますが、物事はまだあります。)Logbackがプライドを和らげ、Apacheに移行すると、彼の位置は大きく改善すると思います。

おわりに


問題の歴史をプログラマーの心理学の観点から見るのは興味深い。実際、原則として、これはすべてスパイラルです(そして、それは進歩的であるようです!)運動-無限の「車輪の再発明」。つまり、「既存の変更」と「独自の操作」という2つのオプションのうち、2番目のオプションが常に選択されていました。したがって、言及されたプロジェクトのいずれも、(非常に事実上の基準に基づいて)無条件のリーダーに分割されていません。代わりに、開発者は、一緒に行動するのではなく、異なるプロジェクトで異なる時間に「切り刻まれ」、別々に行動しました。すべての著者が1つのチームで作業できるという事実ではありませんが。政治的な瞬間があり(グラハムハミルトンがIBMをどのように愛していたかを思い出してください)、チームの些細な口論だけでした。コミュニティに「選択の自由」を提供したいというジャカルタコモンズの参加者の欲求は、一般にコミュニティの長い「ラッパーの流行」に変わりました。

一般に、これらの悪はすべてオープンコミュニティの典型です。この10年以上の歴史は、SunがJavaコミュニティで何かを決定しているように思われた今、その信念がどれほど誤っているかを示しています。Sunに反して、Sunから独立して多くのことが起こったことがわかります。一言で言えば、私はそれがさらにどのように進むのだろうか。私は一つのことを確信しています-プロジェクトは行き来し、人々は変わりません:)

Source: https://habr.com/ru/post/J113145/


All Articles