第25章:ログ設計② 相関ID+レジリエンス+ミニ総合演習🧵⏳🎓
この章は「追える(追跡)+耐える(レジリエンス)」を、いちど“実戦っぽく”通しで体験する回だよ〜😊✨ ミニプロジェクト(推し活グッズ購入管理🛍️💖)を題材に、**相関ID(Correlation ID)**と タイムアウト/キャンセル/リトライを入れて「運用で助かる設計」を完成させるよ🔥
この章のゴール✅✨
最後に、受講者がこう言える状態にするよ😊🎓
- 「このエラー、どのリクエストの出来事かすぐ追える!🧵🔎」
- 「外部APIが遅い/落ちた時でも、待ちすぎず・壊れにくい⏳🛡️」
- 「ログにもAPIエラー(ProblemDetails)にも、問い合わせ用IDが入ってる🧾🆔」
0. 今日の授業の流れ(おすすめ⏰)
- 10分:相関IDってなに?🧵
- 25分:相関IDをログへ必ず載せる(実装)🔎
- 25分:外部HTTP呼び出しに相関IDを引き回す+ProblemDetailsに入れる🧾
- 25分:レジリエンス(タイムアウト/キャンセル/リトライ)実装⏳🔁🛑
- 20分:ミニ総合演習(通しで動かす)🛍️💖
1. 相関IDってなに?🧵🤔

