Skip to content

非公式本サイトは非公式の日本語ドキュメントであり、Cloudflare 公式サイトではありません。最新情報はdevelopers.cloudflare.comをご確認ください。

カスタム span

最終更新 Markdown で表示Agent セットアップ

Cloudflare Workers は、fetch 呼び出し、KV の読み取り、D1 のクエリといったプラットフォーム操作を 自動で計装 します。カスタム span を使うと、この可視化を独自のアプリケーションロジックまで広げ、組み込みの計装とあわせてカスタムのコードパスをトレースできます。

カスタム span の API は 2 通りで使えます。どちらも同じメソッドを提供し、動作は同じです。

  • import { tracing } from "cloudflare:workers" — コードベースのどこでも使えます。ユーティリティ関数、ライブラリ、ハンドラーのコンテキストにアクセスできないモジュールも含みます。
  • ctx.tracing — ハンドラーに渡される ExecutionContext で使えます。すでにハンドラー内で作業しているときに便利です。

span の作成メソッドは 2 つあります。

  • enterSpan() — コールバックが戻るか、返した Promise が解決したときに自動で終わる span を作成します。ほとんどの計装ではこちらを使います。
  • startActiveSpan()span.end() を呼んで手動で終了する span を作成します。ストリームの計装など、span がコールバックより長く生きる必要があるときに使います。

トレーシングを有効にする

カスタム span を使うには、Worker でトレーシングを有効にする必要があります。まだの場合は、Wrangler 設定ファイルobservability.traces.enabledtrue にします。

{
  "$schema": "./node_modules/wrangler/config-schema.json",
  "observability": {
    "traces": {
      "enabled": true
    }
  }
}
[observability.traces]
enabled = true

カスタム span を作成する

tracing.enterSpan() で、コードの一区間を名前付き span で囲みます。span は、その時点でアクティブな span の子になり、コールバックが戻るか、返した Promise が解決したときに終わります。

次の例は、cloudflare:workers の import と ctx.tracing の両方を使い、互いに置き換え可能であることを示します。

src/index.jsjs
import { tracing } from "cloudflare:workers";

export default {
	async fetch(request, env, ctx) {
		// Using the import
		return tracing.enterSpan("handleRequest", async (span) => {
			span.setAttribute("url.path", new URL(request.url).pathname);

			const user = await ctx.tracing.enterSpan("auth", async () => {
				// Using ctx.tracing
				return authenticate(request, env);
			});

			return buildResponse(user);
		});
	},
};
src/index.tsts
import { tracing } from "cloudflare:workers";

export default {
	async fetch(request: Request, env: Env, ctx: ExecutionContext) {
		// Using the import
		return tracing.enterSpan("handleRequest", async (span) => {
			span.setAttribute("url.path", new URL(request.url).pathname);

			const user = await ctx.tracing.enterSpan("auth", async () => {
				// Using ctx.tracing
				return authenticate(request, env);
			});

			return buildResponse(user);
		});
	},
};

API リファレンス

tracing.enterSpan(name, callback, ...args)

新しい span を作成し、その中で callback を実行します。コールバックが戻る(同期・非同期)か、例外を投げると、span は自動で終わります。

パラメーター:

パラメーター 説明
name string span の名前です。トレースの可視化に表示されます。
callback (span: Span, ...args: A) => T span 内で実行する関数です。最初の引数として Span オブジェクトを受け取り、続けて enterSpan に渡した追加引数を受け取ります。
...args A span パラメーターの後にコールバックへ転送する、省略可能な追加引数です。

戻り値: callback の戻り値です。

動作:

  • 新しい span は、非同期コンテキスト上で現在アクティブな span の子になります。アクティブな span がなければ、リクエストのルート span の子になります。
  • ネストした enterSpan の呼び出しと、コールバック内で動くランタイム作成の span(fetch や KV 操作など)は、自動でこの span の子になります。
  • コールバックが同期的に戻る、同期的に例外を投げる、または返した Promise が fulfill / reject したときに、span は終わります。
// Synchronous callback — span ends when the function returns
const result = tracing.enterSpan("parse", (span) => {
	span.setAttribute("format", "json");
	return JSON.parse(body);
});

// Async callback — span ends when the promise settles
const data = await tracing.enterSpan("fetchData", async (span) => {
	const res = await fetch("https://api.example.com/data");
	span.setAttribute("http.response.status_code", res.status);
	return res.json();
});

// Forwarding arguments
const doubled = tracing.enterSpan("compute", (span, x) => x * 2, 21);

tracing.startActiveSpan(name, callback, ...args)

新しい span を作成し、callback の実行中にそれをアクティブな span にし、自動では終わらせずに コールバックの結果を返します。処理が完了したら、明示的に span.end() を呼ぶ必要があります。

パラメーター:

