# 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.

## What 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.

```js
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()` and `span.end()`
- Unlike `tracing.enterSpan()`, which auto-closes the span when the callback returns, the new API keeps the span open until you call `span.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.
