.NET 6.0 におけるログ記録のカスタマイズ手法

はじめに

本記事では、.NET 6.0 アプリケーションにおいてデフォルトのログ記録動作を変更する方法を解説します。標準設定では、ログ出力先はコンソールまたはデバッグウィンドウに限定されますが、実際の開発や運用ではファイル保存やデータベースへの記録、追加情報の付与が必要となるケースが多々あります。本章では、以下の3点に焦点を当てます。

  • ログ記録の基本設定
  • カスタムログプロバイダの実装
  • サードパーティ製フレームワーク(NLog)の統合

これらの内容は、ASP.NET Core のホスティングレイヤーに関わるものです。

動作環境の準備

サンプルプロジェクトとして、ASP.NET Core MVC アプリケーションを作成します。任意のターミナル(コマンドプロンプト、シェル、Bash など)を開き、次のコマンドを実行してください。

dotnet new mvc -n LoggingSample -o LoggingSample

Visual Studio で開く場合はプロジェクトファイルをダブルクリック、Visual Studio Code を使用する場合は以下を実行します。

cd LoggingSample
code .

ログ記録の基本設定

ASP.NET Core 2.0 以前では、ログ設定は Startup.cs に記述されていました。しかし、その後のバージョンアップに伴い、設定は Program.cs 内の WebHostBuilder に段階的に移行されました。

ASP.NET Core 3.1 以降では、Program.cs がより汎用的な構造になり、最初に IHostBuilder が作成されるようになりました。このビルダーはアプリケーション起動の基盤となり、IWebHostBuilder を介して ASP.NET Core の設定を行います。

public class Program
{
    public static void Main(string[] args)
    {
        CreateHostBuilder(args).Build().Run();
    }

    public static IHostBuilder CreateHostBuilder(string[] args) =>
        Host.CreateDefaultBuilder(args)
            .ConfigureWebHostDefaults(webBuilder =>
            {
                webBuilder.UseStartup<Startup>();
            });
}

.NET 6.0 では、Microsoft は minimal API というアプローチを導入し、Startup ファイルを使用せずに Program.cs で全ての設定を完結させることが可能になりました。

var builder = WebApplication.CreateBuilder(args);

// サービスをコンテナに追加
builder.Services.AddControllersWithViews();

var app = builder.Build();
// ...

ASP.NET Core では、ログ記録を含むほぼ全ての機能を上書き・カスタマイズできます。IWebHostBuilder は、既定の動作を変更するための拡張メソッドを多数提供しており、ログ記録の場合は ConfigureLogging を使用します。

次のコードは、ConfigureWebHostDefaults 内で既定のログ設定を明示的に行った例です。

Host.CreateDefaultBuilder(args)
    .ConfigureWebHostDefaults(webBuilder =>
    {
        webBuilder.ConfigureLogging((hostingContext, logging) =>
        {
            logging.AddConfiguration(hostingContext.Configuration.GetSection("Logging"));
            logging.AddConsole();
            logging.AddDebug();
        })
        .UseStartup<Startup>();
    });

minimal API の場合は、以下のように WebApplicationBuilder.Logging プロパティを通じて設定します。

builder.Logging.AddConfiguration(builder.Configuration.GetSection("Logging"));
builder.Logging.AddConsole();
builder.Logging.AddDebug();

カスタムログプロバイダの実装

ここでは、コンソール出力のログレベルに応じて文字色を変更する、シンプルなカスタムロガー「ColoredConsoleLogger」を作成します。このロガーは ILoggerProvider を使用して生成され、動作を制御するための設定クラスも別途用意します。

以下の3つのクラスを、Program.cs と同じフォルダに CustomLogger.cs として作成します。

1. ColoredConsoleLoggerConfiguration

この設定クラスは、ログレベル、イベントID、コンソールの色を保持します。

public class ColoredConsoleLoggerConfiguration
{
    public LogLevel LogLevel { get; set; } = LogLevel.Warning;
    public int EventId { get; set; } = 0;
    public ConsoleColor Color { get; set; } = ConsoleColor.Yellow;
}

2. ColoredConsoleLoggerProvider

プロバイダは設定を受け取り、実際のロガーインスタンスを生成します。

using System.Collections.Concurrent;

public class ColoredConsoleLoggerProvider : ILoggerProvider
{
    private readonly ColoredConsoleLoggerConfiguration _config;
    private readonly ConcurrentDictionary<string, ColoredConsoleLogger> _loggers =
        new ConcurrentDictionary<string, ColoredConsoleLogger>();

    public ColoredConsoleLoggerProvider(ColoredConsoleLoggerConfiguration config)
    {
        _config = config;
    }

    public ILogger CreateLogger(string categoryName)
    {
        return _loggers.GetOrAdd(categoryName,
            name => new ColoredConsoleLogger(name, _config));
    }

    public void Dispose()
    {
        _loggers.Clear();
    }
}

3. ColoredConsoleLogger

実際にログ出力を行うコアのクラスです。

public class ColoredConsoleLogger : ILogger
{
    private static readonly object _lock = new object();
    private readonly string _name;
    private readonly ColoredConsoleLoggerConfiguration _config;

