ロギングアーキテクチャ

私の開発経験は、主にWindowsおよびLinux用のさまざまなネットワークサービスを中心に構築されています。 私は通常、バイナリ互換性まで最大のクロスプラットフォームを達成するよう努めています。 そしてもちろん、ロギングに関連する多くの安定したソリューションが蓄積されています。

このトピックはこの記事の続きとして書かれており、主に初心者のプログラマーに役立ちます。

ここでは、次の問題を強調します。



したがって、前の記事への追加から始めます。
著者と同様に、私はNLogを使用し、もちろん、その機能を広く使用しています。 もちろん後
他のロガーでの実装では、以下で説明する方法を適用できます。

ところで、log4netは進化し続けています

実際には、「立派なロガーの機能」に必要な追加は2つだけです。
  1. 構成ファイルを観察/再読み込みすることは、単なる有用なスキルではなく、非常に必要なスキルです。
  2. オフ状態での最小限の介入。


ボンネットの下のNLog


すぐに2番目の機能の有用性について説明します。

多くの場合、コードの開発時には、実行中に変数の値を調べることが必要になります。 通常、デバッガーを使用して、目的の場所でプログラムを停止します。 私にとって、これはTraceの結論がこの場所で役立つという明確な兆候です。 単体テストを完了すると、すぐにこの変数のスキャンと、他の条件のテストと比較するためのプロトコルが取得されます。 したがって、私は実質的にデバッガーを使用しません。

明らかに、軍事用途では、詳細なログ記録をオフにしても、実行速度と並列処理の両方に干渉する可能性があります。

NLogは次の手法を使用します。


クラスのソースコードはこちらにあります

いいね! 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である場合、対応するコードの開始イベントと終了イベントを処理する必要があります。

ログに記録する量


開発者にとって深刻な問題。 あなたは常により多くの情報を取得したいのですが、コードは非常に悪く見え始めます。 以下の考慮事項に導かれます。

ロギングは基本的にコメントです。 ほとんどの部分のトレースレベルロギングは、それらを置き換えます。
トレースレベルとデバッグレベルは開発者によって読み取られ、それよりも高いのはテクニカルサポートと管理者だけです。 したがって、 情報のレベルまでメッセージは質問に正確に答える必要があります。「何が起こったのですか?」、「なぜ?」、そして可能であれば、「修正方法」 これは、構成ファイルのエラーに特に当てはまります。

ロギングレベルの定性的構成は前の記事ですでに分析されています。ここでは、定量的構成のみを考慮します。


戦闘展開



開発が実装に到達したとします。
ログのローテーション、ファイルサイズ、履歴の深さの問題は延期します。 これはすべて各プロジェクトに非常に固有のものであり、サービスの実際の動作に応じて構成されます。

ファイルのセマンティック編成についてのみ説明します。 それらは3つのグループに分けられるべきです。 モジュールのログを異なるファイルに分ける必要があるかもしれませんが、これからはグループごとに1つのファイルについて説明します。

  1. すべてのソースに適切なレベルの情報グループ。 これは管理者向けの情報です。 ここには、アプリケーションの起動時、構成が正しく読み取られているか、必要なサービスが利用可能かなどがあります。 その主なプロパティ:ファイルは、アプリケーションの再起動時にのみサイズ変更されます。 その過程で、ファイルは大きくなりません。 これにより、アプリケーションの起動の成功を自動で外部制御できます。 ファイルにキーワードErrorおよびFatalがないことを確認するだけで十分です。 検証には常に予測可能なほど短い時間がかかります。
  2. 警告グループ。 これは管理者向けの情報でもあります。 このファイルは、通常の操作中は存在しないか空である必要があります。 したがって、その状態を監視すると、すぐに誤動​​作が示されます。 さまざまなソースのフィルターを柔軟に調整することにより、一般的にサービスに注意を払う必要がある場合に、かなり正確な基準を選択できます。
  3. グループ観察 。 原則として、実装中にいくつかの問題モジュールが強調表示されます。 デバッグドリルダウンのそれらからの情報は、ここに送信されます。


アプリケーションが正常に実装されると、最初の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には何が欠けていますか?



NLog、Log4Net、Enterprise Library、SmartInspect ...


ロガー同士のさまざまな比較では、1つの重要な詳細を見逃しています。
制限/機会だけでなく、独自のウィッシュリストをすばやく追加する機能も比較することが重要です。

したがって、私は今のところNLogと友達になります。
私があなたに望むこと。

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


All Articles