メインコンテンツまでスキップ

第25章:コンテキスト伝播🔗🌬️(つながる条件を理解)

トレースって、「1リクエストの旅」🧵がちゃんと1本の線になって初めて強いんだよね✨ でも現場あるあるで、途中でプツッ✂️と切れます…! この章はそれを **「切れない条件」**としてスッキリ整理して、切れた時の原因を秒で絞れるようにする回だよ〜🕵️‍♀️💨


この章でできるようになること🎯✨

  • 「なぜトレースが途切れるの?」を 言語化できる🗣️
  • HTTP呼び出しで traceparent が渡ってるかチェックできる👀
  • 非同期・バックグラウンドで Activity.Current が消えるパターンを知って回避できる🧯
  • 「つながらない時の原因チェック表」🔍 を自分のチーム用に作れる📋✨

まず結論:つながる条件はコレ✅

画像を挿入予定

コンテキスト伝播(Context Propagation)は、ざっくり言うと…

**「親トレース情報(trace id / span id など)を、境界を越えるときに “入れて→渡して→取り出す”」**こと🔗

境界って主に2つ:

  • プロセス境界:サービスA → サービスB(HTTP / gRPC / メッセージング)🌐
  • スレッド/非同期境界:await / Task / Background など⚙️

OpenTelemetryでは、これを **Propagator(プロパゲータ)**が担当するよ🧰 「注入(Inject)」と「抽出(Extract)」で、メッセージのヘッダ等に出し入れするイメージ!📦✨ (OpenTelemetry)


1) HTTP越し:traceparent / tracestate が命🫀🌍

W3C Trace Context ってなに?🧾

いまの分散トレースの基本は、W3C Trace Context(標準のHTTPヘッダ)だよ📌 代表がこれ👇

  • traceparent
  • tracestate

この仕様自体が「HTTPヘッダでトレース文脈を渡す」ことを標準化してる✨ (w3.org)

traceparent はだいたいこういう形(覚えなくてOK!“あるかどうか”が大事)👇 (w3.org)

traceparent: 00-<trace-id>-<parent-id>-<trace-flags>

だいたいは自動で繋がる🤝✨(でも条件あり)

ASP.NET Core側で受け、HttpClientで呼ぶ、みたいな「よくある形」だと 計測ライブラリ(Instrumentation)が 受信でExtract・送信でInject をやってくれて、繋がりやすいよ〜🎉 (OpenTelemetry)


2) 非同期越し:Activity.Current が消えると切れる🫥⚡

.NETの分散トレースの中心は System.Diagnostics.Activity だよ🧵 そして いまの実行の親コンテキストActivity.Current に入る感じ👀✨ (Microsoft Learn)

しかも Activity.Current非同期呼び出し間でフローする(つまりawaitをまたいでも基本ついてくる)って明記されてるよ✅ (Microsoft Learn)

それでも切れやすい “あるある” 😭

  • 🔥 await しないで投げっぱなし(親が先に終わる)
  • 🧵 新しいスレッド/独自の実行基盤に飛ぶ(文脈を持っていかない)
  • 🧪 手動でActivityを作ったけど親を渡してない(“新しい旅”になっちゃう)
  • 🧹 ExecutionContextのフローを抑制する系(高度だけど現場で稀に遭遇)

3) 「外部に渡す」も設計だよ⚠️🌍

伝播って便利だけど、外部サービスや公開APIへ何でも送るのは危ないこともあるよ〜🫣 トレースIDやBaggageが、内部事情をうっすら漏らす可能性があるから、どこまで伝播するかは方針が必要✨ (OpenTelemetry)


4) ハンズオン:HTTPで「つながってる」を体感しよ🧪👀✨

ここは「A → B」で、同じトレースになってるのを確認するよ🧵🔗 (表示はコンソールでもOK!)

✅ ざっくり構成

  • ServiceA:受け口(外から叩く)🚪
  • ServiceB:呼ばれる側📞

A) ServiceB(呼ばれる側)Program.cs例📌

using OpenTelemetry.Resources;
using OpenTelemetry.Trace;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddOpenTelemetry()
.WithTracing(tpb =>
{
tpb
.SetResourceBuilder(ResourceBuilder.CreateDefault().AddService("ServiceB"))
.AddAspNetCoreInstrumentation()
.AddConsoleExporter();
});

var app = builder.Build();

app.MapGet("/b", () => Results.Ok("Hello from B"));

