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

第23章:トレース入門🧵✨(Trace/Spanの世界)

ねらい🎯

画像を挿入予定

  • 1リクエストの「旅」を Trace としてイメージできるようになる🧳✨ (OpenTelemetry)
  • 旅の「区間」を Span として切り出して、親子関係で説明できるようになる🌳🧵 (Microsoft Learn)
  • C#/.NET では「Span ≒ Activity」って感覚をつかむ🧠🔧 (Microsoft Learn)

1. まず“トレース”って何?🧵👀(超ざっくり)

あるユーザーが /work を叩いたとするよね? そのときアプリの中では、

  • 入口で受け取る(HTTP)🌐
  • DBに聞く🗄️
  • 外部APIを叩く📡
  • 計算する🧮
  • 返す📮

…みたいに、いろんな処理が連鎖する。

この 「1回のリクエストが通った道のりぜんぶ」Trace(トレース)だよ🧵✨ (OpenTelemetry) そして 道のりを区間に分けた1つ1つSpan(スパン)!🚏🚃 (OpenTelemetry)


2. Trace / Span の関係を“旅の地図”で覚える🗺️🧵

  • Trace = 旅の全体(1リクエスト分のストーリー)🧳
  • Span = 旅の区間(DB問い合わせ、外部API呼び出し…みたいな1操作)🚉
  • Spanは親子になる:親Spanの中に子Spanがぶら下がって、木構造になる🌳 (OpenTelemetry)

たとえばこんな木🌳👇

Trace(/work)
└─ Span: HTTP GET /work(入口)
├─ Span: Validate input(入力チェック)
├─ Span: DB SELECT Orders(DB)
├─ Span: Call Payment API(外部API)
└─ Span: Build Response(組み立て)

ここで大事なのは、「どこで時間を食った?」 を Span の所要時間で“指差し”できること⏱️👉✨


3. Spanって何が入ってるの?🎒✨(最低限これだけ)

OpenTelemetryの概念として、Spanにはだいたいこういう情報が入るよ👇 (OpenTelemetry)

  • 名前(例:DB SELECT Orders)🏷️
  • 開始時刻 / 終了時刻(=所要時間)⏱️
  • 親Span ID(root以外)👨‍👩‍👧‍👦
  • TraceId / SpanId(ひも付けの番号)🔢
  • Attributes(タグ)(例:db.system=postgresql)🏷️
  • Events(Spanの途中の“メモ”)📝
  • Status(OK/ERROR)✅💥

ここでは「へぇ〜 Spanって“メタ情報付きの区間”なんだ〜」くらいでOKだよ😊🫶


4. つながる条件:コンテキスト伝播🔗🌬️(超入門だけ)

分散トレースが成立するには、サービス間で「このリクエスト同じ旅だよ〜」って情報を渡す必要があるよね? そのために 標準のHTTPヘッダーtraceparent / tracestate など)でコンテキストを運ぶ仕組みがあるよ📦✨ (w3.org)

※ここは第25章でガッツリやるから、今日は「ヘッダーでつないでるんだな〜」だけ持ち帰ろう😉🧡


5. C#/.NETでは Span ≒ Activity だよ🧠🔧

.NETの世界では、トレース上の「作業単位」は System.Diagnostics.Activity で表されるよ(Activityの木=Traceの木)🌳✨ (Microsoft Learn)

そして「Activityを作る入口」が ActivitySource! アプリやライブラリは ActivitySource と Activity を使って計装してね、って公式にも書かれてるよ🧩 (Microsoft Learn)

さらにコツ👇

  • ActivitySourceは毎回newしない(基本は1つを使い回す)🧯

    • “staticで1回作って使い回してね”がベストプラクティスだよ✨ (Microsoft Learn)

6. ミニ実装①:まずは「自動でSpanが出る」体験をする🧪✨

ここでは「自分でSpan切らなくても、入口/外部呼び出しは自動で出るんだ〜!」を体験するよ🎢 (次で手動Spanも足すよ!)

6.1 ざっくり構成🧩

  • ASP.NET Core Web API に OpenTelemetry を入れる
  • Console Exporter で “とりあえず出力” して見る
  • /work を叩いて Span を眺める👀

※OpenTelemetryは「Exporterで出力先を変えられる」設計だよ(標準出力やCollector、各種バックエンドへ)🧰✨ (OpenTelemetry)

6.2 サンプル(Program.csのイメージ)🧁

using OpenTelemetry.Resources;
using OpenTelemetry.Trace;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddOpenTelemetry()
.WithTracing(tracing =>
{
tracing
.SetResourceBuilder(ResourceBuilder.CreateDefault().AddService("SampleApp"))
.AddAspNetCoreInstrumentation()
.AddHttpClientInstrumentation()
.AddConsoleExporter(); // まずは見える化優先✨
});

builder.Services.AddHttpClient();
var app = builder.Build();

