現在作業中のアプリケーションにロギングを追加したい。以前にロギングを追加しましたが、ここでは問題ではありません。
しかし、オブジェクト指向言語の設計の観点から、OOPとパターンに従うロギングのベストプラクティスは何ですか?
注:私は現在C#でこれを行っているので、C#の例は明らかに歓迎されています。また、JavaとRubyの例をご覧ください。
編集:私はlog4netを使用しています。プラグインするのに最適な方法がわからないだけです。
現在作業中のアプリケーションにロギングを追加したい。以前にロギングを追加しましたが、ここでは問題ではありません。
しかし、オブジェクト指向言語の設計の観点から、OOPとパターンに従うロギングのベストプラクティスは何ですか?
注:私は現在C#でこれを行っているので、C#の例は明らかに歓迎されています。また、JavaとRubyの例をご覧ください。
回答:
私が働いている場所では、多くの.NETデスクトップアプリを作成しています。通常、コンポーネントに2つのイベントを実装します。1つは情報のロギング用で、もう1つは例外のロギング用です(ただし、個別のイベントを発生させる代わりに例外を発生させます。状況によって異なります)。このアーキテクチャを使用すると、どのライブラリもロギングの実装方法や情報の使用方法、保存方法、処理方法を知る必要がありません。次に、アプリケーションに適切な方法でロギングイベントを処理させます。数年前、このアーキテクチャにより、MS Enterprise Libraryのログ作成からBitFactoryのログ作成コンポーネントへの切り替えが非常に簡単になりました。
個人的には、選択したロギングフレームワーク(私の場合は.NETで作業しているためEntlib)を使用し、ロギング用のAOPアスペクトを作成します。
その後、メソッド/プロパティ/クラス/名前空間を属性付けし、ソースを乱雑にすることなくそれらにロギングを追加できます。
私が現在取り組んでいるシステムは、イベント駆動型のアーキテクチャとメッセージングを使用しているため、システム内のほとんどのアクションはコマンドの結果であり、イベント(標準のデリゲートイベントではなく、ディスパッチされるDTOクラスとして)になります。ロギングの処理を唯一の目的とするイベントハンドラを添付します。この設計により、自分自身を繰り返す必要がなくなり、既存のコードを変更して機能を追加/変更する必要もなくなります。
以下に、アプリケーションの狭いセクション(インポート元の特定のコンテンツソースに関するもの)からすべてのイベントを記録するログクラスの例を示します。
頻繁に何をどのように記録するかについて考えを変えるように見えるため、これが必ずしもベストプラクティスであるとは言いません。ログを使用して問題を診断する必要があるたびに、記録する情報。
ただし、適切な情報を(特にCtrl-F /検索可能な方法で)記録することが最も重要な部分です。
2番目に重要な部分は、ロギングコードをメインロジックから遠ざけることです。これにより、メソッドがく長くなり、非常に複雑になります。
public class MctLogger :
IEventHandler<StoryImported>,
IEventHandler<StoryScanned>,
IEventHandler<SourceDirectoryMissing>,
IEventHandler<SourceDirectoryAccessError>,
IEventHandler<CannotCreateScannedStoryDirectory>,
IEventHandler<CannotReadStoryDocument>,
IEventHandler<StorySkippedPastCutoff>,
IEventHandler<StorySkippedDuplicateUniqueId>,
IEventHandler<StorySkippedByFilter>
{
public void Observe(StoryImported e)
{
var log = Slf.LoggerService.GetLogger("RoboChef.Content.Mct.StoryImported");
log.Info("Story Unique ID: {Story.UniqueId}, Content ID: {ContentId}, Title: {Story.Headline}".SmartFormat(e));
}
public void Observe(StoryScanned e)
{
var log = Slf.LoggerService.GetLogger("RoboChef.Content.Mct.StoryScanned");
log.Info("Story Unique ID: {Story.UniqueId}, File: {FilePath}, Title: {Story.Headline}".SmartFormat(e));
}
public void Observe(SourceDirectoryMissing e)
{
var log = Slf.LoggerService.GetLogger("RoboChef.Content.Mct.SourceDirectoryMissing");
log.Error("Directory: " + e.Directory);
}
public void Observe(SourceDirectoryAccessError e)
{
var log = Slf.LoggerService.GetLogger("RoboChef.Content.Mct.SourceDirectoryAccessError");
log.Error(e.Exception, "Exception: " + e.Exception.Message);
}
public void Observe(CannotCreateScannedStoryDirectory e)
{
var log = Slf.LoggerService.GetLogger("RoboChef.Content.Mct.CannotCreateScannedStoryDirectory");
log.Error(e.Exception, "Directory: {Directory}, Exception: {Exception.Message}".SmartFormat(e));
}
public void Observe(CannotReadStoryDocument e)
{
var log = Slf.LoggerService.GetLogger("RoboChef.Content.Mct.CannotReadStoryDocument");
if (e.Exception == null) {
log.Warn("File: {FilePath}".SmartFormat(e));
}
else {
log.Warn(e.Exception, "File: {FilePath}, Exception: {Exception.Message}".SmartFormat(e));
}
}
public void Observe(StorySkippedPastCutoff e)
{
var log = Slf.LoggerService.GetLogger("RoboChef.Content.Mct.StorySkippedPastCutoff");
log.Warn("Story Unique ID: {Story.UniqueId}, File: {FilePath}, Title: {Story.Headline}".SmartFormat(e));
}
public void Observe(StorySkippedDuplicateUniqueId e)
{
var log = Slf.LoggerService.GetLogger("RoboChef.Content.Mct.StorySkippedDuplicateUniqueId");
log.Warn("Story Unique ID: {Story.UniqueId}, File: {FilePath}, Title: {Story.Headline}".SmartFormat(e));
}
public void Observe(StorySkippedByFilter e)
{
var log = Slf.LoggerService.GetLogger("RoboChef.Content.Mct.StorySkippedByFilter");
log.Warn("Story Unique ID: {Story.UniqueId}, Reason: {Reason}, File: {FilePath}, Title: {Story.Headline}".SmartFormat(e));
}
}
他の人が言ったようにlog4j
、log4net
または他の適切に構築されたロギングフレームワークを使用します。
私は、コードのロギングがビジネスロジックの邪魔になることを本当に嫌う傾向があります。それが私が使用する理由ですLog4PostSharp
。つまり、アスペクト指向プログラミングを使用して、次のようなメソッドに注釈を付けることができます。
[Log(LogLevel.Info, "Counting characters.")]
int CountCharacters(string arg)
{
return arg.Length;
}
または、次のようなアセンブリのすべてのメソッド:
[assembly: Log(AttributeTargetTypes = "*",
EntryLevel = LogLevel.Debug, ExitLevel = LogLevel.Debug,
ExceptionLevel = LogLevel.Error)]
フレームワークがこれを行うかどうかはわかりませんが、設計の観点から、主に3つのカテゴリにログインする必要がある情報をモデル化します。
最初の2つのカテゴリについては、私の理想的なロギングフレームワークは、ポストビルドプロセスとして開発者に透過的に処理する必要があります。次のように、ロギングをアセンブリに宣言的に追加すると便利です。
Trace YourNamespace.* [public methods|constructors]
{ # options
ignore trivial methods,
format: "{time stamp}: {method name}({parameter list})",
condition: "{Context.Instance.UserID in (12432,23432)}",
}
Exception YourNamespace.Program.Main [unhandled exceptions]
{
format: "{time stamp}: {Context.Instance.UserId} {exception}",
action: die, # options are throw, swallow,
}
3番目のカテゴリでは、プログラマは1つ以上の専用の「ロギング」メソッドを作成し、最初のカテゴリのトレースを活用できます。ロギングメソッドは、トレースルールを適用できるスタブポイントを提供するだけです。