app.Run();

B) ServiceA(呼ぶ側)Program.cs例📌

using OpenTelemetry.Resources;
using OpenTelemetry.Trace;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddHttpClient("toB", c =>
{
c.BaseAddress = new Uri("http://localhost:5102"); // ServiceB
});

builder.Services.AddOpenTelemetry()
.WithTracing(tpb =>
{
tpb
.SetResourceBuilder(ResourceBuilder.CreateDefault().AddService("ServiceA"))
.AddAspNetCoreInstrumentation()
.AddHttpClientInstrumentation()
.AddConsoleExporter();
});

var app = builder.Build();

app.MapGet("/a", async (IHttpClientFactory f) =>
{
var client = f.CreateClient("toB");
var text = await client.GetStringAsync("/b");
return Results.Ok($"A got: {text}");
});

app.Run();

C) 観察ポイント👀✨

  • /a を叩いたとき

    • ServiceA側のトレースが出る🧵
    • ServiceB側にもトレースが出る🧵
    • 同じ trace-id になってたら成功🎉

Instrumentationを使うのが基本的にラクで安全、というベストプラクティスも公式で推されてるよ✅ (OpenTelemetry)


5) HTTP以外(メッセージング等)は「自前Inject/Extract」が出やすい📦🧩

キューや独自プロトコルだと、自動でやってくれない場面があるから ヘッダに入れて渡すを自分でやることがあるよ〜💪

OpenTelemetryのPropagatorは、まさにそのためのAPI✨ (OpenTelemetry) そして .NETのOpenTelemetry SDKでは、既定が tracecontext + baggage の複合になっている実装が示されてるよ📌 (GitHub)

さらに、AMQP(メッセージング)向けに「どうエンコードするか」仕様も出てる(W3C側)ので、こういう世界もあるんだ〜でOK!📦📨 (W3C)


6) つながらない時の原因チェック表🔍📋✨(ここが本題!)

「切れた!」って時は、順番に潰すのが最短だよ🕵️‍♀️💨

✅ Step 1:プロセス境界(HTTP等)で切れてない?🌐

  • 送信側:traceparent を送ってる?(Instrumentationが入ってる?) (w3.org)
  • 受信側:traceparent を受け取ってExtractしてる?(AspNetCore instrumentation等) (OpenTelemetry)
  • 途中に API Gateway / Proxy / LB がいてヘッダ落としてない?
  • 外部向けに “送らない方針” にしてない?(意図的に遮断してない?) (OpenTelemetry)

✅ Step 2:非同期境界で切れてない?⚙️

  • Activity.Current が欲しい地点で nullになってない?(親が居ない) (Microsoft Learn)
  • awaitせず投げた処理で 親が先に終了してない?
  • Background/Queue処理で 親コンテキストを渡してない

✅ Step 3:手動Spanの作り方で切れてない?🧵

  • ActivitySource.StartActivity() の親が想定どおり?
  • 「新しいトレース」になってもOKな処理と混ざってない?

✅ Step 4:メッセージングで切れてない?📦

  • Producerが Inject してる?
  • Consumerが Extract して親として開始してる?
  • そもそもヘッダを運べる設定になってる?

7) AI活用(Copilot / Codex)プロンプト例🤖✨

そのまま貼ってOKだよ〜📋💕

  • 「ASP.NET Core + HttpClient で OpenTelemetry を使い、A→B呼び出しが同一Traceになる最小サンプルを作って。ConsoleExporterで trace-id を見たい。」
  • 「BackgroundServiceで処理すると trace が途切れる。親traceをメッセージに載せて、consumer側でExtractして継続する設計案と実装例を出して。」
  • 「外部APIへは traceparent を送らない方針にしたい。送信先ホストで伝播を止める設計を提案して(セキュリティ理由も一言)。」 (OpenTelemetry)

まとめ🎁✨

  • トレースが繋がる鍵は 「境界で Inject/Extract」 🔗 (OpenTelemetry)
  • HTTPは W3C Trace Context(traceparent等) が基本🧾 (w3.org)
  • .NETは Activity.Current が親コンテキストで、非同期でも基本フローするよ🧵✨ (Microsoft Learn)
  • 切れたら、チェック表で **「どの境界で切れたか」**を特定するのが最短🔍📋

次の第26章は、トレースに「意味」を足していく回(属性🎒✨)だよ〜!