app.MapGet("/work", async (IHttpClientFactory httpClientFactory) =>
{
var client = httpClientFactory.CreateClient();
// 適当な外部呼び出し(例)※実際は自分の題材アプリの外部APIでもOK
var _ = await client.GetAsync("https://example.com");
return Results.Ok(new { ok = true });
});

app.Run();

ポイント👀✨

  • AddAspNetCoreInstrumentation() → 入口のHTTP Spanが出やすい🌐
  • AddHttpClientInstrumentation() → 外向きHTTPのSpanが出やすい📡 (この“自動で出る範囲”があるのが、トレース入門の楽しいとこ🥳) (OpenTelemetry)

7. ミニ実装②:手動Spanを1本だけ足してみる🧵➕

自動Spanだけだと、「業務の中身」がブラックボックスになりがち😵‍💫 そこで “ここが大事!”って場所に手動Span を足すよ✨

7.1 ActivitySourceを用意(使い回し!)♻️

using System.Diagnostics;

public static class MyTelemetry
{
public static readonly ActivitySource ActivitySource = new("SampleApp.Work", "1.0.0");
}

ActivitySourceは使い回しが基本だよ♻️✨ (Microsoft Learn)

7.2 例:/work の中に「BusinessWork」Spanを追加🧵

app.MapGet("/work", async (IHttpClientFactory httpClientFactory) =>
{
using var span = MyTelemetry.ActivitySource.StartActivity("BusinessWork", ActivityKind.Internal);

span?.SetTag("feature", "work");
span?.SetTag("step", "start");

var client = httpClientFactory.CreateClient();
var res = await client.GetAsync("https://example.com");

span?.SetTag("http.status_code", (int)res.StatusCode);

return Results.Ok(new { ok = true });
});

これでトレースを見ると、だいたい👇みたいな並びになって嬉しい😊🧡


8. 演習:Spanの候補を洗い出そう🗺️🖊️(超重要!)

ここがこの章のメインだよ〜!!🎉

8.1 ルール(今日の暫定ルールでOK)📏

Span候補はまずこの3カテゴリだけで考えると迷いにくい✨

  1. 入口/出口(HTTP受信・レスポンス返却)🌐
  2. 外部I/O(DB・外部API・Queue・ファイル)🗄️📡
  3. 時間を食いそうな業務の山(計算・集計・変換)⛰️🧮

8.2 例題🎭(あなたの題材アプリに置き換えてOK)

「注文確定 /checkout」があるとして…👇

  • 入口:HTTP POST /checkout 🌐
  • 業務:ValidateCart / CalculateTotal 🧮
  • 外部:DB Insert Order / Call Payment API 🗄️📡
  • 仕上げ:BuildResponse 🎁

8.3 ワークシート(埋めてみてね📝✨)

Trace名(入口):
- [ ] 入口Span:____________________

子Span候補(まずは5個まで):
1. [ ] 外部I/O:____________________(例:DB/外部API)
2. [ ] 外部I/O:____________________
3. [ ] 業務の山:____________________(“時間かかりそう”)
4. [ ] 業務の山:____________________
5. [ ] 仕上げ:____________________

“Spanにしない”候補(細かすぎ注意):
- ____________________
- ____________________

9. よくある「つまずき」あるある😵‍💫➡️😊

9.1 Spanが出ない…💥

  • OpenTelemetry側の TracerProvider(初期化)が無いと no-op になることがあるよ(=作っても出ない)🫠 (OpenTelemetry)

9.2 ActivitySourceをあちこちでnewしてぐちゃぐちゃ😇

  • ActivitySourceは“高頻度でnewしないで、使い回し”が推奨だよ♻️ (OpenTelemetry)

9.3 Span名が「DoWork」だらけ🫥

  • 読めないトレースになる! → 次章(第24章)で「切り方と命名」を気持ちよく整えるよ🏷️✨

10. AI活用🤖✨(この章で使うと強いプロンプト例)

10.1 Span候補を提案してもらう🗺️

  • 「このエンドポイントの処理手順はこう。どこをSpanに切ると調査しやすい? 5〜10個で」

10.2 Span名を整えてもらう🏷️

  • 「Span名が曖昧になりがち。短く・具体的に、名詞/動詞のルールで10案出して」

10.3 “細かすぎ問題”のチェック⚖️

  • 「Spanが増えすぎそう。削るべきSpan候補と理由を教えて」

まとめ🧵✨

  • Trace = 1リクエストの旅Span = 旅の区間🧳🚉 (OpenTelemetry)
  • 親子Spanで木になる🌳(どこで時間を食ったか見える)⏱️ (Microsoft Learn)
  • .NETでは Spanっぽいものは Activity、作るのは ActivitySource🔧 (Microsoft Learn)
  • 次は「Spanの切り方&命名」で、読みやすいトレースに進化させるよ🏷️🚀