ロガーラッパーのベストプラクティス


91

アプリケーションでnloggerを使用したいのですが、将来的には、ログシステムを変更する必要があります。だから私はロギングファサードを使いたいです。

既存の例の推奨事項を、それらの記述方法を知っていますか?または、この分野のベストプラクティスへのリンクを教えてください。


4
それはすでに存在しています:netcommon.sourceforge.net
R.


回答:


207

Common.Loggingなどのロギングファサードを使用していました(自分のCuttingEdge.Loggingライブラリを非表示にする場合でも)が、現在は依存関係注入パターンを使用しているため、両方の依存関係に準拠する独自の(単純な)抽象化の背後にロガーを非表示にできます逆転の原理インターフェース分離の原理(ISP)メンバーが1つであり、インターフェースがアプリケーションによって定義されているため。外部ライブラリではありません。アプリケーションのコア部分が外部ライブラリの存在について持っている知識を最小限に抑えることは、より良いことです。ロギングライブラリを置き換える意図がない場合でも。外部ライブラリへの依存度が高いため、コードのテストが難しくなり、アプリケーション用に特別に設計されたことのないAPIでアプリケーションが複雑になります。

これは、私のアプリケーションで抽象化がよく見えるものです。

public interface ILogger
{
    void Log(LogEntry entry);
}

public enum LoggingEventType { Debug, Information, Warning, Error, Fatal };

// Immutable DTO that contains the log information.
public class LogEntry 
{
    public readonly LoggingEventType Severity;
    public readonly string Message;
    public readonly Exception Exception;

    public LogEntry(LoggingEventType severity, string message, Exception exception = null)
    {
        if (message == null) throw new ArgumentNullException("message");
        if (message == string.Empty) throw new ArgumentException("empty", "message");

        this.Severity = severity;
        this.Message = message;
        this.Exception = exception;
    }
}

必要に応じて、この抽象化をいくつかの単純な拡張メソッドで拡張できます(インターフェイスが狭いままで、ISPに準拠し続けることができます)。これにより、このインターフェースのコンシューマー向けのコードがはるかに単純になります。

public static class LoggerExtensions
{
    public static void Log(this ILogger logger, string message) {
        logger.Log(new LogEntry(LoggingEventType.Information, message));
    }

    public static void Log(this ILogger logger, Exception exception) {
        logger.Log(new LogEntry(LoggingEventType.Error, exception.Message, exception));
    }

    // More methods here.
}

インターフェイスには単一のメソッドしか含まれていないため、log4netSerilogMicrosoft.Extensions.Logging、NLog、またはその他のログライブラリのプロキシとなるILogger実装を簡単に作成し、DIコンテナーを構成して、コンストラクタ。ILogger

単一のメソッドを持つインターフェースの上に静的な拡張メソッドを持つことは、多くのメンバーを持つインターフェースを持つこととはかなり異なることに注意してください。拡張メソッドは、LogEntryメッセージを作成し、それをILoggerインターフェース上の唯一のメソッドに渡すヘルパーメソッドにすぎません。拡張メソッドは、消費者のコードの一部になります。抽象化の一部ではありません。これにより、抽象化、拡張メソッド、およびLogEntryロガーがスタブ化またはモック化されている場合でも、ロガー抽象化が使用されると、コンストラクターは常に実行されます。これにより、テストスイートで実行した場合のロガーへの呼び出しの正確さがより確実になります。1メンバーのインターフェースにより、テストもはるかに簡単になります。多くのメンバーを持つ抽象化があると、実装(モック、アダプター、デコレーターなど)の作成が難しくなります。

これを行う場合、ロギングファサード(またはその他のライブラリ)が提供する静的な抽象化はほとんど必要ありません。


4
@GabrielEspinoza:それは完全に名前空間に依存します。拡張メソッドを配置します。インターフェースと同じ名前空間またはプロジェクトのルート名前空間に配置すると、問題は発生しません。
Steven

2
@ user1829319これは単なる例です。この答えに基づいて、特定のニーズに合った実装を考え出すことができると思います。
スティーブン

2
私はまだそれを取得できません... ILoggerの拡張として5つのLoggerメソッドがあり、ILoggerのメンバーではないことの利点はどこですか?
エリザベス

3
@Elisabeth利点は、「ILogger :: Log」という単一の関数を実装するだけで、ファサードインターフェイスを任意のログフレームワークに適合できることです。拡張メソッドにより、使用するフレームワークに関係なく、「便利な」API(「LogError」、「LogWarning」など)に確実にアクセスできます。これは、C#インターフェイスを使用しているにもかかわらず、一般的な「基本クラス」機能を追加するための遠回りの方法です。
BTownTKD 2016年

2
これが素晴らしい理由をもう一度強調する必要があります。DotNetFrameworkからDotNetCoreへのコードのアップコンバート。私がこれを行ったプロジェクトでは、新しいコンクリートを1つ書くだけで済みました。私がしなかったもの.... gaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa!私はこの「帰り道」を見つけてよかったです。
granadaCoder 2018年


8

