ILogger、ILogger <T>、ILoggerFactory、またはILoggerProviderをライブラリに使用する必要がありますか?


113

これは、AspNet CoreのコンストラクターにILoggerまたはILoggerFactoryを渡すことにいくらか関連している可能性がありますか?ただし、これは特にライブラリの設計に関するものであり、それらのライブラリを使用する実際のアプリケーションがロギングを実装する方法についてではありません。

Nugetを介してインストールされる.net標準2.0ライブラリを作成しています。そのライブラリを使用しているユーザーがデバッグ情報を取得できるようにするには、Microsoft.Extensions.Logging.Abstractionsを使用して標準化されたロガーを挿入できます。

ただし、複数のインターフェイスが表示され、Web上のサンプルコードILoggerFactoryがクラスのctorでロガーを使用および作成する場合があります。ILoggerProviderファクトリの読み取り専用バージョンのように見えるものもありますが、実装は両方のインターフェースを実装する場合と実装しない場合があるため、選択する必要があります。(ファクトリーはプロバイダーよりも一般的です)。

私が見たいくつかのコードは非ジェネリックILoggerインターフェイスを使用しており、同じロガーの1つのインスタンスを共有することもあり、いくつかのコードはILogger<T>ctorを受け取り、DIコンテナーがオープンジェネリック型またはILogger<T>ライブラリのすべてのバリエーションの明示的な登録をサポートすることを期待しています使用します。

今のところ、それILogger<T>は正しいアプローチだと思います。おそらく、その引数をとらず、代わりにNull Loggerを渡すだけの俳優です。このようにして、ロギングが必要ない場合、何も使用されません。ただし、一部のDIコンテナーは最大のctorを選択するため、とにかく失敗します。

私は、私は何の興味なって必要に応じて、まだ適切なログ機能のサポートを可能にしながら、ユーザーのための頭痛の最低額を作成するために、ここでやっています。

回答:


113

定義

3つのインターフェースILoggerILoggerProviderありILoggerFactoryます。彼らの責任を見つけるためにソースコードを見てみましょう:

ILogger:特定のログレベルのログメッセージを書き込む責任があります。

ILoggerProvider:のインスタンスを作成する責任がありますILoggerILoggerProviderロガーの作成に直接使用することは想定されていません)

ILoggerFactoryファクトリに 1つ以上ILoggerProviderのを登録できます。ファクトリは、それらすべてを使用してのインスタンスを作成しますILoggerILoggerFactoryのコレクションを保持していますILoggerProviders

以下の例では、2つのプロバイダー(コンソールとファイル)をファクトリーに登録しています。ロガーを作成すると、ファクトリーはこれらのプロバイダーの両方を使用してロガーのインスタンスを作成します。

ILoggerFactory factory = new LoggerFactory().AddConsole();    // add console provider
factory.AddProvider(new LoggerFileProvider("c:\\log.txt"));   // add file provider
Logger logger = factory.CreateLogger(); // <-- creates a console logger and a file logger

したがって、ロガー自体はのコレクションを維持ILoggerしており、ログメッセージをそれらすべてに書き込みます。Loggerのソースコード見るとLogger、配列ILoggers(つまりLoggerInformation[])があり、同時にILoggerインターフェースを実装していることが確認できます。


依存性注入

MSのドキュメントには、ロガーを挿入するための2つの方法が用意されています。

1.工場への注入:

public TodoController(ITodoRepository todoRepository, ILoggerFactory logger)
{
    _todoRepository = todoRepository;
    _logger = logger.CreateLogger("TodoApi.Controllers.TodoController");
}

Category = TodoApi.Controllers.TodoControllerでロガーを作成します

2.ジェネリックを注入するILogger<T>

public TodoController(ITodoRepository todoRepository, ILogger<TodoController> logger)
{
    _todoRepository = todoRepository;
    _logger = logger;
}

カテゴリー= TodoControllerの完全修飾型名でロガーを作成します


