C# と .NET でのログ記録

.NET は、アプリケーションの動作の監視と問題の診断に役立つ、ILogger API を介した高パフォーマンスで構造化されたログ記録をサポートしています。 さまざまなログ プロバイダーを構成することで、ログをさまざまな宛先に書き込むことができます。 基本的なログ プロバイダーは組み込みであり、多くのサードパーティ プロバイダーも利用できます。

作業の開始

この最初の例は、基本を示すものですが、簡易的なコンソール アプリにのみ適しています。 次のセクションでは、スケール、パフォーマンス、構成、一般的なプログラミング パターンを考慮してコードを改善する方法について説明します。

using Microsoft.Extensions.Logging;

using ILoggerFactory factory = LoggerFactory.Create(builder => builder.AddConsole());
ILogger logger = factory.CreateLogger("Program");
logger.LogInformation("Hello World! Logging is {Description}.", "fun");

上記の例の場合:

  • ILoggerFactory を作成します。 ILoggerFactory には、ログ メッセージが送信される場所を決定するすべての構成が格納されます。 この場合、ログ メッセージがコンソールに書き込まれるように、コンソール ログ プロバイダーを構成します。
  • "Program" という名前のカテゴリを持つ ILogger を作成します。 カテゴリは、ILogger オブジェクトによってログに記録される各メッセージに関連付けられた string です。 ログを検索またはフィルター処理するときに、同じクラス (またはカテゴリ) のログ メッセージをグループ化するために使用されます。
  • LogInformation を呼び出して、Information レベルでメッセージをログに記録します。 ログ レベルは、ログに記録されたイベントの重大度を示しており、重要度の低いログ メッセージを除外するために使用されます。 ログ エントリには、メッセージ テンプレート"Hello World! Logging is {Description}."と、キーと値のペアDescription = funも含まれています。 キー名 (またはプレースホルダー) はテンプレート内の中かっこで囲まれている単語から、値は残りのメソッド引数から取得します。

この例のこのプロジェクト ファイルには、次の 2 つの NuGet パッケージが含まれています。

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net8.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.Extensions.Logging" Version="8.0.0" />
    <PackageReference Include="Microsoft.Extensions.Logging.Console" Version="8.0.0" />
  </ItemGroup>

</Project>

ヒント

ログのサンプル ソース コードはすべて、サンプル ブラウザーでダウンロードできます。 詳細については、コード サンプルの参照: .NET でのログ記録に関するページをご覧ください。

簡易的なアプリ以外でのログ記録

あまり簡易的ではないシナリオでログを記録する場合は、前の例に対して、検討すべき変更がいくつかあります。

  • アプリケーションで依存関係の挿入 (DI)、または ASP.NET の WebApplication汎用ホストなどのホストを使用している場合、ILoggerFactory オブジェクトと ILogger オブジェクトは、直接作成するのではなく、それぞれの DI コンテナーから使用する必要があります。 詳細については、DI およびホストとの統合に関する記事を参照してください。

  • 通常、コンパイル時のソース生成のログ記録は、LogInformation のような ILogger 拡張メソッドに代わる優れた方法です。 ログ ソース生成を行うと、パフォーマンスの向上と入力の強化に加え、メソッド全体に string 定数が分散しないようにすることができます。 トレードオフとして、この手法の使用には、コードがもう少し必要であるという点があります。

using Microsoft.Extensions.Logging;

internal partial class Program
{
    static void Main(string[] args)
    {
        using ILoggerFactory factory = LoggerFactory.Create(builder => builder.AddConsole());
        ILogger logger = factory.CreateLogger("Program");
        LogStartupMessage(logger, "fun");
    }

    [LoggerMessage(Level = LogLevel.Information, Message = "Hello World! Logging is {Description}.")]
    static partial void LogStartupMessage(ILogger logger, string description);
}
  • ログ カテゴリ名には、ログ メッセージを作成するクラスの完全修飾名を使用することをお勧めします。 これにより、ログ メッセージを生成したコードにそのログ メッセージを関連付けることができると同時に、ログをフィルター処理するときに適切なレベルの制御を行うことができます。 CreateLogger は、この名前付けを簡単に実行できるようにする Type を受け入れます。
using Microsoft.Extensions.Logging;

internal class Program
{
    static void Main(string[] args)
    {
        using ILoggerFactory factory = LoggerFactory.Create(builder => builder.AddConsole());
        ILogger logger = factory.CreateLogger<Program>();
        logger.LogInformation("Hello World! Logging is {Description}.", "fun");
    }
}
using Microsoft.Extensions.Logging;
using OpenTelemetry.Logs;

