うまくやりたい場合は、まずツールを磨く必要があります。
多くのプログラマーは、アプリケーションの動作を記録することがどれほど重要かを忘れるかもしれません。マルチスレッド環境で高圧によって引き起こされる同時バグに遭遇すると、ログを記録することの重要性を理解できます。
一部の人々は、この文をコードに追加することを非常に喜んでいました:
log.info( "Happy and Carefree Logging");
彼は、メンテナンス、チューニング、障害の識別におけるアプリケーションログの重要性さえ認識していないかもしれません。 SLF4Jは、主にスキーマを注入する素晴らしい方法をサポートしているため、最高のロギングAPIだと思います。
log.debug( "found {}レコードマッチングフィルター: '{}'"、records、filter);
log4jの場合、これのみを行うことができます。
log.debug( "found" + records + "recordsmatchingフィルター: '" +フィルター + "'");
この文章は、より言葉のようで読みやすいだけでなく、効率に影響を与える文字列のスプライシングもあります(このレベルが出力を必要としない場合)。
SLF4Jは{}噴射機能を導入し、ストリングスプライシングは毎回回避されるため、ToStringメソッドは呼び出されず、ISDEBUGENabledを追加する必要はありません。 SLF4Jは外観モードの適用であり、単なるファサードです。特定の実装には、ログバックフレームワークをお勧めします。すでに完全なlog4jではなく、以前に一度宣伝しています。多くの興味深い機能があります。 LOG4Jとは異なり、それはまだ積極的な開発と改善を行っています。
推奨するもう1つのツールはperf4jです。
perf4jはsystem.currenttimemillis()as log4jがsystem.out.println()です。
log4jはSystem.out.printlnのより良い代替品であるように、perf4jはSystem.currenttimemillis()の代替品に似ています。プロジェクトにPerf4Jを導入し、それが高負荷の下でどのように機能するかを観察しました。管理者とビジネスユーザーは、このガジェットが提供する美しいチャートにst然としています。パフォーマンスの問題をいつでも表示できます。 Perf4Jは、話す特別な記事である必要があります。これで、最初にその開発者ガイドを見ることができます。また、CEKIGülcü(Log4J、SLF4J、およびLogBack Projectの作成者)もあり、コモンズログの依存関係を簡単に削除する方法を提供します。
ログレベルを忘れないでください
ログの行を追加するたびに、ここで使用するログレベルはどのログレベルを使用する必要がありますか?プログラマーの約90%がこの問題にあまり注意を払っていません。彼らは1つのレベルを使用して、通常は情報またはデバッグのいずれかを記録します。なぜ?
ログフレームワークには、System.out:分類とレベルと比較して2つの大きな利点があります。どちらも、エラーのトラブルシューティング時に、永続的にログを選択的にフィルタリングすることができます。
エラー:深刻なエラーが発生し、すぐに対処する必要があります。このレベルのエラーは、あらゆるシステムには耐えられません。たとえば、Null Pointerの例外、データベースは利用できず、重要なパスのユースケースは引き続き実行できません。
警告:後続のプロセスは継続されますが、真剣に受け止める必要があります。実際、ここに2つのレベルがあることを願っています。1つはソリューションの明らかな問題(「現在のデータはキャッシュデータを使用していない」など)であり、もう1つは潜在的な問題と提案(「開発モードで実行される」や「管理コンソールのパスワードは十分に安全ではありません」などです。
デバッグ:開発者が懸念していること。後で私はこのレベルで物事を記録すべきものについて話します。
トレース:より詳細な情報、開発段階でのみ使用されます。製品が発売されてから短期間でこの情報に注意を払う必要があるかもしれませんが、これらのログレコードは一時的なものであり、最終的にオフにする必要があります。デバッグとトレースの違いを区別することは困難ですが、ログの行を追加して開発とテスト後に削除すると、ログはトレースレベルにあるはずです。
上記のリストは単なる提案であり、独自のルールに従ってログを記録できますが、特定のルールを作成することが最善です。私の個人的な経験は、コードレベルでログをフィルタリングしないでください。正しいログレベルを使用して、目的の情報をすばやく除外してください。
最後に言うべきことは、この悪名高いものは*有効な条件ステートメントです。各ログの前にこれを追加するのが好きな人もいます:
if(log.isdebugenabled())log.debug( "commercial for your commercial");
個人的には、この厄介なものをコードに追加することを避けるべきだと思います。パフォーマンスはあまり改善されていないようです(特にSLF4Jを使用した後)。これは、時期尚早の最適化のようなものです。また、少し冗長だと思いませんか?まれに、この明示的な判断声明は、ログメッセージ自体の構築が高すぎることを証明しない限り、明示的に必要です。それ以外の場合は、必要なだけ覚えていて、ログフレームワークにこれを心配させることができます。
あなたが録音しているものを知っていますか?
ログの行を書くたびに、ログファイルに印刷しているものを確認してください。ログを読んで、例外がどこにあるかを調べてください。まず、少なくともヌルポインターの例外を避けてください。
log.debug( "IDを使用した処理要求:{}"、request.getId());
リクエストがnullではないことを確認しましたか?
録音コレクションも大きなピットです。 Hibernateを使用してデータベースからドメインオブジェクトのコレクションを取得する場合、誤って次のように書き込みます:log.debug( "returning users:{}"、users);
SLF4Jは、このステートメントが実際に印刷されている場合にのみToStringメソッドを呼び出します。もちろん、これはクールです。ただし、メモリがオーバーフローした場合、n+1の選択の問題、スレッドが死に至る、初期化の遅れ、ログストレージスペースがなくなる...これらはすべて発生する可能性があります。最良の方法は、オブジェクトのIDのみ(またはコレクションのサイズのみ)を記録することです。ただし、IDを収集するには、各オブジェクトのgetIDメソッドを呼び出す必要があります。これは、Javaでは実際に簡単な作業ではありません。 Groovyには優れた拡張オペレーター(ユーザー*.ID)があります。 Javaでは、Commons Beanutilsライブラリを使用してシミュレートできます。
log.debug( "returning user ids:{}"、collect(users、 "id"));
これはおそらく、収集方法の実装方法です。
Public Static Collection Collect(Collection Collection、String PropertyName){Return Collectutils.Collect(Collection、new BeanTopropertyValueTransFormer(PropertyName));}最後に、ToStringメソッドを正しく実装したり、使用したりすることはできません。
まず、ログを記録するために、各クラスのトストリングを作成する多くの方法があります。 ToStringBuilderを使用して生成するのが最善です(ただし、その反射実装のバージョンではありません)。
第二に、アレイと非定型セットに注意してください。アレイと一部の代替コレクションのToString実装は、各要素のToStringメソッドを1つずつ呼び出すことはできません。 JDKが提供する配列#ディープトストリング方法を使用できます。自分に印刷したログを確認して、フォーマットの例外に関する情報があるかどうかを確認してください。
副作用を避けてください
通常、ログ印刷はプログラムのパフォーマンスに大きな影響を与えません。最近、私の友人が、いくつかの特別なプラットフォームで実行されているシステムにHibernate LazyInitializationException例外を投げました。これから推測したように、セッションが接続されているときに初期化コレクションが遅れてロードされるいくつかのログプリントがあります。この場合、ログレベルが上昇した場合、コレクションは初期化されなくなります。このコンテキスト情報がわからない場合は、このバグを発見するのにどれくらい時間がかかりますか。
別の副作用は、プログラムの走行速度に影響を与えることです。この質問に対する簡単な答え:ログが印刷されすぎるか、トストリングと弦のスプライシングが正しく使用されない場合、ログ印刷はパフォーマンスに悪影響を及ぼします。どれくらいの大きさでしょうか?まあ、私は15分ごとにプログラムが再起動するのを見てきました。ログが多すぎるとスレッドが飢えて死ぬからです。これが副作用です!私の経験から、1時間で100メガバイトを印刷することはほぼ上限です。
もちろん、ログ印刷の例外のためにビジネスプロセスが中止された場合、この副作用は大きくなります。私はこれを避けるためにこれを書いている人々をよく見る:
try {log.trace( "id =" + request.getUser()。getId() + "Access" + manager.getPage()。getUrl()。toString())} catch(nullpointerexception e){}これは本当のコードですが、世界をより浄化するために、このように書かないでください。
明確に説明してください
各ログレコードには、データと説明が含まれています。この例を見てください:
log.debug( "message processed"); log.debug(message.getjmsmessageid()); log.debug( "byd '{}' processed"、message.getjmsmessageid());なじみのないシステムでエラーをトラブルシューティングするとき、どのようなログを見たいですか?私を信じてください、これらの例は一般的です。負のモードもあります。
if(textmessageのメッセージインスタンス)
// ...
それ以外
log.warn( "不明なメッセージタイプ");
この警告ログにメッセージタイプ、メッセージIDなどを追加することは難しいですか?エラーが発生したことは知っていますが、それは何ですか?コンテキスト情報は何ですか?
3番目の否定的な例は「魔法のログ」です。実際の例:チームの多くのプログラマーは、3≥数字が続くことを知っています!数字の後に#番号が続き、それに続く擬似ランダム番号ログが続きます。「ID xyzを含むメッセージが受信されました」を意味します。誰もこのログを変更したくありません。誰かがキーボードを入力し、一意の「&&!#」文字列を選択した場合、彼はすぐに自分が望む情報を見つけます。
その結果、ログファイル全体がランダム文字の大きな文字列のように見えます。一部の人々は、これがPERLプログラムであるかどうか疑問に思わずにはいられません。
ログファイルは、読みやすく、明確で、自称する必要があります。マジック番号、レコード値、数字、ID、およびそのコンテキストを使用しないでください。処理されたデータとその意味を記録します。プログラムが何をしているかを記録します。適切なログは、プログラムコードの優れたドキュメントである必要があります。
パスワードを印刷せず、個人情報を持っていると言及しましたか?そのような愚かなプログラマーはいないと思います。
フォーマットを調整します
ログ形式は非常に便利なツールであり、ログに貴重なコンテキスト情報を目に見えて追加します。ただし、どのような情報がフォーマットに含まれているかについて明確に考える必要があります。たとえば、ログ名にはすでにこの情報が含まれているため、1時間ごとのサイクルごとに記述されたログに日付を記録することは意味がありません。それどころか、スレッド名を記録しない場合、2つのスレッドが並行して動作する場合、ログを介してスレッドを追跡することはできません - ログは一緒にオーバーラップされています。シングルスレッドアプリケーションでは、これを行うことは問題ありませんが、それはすでに過去のことです。
私の経験から、理想的なログ形式には(もちろん、ログ情報自体を除く):現在の時刻(日付なし、ミリ秒の精度)、ログレベル、スレッド名、単純なログ名(フルネームではなく)、メッセージを含める必要があります。ログバックでは、次のようになります。
<appender name = "stdout"> <encoder> <pattern>%d {hh:mm:ss.sss}%-5level [%thread] [%logger {0}]%m%n </pattern> </encoder> </appender>ファイル名、クラス名、行番号、リストする必要はありませんが、便利ですが、リストする必要はありません。また、コードの空のログが表示されています。
log.info( "");プログラマーは、行番号がログ形式の一部になると考えており、ファイルの行67に空のログメッセージが表示された場合、ユーザーが認証されていることを意味することを知っています。それだけでなく、クラス名のメソッド名、または行番号を記録することは、パフォーマンスに大きな影響を与えます。
ロギングフレームワークのより高度な機能は、マッピングされた診断コンテキストです。 MDCは、スレッドにローカルのマップにすぎません。このマップにキー価値のペアを配置して、このスレッドのすべてのログレコードが出力形式の一部としてこのマップから対応する情報を取得できるようにすることができます。
メソッドのパラメーターと戻り値を記録します
開発段階でバグを見つけた場合、通常、デバッガーを使用して特定の理由を追跡します。ここで、デバッガーはもう使用しないとしましょう。たとえば、このバグは数日前にユーザー環境に表示されていたため、手に入れることができるのはいくつかのログだけです。これから何を見つけることができますか?
各メソッドの内外パラメーターの印刷の単純な原則に従う場合、デバッガーはまったく必要ありません。もちろん、各方法は外部システム、ブロック、待機などにアクセスする場合があり、これらを考慮する必要があります。次の形式を参照してください。
public string printdocument(document doc、モードモード){log.debug( "printdocument(doc = {}、mode = {})"、doc、mode);文字列id = ...; //長い印刷操作log.debug( "printdocument():{}"、id); IDを返す;}メソッドの最初と終わりにログをログに記録するため、非効率的なコードを手動で見つけることができ、デッドロックや飢erを引き起こす可能性のある原因を検出することさえできます。メソッド名の意味が明確な場合、ログをクリアするのは楽しいことです。同様に、各ステップで何をしているのかを知っているため、例外を分析する方が簡単です。コードに記録する多くの方法がある場合は、AOPセクションを使用してそれを完了できます。これにより重複コードが削減されますが、使用する際には非常に注意する必要があります。注意しないと、ログ出力が大量に発生する可能性があります。
この種のログに最適なレベルは、デバッグとトレースです。メソッドがあまりにも頻繁に呼び出され、ログを記録することがパフォーマンスに影響を与える可能性があることがわかった場合、ログレベルを下げるか、ログを直接削除する必要があります(またはメソッド呼び出し全体の1つだけ)。ロギングを単体テストと考えてください。単体テストがどこにでもあるのと同じように、コードはログでカバーする必要があります。システムの一部にはまったくログが必要ありません。システムが適切に機能しているかどうかを知る必要があることを忘れないでください。また、画面に絶えずあふれているログのみを表示できます。
外部システムを観察します
この提案は、以前のシステムとは少し異なります。外部システムと通信している場合は、システムの発信データと読み取りデータを記録することを忘れないでください。システムの統合は雑用であり、2つのアプリケーション間の問題の診断(さまざまな企業、環境、技術チームを想像してください)が特に困難です。最近、Apache CXFのSOAPやHTTPヘッダーを含む完全なメッセージコンテンツの記録が、システムの統合およびテスト段階で非常に効果的であることがわかりました。
これは高価であり、パフォーマンスに影響を与える場合、ログのみをオフにすることができます。しかし、このようにして、あなたのシステムは非常に迅速に実行され、すぐに吊るされる可能性があるので、あなたはそれについて何もすることができませんか?外部システムと統合する場合、あなたは特別な注意を払うことしかできず、頭上を犠牲にするように準備することができます。幸運であり、システム統合がESBによって処理された場合、バスでリクエストと応答を記録することをお勧めします。ラバのこのログコンポーネントを参照できます。
外部システムと交換されるデータの量が、すべてを書き留めることが不可能であると判断する場合があります。一方、テストフェーズおよび早期リリース段階の間、すべてをログ内に保持し、パフォーマンスを犠牲にする準備をすることが最善です。これは、ログレベルを調整することで実行できます。次のヒントを見てください。
collection <integer> requestIds = // ... if(log.isdebugenabled())log.debug( "processing id:{}"、requestIds); else log.info( "processing ids size:{}"、requestids.size());このロガーがデバッグレベルに構成されている場合、リクエストIDの完全なコレクションを印刷します。情報情報を印刷するように構成されている場合、セットのサイズのみを出力します。 IsInfoEnabledの状態を忘れているかどうかを尋ねることができます。2番目の提案をご覧ください。ここで注目に値するもう1つのことは、IDのセットがnullにならないということです。 nullとしてデバッグの下で正常に印刷できますが、情報として構成された場合、大きなヌルポインターです。第4回の提案で言及されている副作用を覚えていますか?
正しいロギングの例外
まず、例外を記録しないでください。フレームワークまたはコンテナにこれを行わせます。もちろん、例外があります。リモートサービスから例外(RMI、EJBなど)をスローする場合、例外はシリアル化され、クライアントに返すことができることを確認します(APIの一部)。それ以外の場合、クライアントは、実際のエラーメッセージの代わりに、NoclassDeffounderrorまたは他の奇妙な例外を受け取ります。
例外記録はロギングの最も重要な責任の1つですが、多くのプログラマーは例外を処理する方法としてロギングを使用する傾向があります。通常、デフォルト値(通常はnull、0または空の文字列)を返すだけで、何も起こらなかったふりをします。時々、彼らは最初に例外を記録し、次に例外をラップしてから捨てます。
log.Error( "IO例外"、e);新しいCustomexception(e)を投げる;
これにより、MyCustomexceptionの例外がキャッチされる場所が再び印刷されるため、スタック情報は通常2回印刷されます。ログレコード、またはラップして捨てて、同時に使用しないでください。そうしないと、ログが混乱します。
本当にログをしたい場合はどうなりますか?何らかの理由で(おそらくAPIやドキュメントを読んでいないのでしょうか?)、ロギングの約半分は間違っていると思います。小さなテストは、次のログステートメントのうち、Null Pointerの例外を正しく印刷できるものはどれですか?
{integer x = null; ++ x;} catch(例外e){log.error(e); // log.error(e、e); // b log.error( "" + e); // c log.error(e.tostring()); // d log.error(e.getmessage()); // e log.error(null、e); // f log.error( ""、e); // g log.error( "{}"、e); // h log.error( "{}"、e.getmessage()); // i Log.Error( "ERROR読み取り構成ファイル:" + e); // j log.Error( "エラー読み取り構成ファイル:" + e.getMessage()); // k log.error( "ERROR読み取り構成ファイル"、e); // l}GとL(これが良い)だけが正しいのは奇妙です! AとBは、SLF4Jの下でまったくコンパイルされていません。その他は、スタックトレース情報を捨てるか、誤った情報を印刷します。たとえば、nullポインターの例外自体が例外情報を提供せず、スタック情報が印刷されていないため、Eは何も印刷しません。最初のパラメーターは通常、エラー自体に関するテキスト情報であることを忘れないでください。その中に例外情報を記述しないでください。スタック情報の前で、ログを印刷した後に自動的に出てきます。ただし、これを印刷したい場合は、もちろん例外を2番目のパラメーターに渡す必要があります。
ログは読みやすく、解析しやすくする必要があります
現在、あなたのログに興味を持っているユーザーの2つのグループがあります。私たち人間(あなたが同意するかどうかにかかわらず、コーダーがここにいる)とコンピューター(通常、システム管理者によって書かれたシェルスクリプト)です。ログは、両方のユーザーが理解するのに適している必要があります。誰かがあなたのプログラムのログをあなたの後ろに見て、これを見た場合:
それからあなたは間違いなく私のアドバイスに従わなかった。ログは、コードと同じくらい読みやすく理解しやすい必要があります。
一方、プログラムが1時間ごとに半GBのログを生成した場合、グラフィカルテキストエディターがそれらを完了できません。この時点で、私たちの老人、グレップ、セド、awkは、彼らがフィールドに来たときに来ました。可能であれば、記録したログが両方のコンピューターにそれを明確にするのが最適です。フォーマット番号を付けず、通常のマッチング可能な形式などを使用します。不可能な場合は、2つの形式でデータを印刷します。
log.debug( "要求TTL SET:{}({{})"、new Date(ttl)、ttl); //要求TTL設定:4月28日20:14:12 CEST 2010(1272478452437)最終文字列duration = durationformatutilswords {} ms({{}) "、durationmillis、duration); //インポートテイク:123456789ms(1日10時間17分36秒)コンピューターは「1970年以降のMS」を見るこのような時間形式はあなたに感謝しますが、人々は「1日10時間17分36秒」のようなものを見て喜んでいます。
要するに、ジャーナルは、あなたがそれについて考えても構わないと思うなら、詩のようにエレガントに書くこともできます。
上記は、Javaログ調整出力形式に関する情報のコレクションです。興味のある友達はそれを参照できます。