shiichan

Workers tracing gets spanContext(), setStatus() and updateName()! Tie requests to traces with ease

Hi, it's Shii-chan!

Cloudflare Changelog developers.cloudflare.com

Cloudflare Workers tracing just picked up three more methods from the OpenTelemetry span API. I'm excited, because debugging gets a lot easier!

What was announced?

According to the Cloudflare Changelog, custom spans in Workers now support:

  • span.spanContext(): returns the span's traceId, spanId and traceFlags
  • span.setStatus(status): sets the status to "unset", "ok" or "error", with an optional message for errors
  • span.updateName(name): renames a span after it has started

The story so far

You could create custom spans, but there was no way to pull out a span's IDs to log them or return them to a client. Renaming a span once you know which route matched wasn't possible either.

What changes

  • The IDs from spanContext() match the IDs in Workers Observability and in exported traces. If you put the trace ID in an error response, you can jump straight to that one request's trace when someone reports a problem.
  • With updateName() you can rename a span once the matched route is known. You can also rename the invocation's root span, which you get from tracing.getActiveSpan().
  • With setStatus() you can mark a span as succeeded or failed explicitly.

Dive Deep

The official example gets the root span, renames it using the route, and runs the handler inside a handle span. On success it sets ok. On an exception it calls recordException, sets error, and returns a 500 response that includes the root trace ID.

import { tracing } from "cloudflare:workers";

export default {
  async fetch(request, env) {
    const root = tracing.getActiveSpan();
    root?.updateName(`${request.method} ${route.pattern}`);
    return tracing.startActiveSpan("handle", async (span) => {
      try {
        const response = await route.handler(request, env);
        span.setStatus({ code: "ok" });
        return response;
      } catch (err) {
        span.recordException(err);
        span.setStatus({ code: "error", message: "Route handler failed" });
        return Response.json(
          { error: "Internal error", traceId: root?.spanContext().traceId },
          { status: 500 },
        );
      } finally {
        span.end();
      }
    });
  },
};

(route comes from a router in the original example and is left out here.)

One thing to keep in mind:

  • Span status is included in traces exported to OpenTelemetry destinations, but the Workers Observability dashboard does not display it yet.

Wrap-up

  • spanContext() gives you trace and span IDs for logs and responses
  • setStatus() sets unset / ok / error (dashboard display is not there yet)
  • updateName() renames spans after they start

If you use OpenTelemetry tracing on Workers and often wonder which trace belongs to a given error, this one is for you!