第26章:コンテキスト伝播② “繋げるための設計” 🤝🔗✨
前の章(25章)で「なんでトレースが途中で途切れるの?」を理解したので、今日はその逆! “どう設計したら、端から端までちゃんと繋がるの?” を、TS/Nodeの感覚でまとめるよ〜🧵🧠💕
1) 今日のゴール 🎯✨
この章が終わったら、次の3つができるようになるのが目標だよ👇
- 「繋がる条件」を3つで説明できる🔗
- HTTPで 親子関係(Parent/Child)を崩さずに伝播できる📨
- Node/TSで 途切れやすいポイントを避ける設計ができる🧯
2) まず「何を繋ぐの?」を超ざっくり理解 🧵👀
分散トレースで繋げたいのは、ざっくり言うとこの2つ👇
- Trace(旅のID):このリクエストの“旅全体”のID 🧳
- Span(区間):旅の途中の“区間ごとの記録” 🚃
そして Context(文脈) は、「いま自分がどのSpanの中にいるか」を持ってる“見えないバトン”だよ🏃♀️💨 このバトンが切れると、**Spanが親を失って別の旅(別Trace)**に見えちゃうの🥲
JS/NodeではこのContextを成立させるのに Context Manager が必須だよ(無いと“現在の文脈”が保持できない)🧠🔧 (OpenTelemetry)
3) “繋がる条件”はこの3つだけ覚えてOK ✅🔗✅🔗✅🔗