私の意見では、ドキュメントを混乱させるのは、非ジェネリックを注入することについては何も言及されていないということILoggerです。上記と同じ例では、非ジェネリックITodoRepositoryを挿入していますが、に対して同じことを行わない理由は説明されていませんILogger

よると、マーク・シーマン

インジェクションコンストラクターは、依存関係を受信するだけです。

ロガーを初期化する(SRPの違反)のはコントローラーの責任ではないため、ファクトリーをコントローラーに注入することは良いアプローチではありません。同時に、ジェネリックILogger<T>を注入すると不要なノイズが追加されます。詳細については、Simple Injectorのブログを参照してください。ASP.NETCore DI抽象化の何が問題になっていますか?

注入する必要があるもの(少なくとも上記の記事によると)は総称ILoggerではありませんが、それはMicrosoftの組み込みDIコンテナが実行できることではなく、サードパーティのDIライブラリを使用する必要があります。これら 2つのドキュメントでは、.NET Coreでサードパーティライブラリを使用する方法について説明しています。


これはニコラ・マロビッチによる別の記事で、彼はIoCの5つの法則を説明しています。

ニコラのIoCの第4法則

解決されるクラスのすべてのコンストラクターは、独自の依存関係のセットを受け入れる以外に実装があってはなりません。


3
あなたの答えとスティーブンの記事は最も正確であり、同時に最も憂鬱なものです。
bokibeg

30

これらはを除いてすべて有効ですILoggerProviderILoggerそしてILogger<T>何をロギング用に使うことになっているされています。を取得するにはILogger、を使用しILoggerFactoryます。ILogger<T>特定のカテゴリのロガーを取得するためのショートカットです(カテゴリとしてのタイプのショートカット)。

を使用しILoggerてロギングを実行すると、登録済みの各ILoggerProviderがそのログメッセージを処理する機会を得ます。消費するコードがILoggerProvider直接呼び出すことは実際には有効ではありません。


ありがとう。これは、ILoggerFactory依存関係を1つだけ持つことで消費者のDIを簡単に結び付ける方法としてはすばらしいと思います(「ファクトリーを与えれば、自分のロガーを作成します」)が、既存のロガーの使用を妨げるでしょう(消費者がラッパーを使用しない限り)。撮影ILogger-ジェネリックかは-消費者は私に、彼らが特別に準備ロガーを与えることができますが、潜在的に多くの、より複雑な(DIコンテナでない限りサポートするオープンジェネリック医薬品が使用されていること)、DIの設定を行います。それは正しい音ですか?その場合は工場に行こうと思います。
Michael Stum

3
@MichaelStum-私はここであなたの論理に従っているのかわかりません。コンシューマーがDIシステムを使用することを期待していますが、ライブラリ内の依存関係を引き継ぎ、手動で管理したいとお考えですか?なぜそれが正しいアプローチに見えるのでしょうか?
Damien_The_Unbeliever 2018

@Damien_The_Unbelieverそれは良い点です。工場を取るのは奇妙に思えます。私は、をとる代わりにILogger<T>ILogger<MyClass>またはを代わりに取ると思いますILogger-そのようにして、ユーザーは、DIコンテナーにオープンなジェネリックを必要とせずに、単一の登録のみでそれを接続できます。non-genericに傾いてILoggerいますが、週末にたくさんの実験を行います。
Michael Stum

10

これILogger<T>はDI用に作成された実際の1つでした。ILoggerは、asp.netコアで最も賢い決定の1つであるDIとファクトリロジックをすべて自分で書く代わりに、ファクトリパターンの実装をはるかに簡単にするために導入されました。

次の中から選択できます。

ILogger<T>コードでファクトリーおよびDIパターンを使用する必要がある場合、またはを使用してILogger、DIを必要としない単純なロギングを実装する場合。

それを考えると、The ILoggerProviderは登録されたログのメッセージのそれぞれを処理するための単なる橋です。コードに介入する必要があることには何の影響もないため、使用する必要はありません。登録されたILoggerProviderをリッスンし、メッセージを処理します。それだけです。


