INTEGRITY Dokumentace

Vlastní spany

Cloudflare Workers automaticky instrumentuje operace platformy, jako jsou volání fetch, čtení z KV a dotazy D1. Vlastní spans umožňují tuto viditelnost rozšířit i na vaši aplikační logiku, takže můžete trasovat vlastní cesty kódu společně s vestavěnou instrumentací.

API pro vlastní spans je k dispozici dvěma způsoby, oba nabízejí stejné metody a chovají se identicky:

Existují dvě metody vytváření spanů:

Povolit trasování

Vlastní spany vyžadují, aby bylo u vašeho Workeru zapnuté trasování. Pokud jste to ještě neudělali, nastavte observability.traces.enabled na true ve vašem Konfigurační soubor Wrangler:

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

Vytvořte vlastní span

Použijte tracing.enterSpan() k obalení části kódu pojmenovaným spanem. Span se automaticky stane potomkem toho spanu, který je aktuálně aktivní, a končí ve chvíli, kdy callback vrátí hodnotu nebo se vrácený promise vyřeší.

Následující příklad používá oba přístupové způsoby, konkrétně cloudflare:workers import a ctx.tracing, to show that they are interchangeable:

src/index.js
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.ts
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);
		});
	},
};

Referenční dokumentace API

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

Vytvoří nový span a spustí callback uvnitř něj. Span se automaticky ukončí, jakmile callback vrátí hodnotu (synchronně nebo asynchronně) nebo vyvolá výjimku.

Parametry:

Parametr Typ Popis
name string Název spanu. Zobrazuje se ve vizualizacích trasování.
callback (span: Span, ...args: A) => T Funkce, která se má spustit v rámci spanu. Přijímá Span objekt jako první argument, následovaný dalšími argumenty předanými do enterSpan.
...args A Volitelné další argumenty předávané callbacku za span .

Vrací: Návratová hodnota callback.

Chování:

// 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)

Vytvoří nový span, učiní ho aktivním spanem, zatímco callback se spustí a vrátí výsledek callbacku bez automaticky ukončuje span. Musíte zavolat span.end() explicitně, jakmile je operace dokončena.

Parametry:

Parametr Typ Popis
name string Název spanu. Zobrazuje se ve vizualizacích trasování.
callback (span: Span, ...args: A) => T Funkce, která se má spustit, dokud je span aktivní. Přijímá Span objekt jako první argument, následovaný dalšími argumenty.
...args A Volitelné další argumenty předávané callbacku za span .

Vrací: Návratová hodnota callback.

Chování:

Použijte startActiveSpan když potřebujete, aby span pokrýval operaci přesahující jediný callback, například při instrumentaci stream pipeline, kde má span zůstat otevřený, dokud není stream zcela zpracován:

src/index.js
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.ts
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);
	},
};

Referenci na span můžete také zachytit pro pozdější použití bez streamů:

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 objekt je předán do enterSpan a startActiveSpan callbacky. Poskytuje metody pro doplnění spanu o metadata a řízení jeho životního cyklu.

span.setAttribute(key, value)

Nastaví atribut spanu.

Parametr Typ Popis
key string Název atributu.
value string | number | boolean | undefined Hodnota atributu. Předání undefined nemá žádný efekt (no-op).

Atributy se zobrazují spolu se span ve vašich trasování a exportech OpenTelemetry.

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

span.isTraced

A readonly boolean označující, zda se toto vyvolání trasuje. Pokud požadavek není vzorkován (na základě vašeho head_sampling_rate), isTraced je false a enterSpan stále spustí callback, ale nezaznamená žádnou telemetrii.

Toho můžete využít k přeskočení nákladného výpočtu atributů, pokud požadavek není trasován:

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

span.end()

Ukončí span a odešle jeho atributy do systému trasování. Tato metoda je idempotentní, opakované volání po prvním volání nemá žádný účinek. Po end() je volána, span.isTraced vrací false a jakékoli další setAttribute volání se tiše ignorují, včetně volání z asynchronní práce na pozadí, která ještě nebyla dokončena.

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

Vnořené spany

Spany se automaticky vnořují podle asynchronního kontextu JavaScriptu. Jakékoli enterSpan volání nebo operaci platformy (jako je fetch a env.MY_KV.get()) který běží uvnitř callbacku, se stává potomkem obklopujícího spanu.

src/index.js
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.ts
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 }));
	});
}
Vodopádové zobrazení trasování ukazující vlastní rozpětí vnořená vedle automatické instrumentace KV a fetch

Protokolování v rámci spanů

console.log() a další metody konzole vysílají log eventy, které jsou automaticky přiřazeny aktuálně aktivnímu spanu. To znamená, že výstup protokolu zevnitř enterSpan nebo startActiveSpan callback je přiřazen k danému spanu ve vašich traces a exportech OpenTelemetry.

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"
});

Typy TypeScript

Kompletní deklarace typů pro API vlastních spanů:

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;
	}
}

Stejné API je k dispozici v kontextu handleru jako ctx.tracing, se stejnými typy.

Volba mezi enterSpan a startActiveSpan

enterSpan startActiveSpan
Span končí Automaticky ve chvíli, kdy callback vrátí hodnotu, vyhodí výjimku, nebo se jeho vrácený promise vypořádá Ručně, při volání span.end()
Rozsah aktivního kontextu Během callbacku Během callbacku
Případ použití Většina instrumentace: synchronní i asynchronní práce, která se vejde do jednoho callbacku Operace, které přetrvávají déle než callback, například stream pipelines
Zpracování chyb Span se při throw automaticky ukončí Span zůstává otevřený i při throw, zavolejte span.end() nebo se spolehnout na pojistku runtime

Obě metody nastaví span jako rodiče aktivního kontextu pouze během callbacku. Jakmile se callback vrátí, span přestává být aktivním rodičem. S enterSpan, na tomto rozdílu nezáleží, protože span je také ukončen. S startActiveSpan, span zůstává otevřený, ale už není nadřazeným kontextem. Nové spany vytvořené po návratu callbacku už nejsou potomky tohoto spanu.

Omezení

Další omezení trasování najdete v známá omezení stránce.