ここがこの章の核だよ〜!💎
条件A:アプリ内で Context が切れない(非同期でも!)🧵⚙️
- Nodeは非同期が多いから、AsyncLocalStorage / async_hooks を使って「今の文脈」を追跡するのが基本✨
- OpenTelemetryでは Node向けに AsyncLocalStorageContextManager / AsyncHooksContextManager が用意されてるよ(基本はALS推し)🌟 (Socket)
条件B:外に出るとき “標準ヘッダー”で渡す 📨🌍
- HTTPなら traceparent / tracestate(W3C Trace Context)を使うのが王道🧾
- ここがズレると、別サービスが受け取れない/解釈できない💦 (OpenTelemetry)
条件C:受け取った側が “親として採用”する 👨👧🔗
- 受信側がヘッダーから取り出して(extract)
- そのコンテキストを 「このリクエストの親」 としてサーバーSpanを作る
- これで「親子」が繋がるよ🧵✨ (OpenTelemetry)
4) “何を渡す?”の設計:最小セットで強くする 🧠🧳✨
まずはコレだけでOK(標準)📦
traceparent(必須)tracestate(あれば)
W3C Trace Context ではこの2つが基本で、仕様としても「こう扱ってね」が決まってるよ📘 (OpenTelemetry)
baggage は“慎重に”🎒⚠️
OpenTelemetryでは baggage(追加のkey/value)もあるけど、入れすぎると事故るので注意! 「少量・低カードinality・個人情報ナシ」くらいの気持ちで🙅♀️
5) “どこを通って渡す?”の設計:経路ごとにルール化 🗺️🔗
(1) HTTP(王道)🌐
- 受信(server):ヘッダーからextract → server spanの親にする
- 送信(client):いまのcontextをinject → ヘッダーで渡す
OpenTelemetryのJS向けPropagation解説でも、この inject/extract を基本として説明されてるよ✨ (OpenTelemetry)
(2) キュー / 非同期ジョブ(ちょい難)📮⏳
ここは設計が分かれるよ👇
-
同じ“旅”として繋ぐ(Childにする)
- “そのジョブがこのリクエストの続き”ならOK
-
別の旅にする(Linkで関連付け)
- “あとで実行される・並行で走る・再試行される”系は、無理に親子にしない方が読みやすいこと多い✨
(この「Linkで繋ぐ」発想は、後の章でめっちゃ効いてくるよ🧵💡)
6) 実装テンプレ:Node/TSで“繋がる初期化”を作る 🧩✨
ここでは OpenTelemetryのNode SDK を使った “まず繋げる最小構成” を作るよ!
(2026-01-17時点で、@opentelemetry/sdk-node は 0.210.0 が最新として表示されてるよ)(Npm)
@opentelemetry/context-async-hooks は 2.3.0 が最新表示だよ🧵 (Npm)
@opentelemetry/auto-instrumentations-node は 0.68.0 が最新表示だよ🧰 (Npm)
6-1) インストール(例)📦✨
npm i @opentelemetry/api
npm i @opentelemetry/sdk-node @opentelemetry/auto-instrumentations-node
npm i @opentelemetry/context-async-hooks @opentelemetry/core
npm i @opentelemetry/exporter-trace-otlp-http
6-2) src/otel.ts(初期化ファイル)🔧🧵
ポイントはここ👇
- AsyncLocalStorageContextManager を enable
- W3C Trace Context の propagator をセット(traceparent/tracestate)
- アプリ本体より先に起動(順番が超大事!)
import { diag, DiagConsoleLogger, DiagLogLevel, context, propagation } from "@opentelemetry/api";
import { NodeSDK } from "@opentelemetry/sdk-node";
import { getNodeAutoInstrumentations } from "@opentelemetry/auto-instrumentations-node";
import { AsyncLocalStorageContextManager } from "@opentelemetry/context-async-hooks";
import { CompositePropagator, W3CTraceContextPropagator, W3CBaggagePropagator } from "@opentelemetry/core";
import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http";
// デバッグしたい時だけ(本番は控えめにね)
diag.setLogger(new DiagConsoleLogger(), DiagLogLevel.INFO);
// ① Context Manager(これがないと “今のSpan” が保持されない)
const cm = new AsyncLocalStorageContextManager();
cm.enable();
context.setGlobalContextManager(cm);
// ② Propagator(traceparent / tracestate を扱う:W3C)
propagation.setGlobalPropagator(
new CompositePropagator({
propagators: [new W3CTraceContextPropagator(), new W3CBaggagePropagator()],
})
);
const traceExporter = new OTLPTraceExporter({
// 例: OpenTelemetry Collector / APM のエンドポイントに合わせる
url: process.env.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT ?? "http://localhost:4318/v1/traces",
});
export const otelSdk = new NodeSDK({
traceExporter,
instrumentations: [getNodeAutoInstrumentations()],
});
export async function startOtel() {
await otelSdk.start();
}
- Propagatorの考え方(Composite + W3C tracecontext + baggage)は、OpenTelemetryのJSドキュメント側でも紹介されてるよ✨ (OpenTelemetry)
6-3) src/main.ts(起動順を守る)🚀
import { startOtel } from "./otel";
import { startServer } from "./server";
async function main() {
await startOtel(); // ← 先にOTel!!
await startServer(); // ← あとでアプリ本体
}
main().catch((e) => {
console.error(e);
process.exit(1);
});
この「先にOTel」がズレると、最初のリクエストが繋がらないが起きやすいよ🥲⚡
7) “繋がらないあるある”と設計での予防線 🧯😵💫
あるある①:ContextManager を enable し忘れ 🧠💤
new AsyncLocalStorageContextManager()しただけで満足しがち- enable & setGlobal までがセットだよ🧵 (GitHub)
あるある②:fire-and-forget(awaitしない)🔥
awaitしない処理は、文脈が切れたり、Spanが閉じる前に処理が終わったりする- 設計として「非同期に逃がす処理」は Link設計 も検討すると読みやすいよ📮✨
あるある③:別スレッド/別プロセス/キューで消える 🧵➡️📦➡️🧵
- Workerや別プロセスは、自動では繋がらないと思って設計する
- “traceparentをどの入れ物で渡す?”を先に決めるのが勝ち🏆
8) 2026年1月の“最新注意点”⚠️🗓️(超だいじ)
最近(2026年1月中旬)、Node.js側で async_hooks / AsyncLocalStorage絡みのクラッシュ(DoSにつながる) が話題になって、セキュリティリリースも出てるよ。
async_hooks.createHook() が有効な場合に「Maximum call stack size exceeded」が例外処理に届かずプロセスが落ちる、という内容が公式に書かれてる🧨 (Node.js)
なので運用設計としては👇
- Nodeの該当リリースラインを更新しておく(例として 20.20.0 / 22.22.0 / 24.13.0 / 25.3.0 が案内されている)🛡️ (窓の杜)
- OpenTelemetry側も、この件について声明を出してるよ📝 (OpenTelemetry)
- そもそも context-async-hooks は async_hooks の影響を受けるので、最新のNode LTS推奨という注意もあるよ📌 (Socket)
9) ミニ演習:あなたの “繋がる3条件” を完成させよう 📝💖
演習①(設計)🧠
次の文章を、自分の言葉で短くしてみてね👇(各1行でOK!)
- 条件A:
- 条件B:
- 条件C:
演習②(動作チェック)🔍
-
APIを叩いて、ログやトレースUIで 「Aサービスのspan → Bサービスのspan が親子」 になってるか確認👀🧵
-
もし親がいなかったら、まず見るのはこの順👇
- OTelの起動順(先に起動できてる?)
- ContextManager enable(できてる?)
- traceparent が送れてる?(受信してる?)
Copilot / Codex に投げると便利なお願い🤖✨
- 「Express/Fastifyで、受信時にtraceIdをログに出すミドルウェア作って」
- 「fetch/axiosの送信前に、traceparentをcarrierにinjectするサンプル書いて」
- 「キューにtraceparentを載せる設計案を3パターン比較して」
まとめ 🌸✨
- “繋がる条件”は3つ(アプリ内Context維持・標準ヘッダーで渡す・受信側が親にする)🔗
- Node/TSは AsyncLocalStorage系のContextManager が鍵🧵 (Npm)
- HTTPは W3C Trace Context(traceparent/tracestate) を軸に設計すると強い📨 (OpenTelemetry)
- 2026年1月は async_hooks/ALS絡みの注意もあるので、Node更新も設計に含める🛡️ (Node.js)
次の章(27章)は、繋がったトレースに 「意味のある属性(attributes)」 を足して、調査のスピードを爆上げしていくよ〜🎒✨