このトピックはこの記事の続きとして書かれており、主に初心者のプログラマーに役立ちます。
ここでは、次の問題を強調します。
- ロガーの内部プロパティとそれを使用するソフトウェアの例。
- ログメッセージのボリューム、レベル、および詳細。
- 開発中、戦闘中、および調査中の一般的な構成ルール。
したがって、前の記事への追加から始めます。
著者と同様に、私はNLogを使用し、もちろん、その機能を広く使用しています。 もちろん後
他のロガーでの実装では、以下で説明する方法を適用できます。
ところで、log4netは進化し続けています 。
実際には、「立派なロガーの機能」に必要な追加は2つだけです。
- 構成ファイルを観察/再読み込みすることは、単なる有用なスキルではなく、非常に必要なスキルです。
- オフ状態での最小限の介入。
ボンネットの下のNLog
すぐに2番目の機能の有用性について説明します。
多くの場合、コードの開発時には、実行中に変数の値を調べることが必要になります。 通常、デバッガーを使用して、目的の場所でプログラムを停止します。 私にとって、これはTraceの結論がこの場所で役立つという明確な兆候です。 単体テストを完了すると、すぐにこの変数のスキャンと、他の条件のテストと比較するためのプロトコルが取得されます。 したがって、私は実質的にデバッガーを使用しません。
明らかに、軍事用途では、詳細なログ記録をオフにしても、実行速度と並列処理の両方に干渉する可能性があります。
NLogは次の手法を使用します。
- 揮発性変数のログレベルキャッシュ。 これには、ブロッキングまたはスレッドの同期は必要ありません。
- ロギングの必要性を見つける簡単な方法 状況によっては、jitコンパイル中にコードがインラインで挿入される可能性があります。 そして、これにより、ロジックがコードのセクションの単純な条件付きスキップに変わり、パラメーターをコピーしてロギング関数に渡す必要がなくなります。
- ロギングメソッドの詳細なオーバーロード。 ロギングの必要性を知る前に、型変換を最小限に抑え、無駄なボクシングを避けてください。
- デリゲートによる遅延メッセージ生成。 複雑なメッセージ生成を明確かつ簡単に設定できます。
クラスのソースコードはこちらにあります 。
いいね! NLogの場合、任意の詳細なメッセージを無効にできることを確認できます。これにより、パフォーマンスへの影響は最小限になります。 しかし、これはコードの半分をロギングに費やす理由ではありません。
何をどのように記録するか
ルールに従う必要があります。
- ログへの出力は別の行に配置する必要があります。
- メッセージはできるだけ短く、有益なものにする必要があります。
- ローカライズできるのは、まれなメッセージまたは致命的なメッセージのみです。
- メソッドのローカル変数または特定のメソッドの内部クラス変数から引数を取ることをお勧めします。
- 型変換を使用することはお勧めできません。
一般に、ログに出力するときは、ログが無効になっている場合に必要となる可能性のある不要な計算の数に常に注意してください。
簡単な例(一部のクラスのフラグメント):
private static Logger Log = LogManager. GetCurrentClassLogger ( ) ; <br/>
<br/>
public string Request ( string cmd, string getParams ) <br/>
{ <br/>
Uri uri = new Uri ( _baseUri, cmd + "?" + getParams ) ; <br/>
Log. Debug ( "Request for uri:`{0}'" , uri ) ; <br/>
HttpWebRequest webReq = ( HttpWebRequest ) WebRequest. Create ( uri ) ; <br/>
webReq. Method = "GET" ; <br/>
webReq. Timeout = _to ; <br/>
<br/>
string respText ; <br/>
try <br/>
{ <br/>
string page ; <br/>
using ( WebResponse resp = webReq. GetResponse ( ) ) <br/>
using ( Stream respS = resp. GetResponseStream ( ) ) <br/>
using ( StreamReader sr = new StreamReader ( respS ) ) <br/>
page = sr. ReadToEnd ( ) ; <br/>
Log. Trace ( "Response page:`{0}'" , page ) ; <br/>
return page ; <br/>
} <br/>
catch ( Exception err ) <br/>
{ <br/>
Log. Warn ( "Request for uri:`{0}' exception: {1}" , uri, err. Message ) ; <br/>
throw ; <br/>
} <br/>
}
private static Logger Log = LogManager. GetCurrentClassLogger ( ) ; <br/>
<br/>
public string Request ( string cmd, string getParams ) <br/>
{ <br/>
Uri uri = new Uri ( _baseUri, cmd + "?" + getParams ) ; <br/>
Log. Debug ( "Request for uri:`{0}'" , uri ) ; <br/>
HttpWebRequest webReq = ( HttpWebRequest ) WebRequest. Create ( uri ) ; <br/>
webReq. Method = "GET" ; <br/>
webReq. Timeout = _to ; <br/>
<br/>
string respText ; <br/>
try <br/>
{ <br/>
string page ; <br/>
using ( WebResponse resp = webReq. GetResponse ( ) ) <br/>
using ( Stream respS = resp. GetResponseStream ( ) ) <br/>
using ( StreamReader sr = new StreamReader ( respS ) ) <br/>
page = sr. ReadToEnd ( ) ; <br/>
Log. Trace ( "Response page:`{0}'" , page ) ; <br/>
return page ; <br/>
} <br/>
catch ( Exception err ) <br/>
{ <br/>
Log. Warn ( "Request for uri:`{0}' exception: {1}" , uri, err. Message ) ; <br/>
throw ; <br/>
} <br/>
}
すべてのロギング引数はロジックに必要でした。 デバッグメッセージは、関数に到達した引数をマークします。 エラーハンドラーでは、 デバッグレベルが無効になっている場合に入力パラメーターを複製します。 そして、これはユニットテストを書くために必要であれば既に情報を提供します。 これをより高いレベルのハンドラーにすることは可能なため、例外スタックはスローしません。
一般に、現在のエラーハンドラーでは、例外の原因となったコンテキストと例外の特定の機能を詳細に説明すると便利です。 この例では、 WebExceptionケースのStatusフィールドを表示すると便利です。
ログセーフガード
NLogの自動ログ機能の一部にもかかわらず、プロセスの終了時にログが安全であるという保証はありません。
興味深いことに、 AppDomain.ProcessExitイベントを処理して記録を完了しようとする試みは完全に正しいわけではありません。 この構成は、ネットワーク経由など、さまざまな方法でログに書き込むように構成できます。 そして、このイベントのハンドラーは限られた環境にあります。 .Netでは、この実行時間は2秒以下ですが、Monoでは停止したThreadPoolです。 したがって、より友好的な環境でプロセスを完了するように注意することが役立ちます。
最初に行うことは、 AppDomain.UnhandledExceptionイベントを処理することです。 その中で、完全なエラー情報がログに書き込まれ、 LogManager.Flush()が呼び出されます。 このイベントのハンドラーは、例外を引き起こしたのと同じスレッドを使用し、最後に、アプリケーションをすぐにアンロードします。
private static readonly Logger Log = LogManager. GetCurrentClassLogger ( ) ; <br/>
<br/>
public static void Main ( string [ ] args ) <br/>
{ <br/>
AppDomain. CurrentDomain . UnhandledException += OnUnhandledException ; <br/>
( ... ) <br/>
LogManager. Flush ( ) ; <br/>
} <br/>
<br/>
static void OnUnhandledException ( object sender, UnhandledExceptionEventArgs e ) <br/>
{ <br/>
Log. Fatal ( "Unhandled exception: {0}" , e. ExceptionObject ) ; <br/>
LogManager. Flush ( ) ; <br/>
}
private static readonly Logger Log = LogManager. GetCurrentClassLogger ( ) ; <br/>
<br/>
public static void Main ( string [ ] args ) <br/>
{ <br/>
AppDomain. CurrentDomain . UnhandledException += OnUnhandledException ; <br/>
( ... ) <br/>
LogManager. Flush ( ) ; <br/>
} <br/>
<br/>
static void OnUnhandledException ( object sender, UnhandledExceptionEventArgs e ) <br/>
{ <br/>
Log. Fatal ( "Unhandled exception: {0}" , e. ExceptionObject ) ; <br/>
LogManager. Flush ( ) ; <br/>
}
さらに、プロセスが終了する可能性がある場合はいつでもLogManager.Flush()を呼び出す必要があります。 すべての非バックグラウンドスレッドの最後。
アプリケーションがwin-serviceまたはAsp.Netである場合、対応するコードの開始イベントと終了イベントを処理する必要があります。
ログに記録する量
開発者にとって深刻な問題。 あなたは常により多くの情報を取得したいのですが、コードは非常に悪く見え始めます。 以下の考慮事項に導かれます。
ロギングは基本的にコメントです。 ほとんどの部分のトレースレベルロギングは、それらを置き換えます。
トレースレベルとデバッグレベルは開発者によって読み取られ、それよりも高いのはテクニカルサポートと管理者だけです。 したがって、 情報のレベルまで、メッセージは質問に正確に答える必要があります。「何が起こったのですか?」、「なぜ?」、そして可能であれば、「修正方法」 これは、構成ファイルのエラーに特に当てはまります。
ロギングレベルの定性的構成は前の記事ですでに分析されています。ここでは、定量的構成のみを考慮します。
- トレース -すべての結論。 デバッグがエラーのローカライズを許可しない場合。 さまざまなブロック操作および非同期操作の呼び出しに注意すると便利です。
- デバッグ -「大規模」操作の呼び出しの瞬間を記録します。 フロー、ユーザーリクエストなどの開始/停止
- 情報 -非常にまれにしか繰り返されないが、定期的ではない1回限りの操作。 (設定の読み込み、プラグイン、バックアップの開始)
- 警告 -予期しない呼び出しパラメーター、奇妙な要求形式、誤った値と引き換えにデフォルト値を使用。 一般に、非正規の使用を示す可能性のあるすべてのもの。
- エラー -開発者の注意の理由。 ここで、エラーの特定の場所の環境は興味深いです。
- 致命的 -ここで、それは明らかです。 アプリケーションはこれ以上動作しないため、到達可能なすべてのものを推測します。
戦闘展開
開発が実装に到達したとします。
ログのローテーション、ファイルサイズ、履歴の深さの問題は延期します。 これはすべて各プロジェクトに非常に固有のものであり、サービスの実際の動作に応じて構成されます。
ファイルのセマンティック編成についてのみ説明します。 それらは3つのグループに分けられるべきです。 モジュールのログを異なるファイルに分ける必要があるかもしれませんが、これからはグループごとに1つのファイルについて説明します。
- すべてのソースに適切なレベルの情報グループ。 これは管理者向けの情報です。 ここには、アプリケーションの起動時、構成が正しく読み取られているか、必要なサービスが利用可能かなどがあります。 その主なプロパティ:ファイルは、アプリケーションの再起動時にのみサイズ変更されます。 その過程で、ファイルは大きくなりません。 これにより、アプリケーションの起動の成功を自動で外部制御できます。 ファイルにキーワードErrorおよびFatalがないことを確認するだけで十分です。 検証には常に予測可能なほど短い時間がかかります。
- 警告グループ。 これは管理者向けの情報でもあります。 このファイルは、通常の操作中は存在しないか空である必要があります。 したがって、その状態を監視すると、すぐに誤動作が示されます。 さまざまなソースのフィルターを柔軟に調整することにより、一般的にサービスに注意を払う必要がある場合に、かなり正確な基準を選択できます。
- グループ観察 。 原則として、実装中にいくつかの問題モジュールが強調表示されます。 デバッグドリルダウンのそれらからの情報は、ここに送信されます。
アプリケーションが正常に実装されると、最初の2つのグループのみが動作し続けます。
衝突調査
実行中のサービスがエラーの兆候を示している場合、すぐに再起動しないでください。 おそらく、誤ったスレッド同期に関連するエラーをキャッチできるのは「幸運」です。 そして、次回の再発をどれだけ待つかは不明です。
まず最初に、監視グループの事前構成済み構成を接続する必要があります。 これにより、適切なロガーを作成できます。 新しい構成が正常に適用されたという確認を受け取ったとき、再び障害を引き起こそうとしています。 何度か望ましいです。 これは、「実験室」条件での複製の機会を提供します。 次はプログラマーの仕事です。 それまでの間、再起動できます。
ログへの出力を非同期にすることが望ましいです。
例、戦闘設定。
<nlog autoReload = "true" > <br/>
<targets > <br/>
<target name = "fileInfo" type = "AsyncWrapper" queueLimit = "5000" overflowAction = "Block" > <br/>
<target type = "File" fileName = "${basedir}/logs/info.log" /> <br/>
</target > <br/>
<target name = "fileWarn" type = "AsyncWrapper" queueLimit = "5000" overflowAction = "Block" > <br/>
<target type = "File" fileName = "${basedir}/logs/warn.log" /> <br/>
</target > <br/>
</targets > <br/>
<br/>
<rules > <br/>
<logger name = "*" minlevel = "Info" writeTo = "fileInfo" /> <br/>
<logger name = "*" minlevel = "Warn" writeTo = "fileWarn" /> <br/>
</rules > <br/>
</nlog >
<nlog autoReload = "true" > <br/>
<targets > <br/>
<target name = "fileInfo" type = "AsyncWrapper" queueLimit = "5000" overflowAction = "Block" > <br/>
<target type = "File" fileName = "${basedir}/logs/info.log" /> <br/>
</target > <br/>
<target name = "fileWarn" type = "AsyncWrapper" queueLimit = "5000" overflowAction = "Block" > <br/>
<target type = "File" fileName = "${basedir}/logs/warn.log" /> <br/>
</target > <br/>
</targets > <br/>
<br/>
<rules > <br/>
<logger name = "*" minlevel = "Info" writeTo = "fileInfo" /> <br/>
<logger name = "*" minlevel = "Warn" writeTo = "fileWarn" /> <br/>
</rules > <br/>
</nlog >
フィルターを設定するとき、各サブシステムのログレベルの相対性を考慮する必要があります。 たとえば、接続されている各ユーザーに対して、 Info初期化メッセージを持つモジュールを作成できます。 もちろん、 Infoグループへの出力は、 警告レベルに制限する必要があります。
ロガーですべきでないこと
ロガーは、ハンマーのようにシンプルで信頼できるものでなければなりません。 そして、特定のプロジェクトにおける彼の範囲を明確に定義する必要があります。 残念ながら、開発者は維持するのが難しいことがよくあります。 デザインパターン;これはほとんど役に立ちますが、この場合は役に立ちません。 かなり頻繁に、ロガーの汎用インターフェースを選択する提案( 例 )に気づき始め、後でNLogとlog4netを選択する苦労を延期するためにプロジェクトにラッパーを実装しました。
理由が何であれ、そもそもそのような設備はコンパイラーに最適化の可能性を完全に殺すことを忘れてはなりません。
フィルタを使用しても、ロガー情報をユーザーに直接表示する必要はありません。 問題は、この情報がプログラムの内部構造に依存することです。 リファクタリングのプロセスでロガーの出力を保存しようとすることはほとんどありません。 おそらく、機能を考えて共有するだけの価値があります。 おそらく、プロジェクトには別のレベルのロギングが必要なだけです。 この場合、ロガーのラッパーはまさに正しいものです。
NLogには何が欠けていますか?
- ラムダ関数のクラスの生成を回避するためのロガーメソッドの追加のオーバーロード 。 私はフォームの呼び出しを持ちたいです
Log.Trace <TArg1、TArg2>(Func <TArg1、TArg2、string> messageCreater、TArg1 arg1、TArg2 arg2)
しかし、現時点では、人間が読み取れる最も短いオプションには、隠しクラスの生成が含まれます。
Log.Trace(()=> MessageCreate(arg1、arg2)) - バイナリ互換性 さまざまなプラットフォームでサービスをすばやくテストするのに役立ちます。 NLogには、プラットフォームに応じて多くの条件付きコンパイル命令があります。 つまり MonoのバイナリはDotNetで予期せず動作する場合があります。 また、限られた構成であっても、予測可能性は非常に望ましいものです。
- 条件付き拡張性。 バイナリ互換性を使用する場合、機能を犠牲にする必要があることは明らかですが、便利な拡張メカニズムがすでにあります。 残っているのは、プラットフォームに応じて拡張機能をフィルタリングすることだけです。 前の機能と一緒に、ILバイナリを使用してディレクトリをコピーすることにより、簡単な展開も提供します。
- 一般的なコンテキストでの内部メッセージの記録。 システムで作成されたロガーのリストが役立ちます。 残念ながら、再帰を回避できるかどうかはわかりません。 たとえば、ファイルへの出力がそれ自体への出力エラーの書き込みを開始した場合。
NLog、Log4Net、Enterprise Library、SmartInspect ...
ロガー同士のさまざまな比較では、1つの重要な詳細を見逃しています。
制限/機会だけでなく、独自のウィッシュリストをすばやく追加する機能も比較することが重要です。
したがって、私は今のところNLogと友達になります。
私があなたに望むこと。