パラメーター 説明
name string span の名前です。トレースの可視化に表示されます。
callback (span: Span, ...args: A) => T span がアクティブなあいだに実行する関数です。最初の引数として Span オブジェクトを受け取り、続けて追加引数を受け取ります。
...args A span パラメーターの後にコールバックへ転送する、省略可能な追加引数です。

戻り値: callback の戻り値です。

動作:

  • enterSpan と違い、コールバックが戻っても例外を投げても、span は 自動では終わりませんspan.end() を呼ぶ責任は呼び出し側にあります。
  • span.end() を呼び忘れると、リクエスト所有の span オブジェクトが破棄されるときに、保険として span は送信されます。この動作に頼らないでください。常に明示的に span.end() を呼んでください。

ストリームパイプラインの計装など、1 つのコールバックを超えて続く操作を span で覆いたいときに startActiveSpan を使います。span は、ストリームがすべて消費されるまで開いたままにします。

src/index.jsjs
import { tracing } from "cloudflare:workers";

export default {
	async fetch(request, env, ctx) {
		const body = request.body;
		if (!body) return new Response("No body", { status: 400 });

		// The span is active during the callback, so the pipeThrough
		// operation is correctly nested. The span stays open after
		// the callback returns, until flush() calls span.end().
		const stream = tracing.startActiveSpan("process-stream", (span) => {
			span.setAttribute(
				"request.content_type",
				request.headers.get("content-type") ?? "unknown",
			);

			return body.pipeThrough(
				new TransformStream({
					transform(chunk, controller) {
						// Process each chunk
						controller.enqueue(chunk);
					},
					flush() {
						span.setAttribute("stream.status", "complete");
						span.end();
					},
					cancel() {
						span.setAttribute("stream.status", "cancelled");
						span.end();
					},
				}),
			);
		});

		return new Response(stream);
	},
};
src/index.tsts
import { tracing } from "cloudflare:workers";

export default {
	async fetch(request: Request, env: Env, ctx: ExecutionContext) {
		const body = request.body;
		if (!body) return new Response("No body", { status: 400 });

		// The span is active during the callback, so the pipeThrough
		// operation is correctly nested. The span stays open after
		// the callback returns, until flush() calls span.end().
		const stream = tracing.startActiveSpan("process-stream", (span) => {
			span.setAttribute(
				"request.content_type",
				request.headers.get("content-type") ?? "unknown",
			);

			return body.pipeThrough(
				new TransformStream({
					transform(chunk, controller) {
						// Process each chunk
						controller.enqueue(chunk);
					},
					flush() {
						span.setAttribute("stream.status", "complete");
						span.end();
					},
					cancel() {
						span.setAttribute("stream.status", "cancelled");
						span.end();
					},
				}),
			);
		});

		return new Response(stream);
	},
};

ストリームなしで、あとから使うために span の参照を保持することもできます。

let capturedSpan;
const value = tracing.startActiveSpan("manual-operation", (span) => {
	capturedSpan = span;
	span.setAttribute("phase", "started");
	return computeResult();
});

// The span is still open here — you can set more attributes
capturedSpan.setAttribute("phase", "complete");
capturedSpan.end(); // Now the span is submitted

Span

Span オブジェクトは enterSpanstartActiveSpan のコールバックに渡されます。メタデータで span に注釈を付け、ライフサイクルを制御するメソッドを提供します。

span.setAttribute(key, value)

span に属性を設定します。

パラメーター 説明
key string 属性名です。
value string | number | boolean | undefined 属性値です。undefined を渡しても何も起きません。

属性は、トレースと OpenTelemetry のエクスポートで、span と一緒に表示されます。

span.setAttribute("user.plan", "enterprise");
span.setAttribute("item.count", 42);
span.setAttribute("cache.hit", true);

span.isTraced

この呼び出しがトレースされているかを示す readonly boolean です。リクエストがサンプリングされていない場合(head_sampling_rate に基づく)、isTracedfalse です。enterSpan はコールバックを実行しますが、テレメトリは記録しません。

リクエストがトレースされていないときに、高価な属性計算をスキップできます。

tracing.enterSpan("process", (span) => {
	if (span.isTraced) {
		span.setAttribute(
			"request.body.preview",
			JSON.stringify(body).slice(0, 200),
		);
	}
	return processBody(body);
});

span.end()

span を終了し、属性をトレーシングシステムへ送信します。このメソッドは冪等です。最初の呼び出しの後は、何度呼んでも効果はありません。end() のあと、span.isTracedfalse を返し、以降の setAttribute は黙って無視されます。まだ完了していない進行中の非同期処理からの呼び出しも含みます。

  • enterSpan で作成した span では、end() を呼ぶ必要はありません。ランタイムが自動で呼びます。自分で end() を呼んでも安全ですが、ランタイムがすでに span を終えているため効果はありません。
  • startActiveSpan で作成した span では、送信するために 必ず end() を呼ぶ必要があります。
let mySpan;
const result = tracing.startActiveSpan("manual-op", (span) => {
	mySpan = span;
	span.setAttribute("step", "processing");
	return doWork();
});

