第23章 ログをDIで扱う:ILoggerの気持ち良さ🧾✨
この章のテーマはズバリ👇 「ログも “外部I/O” だから、DIで差し替えできるようにすると超ラク」 です😊✨
(Past chat)(Past chat)(Past chat)(Past chat)
1) この章でできるようになること🎯
ILogger<T>を コンストラクタ注入で受け取ってログを出せる💉- ログレベル(Information / Warning / Error…)を使い分けできる🚦
{UserId}みたいな 構造化ログ(あとで検索しやすいログ)を書ける🔍✨- テストでログを検査できる(ダミーLoggerでOK)🧪💕
2) なんで「ログ」をDIで扱うの?🤔🧾

ログって、アプリの外に出る情報だよね(コンソール、デバッグ出力、Windowsイベントログ、クラウド監視…)🌍 つまりログは 外部I/O寄りで、状況によって出し先や粒度が変わりがち。
DIにしておくと👇
- 開発中:コンソール+デバッグに出す👩💻
- 本番:監視基盤に送る📡
- テスト:メモリに貯めて検査する🧪
みたいに、中身のロジックを変えずに差し替えできるのが最高なんだよね😊✨
3) ILogger<T> の基本(ここだけ押さえればOK)💡
✅ ILogger<T> ってなに?
Tは「このクラスのログですよ〜」っていう カテゴリ名になる🏷️- DIコンテナが 適切なカテゴリのLoggerを自動で用意して注入してくれる✨(なければ
ILoggerFactoryから作ってくれる) (Microsoft Learn) - カテゴリ名は「ログがどのクラスから出たか」を追いやすくするのが目的😊 → 公式も「クラス名(完全修飾名)をカテゴリにするのがおすすめ」って言ってるよ (Microsoft Learn)
4) まずは動かそう!3分ハンズオン⌛✨(Generic Host版)
Host.CreateApplicationBuilder() は、最初から 標準のログプロバイダが入ってるのが便利〜!
(Workerテンプレだと、Console / Debug / EventSource / EventLog(Windowsのみ) が最初から追加されるよ) (Microsoft Learn)
✅ 最小サンプル:ILogger<T> を注入してログ出力🧾
**Program.cs(1ファイルでもOK)**👇
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
var builder = Host.CreateApplicationBuilder(args);
// いったんログの出し先を「分かりやすいコンソール」に寄せる✨
builder.Logging.ClearProviders();
builder.Logging.AddConsole();
builder.Services.AddTransient<PaymentService>();
using var host = builder.Build();
var svc = host.Services.GetRequiredService<PaymentService>();
svc.Pay(userId: 42, amountYen: 1200);
public sealed class PaymentService
{
private readonly ILogger<PaymentService> _logger;
public PaymentService(ILogger<PaymentService> logger)
=> _logger = logger;
public void Pay(int userId, int amountYen)
{
_logger.LogInformation("Pay start: UserId={UserId}, AmountYen={AmountYen}", userId, amountYen);
if (amountYen <= 0)
{
_logger.LogWarning("Invalid amount: UserId={UserId}, AmountYen={AmountYen}", userId, amountYen);
return;
}
// ここに本当の支払い処理がある想定✨
_logger.LogInformation("Pay success: UserId={UserId}", userId);
}
}
🌟ポイント
"Pay start: ... {UserId} ..."の{UserId}は 構造化ログ(あとで検索・集計しやすい)🔍✨ClearProviders()→AddConsole()で「どこに出るか」を明確にできる(デバッグでも追いやすい)🧭 (Microsoft Learn)
5) ログレベルの使い分け🚦✨(超ざっくりルール)
LogTrace:細かすぎる追跡(普段はOFF)🧵LogDebug:開発中に便利(本番は抑えがち)🔧LogInformation:通常の流れ(開始・終了・重要な状態)📝LogWarning:ちょい危険(想定外だけど継続できる)⚠️LogError:失敗(例外・処理失敗)💥LogCritical:致命的(サービス継続が危ない)🚨
6) 例外をログに残す基本形💥🧾
例外は 例外オブジェクトも一緒に渡すのがコツだよ👇
try
{
// 何かの処理
}
catch (Exception ex)
{
_logger.LogError(ex, "Payment failed: UserId={UserId}", userId);
throw; // 必要なら再スロー
}
✅ これで「メッセージ」+「スタックトレース」も追いやすくなる✨
7) スコープで「関連するログ」をひとまとまりにする🧺✨
「このリクエストのログ全部に TraceId つけたい〜!」みたいな時に便利😊
using (_logger.BeginScope("TraceId={TraceId}", traceId))
{
_logger.LogInformation("Step1");
_logger.LogInformation("Step2");
}
8) 章末課題:ログをテストで確認しよう🧪💖(ダミーLogger)
「ログが出たか」をテストしたいとき、本物のログ出力先は使わず、メモリに貯めるダミーでOK🙆♀️✨
✅ すごく小さい TestLogger<T>(最低限)
using Microsoft.Extensions.Logging;
using System.Collections.Concurrent;
public sealed class TestLogger<T> : ILogger<T>
{
public ConcurrentQueue<(LogLevel Level, string Message, Exception? Exception)> Entries { get; } = new();
public IDisposable BeginScope<TState>(TState state) => NullScope.Instance;
public bool IsEnabled(LogLevel logLevel) => true;
public void Log<TState>(LogLevel logLevel, EventId eventId,
TState state, Exception? exception, Func<TState, Exception?, string> formatter)
{
Entries.Enqueue((logLevel, formatter(state, exception), exception));
}
private sealed class NullScope : IDisposable
{
public static readonly NullScope Instance = new();
public void Dispose() { }
}
}
✅ テスト例(超ミニ)
(xUnit想定。雰囲気だけでもOKだよ😊)
using Xunit;
public class PaymentServiceTests
{
[Fact]
public void Pay_amount_is_invalid_logs_warning()
{
var logger = new TestLogger<PaymentService>();
var svc = new PaymentService(logger);
svc.Pay(userId: 1, amountYen: 0);
Assert.Contains(logger.Entries, x => x.Level == Microsoft.Extensions.Logging.LogLevel.Warning
&& x.Message.Contains("Invalid amount"));
}
}
9) よくある落とし穴🕳️(ここだけ注意!)
- ❌ ログに個人情報・秘密情報をそのまま出す(パスワード、トークン等)🙅♀️
- ❌ なんでも
Informationにして ログが洪水🌊 - ❌ 例外を握りつぶして「ログだけ出してOK」にしちゃう(境界で方針を決めよ)🧭
- ✅ 基本は
ILogger<T>を注入(Factoryを握るのは必要な時だけ)💉 (Microsoft Learn)
10) AI(Copilot / Codex)活用プロンプト🤖✨
- 「このクラスに
ILogger<T>を注入して、開始・終了・失敗のログを追加して」🧾 - 「このログを構造化ログ(
{Name}プレースホルダ)に直して」🔍 - 「Warningにすべき条件と、Errorにすべき条件を提案して」🚦
- 「このテスト用に、ログを収集できる簡単な
ILoggerを実装して」🧪
11) まとめチェック✅💕
- ログは “外部I/O寄り” だからDIで差し替えると楽✨
-
ILogger<T>はコンストラクタ注入で受け取れる💉 (Microsoft Learn) -
{UserId}みたいな構造化ログが書ける🔍 - テストではダミーLoggerでログを検査できる🧪
次の章(第24章)は「HTTPをDIで扱う」だったよね🌐✨ ログと同じく外部I/Oだから、今回の感覚がそのまま効いてくるよ〜😊