相関IDは、ざっくり言うと **「このリクエストの一生を追うための番号」**だよ📌✨ ログが1000行あっても、相関IDが同じなら「この一連の出来事だな」って一瞬でつながる😊🔎
1-1. “TraceId”が超おすすめな理由🧵✨
HTTPの世界では、分散トレーシング向けに W3C Trace Context(traceparent ヘッダ)が標準で決まってるよ🌐📏 (Qiita)
ASP.NET Core のログも、設定すると TraceId / SpanId / ParentId などを“自動でスコープに載せられる”のが強い✨ (Microsoft Learn)
つまり…
- できれば TraceId(分散トレースのID) を「相関IDの本命」にする🧵
- 追加で
X-Correlation-IDみたいなヘッダも“受付/返却”すると親切(クライアントが使える)💌✨
2. 実装①:ログに TraceId を自動で載せる🔎🧵
ASP.NET Core のログは、設定しだいで TraceId などをスコープへ自動付与してくれるよ✨ (Microsoft Learn)
2-1. Program.cs:スコープ+ActivityTrackingOptions をON✅
using Microsoft.Extensions.Logging;
using System.Diagnostics;
var builder = WebApplication.CreateBuilder(args);
// ✅ スコープ表示をON(コンソールでも TraceId が見えるようにする)
builder.Logging.AddSimpleConsole(o => o.IncludeScopes = true);
// ✅ TraceId / SpanId / ParentId / Baggage / Tags をスコープに自動追加
builder.Logging.Configure(o =>
{
o.ActivityTrackingOptions =
ActivityTrackingOptions.SpanId |
ActivityTrackingOptions.TraceId |
ActivityTrackingOptions.ParentId |
ActivityTrackingOptions.Baggage |
ActivityTrackingOptions.Tags;
});
var app = builder.Build();
app.MapGet("/", (ILoggerFactory lf) =>
{
var logger = lf.CreateLogger("Demo");
logger.LogInformation("Hello with TraceId!");
return Results.Ok("ok");
});
app.Run();
ポイント💡
IncludeScopes = trueが超大事だよ〜😊traceparentが入ってきた場合、ParentIdが W3C の値としてログに出る動きも説明されてるよ🧵🌐 (Microsoft Learn)
3. 実装②:相関IDミドルウェア(X-Correlation-ID 対応)💌🧵
「クライアントが相関IDを送ってきたら尊重する」+「返す」までやると、問い合わせ対応がめっちゃ楽になるよ😊📞✨
3-1. CorrelationIdMiddleware(最小だけど実戦向け)
using System.Diagnostics;
public sealed class CorrelationIdMiddleware
{
public const string HeaderName = "X-Correlation-ID";
private readonly RequestDelegate _next;
private readonly ILogger<CorrelationIdMiddleware> _logger;
public CorrelationIdMiddleware(RequestDelegate next, ILogger<CorrelationIdMiddleware> logger)
{
_next = next;
_logger = logger;
}
public async Task InvokeAsync(HttpContext ctx)
{
// ① クライアント指定があれば使う(なければ TraceId を採用)
var fromHeader = ctx.Request.Headers[HeaderName].FirstOrDefault();
var traceId = Activity.Current?.TraceId.ToString() ?? ctx.TraceIdentifier;
// ゆるバリデーション(長すぎは捨てる)
var correlationId = (!string.IsNullOrWhiteSpace(fromHeader) && fromHeader.Length <= 64)
? fromHeader
: traceId;
// ② 後続で参照できるように保存
ctx.Items[HeaderName] = correlationId;
// ③ レスポンスにも返す(クライアントが控えられる)
ctx.Response.Headers[HeaderName] = correlationId;
// ④ ログスコープに入れる(どのログ行にも乗る)
using (_logger.BeginScope(new Dictionary<string, object?>
{
["CorrelationId"] = correlationId
}))
{
await _next(ctx);
}
}
}
3-2. Program.cs に組み込み🔧
var builder = WebApplication.CreateBuilder(args);
// (ログ設定は前節のとおり)
var app = builder.Build();
app.UseMiddleware<CorrelationIdMiddleware>();
app.MapGet("/demo", (HttpContext ctx, ILoggerFactory lf) =>
{
var logger = lf.CreateLogger("Demo");
var correlationId = ctx.Items[CorrelationIdMiddleware.HeaderName]?.ToString();
logger.LogInformation("Hello! correlationId={CorrelationId}", correlationId);
return Results.Ok(new { correlationId });
});
app.Run();
4. 実装③:ProblemDetails に「問い合わせ用ID」を入れる🧾🆔
ProblemDetails は RFC 9457 の標準形式だよ🧾✨ (ねののお庭。)
そして ASP.NET Core は AddProblemDetails で ProblemDetails を生成できる(例外/ステータスコードページ等)って公式に書かれてるよ😊 (Microsoft Learn)
さらに RFC 9457 は「拡張メンバー(extension)」も持てるので、traceId みたいなのを追加してOKな設計にできるよ🧠✨ (ねののお庭。)
4-1. AddProblemDetails + CustomizeProblemDetails
using System.Diagnostics;
using Microsoft.AspNetCore.Diagnostics;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddProblemDetails(options =>
{
options.CustomizeProblemDetails = ctx =>
{
// traceId(本命)🧵
var traceId = Activity.Current?.TraceId.ToString() ?? ctx.HttpContext.TraceIdentifier;
ctx.ProblemDetails.Extensions["traceId"] = traceId;
// correlationId(クライアント向け)💌
if (ctx.HttpContext.Items.TryGetValue(CorrelationIdMiddleware.HeaderName, out var cid) && cid is string s)
{
ctx.ProblemDetails.Extensions["correlationId"] = s;
}
};
});
var app = builder.Build();
app.UseMiddleware<CorrelationIdMiddleware>();
// ✅ 例外→ProblemDetails(標準の仕組み)
app.UseExceptionHandler();
app.UseStatusCodePages();
app.MapGet("/boom", () =>
{
throw new InvalidOperationException("バグっぽい例外💥");
});
app.Run();
※ AddProblemDetails と、例外/ステータスコードから ProblemDetails を作る流れは公式の説明どおりだよ📘✨ (Microsoft Learn)
5. レジリエンス入門:待ちすぎない&壊れにくい⏳🛡️
ここからは「外部APIが遅い/落ちる」は“普通に起こる”前提で守るよ😊🌩️
5-1. まずは3点セット🔰
- タイムアウト:いつまでも待たない⏳✋
- キャンセル:ユーザーが戻った/画面閉じたら止める🛑
- リトライ:一時的な失敗だけ、条件つきで再試行🔁
5-2. 「リトライしていい?」の判断(超重要⚠️)
- GET みたいな 読み取りは比較的リトライしやすい😊
- POST で「購入を作る」みたいな 追加/更新は、リトライすると二重購入になりがち😱 → だから、Unsafe HTTP methods のリトライを無効化するのが実戦ではよくある✨ (Microsoft Learn)
6. 実装④:HttpClient に標準レジリエンスを付ける🛡️🌐
Microsoft公式で、Microsoft.Extensions.Http.Resilience を使って HttpClient にレジリエンスを入れられるよ〜😊✨ (Microsoft Learn)
AddStandardResilienceHandler() は、タイムアウト/リトライ/サーキットブレーカー等を標準構成で積むって説明されてるよ🧱✨ (Microsoft Learn)
6-1. 標準ハンドラの“中身”(知ってると強い💪)
標準ハンドラは、既定でだいたいこういう構成になってるよ(順番も大事)🧠
- Total timeout(全体の制限)
- Retry(最大3回、指数バックオフ+ジッター)
- Circuit breaker(壊れてる依存先を一時停止)
- Attempt timeout(1回の試行を10秒制限) …などなど✨ (Microsoft Learn)
6-2. “購入POST”みたいな危険操作はリトライ禁止🚫🔁
公式ドキュメントでも「POSTをリトライすると重複の危険」って例が出てるよ⚠️ (Microsoft Learn)
builder.Services.AddHttpClient<ShippingClient>(client =>
{
client.BaseAddress = new Uri("https://example.invalid/");
})
.AddStandardResilienceHandler(options =>
{
// ✅ 危険メソッドはリトライしない(まず安全側)
options.Retry.DisableForUnsafeHttpMethods();
});
7. ミニ総合演習:推し活グッズ購入管理🛍️💖(通しで完成🎓)
7-1. 題材(最小の仕様)📌
- グッズには在庫がある📦
- 予算がある💰
- 購入すると在庫が減り、予算も減る🧾
- 配送料は外部APIから取得(たまに遅い/失敗する)🌐🌩️
7-2. 成果物(この章の担当ぶん)🎁
- ✅ ログに TraceId/CorrelationId が載る🧵
- ✅ ProblemDetails に traceId/correlationId が載る🧾
- ✅ 外部API呼び出しが タイムアウト/リトライ で耐える⏳🔁
- ✅ それでもダメなら インフラエラーResult に変換して返す🎁
8. 演習ステップ(手順どおりでOK😊🧩)
Step A:購入APIを1本作る🛍️
-
POST /purchases- 入力:
itemId,qty - 成功:200 + 購入結果
- 失敗(想定内):在庫不足/予算不足 → Result失敗 → ProblemDetails(409など)
- 入力:
Step B:相関IDミドルウェアを入れる🧵
X-Correlation-IDを受け取る/返す- ログスコープに
CorrelationIdを入れる
Step C:ProblemDetailsにIDを入れる🧾🆔
-
traceIdとcorrelationIdを extensions に入れる- これで「お問い合わせのとき、このIDください🙏」ができる📞✨
Step D:外部API(配送料)呼び出しにレジリエンス🛡️
ShippingClient.GetFeeAsync()(GET想定)AddStandardResilienceHandler()を付ける- “購入POST自体”は危険なので、POSTリトライはしない(ここ大事)⚠️ (Microsoft Learn)
9. 仕上げのチェックリスト✅✨(ここ通れば合格🎓)
- ログ1行1行に
TraceIdが入ってる🧵 (Microsoft Learn) -
X-Correlation-IDを送ると、レスポンスにも同じIDが返る💌 - 失敗レスポンス(ProblemDetails)に
traceIdが入ってる🧾 (ねののお庭。) - 外部APIが落ちても、無限に待たずにタイムアウトする⏳🛑 (Microsoft Learn)
- “危険操作”はリトライされない(重複購入しない)🚫🔁 (Microsoft Learn)
10. AI活用(Copilot/Codex)おすすめプロンプト集🤖✨
そのままコピペでOKだよ😊💕
- 「ASP.NET Core のミドルウェアで
X-Correlation-IDを受け取り、レスポンスヘッダに返して、ILogger.BeginScopeでCorrelationIdをスコープに入れる実装を書いて」 - 「AddProblemDetails の CustomizeProblemDetails で traceId と correlationId を extensions に入れるコードを書いて」
- 「HttpClientFactory で AddStandardResilienceHandler を使い、Unsafe HTTP methods のリトライを無効化する設定例を書いて」 (Microsoft Learn)
- 「“購入POSTはリトライ危険”の理由を、初心者向けにやさしく説明して(例つきで)」 (Microsoft Learn)