13
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【.NET】ILogger周りが分かりにくいので図とコードで整理してみた

13
Posted at

📝 はじめに

.NETのロギング機構を使い始めたとき、ILoggerILoggerProviderILoggerFactoryLoggerFactoryと似た名前のインターフェースやクラスが登場し、混乱しました。

特に LoggerFactory.CreateILoggerFactory.CreateLogger はメソッド名が似ていて紛らわしいです。

この記事では、これらの関係を 図とコード例 で整理します。

🔰 ILoggerとは

ILogger実際にログを出力する役割のインターフェースです。

出力先に応じて異なる実装があります。

実装 出力先
ConsoleLogger 🖥️ コンソール
DebugLogger 🐛 デバッグウィンドウ
EventLogLogger 📋 Windowsイベントログ
// ILoggerを使ってログを出力する
logger.LogInformation("処理を開始します");
logger.LogWarning("ファイルが見つかりません: {FileName}", fileName);
logger.LogError(ex, "処理中にエラーが発生しました");

🏭 ILoggerProviderとは

ILoggerProviderILoggerのインスタンスを生成するインターフェースです。

.NETではコンソールやWindowsイベントログ等に出力するプロバイダが標準で提供されています。
ただし、ファイル出力のプロバイダは.NETでは提供されていません。ファイルにログを出力したい場合は、SerilogやNLog等のサードパーティ製ライブラリを使用するのが一般的です。

ILoggerおよびILoggerProviderを実装すれば、独自の出力先のロガーを作成することもできます。

🔧 LoggerFactoryの役割

LoggerFactory.Create と ILoggerFactory.CreateLogger の違い

ここが最初に混乱したポイントです 😵

メソッド 役割
LoggerFactory.Create LoggerFactoryのインスタンスを生成する静的メソッド
ILoggerFactory.CreateLogger ILoggerのインスタンスを生成するメソッド

LoggerFactory.Createで生成したLoggerFactoryには、ILoggerProviderを登録できます。

ILoggerFactory.CreateLoggerで生成したILoggerは、内部的にLoggerFactoryに登録されたすべてのILoggerProviderで生成したILoggerを保持しています。

ILogger.Logを呼び出すと、すべてのプロバイダに対してログを出力できます。つまり、ILoggerFactoryは個別プロバイダのラッパー的な役割を果たしています。

🔀 複数プロバイダでのログ出力

複数のプロバイダをILoggerFactoryに登録しておくと、ILogger.Logを呼び出したタイミングですべてのプロバイダのILogger.Logメソッドが呼ばれ、すべての出力先にログを出力できます。

💻 コード例

DIでのILogger登録(Serilog)

実際のプロジェクトでは、Program.csでSerilogをDIコンテナに登録し、各サービスにILogger<T>を注入するパターンが一般的です。

Program.cs

var builder = Host.CreateApplicationBuilder(args);

// 設定ファイルの読み込み
builder.Configuration
    .SetBasePath(AppContext.BaseDirectory)
    .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
    .AddJsonFile($"appsettings.{builder.Environment.EnvironmentName}.json", optional: true, reloadOnChange: true);

// Serilogの設定・DI登録
Log.Logger = new LoggerConfiguration()
    .ReadFrom.Configuration(builder.Configuration)
    .WriteTo.Console()
    .WriteTo.File("logs/app.txt", rollingInterval: RollingInterval.Day)
    .CreateLogger();

builder.Services.AddSerilog(Log.Logger);

var host = builder.Build();
await host.RunAsync();

appsettings.json

{
  "Serilog": {
    "MinimumLevel": {
      "Default": "Information",
      "Override": {
        "Microsoft": "Warning",
        "System": "Warning"
      }
    }
  }
}

WriteTo.Console()WriteTo.File(...)コンソール+ファイルの2つのプロバイダを登録
AddSerilogでSerilogをDIに登録すると、各サービスでILogger<T>を注入できるようになる

コンストラクタインジェクション

C# 12のprimary constructorを使ったパターンです。

public class OrderService(
    IOrderRepository orderRepository,
    ILogger<OrderService> logger) : IOrderService
{
    private readonly IOrderRepository _orderRepository = orderRepository;
    private readonly ILogger<OrderService> _logger = logger;

    public async Task<Order> GetOrderAsync(int orderId, CancellationToken ct = default)
    {
        _logger.LogDebug("注文取得開始: {OrderId}", orderId);

        var order = await _orderRepository.FindByIdAsync(orderId, ct);

        if (order is null)
        {
            _logger.LogWarning("注文が見つかりません: {OrderId}", orderId);
            throw new NotFoundException($"注文ID {orderId} は存在しません。");
        }

        _logger.LogDebug("注文取得完了: {OrderId}", orderId);
        return order;
    }
}

ILogger<OrderService>で型パラメータを指定すると、ログにカテゴリ名(=クラス名)が自動付与される
✅ primary constructorで受け取り、private readonlyフィールドに代入するのが定番パターン

構造化ログ

.NETのILoggerは構造化ログをサポートしています。{プレースホルダー名}で名前付きパラメータを渡すことで、ログの検索や分析が容易になります。

// 構造化ログ(名前付きプレースホルダー)
_logger.LogInformation("ファイル処理完了: {FileName}, レコード数: {Count}", fileName, recordCount);

// 例外付きログ
try
{
    await ProcessAsync(ct);
}
catch (Exception ex)
{
    _logger.LogError(ex, "処理中にエラーが発生しました。対象: {TargetName}", targetName);
    throw;
}

⚠️ 文字列補間($"...{variable}...")を使わないでください。構造化ログのメリットが失われます。

// ❌ NG: 文字列補間 → 構造化されない
_logger.LogInformation($"処理完了: {fileName}");

// ⭕ OK: プレースホルダー → 構造化される
_logger.LogInformation("処理完了: {FileName}", fileName);

📊 ログレベルの使い分け

レベル 用途 メソッド
🔍 Trace 最も詳細なデバッグ情報 LogTrace
🐛 Debug 開発時のデバッグ情報 LogDebug
ℹ️ Information 通常のアプリケーション動作 LogInformation
⚠️ Warning 異常だが処理は続行可能 LogWarning
❌ Error エラー発生、該当処理は失敗 LogError
🔥 Critical アプリケーション全体に影響する致命的エラー LogCritical

🗺️ 全体像のまとめ

図の見方:

  1. LoggerFactory作成LoggerFactory.Create(静的メソッド)でLoggerFactoryインスタンスを作成
  2. プロバイダ登録 ― 各ILoggerProvider実装をLoggerFactoryに登録。各プロバイダは対応するILoggerを生成する機能を持つ
  3. ILogger生成と使用CreateLoggerメソッドは、登録された全プロバイダのILoggerをラップした複合ロガーを返す。アプリケーションはこの複合ロガーを通じて、すべての出力先にログを書き込める

✨ おわりに

ILoggerの仕組みをまとめると:

  • 📤 ILogger ― 実際にログを出力するインターフェース
  • 🏭 ILoggerProvider ― ILoggerのインスタンスを生成するインターフェース
  • 🔧 LoggerFactory ― 複数のプロバイダを束ね、すべての出力先にログを流す複合ロガーを生成する

この設計により、アプリケーションコードはILoggerだけに依存し、出力先の変更はプロバイダの登録を変えるだけで済みます。Serilog等のサードパーティライブラリも、この仕組みに乗ることでシームレスに統合できます 👍

🔗 参考

13
5
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
13
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?