1
ILoggerとILogger <T>はDIとどのような関係がありますか?どちらが注入されるでしょうか?
マシューホステラー

実際には、MS DocsではILogger <TCategoryName>です。これはILoggerから派生し、ログに記録するクラス名以外の新しい機能を追加しません。これはまた、固有のタイプを提供するため、DIは正しい名前のロガーを注入します。Microsoft拡張機能は、非ジェネリックを拡張しthis ILoggerます。
Samuel Danielson

8

質問にこだわって、ILogger<T>他のオプションの欠点を考慮して、私は正しいオプションだと思います:

  1. 注入するILoggerFactoryと、ユーザーは変更可能なグローバルロガーファクトリの制御をクラスライブラリに譲渡します。さらに、ILoggerFactoryクラスを受け入れることで、CreateLoggerメソッドを使用して任意のカテゴリ名でログに書き込むことができます。一方でILoggerFactoryDIコンテナにシングルトンとして通常利用可能な任意のライブラリはそれを使用する必要がありますなぜ、ユーザーとして私は疑うでしょう。
  2. メソッドILoggerProvider.CreateLoggerはそのように見えますが、注入用ではありません。ILoggerFactory.AddProviderファクトリを使用して、登録済みの各プロバイダーから作成されILoggerた複数に書き込む集約を作成できるようにするために使用ILoggerされます。これは、実装を検査すると明らかです。LoggerFactory.CreateLogger
  3. 受け入れることILoggerも進むべき道のように見えますが、.NET Core DIでは不可能です。これは、彼らがILogger<T>そもそも提供する必要があった理由のように思えます。

つまりILogger<T>、これらのクラスから選択するのであれば、結局のところ、以外に選択肢はありません。

別のアプローチはILogger、非ジェネリックをラップする何かを注入することです。この場合、ジェネリックではありません。独自のクラスでラップすることにより、ユーザーがそれを構成する方法を完全に制御できるという考え方です。


6

デフォルトのアプローチはであることを意図していますILogger<T>。つまり、完全なクラス名がコンテキストとして含まれるため、ログには特定のクラスのログが明確に表示されます。たとえば、クラスのフルネームがである場合、MyLibrary.MyClassこのクラスによって作成されたログエントリでこれを取得します。例えば:

MyLibrary.MyClass:情報:私の情報ログ

ILoggerFactory独自のコンテキストを指定する場合は、を使用する必要があります。たとえば、ライブラリのすべてのログは、すべてのクラスではなく同じログコンテキストを持っています。例えば:

loggerFactory.CreateLogger("MyLibrary");

そして、ログは次のようになります。

MyLibrary:情報:私の情報ログ

すべてのクラスでこれを行うと、コンテキストはすべてのクラスのMyLibraryになります。ログの内部クラス構造を公開したくない場合は、ライブラリに対してそれを実行したいと思うでしょう。

オプションのロギングについて。私は常にコンストラクターにILoggerまたはILoggerFactoryを必要とし、ライブラリーのコンシューマーに任せてオフにするか、ロギングが必要ない場合は依存性注入で何もしないLoggerを提供する必要があると思います。構成内の特定のコンテキストのロギングをオフにするのは非常に簡単です。例えば:

{
  "Logging": {
    "LogLevel": {
      "Default": "Warning",
      "MyLibrary": "None"
    }
  }
}

5

ライブラリ設計の場合、適切なアプローチは次のとおりです。

1.ユーザーにクラスにロガーを注入するように強制しないでください。NullLoggerFactoryを渡す別のctorを作成するだけです。

class MyClass
{
    private readonly ILoggerFactory _loggerFactory;

    public MyClass():this(NullLoggerFactory.Instance)
    {

    }
    public MyClass(ILoggerFactory loggerFactory)
    {
      this._loggerFactory = loggerFactory ?? NullLoggerFactory.Instance;
    }
}

2.ロガーを作成するときに使用するカテゴリの数を制限して、消費者が簡単にログのフィルタリングを設定できるようにします。 this._loggerFactory.CreateLogger(Consts.CategoryName)

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