現在、最善の策は、Microsoft.Extensions.Loggingパッケージを使用することです(Julianによって指摘されています)。ほとんどのロギングフレームワークはこれで使用できます。

Stevenの回答で説明されているように、独自のインターフェースを定義することは、単純なケースでは問題ありませんが、私が重要だと考えるいくつかのことを欠いています。

  • 構造化されたロギングおよび構造化解除オブジェクト(SerilogおよびNLogの@表記)
  • 遅延文字列の構築/フォーマット:文字列を取得するため、呼び出されたときにすべてを評価/フォーマットする必要があります。最終的にはしきい値を下回ったためにイベントがログに記録されません(パフォーマンスコスト、前のポイントを参照)。
  • IsEnabled(LogLevel)パフォーマンス上の理由から、希望する条件付きチェックをもう一度

おそらく、これらすべてを独自の抽象化で実装できますが、その時点で車輪を再発明することになります。


4

一般的に私は次のようなインターフェースを作成することを好みます

public interface ILogger
{
 void LogInformation(string msg);
 void LogError(string error);
}

ランタイムでは、このインターフェイスから実装される具象クラスを注入します。


11
LogWarningおよびLogCriticalメソッドとそのすべてのオーバーロードを忘れないでください。これを行うと、インターフェース分離の原則に違反します。ILogger単一のLogメソッドでインターフェースを定義することをお勧めします。
Steven

2
すみません、それは私の意図ではありませんでした。恥ずかしがる必要はありません。多くの開発者が人気のあるlog4net(この正確な設計を使用する)を例として使用しているため、この設計は非常によく見られます。残念ながら、そのデザインは本当に良くありません。
スティーブン

2
@Stevenの答えよりも私はこれを好みます。彼はへの依存LogEntry、つまりへの依存を導入していLoggingEventTypeます。ILogger実装は、これらに対処しなければならないLoggingEventTypesだろうが、case/switchである、コードのにおい。なぜLoggingEventTypes依存関係を隠すのですか?実装はとにかくログレベルを処理する必要があるため、一般的な引数を持つ単一のメソッドの後ろにそれを隠すよりも、実装が何をすべきかについて明示する方が良いでしょう。
DharmaTurtle 2017

1
極端な例として、ICommandHandleをとるを想像してくださいobject。実装はcase/switch、インターフェースのコントラクトを満たすために、可能なタイプを超える必要があります。これは理想的ではありません。とにかく処理する必要のある依存関係を隠す抽象化はありません。代わりに、何が期待されているかを明確に示すインターフェイスを用意します。「すべてのロガーが警告、エラー、致命的エラーなどを処理することを期待しています」。これは「すべてのロガー警告、エラー、致命的エラーなどを含むメッセージを処理することを期待しています」よりも望ましい方法です。
DharmaTurtle 2017

@Stevenと@DharmaTurtleの両方に同意します。さらに、型はクラスであり、OOPでそのようにコーディングするLoggingEventType必要があるため、これらを呼び出す必要がありますLoggingEventLevel。私にとって、インターフェースメソッドを使用しないことと、対応するenum値を使用しないことの間に違いはありません。代わりErrorLoggger : ILoggerInformationLogger : ILogger、すべてのロガーが独自のレベルを定義するを使用します。次に、DIはおそらくキー(列挙型)を介して必要なロガーを挿入する必要がありますが、このキーはインターフェースの一部ではなくなりました。(あなたは今、固いです)。
Wouter

4

この問題に対する優れた解決策は、LibLogプロジェクトの形で現れました。

LibLogは、Serilog、NLog、Log4net、Enterpriseロガーなどの主要なロガーを組み込みでサポートするロギング抽象化です。NuGetパッケージマネージャーを介して、.dll参照ではなくソース(.cs)ファイルとしてターゲットライブラリにインストールされます。このアプローチにより、ライブラリーに外部依存関係を強制することなく、ロギングの抽象化を組み込むことができます。また、ライブラリの作成者が、使用するアプリケーションにライブラリにロガーを明示的に提供させることなく、ロギングを含めることができます。LibLogはリフレクションを使用して、どの具体的なロガーが使用されているかを把握し、ライブラリプロジェクトの明示的な配線コードなしでそれに接続します。

したがって、LibLogは、ライブラリプロジェクト内のロギングに最適なソリューションです。メインのアプリケーションまたはサービスで具象ロガー(勝利のためのSerilog)を参照して構成し、LibLogをライブラリに追加するだけです!


これを使用して、log4netの重大な変更の問題を回避しました(yuck)(wiktorzychla.com/2012/03/pathetic-breaking-change-between.html)これをnugetから取得すると、実際には.csファイルが作成されますプリコンパイル済みdllへの参照を追加するのではなく、コード内で。.csファイルはプロジェクトの名前空間になります。したがって、異なるレイヤー(csprojs)がある場合は、複数のバージョンがあるか、共有のcsprojに統合する必要があります。あなたがそれを使おうとするとき、あなたはこれを理解するでしょう。しかし、私が言ったように、これはlog4netの重大な変更の問題を抱えた命の恩人でした。
granadaCoder 2018


弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.