using ILoggerFactory factory = LoggerFactory.Create(builder =>
{
    builder.AddOpenTelemetry(logging =>
    {
        logging.AddOtlpExporter();
    });
});
ILogger logger = factory.CreateLogger("Program");
logger.LogInformation("Hello World! Logging is {Description}.", "fun");

ホストおよび依存関係の挿入との統合

アプリケーションで依存関係の挿入 (DI)、または ASP.NET の WebApplication汎用ホストなどのホストを使用している場合、ILoggerFactory オブジェクトと ILogger オブジェクトは、直接作成するのではなく、DI コンテナーから使用する必要があります。

DI から ILogger を取得する

次の例では、ASP.NET Minimal API を使用して、ホストされているアプリ内にある ILogger オブジェクトを取得します。

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddSingleton<ExampleHandler>();

var app = builder.Build();

var handler = app.Services.GetRequiredService<ExampleHandler>();
app.MapGet("/", handler.HandleRequest);

app.Run();

partial class ExampleHandler(ILogger<ExampleHandler> logger)
{
    public string HandleRequest()
    {
        LogHandleRequest(logger);
        return "Hello World";
    }

    [LoggerMessage(LogLevel.Information, "ExampleHandler.HandleRequest was called")]
    public static partial void LogHandleRequest(ILogger logger);
}

上記の例の場合:

  • ExampleHandler というシングルトン サービスを作成したとともに、ExampleHandler.HandleRequest 関数の実行の受信 Web 要求をマップしました。
  • 8 行目には、C# 12 で追加された機能である、ExampleHandler のプライマリ コンストラクターが定義されています。 以前のスタイルの C# コンストラクターを使用しても同様に機能しますが、こちらはもう少し細やかに機能します。
  • コンストラクターを使用すると、ILogger<ExampleHandler> 型のパラメーターが定義されます。 ILogger<TCategoryName> は、ILogger から派生し、ILogger オブジェクトに含まれるカテゴリを示します。 DI コンテナーでは、適切なカテゴリを持つ ILogger を検索して、これをコンストラクター引数として指定します。 そのカテゴリを持つ ILogger がまだ存在しない場合は、DI コンテナーによってサービス プロバイダーの ILoggerFactory から自動的に作成されます。
  • コンストラクターで受け取った logger パラメーターは、HandleRequest 関数のログ記録に使用されました。

ホスト提供の ILoggerFactory

ホスト ビルダーを使用すると、既定の構成が初期化され、ホストのビルド時に、構成済みの ILoggerFactory オブジェクトがホストの DI コンテナーに追加されます。 ホストをビルドする前に、他のホストの HostApplicationBuilder.Logging API、WebApplicationBuilder.Logging API、または同様の API を使用してログの構成を調整できます。 また、ホストでは、既定の構成ソースからのログの構成を、appsettings.json および環境変数として適用します。 詳細については、「.NET での構成」を参照してください。

この例では、前述のものを展開して、WebApplicationBuilder によって指定された ILoggerFactory をカスタマイズします。 これにより、OpenTelemetry は、OTLP (OpenTelemetry プロトコル) でログを送信するログ プロバイダーとして追加されます。

var builder = WebApplication.CreateBuilder(args);
builder.Logging.AddOpenTelemetry(logging => logging.AddOtlpExporter());
builder.Services.AddSingleton<ExampleHandler>();
var app = builder.Build();

DI で ILoggerFactory を作成する

ホストなしで DI コンテナーを使用している場合は、AddLogging を使用し、ILoggerFactory を構成してコンテナーに追加します。

using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;

// Add services to the container including logging
var services = new ServiceCollection();
services.AddLogging(builder => builder.AddConsole());
services.AddSingleton<ExampleService>();
IServiceProvider serviceProvider = services.BuildServiceProvider();

// Get the ExampleService object from the container
ExampleService service = serviceProvider.GetRequiredService<ExampleService>();

// Do some pretend work
service.DoSomeWork(10, 20);

class ExampleService(ILogger<ExampleService> logger)
{
    public void DoSomeWork(int x, int y)
    {
        logger.LogInformation("DoSomeWork was called. x={X}, y={Y}", x, y);
    }
}