    public ColoredConsoleLogger(string name, ColoredConsoleLoggerConfiguration config)
    {
        _name = name;
        _config = config;
    }

    public IDisposable BeginScope<TState>(TState state)
    {
        return null;
    }

    public bool IsEnabled(LogLevel logLevel)
    {
        return logLevel == _config.LogLevel;
    }

    public void Log<TState>(LogLevel logLevel, EventId eventId,
        TState state, Exception exception,
        Func<TState, Exception, string> formatter)
    {
        if (!IsEnabled(logLevel))
            return;

        lock (_lock)
        {
            if (_config.EventId == 0 || _config.EventId == eventId.Id)
            {
                var originalColor = Console.ForegroundColor;
                Console.ForegroundColor = _config.Color;
                Console.Write($"{logLevel} - ");
                Console.Write($"{eventId.Id} - {_name} - ");
                Console.Write($"{formatter(state, exception)}\n");
                Console.ForegroundColor = originalColor;
            }
        }
    }
}

lock を使用しているのは、コンソール出力がスレッドセーフではないため、色の表示が乱れるのを防ぐためです。

実装後、Program.cs で次のように登録します。

using LoggingSample;

builder.Logging.ClearProviders();
var config = new ColoredConsoleLoggerConfiguration
{
    LogLevel = LogLevel.Information,
    Color = ConsoleColor.Red
};
builder.Logging.AddProvider(new ColoredConsoleLoggerProvider(config));

ClearProviders() を呼び出して既存のプロバイダを削除した後、新しいプロバイダを追加しています。

この方法を応用すれば、特定のログレベルでメールを送信したり、デバッグメッセージを別のログシンクに書き込むことも可能です。

多くの場合、独自のログフレームワークをゼロから作るよりも、ELMAH、log4net、NLog といった既存の優れたソリューションを利用する方が効率的です。次に、NLog の統合方法を説明します。

サードパーティフレームワーク NLog の利用

NLog は、ASP.NET Core 向けのプロバイダプラグインを提供しており、容易に統合できます。

1. NLog 設定ファイルの作成

NLog.Config ファイルを作成し、以下のように2つのルールを定義します。

  • 全ての標準メッセージを1つのログファイルに記録
  • カスタムメッセージを別のログファイルに記録
<targets>
    <!-- 標準メッセージ用 -->
    <target xsi:type="File" name="allfile"
            fileName="C:\git\dotnetconf\001-logging\nlog-all-${shortdate}.log"
            layout="${longdate}|${event-properties:item=EventId.Id}|${logger}|${uppercase:${level}}|${message} ${exception}" />

    <!-- カスタムメッセージ用 -->
    <target xsi:type="File" name="ownFile-web"
            fileName="C:\git\dotnetconf\001-logging\nlog-own-${shortdate}.log"
            layout="${longdate}|${event-properties:item=EventId.Id}|${logger}|${uppercase:${level}}|  ${message} ${exception}|url: ${aspnet-request-url}|action: ${aspnet-mvc-action}" />

    <target xsi:type="Null" name="blackhole" />
</targets>

<rules>
    <!-- Microsoft を含む全ログを allfile に出力 -->
    <logger name="*" minlevel="Trace" writeTo="allfile" />

    <!-- Microsoft のログは blackhole に出力し、以降のルールを適用しない -->
    <logger name="Microsoft.*" minlevel="Trace" writeTo="blackhole" final="true" />

    <!-- それ以外のログを ownFile-web に出力 -->
    <logger name="*" minlevel="Trace" writeTo="ownFile-web" />
</rules>

2. NuGet パッケージの追加

ターミナルで以下のコマンドを実行し、NLog の ASP.NET Core 対応パッケージをインストールします。

dotnet add package NLog.Web.AspNetCore

3. IWebHostBuilder との統合

従来の Program.cs スタイルの場合、ConfigureLogging で他のプロバイダをクリアし、UseNLog() を呼び出します。

Host.CreateDefaultBuilder(args)
    .ConfigureWebHostDefaults(webBuilder =>
    {
        webBuilder.ConfigureLogging((hostingContext, logging) =>
        {
            logging.ClearProviders();
            logging.SetMinimumLevel(LogLevel.Trace);
        })
        .UseNLog()
        .UseStartup<Startup>();
    });

minimal API での設定は、より簡潔になります。

using NLog.Web;

var builder = WebApplication.CreateBuilder(args);

builder.Logging.ClearProviders();
builder.Logging.SetMinimumLevel(LogLevel.Trace);
builder.WebHost.UseNLog();

このように、NLog の強力な機能を ASP.NET Core に簡単に組み込むことができます。

まとめ

本記事では、.NET 6.0 におけるログ記録のカスタマイズ手法として、以下の内容を解説しました。

  • ログ記録の基本設定(ConfigureLogging の使用)
  • カスタムログプロバイダの実装(ColoredConsoleLogger の例)
  • NLog を利用したサードパーティ製フレームワークの統合

タグ: .NET 6.0 ASP.NET Core ログ ILogger カスタムログ

8月14日 14:44 投稿