// Later, when the work is truly complete:
mySpan.end(); // Span is submitted
mySpan.end(); // No-op, safe to call again

ネストした span

span は、JavaScript の非同期コンテキストに基づいて自動でネストされます。コールバック内で動く enterSpan の呼び出しやプラットフォーム操作(fetchenv.MY_KV.get() など)は、囲んでいる span の子になります。

src/index.jsjs
import { tracing } from "cloudflare:workers";

async function handleOrder(env, orderId) {
	return tracing.enterSpan("handleOrder", async (span) => {
		span.setAttribute("order.id", orderId);

		// This KV read is automatically a child of "handleOrder"
		const order = await env.ORDERS_KV.get(orderId, "json");

		// This nested span is also a child of "handleOrder"
		const total = tracing.enterSpan("calculateTotal", (innerSpan) => {
			innerSpan.setAttribute("item.count", order.items.length);
			return order.items.reduce((sum, item) => sum + item.price, 0);
		});

		// This fetch is a child of "handleOrder"
		await fetch("https://api.example.com/notify", {
			method: "POST",
			body: JSON.stringify({ orderId, total }),
		});

		return new Response(JSON.stringify({ orderId, total }));
	});
}
src/index.tsts
import { tracing } from "cloudflare:workers";

async function handleOrder(env: Env, orderId: string) {
	return tracing.enterSpan("handleOrder", async (span) => {
		span.setAttribute("order.id", orderId);

		// This KV read is automatically a child of "handleOrder"
		const order = await env.ORDERS_KV.get(orderId, "json");

		// This nested span is also a child of "handleOrder"
		const total = tracing.enterSpan("calculateTotal", (innerSpan) => {
			innerSpan.setAttribute("item.count", order.items.length);
			return order.items.reduce(
				(sum: number, item: any) => sum + item.price,
				0,
			);
		});

		// This fetch is a child of "handleOrder"
		await fetch("https://api.example.com/notify", {
			method: "POST",
			body: JSON.stringify({ orderId, total }),
		});

		return new Response(JSON.stringify({ orderId, total }));
	});
}
カスタム span が、自動の KV および fetch 計装と並んでネストされているトレースのウォーターフォール

span 内のログ

console.log() とその他の console メソッドは、現在アクティブな span に自動で紐づくログイベントを出します。つまり、enterSpan または startActiveSpan のコールバック内のログ出力は、トレースと OpenTelemetry のエクスポートでその span に関連付けられます。

tracing.enterSpan("processPayment", async (span) => {
	console.log("Starting payment processing"); // attributed to "processPayment"
	const result = await chargeCard(token, amount);
	console.log("Payment complete", result.id); // also attributed to "processPayment"
});

TypeScript の型

カスタム span API の型宣言です。

declare module "cloudflare:workers" {
	namespace tracing {
		function enterSpan<T, A extends unknown[]>(
			name: string,
			callback: (span: Span, ...args: A) => T,
			...args: A
		): T;

		function startActiveSpan<T, A extends unknown[]>(
			name: string,
			callback: (span: Span, ...args: A) => T,
			...args: A
		): T;
	}

	class Span {
		readonly isTraced: boolean;
		setAttribute(
			key: string,
			value: string | number | boolean | undefined,
		): void;
		end(): void;
	}
}

同じ API が、同じ型でハンドラーのコンテキスト上の ctx.tracing としても使えます。

enterSpanstartActiveSpan の選び方

enterSpan startActiveSpan
span の終了 自動。コールバックが戻る、例外を投げる、または返した Promise が解決したとき 手動。span.end() を呼んだとき
アクティブコンテキストの範囲 コールバック中 コールバック中
用途 ほとんどの計装。1 つのコールバックに収まる同期・非同期の処理 ストリームパイプラインなど、コールバックより長く続く操作
エラー処理 例外時に span は自動終了 例外時も span は開いたまま。span.end() を呼ぶか、ランタイムの保険に頼ります

どちらのメソッドも、span をアクティブなコンテキストの親にするのは コールバック中だけ です。コールバックが戻った後、span はアクティブな親ではなくなります。enterSpan では、span も終了するため、この区別は問題になりません。startActiveSpan では、span は開いたままですが、コンテキストの親ではなくなります。コールバックが戻ったあとに作った新しい span は、この span の子にはなりません。

制限

  • 親子関係の手動指定はできません。 親子関係は、JavaScript の非同期コンテキストが自動で決めます。
  • setAttributes(一括設定)はまだありません。 個別の setAttribute を使います。一括設定は今後のリリースで予定しています。
  • spanContext()(trace / span ID)はまだありません。 境界をまたぐ手動伝播のための trace / span 識別子へのアクセスは、今後のリリースで予定しています。
  • setOutcome はまだありません。 span の結果ステータスの設定は、今後のリリースで予定しています。

その他のトレーシングの制限は、既知の制限 を参照してください。

役に立ちましたか?