上記の例の場合:

  • コンソールに書き込むように構成された ILoggerFactory を含む DI サービス コンテナーを作成しました
  • コンテナーにシングルトン ExampleService を追加しました
  • DI コンテナーから ExampleService のインスタンスを作成しました。これにより、コンストラクター引数として使用する ILogger<ExampleService> も自動的に作成されました。
  • ILogger<ExampleService> を使用してコンソールにメッセージを記録する ExampleService.DoSomeWork を呼び出しました。

ログの構成

ログの構成は、コード内に設定されるか、構成ファイルや環境変数などの外部ソースを通じて設定されます。 可能な場合は外部構成を使用すると、アプリケーションをリビルドしなくても変更できるので便利です。 ただし、ログ プロバイダーの設定などの一部のタスクは、コードからのみ構成できます。

コードなしでログを構成する

ホストを使用するアプリの場合、一般的に、ログの構成は、appsettings.{Environment}.json ファイルの "Logging" セクションで指定されます。 ホストを使用しないアプリの場合、代わりに、外部構成ソースは、明示的に設定されるか、コードに構成されます。

次の appsettings.Development.json ファイルは、.NET Worker サービス テンプレートによって生成されます。

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft": "Warning",
      "Microsoft.Hosting.Lifetime": "Information"
    }
  }
}

上記の JSON では、次のように指定されています。

  • "Default""Microsoft""Microsoft.Hosting.Lifetime" ログ レベルのカテゴリが指定されています。
  • "Default" 値は、特に指定されていないすべてのカテゴリに適用され、実質的にすべてのカテゴリのすべての既定値が "Information" になります。 この動作は、カテゴリの値を指定することでオーバーライドできます。
  • "Microsoft" カテゴリは、"Microsoft" で始まるすべてのカテゴリに適用されます。
  • "Microsoft" カテゴリでは、ログ レベルが Warning 以上のログが記録されます。
  • "Microsoft.Hosting.Lifetime" カテゴリは "Microsoft" カテゴリよりも詳細なので、"Microsoft.Hosting.Lifetime" カテゴリではログ レベルが "Information" 以上のログが記録されます。
  • 特定のログ プロバイダーが指定されていないため、LogLevel は、Windows EventLog を除くすべての有効なログ プロバイダーに適用されます。

Logging プロパティには LogLevel およびログ プロバイダーのプロパティを含めることができます。 LogLevel により、選択したカテゴリに対するログの最小レベルが指定されます。 上記の JSON では、InformationWarning のログ レベルが指定されています。 LogLevel はログの重大度を 0 - 6 の範囲で示します。

Trace = 0、Debug = 1、Information = 2、Warning = 3、Error = 4、Critical = 5、None = 6。

LogLevel を指定すると、指定したレベル以上のメッセージに対してログが有効になります。 上記の JSON では、Default カテゴリは Information 以上に対してのみログに記録されます。 たとえば、InformationWarningErrorCritical のメッセージがログに記録されます。 LogLevel が指定されていない場合、ログは既定で Information レベルになります。 詳細については、「ログ レベル」を参照してください。

プロバイダー プロパティで LogLevel プロパティを指定できます。 プロバイダーの下の LogLevel によって、そのプロバイダーのログに記録するレベルが指定され、プロバイダー以外のログ設定がオーバーライドされます。 以下の appsettings.json ファイルについて考えます:

{
    "Logging": {
        "LogLevel": {
            "Default": "Error",
            "Microsoft": "Warning"
        },
        "Debug": {
            "LogLevel": {
                "Default": "Information",
                "Microsoft.Hosting": "Trace"
            }
        },
        "EventSource": {
            "LogLevel": {
                "Default": "Warning"
            }
        }
    }
}

Logging.{ProviderName}.LogLevel の設定により Logging.LogLevel の設定がオーバーライドされます。 上記の JSON では、Debug プロバイダーの既定のログ レベルは Information に設定されています。

Logging:Debug:LogLevel:Default:Information

上記の設定により、Microsoft.Hosting 以外のすべての Logging:Debug: カテゴリに Information ログ レベルが指定されます。 特定のカテゴリが一覧表示されると、特定のカテゴリによって既定のカテゴリがオーバーライドされます。 上記の JSON では、Logging:Debug:LogLevel カテゴリ "Microsoft.Hosting""Default" によって、Logging:LogLevel の設定がオーバーライドされます。

最小ログ レベルは、次のいずれかに指定できます。

  • 特定のプロバイダー: たとえば、Logging:EventSource:LogLevel:Default:Information
  • 特定のカテゴリ: たとえば、Logging:LogLevel:Microsoft:Warning
  • すべてのプロバイダーとすべてのカテゴリ: Logging:LogLevel:Default:Warning

