Workers Tracing Now Covers Streams From Start to Finish!
Hi, it's me, Shiichan! Today I found an update to tracing on Cloudflare Workers. Logging and instrumentation might sound plain, but it really matters, so let's dig in.
Cloudflare ChangelogWhat was announced?
The Cloudflare Changelog announced new runtime APIs for writing custom spans in Workers: tracing.startActiveSpan() and span.end(). They're built for operations that don't finish inside a single callback, like a stream pipeline where you want the span to stay open until the stream is fully consumed.
The story so far
Up until now, the API for writing custom spans was tracing.enterSpan(). But that API automatically ends the span the moment the callback you pass in returns. That made it hard to cleanly track, as a single span, async work that continues beyond the callback, like a response that keeps streaming.
What changes
With the new startActiveSpan(), the span stays open even after the callback returns, and doesn't end until you explicitly call span.end(). In practice, that means:
- You can track work that continues beyond the callback as one continuous span, from start to finish
- You can end the span at whatever moment makes sense, like when a stream is fully consumed or when it's cancelled
- You can record state with
span.setAttribute()right before ending the span, such as whether it succeeded or was cancelled
If you're running a Worker that returns a streaming response, this is a welcome update: you can now visualize "how long the whole stream took" as a single, proper span.
Dive Deep
Here's the official sample code. It tracks everything from the start of the stream to its completion via a timeout, all inside one startActiveSpan call.
import { tracing } from "cloudflare:workers";
const encoder = new TextEncoder();
export default {
fetch() {
return tracing.startActiveSpan("stream-response", (span) => {
let timer;
const body = new ReadableStream({
start(controller) {
controller.enqueue(encoder.encode("Starting...\n"));
timer = setTimeout(() => {
controller.enqueue(encoder.encode("Complete.\n"));
controller.close();
span.setAttribute("stream.status", "complete");
span.end();
}, 1000);
},
cancel() {
if (timer !== undefined) clearTimeout(timer);
span.setAttribute("stream.status", "cancelled");
span.end();
},
});
return new Response(body, {
headers: { "content-type": "text/plain" },
});
});
},
};
The key idea is having two distinct completion paths: when the stream finishes normally, the timer in start sets stream.status to complete before calling span.end(); when the client cancels midway, the cancel handler sets it to cancelled before calling span.end(). The existing tracing.enterSpan() is still there for simpler cases where auto-ending on callback return is exactly what you want. More details are available in the custom spans documentation.
Wrap-up
- The Workers runtime gains
tracing.startActiveSpan()andspan.end() - Unlike
tracing.enterSpan(), which auto-closes the span when the callback returns, the new API keeps the span open until you callspan.end() - Async work that outlives a callback, like a stream pipeline, can now be tracked as a single span
- You can record state with
span.setAttribute()for each kind of ending, like completion or cancellation - A nice pick for anyone who wants to properly instrument streaming responses or long-running async work in Workers.