📝 はじめに
.NETのロギング機構を使い始めたとき、ILogger、ILoggerProvider、ILoggerFactory、LoggerFactoryと似た名前のインターフェースやクラスが登場し、混乱しました。
特に LoggerFactory.Create と ILoggerFactory.CreateLogger はメソッド名が似ていて紛らわしいです。
この記事では、これらの関係を 図とコード例 で整理します。
🔰 ILoggerとは
ILoggerは実際にログを出力する役割のインターフェースです。
出力先に応じて異なる実装があります。
| 実装 | 出力先 |
|---|---|
| ConsoleLogger | 🖥️ コンソール |
| DebugLogger | 🐛 デバッグウィンドウ |
| EventLogLogger | 📋 Windowsイベントログ |
// ILoggerを使ってログを出力する
logger.LogInformation("処理を開始します");
logger.LogWarning("ファイルが見つかりません: {FileName}", fileName);
logger.LogError(ex, "処理中にエラーが発生しました");
🏭 ILoggerProviderとは
ILoggerProviderはILoggerのインスタンスを生成するインターフェースです。
.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 |
🗺️ 全体像のまとめ
図の見方:
-
LoggerFactory作成 ―
LoggerFactory.Create(静的メソッド)でLoggerFactoryインスタンスを作成 -
プロバイダ登録 ― 各
ILoggerProvider実装をLoggerFactoryに登録。各プロバイダは対応するILoggerを生成する機能を持つ -
ILogger生成と使用 ―
CreateLoggerメソッドは、登録された全プロバイダのILoggerをラップした複合ロガーを返す。アプリケーションはこの複合ロガーを通じて、すべての出力先にログを書き込める
✨ おわりに
ILoggerの仕組みをまとめると:
- 📤 ILogger ― 実際にログを出力するインターフェース
- 🏭 ILoggerProvider ― ILoggerのインスタンスを生成するインターフェース
- 🔧 LoggerFactory ― 複数のプロバイダを束ね、すべての出力先にログを流す複合ロガーを生成する
この設計により、アプリケーションコードはILoggerだけに依存し、出力先の変更はプロバイダの登録を変えるだけで済みます。Serilog等のサードパーティライブラリも、この仕組みに乗ることでシームレスに統合できます 👍