最小レベルを下回るログは、次のことが "行われません"。

  • プロバイダーに渡される。
  • ログに記録または表示される。

すべてのログを抑制するには、LogLevel.None を指定します。 LogLevel.None の値は、LogLevel.Critical (5) より大きい 6 です。

プロバイダーでログのスコープがサポートされている場合、IncludeScopes によってそれを有効にするかどうかが指定されます。 詳細については、「ログ スコープ」を参照してください。

次の appsettings.json ファイルには、すべての組み込みプロバイダー用の設定が含まれています。

{
    "Logging": {
        "LogLevel": {
            "Default": "Error",
            "Microsoft": "Warning",
            "Microsoft.Hosting.Lifetime": "Warning"
        },
        "Debug": {
            "LogLevel": {
                "Default": "Information"
            }
        },
        "Console": {
            "IncludeScopes": true,
            "LogLevel": {
                "Microsoft.Extensions.Hosting": "Warning",
                "Default": "Information"
            }
        },
        "EventSource": {
            "LogLevel": {
                "Microsoft": "Information"
            }
        },
        "EventLog": {
            "LogLevel": {
                "Microsoft": "Information"
            }
        },
        "AzureAppServicesFile": {
            "IncludeScopes": true,
            "LogLevel": {
                "Default": "Warning"
            }
        },
        "AzureAppServicesBlob": {
            "IncludeScopes": true,
            "LogLevel": {
                "Microsoft": "Information"
            }
        },
        "ApplicationInsights": {
            "LogLevel": {
                "Default": "Information"
            }
        }
    }
}

上記のサンプルについて:

  • カテゴリとレベルは推奨値ではありません。 このサンプルは、すべての既定のプロバイダーを表示するために提供されています。
  • Logging.{ProviderName}.LogLevel の設定により Logging.LogLevel の設定がオーバーライドされます。 たとえば、Debug.LogLevel.Default のレベルにより LogLevel.Default のレベルがオーバーライドされます。
  • 各プロバイダーの "エイリアス" が使用されています。 各プロバイダーでは "エイリアス" が定義されます。これは構成で完全修飾型名の代わりに使用できます。 組み込みプロバイダーのエイリアスは次のとおりです。
    • Console
    • Debug
    • EventSource
    • EventLog
    • AzureAppServicesFile
    • AzureAppServicesBlob
    • ApplicationInsights

コマンド ライン、環境変数、およびその他の構成でログ レベルを設定する

ログ レベルは、いずれかの構成プロバイダーで設定できます。 たとえば、名前が Logging:LogLevel:Microsoft で値が Information の永続化された環境変数を作成できます。

ログ レベルの値が代入された、永続化された環境変数を作成します。

:: Assigns the env var to the value
setx "Logging__LogLevel__Microsoft" "Information" /M

コマンド プロンプトの "新しい" インスタンスで、環境変数を読み取ります。

:: Prints the env var value
echo %Logging__LogLevel__Microsoft%

上記の環境設定は、環境内に保持されます。 .NET Worker サービス テンプレートで作成されたアプリを使用して設定をテストするには、環境変数に代入した後、プロジェクト ディレクトリ内で dotnet run コマンドを使用します。

dotnet run

ヒント

環境変数を設定した後、統合開発環境 (IDE) を再起動して、新しく追加した環境変数を使用できることを確認します。

Azure App Service の、[設定] > [構成] ページで [新しいアプリケーション設定] を選択します。 Azure App Service アプリケーションの設定は:

  • 保存時に暗号化され、暗号化されたチャネルで送信されます。
  • 環境変数として公開されます。

環境変数を使用して .NET 構成値を設定する方法の詳細については、環境変数に関する記事を参照してください。

コードを使用してログ記録を構成する

コードにログを構成するには、ILoggingBuilder API を使用します。 これには、さまざまな場所からアクセスできます。

この例では、コンソール ログ プロバイダーといくつかのフィルターを設定しています。

using Microsoft.Extensions.Logging;

using var loggerFactory = LoggerFactory.Create(static builder =>
{
    builder
        .AddFilter("Microsoft", LogLevel.Warning)
        .AddFilter("System", LogLevel.Warning)
        .AddFilter("LoggingConsoleApp.Program", LogLevel.Debug)
        .AddConsole();
});

ILogger logger = loggerFactory.CreateLogger<Program>();
logger.LogDebug("Hello {Target}", "Everyone");

