次の方法で共有


.NET ライブラリ作成者のためのログのガイダンス

ライブラリ作成者にとって、ログを公開することは、ライブラリの内部動作に関する分析情報をコンシューマーに提供する優れた方法です。 このガイダンスは、他の .NET ライブラリやフレームワークと一貫性のある方法でログを公開するためのものです。 また、見逃しやすい一般的なパフォーマンスのボトルネックを回避するのにも役立ちます。

ILoggerFactory インターフェイスを使用する状況

ログを出力するライブラリを記述するときは、ログを記録するために ILogger オブジェクトが必要です。 そのオブジェクトを取得するには、API で ILogger<TCategoryName> パラメーターを受け入れるか、ILoggerFactory を受け入れた後に ILoggerFactory.CreateLogger を呼び出します。 どちらの方法を使うべきでしょうか?

  • 1 つのログ オブジェクトを複数のクラスに渡して、すべてでログを出力できるようにする場合は、ILoggerFactoryを使用します。 各クラスで、クラスと同じ名前のカテゴリを指定してログを作成することをお勧めします。 これには、ログを出力するクラスごとに一意の ILogger<TCategoryName> オブジェクトを作成するファクトリが必要です。 一般的な例としては、ライブラリのパブリック エントリ ポイント API や、ヘルパー クラスを内部的に作成する型のパブリック コンストラクターなどがあります。

  • ログ オブジェクトを 1 つのクラス内でのみ使用し、共有しない場合は、ILogger<TCategoryName> (TCategoryName はログを生成する型) を使用します。 この一般的な例としては、依存関係の挿入で作成されるクラスのコンストラクターなどがあります。

長期的に安定している必要のあるパブリック API を設計する場合は、将来的に内部実装のリファクタリングが必要となる可能性があることに注意してください。 クラスが最初は内部ヘルパー型を作成しない場合でも、コードの進化に伴って変更される可能性があります。 ILoggerFactory を使用すると、パブリック API を変更することなく、新しいクラスの新しい ILogger<TCategoryName> オブジェクトを作成できます。

詳細については、「フィルター規則を適用する方法」を参照してください。

ソース生成ログを優先する

ILogger API では、API を使用するのに 2 つの方法がサポートされています。 LoggerExtensions.LogErrorLoggerExtensions.LogInformation などのメソッドを呼び出すか、ログ ソース ジェネレーターを使用して厳密に型指定されたログメソッドを定義できます。 ほとんどの状況では、優れたパフォーマンスと厳密な型指定が提供されるため、ソース ジェネレーターの使用をお勧めします。 また、ソース ジェネレーターを使用すると、メッセージ テンプレート、ID、ログ レベルなどのログに特有の項目を呼び出し元のコードから分離できます。 ソース生成以外の方法は主に、それらの利点を放棄しても、コードを簡潔なものにする必要がある場合に有益です。

using Microsoft.Extensions.Logging;

namespace Logging.LibraryAuthors;

internal static partial class LogMessages
{
    [LoggerMessage(
        Message = "Sold {Quantity} of {Description}",
        Level = LogLevel.Information)]
    internal static partial void LogProductSaleDetails(
        this ILogger logger,
        int quantity,
        string description);
}

上記のコードでは次の操作が行われます。

  • LogMessages という名前の partial class を定義します。これは、ILogger 型の拡張メソッドを定義できるようにするため static です。
  • LogProductSaleDetails 拡張メソッドを LoggerMessage 属性と Message テンプレートで修飾します。
  • ILogger を拡張し、quantitydescription を受け入れる LogProductSaleDetails を宣言します。

ヒント

ソース生成コードは、呼び出し元のコードと同じアセンブリの一部であるため、デバッグ中にステップ インできます。

IsEnabled を使用してコストのかかるパラメーター評価を回避する

パラメーター評価のコストが高くなる場合があります。 前の例を発展させて、description パラメーターがコンピューティング コストの高い string である場合を考えます。 販売された製品のわかりやすい製品説明を取得し、データベース クエリやファイルからの読み取りに依存しているとします。 この場合、ソース ジェネレーターで IsEnabled ガードがスキップされるようにし、呼び出しサイトに IsEnabled ガードを手動で追加できます。 このようにすると、ユーザーがガードを呼び出す位置を決定でき、計算コストの高いパラメーターが必要なときにのみ評価されるようにすることができます。 次のコードがあるとします。

using Microsoft.Extensions.Logging;

namespace Logging.LibraryAuthors;

internal static partial class LogMessages
{
    [LoggerMessage(
        Message = "Sold {Quantity} of {Description}",
        Level = LogLevel.Information,
        SkipEnabledCheck = true)]
    internal static partial void LogProductSaleDetails(
        this ILogger logger,
        int quantity,
        string description);
}

LogProductSaleDetails 拡張メソッドが呼び出されると、IsEnabled ガードが手動で呼び出され、コストの高いパラメーター評価は必要なときにのみ実行されます。 次のコードがあるとします。

if (_logger.IsEnabled(LogLevel.Information))
{
    // Expensive parameter evaluation
    var description = product.GetFriendlyProductDescription();

    _logger.LogProductSaleDetails(
        quantity,
        description);
}

詳細については、「コンパイル時のログ ソース生成」および「.NET での高パフォーマンスのログ」を参照してください。

ログでの文字列補間を回避する

よくある間違いとして、文字列補間を使用してログ メッセージを作成することがあります。 文字列は対応する LogLevel が有効になっていない場合でも評価されるため、ログでの文字列補間はパフォーマンスの問題となります。 文字列補間の代わりに、ログ メッセージ テンプレート、書式設定、および引数リストを使用します。 詳細については、「.NET でのログ記録: ログ メッセージ テンプレート」を参照してください。

no-op のログ既定値を使用する

ILogger または ILoggerFactory のいずれかを必要とするログ API を公開するライブラリを使用するときに、ロガーを提供したくない場合があります。 このような場合、Microsoft.Extensions.Logging.Abstractions NuGet パッケージには、no-op ログの既定値が用意されています。

ILoggerFactory が指定されていない場合、ライブラリ コンシューマーを既定で "null ログ" にできます。 null ログの使用は、型が null 以外であるため、型を null 許容 (ILoggerFactory?) として定義するのとは異なります。 これらの型は何もログに記録しないので、基本的に no-op です。 必要に応じて、使用可能な次の抽象型のいずれかを使用することを検討してください。