前の例では、さまざまなカテゴリで有効になっているログ レベルを調整するために、AddFilter が使用されています。 AddConsole は、コンソール ログ プロバイダーを追加するために使用されます。 既定では、Debug の重大度のログは有効になっていませんが、構成によってフィルターが調整されたため、デバッグ メッセージ "Hello Everyone" がコンソールに表示されます。

フィルター規則を適用する方法

ILogger<TCategoryName> オブジェクトを作成すると、ILoggerFactory オブジェクトによって、そのロガーに適用するプロバイダーごとに 1 つの規則が選択されます。 ILogger インスタンスによって書き込まれるすべてのメッセージは、選択した規則に基づいてフィルター処理されます。 使用できる規則から、各プロバイダーとカテゴリのペアごとに最も明確な規則が選択されます。

特定のカテゴリに ILogger が作成されるときに、各プロバイダーに次のアルゴリズムが使用されます。

  • プロバイダーとそのエイリアスと一致するすべての規則が選択されます。 一致が見つからない場合は、空のプロバイダーですべての規則が選択されます。
  • 前の手順の結果、最も長いカテゴリのプレフィックスが一致する規則が選択されます。 一致が見つからない場合は、カテゴリを指定しないすべての規則が選択されます。
  • 複数の規則が選択されている場合は、最後の 1 つが使用されます。
  • 規則が選択されていない場合は、LoggingBuilderExtensions.SetMinimumLevel(ILoggingBuilder, LogLevel) を使用して最小ログ記録レベルを指定します。

ログのカテゴリ

ILogger オブジェクトが作成されるときに、"カテゴリ" が指定されます。 このカテゴリは、ILogger のインスタンスによって作成される各ログ メッセージと共に含められます。 カテゴリ文字列は任意ですが、慣習的には完全修飾クラス名を使用します。 たとえば、サービスが次のオブジェクトように定義されているアプリケーションでは、カテゴリは "Example.DefaultService" のようになります。

namespace Example
{
    public class DefaultService : IService
    {
        private readonly ILogger<DefaultService> _logger;

        public DefaultService(ILogger<DefaultService> logger) =>
            _logger = logger;

        // ...
    }
}

さらなる分類が必要な場合、慣習的には、次のように完全修飾クラス名にサブカテゴリを追加することで階層的な名前を使用し、LoggerFactory.CreateLogger を使用してカテゴリを明示的に指定します。

namespace Example
{
    public class DefaultService : IService
    {
        private readonly ILogger _logger;

        public DefaultService(ILoggerFactory loggerFactory) =>
            _logger = loggerFactory.CreateLogger("Example.DefaultService.CustomCategory");

        // ...
    }
}

複数のクラスや型で使用する場合、固定名を使用して CreateLogger を呼び出すと、イベントをカテゴリ別に分類できるので便利です。

ILogger<T> は、T の完全修飾型名を使用した CreateLogger の呼び出しと同じです。

ログ レベル

次の表に、LogLevel 値、便利な Log{LogLevel} 拡張メソッド、推奨される使用法を示します。

LogLevel [値] Method 説明
トレース 0 LogTrace 最も詳細なメッセージが含まれます。 これらのメッセージには、機密性の高いアプリ データが含まれる場合があります。 これらのメッセージは既定で無効になっているため、運用環境では有効に "しないでください"。
デバッグ 1 LogDebug デバッグと開発用。 量が多いため、運用環境では慎重に使用してください。
情報 2 LogInformation アプリの一般的なフローを追跡します。 長期的な値が含まれている可能性があります。
警告 3 LogWarning 異常なイベントや予期しないイベント用。 通常、アプリが失敗する原因にならないエラーや条件が含まれます。
エラー 4 LogError 処理できないエラーと例外の場合。 これらのメッセージは、アプリ全体のエラーではなく、現在の操作や要求における失敗を示します。
重大 5 LogCritical 即時の注意が必要なエラーの場合。 例: データ損失のシナリオ、ディスク領域不足。
None 6 メッセージを書き込む必要がないことを指定します。

前の表では、重大度が最も低い方から高い方に LogLevel が一覧表示されています。

Log メソッドの最初のパラメーター LogLevel は、ログの重大度を示します。 ほとんどの開発者は、Log(LogLevel, ...) を呼び出すのではなく、Log{LogLevel} 拡張メソッドを呼び出します。 Log{LogLevel} 拡張メソッドによって、Log メソッドが呼び出され、LogLevel が指定されます。 たとえば、次の 2 つのログ呼び出しは、機能的に同等で、同じログが生成されます。

public void LogDetails()
{
    var logMessage = "Details for log.";

    _logger.Log(LogLevel.Information, AppLogEvents.Details, logMessage);
    _logger.LogInformation(AppLogEvents.Details, logMessage);
}

AppLogEvents.Details はイベント ID であり、暗黙的に Int32 定数値によって表されます。 AppLogEvents はさまざまな名前付き識別子定数を公開するクラスであり、ログ イベント ID セクションに表示されます。

Information および Warning ログを作成するコードを次に示します。

public async Task<T> GetAsync<T>(string id)
{
    _logger.LogInformation(AppLogEvents.Read, "Reading value for {Id}", id);

    var result = await _repository.GetAsync(id);
    if (result is null)
    {
        _logger.LogWarning(AppLogEvents.ReadNotFound, "GetAsync({Id}) not found", id);
    }

    return result;
}

上記のコードでは、最初の Log{LogLevel} パラメーター AppLogEvents.Read は、ログ イベント ID です。 2 つ目のパラメーターは、他のメソッド パラメーターによって提供される引数値のプレースホルダーを含むメッセージ テンプレートです。 メソッド パラメーターについては、この記事の後半のメッセージ テンプレートに関するセクションで説明します。

適切なログ レベルを構成し、適切な Log{LogLevel} メソッドを呼び出して、特定のストレージ メディアに書き込むログ出力の量を制御します。 次に例を示します。

  • 運用環境:
    • Trace または Debug のレベルでログを記録すると、詳細なログ メッセージが大量に生成されます。 コストを制御し、データ ストレージの上限を超えないようにするには、Trace および Debug のレベルのメッセージを、大容量の低コストのデータ ストアに記録します。 TraceDebug を特定のカテゴリに制限することを検討してください。
    • Warning から Critical のレベルでログを記録しても、ログ メッセージはほとんど生成されません。
      • 通常、コストとストレージの制限は考慮されません。
      • ログの数が少ないほど、データ ストアをより柔軟に選択できるようになります。
  • 開発中:
    • Warning に設定します。
    • トラブルシューティングの際に Trace または Debug のメッセージを追加します。 出力を制限するには、調査中のカテゴリに対してのみ Trace または Debug を設定します。

次の JSON は Logging:Console:LogLevel:Microsoft:Information を設定します。

{
    "Logging": {
        "LogLevel": {
            "Microsoft": "Warning"
        },
        "Console": {
            "LogLevel": {
                "Microsoft": "Information"
            }
        }
    }
}

ログ イベント ID

各ログでは、"イベント識別子" を指定できます。EventId は、Id および省略可能な Name 読み取り専用プロパティを持つ構造体です。 サンプル ソース コードでは、AppLogEvents クラスを使用してイベント ID を定義しています。

using Microsoft.Extensions.Logging;

internal static class AppLogEvents
{
    internal static EventId Create = new(1000, "Created");
    internal static EventId Read = new(1001, "Read");
    internal static EventId Update = new(1002, "Updated");
    internal static EventId Delete = new(1003, "Deleted");

    // These are also valid EventId instances, as there's
    // an implicit conversion from int to an EventId
    internal const int Details = 3000;
    internal const int Error = 3001;

    internal static EventId ReadNotFound = 4000;
    internal static EventId UpdateNotFound = 4001;

    // ...
}

ヒント

intEventId に変更する方法については、「EventId.Implicit(Int32 to EventId) 演算子」を参照してください。

イベント ID によって一連のイベントが関連付けられます。 たとえば、リポジトリからの値の読み取りに関連するすべてのログを 1001 にすることができます。

ログ プロバイダーでは、ID フィールドやログ メッセージにイベント ID が記録されたり、またはまったく記録されなかったりする場合があります。 Debug プロバイダーでイベント ID が表示されることはありません。 Console プロバイダーでは、カテゴリの後のブラケット内にイベント ID が表示されます。

info: Example.DefaultService.GetAsync[1001]
      Reading value for a1b2c3
warn: Example.DefaultService.GetAsync[4000]
      GetAsync(a1b2c3) not found

一部のログ プロバイダーでは、イベント ID がフィールドに格納されます。これにより、ID に対するフィルター処理が可能になります。

ログ メッセージ テンプレート

各ログ API では、メッセージ テンプレートが使用されます。 メッセージ テンプレートには、指定される引数のためのプレースホルダーを含めることができます。 プレースホルダーには、数値ではなく名前を使用します。 プレースホルダーの名前ではなく、プレースホルダーの順序によって、値の指定に使用されるパラメーターが決まります。 次のコードでは、パラメーター名がメッセージ テンプレート内のシーケンスの外にあります。

string p1 = "param1";
string p2 = "param2";
_logger.LogInformation("Parameter values: {p2}, {p1}", p1, p2);

上記のコードでは、シーケンス内のパラメーター値を含むログ メッセージが作成されます。

Parameter values: param1, param2

注意

1 つのメッセージ テンプレート内で複数のプレースホルダーを使用するときは注意してください。序数基準になります。 名前は、引数をプレースホルダーに合わせる目的では使用 "されません"。

この方法により、ログ プロバイダーが セマンティック ログまたは構造化ログを実装できるようになります。 書式設定されたメッセージ テンプレートだけでなく、引数自体がログ システムに渡されます。 これにより、ログ プロバイダーはフィールドとしてパラメーター値を格納することができます。 次のロガー メソッドについて考えてみます。

_logger.LogInformation("Getting item {Id} at {RunTime}", id, DateTime.Now);

たとえば、Azure Table Storage にログを記録する場合:

  • 各 Azure Table エンティティには、ID プロパティと RunTime プロパティを含めることができます。
  • プロパティを持つテーブルによって、ログに記録されたデータに対するクエリが簡略化されます。 たとえば、クエリによって、特定の RunTime の範囲内のすべてのログを検索できます。テキスト メッセージから時間を解析する必要はありません。

ログ メッセージ テンプレートの書式設定

ログ メッセージ テンプレートでは、プレースホルダーの書式設定がサポートされています。 テンプレートでは、所与の型引数に対してあらゆる有効な形式を自由に指定できます。 たとえば、次の Information ロガー メッセージ テンプレートについて考えてみます。

_logger.LogInformation("Logged on {PlaceHolderName:MMMM dd, yyyy}", DateTimeOffset.UtcNow);
// Logged on January 06, 2022

前の例では、DateTimeOffset インスタンスは、ロガー メッセージ テンプレートの PlaceHolderName に対応する型になります。 値は序数基準であり、この名前には何でも指定できます。 MMMM dd, yyyy 形式は DateTimeOffset 型で有効です。

DateTime および DateTimeOffset の詳細については、「カスタム日時形式文字列」をご覧ください。

次の例では、{} プレースホルダー構文を使用してメッセージ テンプレートを書式設定する方法が示されます。 さらに、{} プレースホルダー構文をエスケープする例がその出力と共に示されます。 最後に、テンプレート プレースホルダーを使用した文字列補間も示されます。

logger.LogInformation("Number: {Number}", 1);               // Number: 1
logger.LogInformation("{{Number}}: {Number}", 3);           // {Number}: 3
logger.LogInformation($"{{{{Number}}}}: {{Number}}", 5);    // {Number}: 5

ヒント

  • ほとんどの場合、ログ記録時には、常にログ メッセージ テンプレートの書式設定を使用する必要があります。 文字列補間を使用すると、パフォーマンスに問題が発生することがあります。
  • コード分析のルール「CA2254: テンプレートは静的な式にする必要があります」は、ログ メッセージに適切な書式設定が使用されていない場所について警告するのに役立ちます。

例外をログに記録する

ロガー メソッドには、例外パラメーターを受け取るオーバーロードがあります。

public void Test(string id)
{
    try
    {
        if (id is "none")
        {
            throw new Exception("Default Id detected.");
        }
    }
    catch (Exception ex)
    {
        _logger.LogWarning(
            AppLogEvents.Error, ex,
            "Failed to process iteration: {Id}", id);
    }
}

例外ログはプロバイダー固有です。

既定のログ レベル

既定のログ レベルが設定されていない場合、既定のログ レベル値は Information になります。

たとえば、次のようなワーカー サービス アプリについて考えてみます。

  • .NET Worker テンプレートを使用して作成された。
  • appsettings.jsonappsettings.Development.json が削除または名前が変更された。

上記の設定で、プライバシー ページまたはホーム ページに移動すると、カテゴリ名に Microsoft が含まれる多くの TraceDebugInformation のメッセージが生成されます。

次のコードは、既定のログ レベルが構成で設定されていない場合に、既定のログ レベルを設定します。

HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);

builder.Logging.SetMinimumLevel(LogLevel.Warning);

using IHost host = builder.Build();

await host.RunAsync();

フィルター関数

フィルター関数は、構成またはコードによって規則が割り当てられていないすべてのプロバイダーとカテゴリに対して呼び出されます。

HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);

builder.Logging.AddFilter((provider, category, logLevel) =>
{
    return provider.Contains("ConsoleLoggerProvider")
        && (category.Contains("Example") || category.Contains("Microsoft"))
        && logLevel >= LogLevel.Information;
});

using IHost host = builder.Build();

await host.RunAsync();

上記のコードでは、カテゴリに Example または Microsoft が含まれ、ログ レベルが Information 以上の場合にコンソール ログが表示されます。

ログのスコープ

"スコープ" では、論理操作のセットをグループ化します。 このグループ化を使用して、セットの一部として作成される各ログに同じデータをアタッチすることができます。 たとえば、トランザクション処理の一部として作成されるすべてのログに、トランザクション ID を含めることができます。

スコープは:

  • BeginScope メソッドによって返される IDisposable 型です。
  • 破棄されるまで継続します。

次のプロバイダーではスコープがサポートされています。

ロガーの呼び出しを using ブロックでラップすることによって、スコープを使用します。

public async Task<T> GetAsync<T>(string id)
{
    T result;
    var transactionId = Guid.NewGuid().ToString();

    using (_logger.BeginScope(new List<KeyValuePair<string, object>>
        {
            new KeyValuePair<string, object>("TransactionId", transactionId),
        }))
    {
        _logger.LogInformation(
            AppLogEvents.Read, "Reading value for {Id}", id);

        var result = await _repository.GetAsync(id);
        if (result is null)
        {
            _logger.LogWarning(
                AppLogEvents.ReadNotFound, "GetAsync({Id}) not found", id);
        }
    }

    return result;
}

次の JSON では、Console プロバイダーのスコープを有効にしています。

{
    "Logging": {
        "Debug": {
            "LogLevel": {
                "Default": "Information"
            }
        },
        "Console": {
            "IncludeScopes": true,
            "LogLevel": {
                "Microsoft": "Warning",
                "Default": "Information"
            }
        },
        "LogLevel": {
            "Default": "Debug"
        }
    }
}

次のコードでは、Console プロバイダーのスコープを有効にしています。

HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);

builder.Logging.ClearProviders();
builder.Logging.AddConsole(options => options.IncludeScopes = true);

using IHost host = builder.Build();

await host.RunAsync();

Main でログを作成する

次のコードでは、ホストを構築した後に DI から ILogger インスタンスを取得することによって Main でログを記録します。

using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;

using IHost host = Host.CreateApplicationBuilder(args).Build();

var logger = host.Services.GetRequiredService<ILogger<Program>>();
logger.LogInformation("Host created.");

await host.RunAsync();

上記のコードは、次の 2 つの NuGet パッケージに依存しています。

そのプロジェクト ファイルは次のようになるはずです。

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net7.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.Extensions.Hosting" Version="7.0.1" />
    <PackageReference Include="Microsoft.Extensions.Logging" Version="7.0.0" />
  </ItemGroup>

</Project>

非同期でないロガー メソッド

ログ記録は高速に実行され、非同期コードのパフォーマンス コストを下回る必要があります。 ログ データ ストアが低速の場合は、そこへ直接書き込まないでください。 まずログ メッセージを高速なストアに書き込んでから、後で低速なストアに移動することを検討してください。 たとえば、SQL Server にログを記録する場合、Log メソッドは同期しているため、Log メソッドで直接それを行わないでください。 代わりに、ログ メッセージをインメモリ キューに同期的に追加し、バックグラウンド ワーカーにキューからメッセージをプルさせて、SQL Server にデータをプッシュする非同期処理を実行させます。

実行中のアプリのログ レベルを変更する

ログ API には、アプリの実行中にログ レベルを変更するシナリオは含まれていません。 ただし、一部の構成プロバイダーは構成を再読み込みすることができ、ログ構成に直ちに影響します。 たとえば、ファイル構成プロバイダーでは、既定でログ構成が再度読み込まれます。 アプリの実行中にコードの構成が変更された場合、アプリは IConfigurationRoot.Reload を呼び出して、アプリのログ構成を更新できます。

NuGet パッケージ

ILogger<TCategoryName>ILoggerFactory のインターフェイスと実装は、ほとんどの .NET SDK に暗黙的なパッケージ参照として含まれています。 暗黙的に参照されない場合でも、次の NuGet パッケージで明示的に使用することができます。

どの .NET SDK に暗黙的なパッケージ参照が含まれるかの詳細については、.NET SDK: 暗黙的な名前空間へのテーブルに関する記事を参照してください。

関連項目