Observability Hook Structure in @cloudflare/computer: Adapting It for Cloudflare Runtime Tracing

The @cloudflare/computer package provides a span-oriented instrumentation API through the WorkspaceObserver interface that seamlessly bridges to the Cloudflare runtime's native Tracing API using the createCloudflareObserver factory.

The observability hook structure in @cloudflare/computer offers a minimal, façade-based approach to distributed tracing that works across production Workers, Durable Objects, and local test environments without code changes. This article examines the core abstractions, their implementation in the source code, and the exact adapter pattern used for Cloudflare runtime tracing.

Core Observability Abstractions

The foundation lives in [packages/computer/src/observe.ts](https://github.com/cloudflare/computer/blob/main/packages/computer/src/observe.ts), which exports four key constructs:

WorkspaceSpan

A minimal span façade exposing only setAttribute(key, value). This deliberate constraint mirrors the Cloudflare runtime's Span surface, letting adapters forward attributes without importing the full OpenTelemetry API.

WorkspaceObserver

The central hook interface with a single method:

span(name: string, attributes: Record<string, unknown>, run: (span: WorkspaceSpan) => T): T
  • Starts a span named name
  • Seeds it with attributes
  • Executes run with the span
  • Returns the callback's result (or promise) unchanged

noopObserver

The default no-op implementation when no observer is supplied. It invokes the callback directly with a span whose setAttribute is a no-op, ensuring zero overhead when tracing is disabled.

withSpan

The primary helper used throughout the workspace core:

withSpan<T>(
  observer: WorkspaceObserver,
  name: string,
  attributes: Record<string, unknown>,
  run: (span: WorkspaceSpan) => T,
  finalize?: (span: WorkspaceSpan) => void
): T

This helper:

  • Invokes observer.span(...)
  • Automatically records error attributes via recordError if the callback throws
  • Optionally runs finalize to add post-hoc attributes (byte counts, exit codes, etc.)

Span Structure and Nesting

Spans follow logical operation nesting. An exec span contains child sync.push and sync.pull spans. The design accepts any context implementation respecting the WorkspaceObserver.span contract—whether the built-in Cloudflare adapter, an OpenTelemetry bridge, or custom implementations.

Cloudflare Runtime Adapter

The bridge to Cloudflare's native tracing lives in [packages/computer/src/observe/cloudflare.ts](https://github.com/cloudflare/computer/blob/main/packages/computer/src/observe/cloudflare.ts). It maps the generic observer to the runtime's Tracing API (tracing.enterSpan(name, callback)).

Adapter Mechanics

Aspect Implementation
Factory createCloudflareObserver({ tracing }) returns WorkspaceObserver
Graceful degradation Returns pass-through observer if tracing is undefined
Span creation Wraps tracing.enterSpan with WorkspaceSpan façade
Attribute forwarding applyAttributes copies seed attributes, omitting undefined values
Name limits Runtime enforces 64-byte names; workspace stays within bounds

Production Usage

// Inside a Worker or Durable Object
import { tracing } from "cloudflare:workers";
import { createCloudflareObserver } from "@cloudflare/computer/observe/cloudflare";
import { withSpan } from "@cloudflare/computer/observe";

const observer = createCloudflareObserver({ tracing });

async function runCommand(cmd: string) {
  return withSpan(
    observer,
    "workspace.runtime.exec",
    { "command": cmd },
    async (span) => {
      const result = await execCommand(cmd);
      span.setAttribute("exitCode", result.code);
      span.setAttribute("bytesWritten", result.stdout.length);
      return result;
    }
  );
}

Test Environment Compatibility

The same code runs in Node-based unit tests without modification:

// Tracing unavailable in test environment
const observer = createCloudflareObserver({ tracing: undefined });
// observer behaves like noopObserver; no conditional branching needed

Attribute Forwarding Details

The applyAttributes helper in [cloudflare.ts](https://github.com/cloudflare/computer/blob/main/packages/computer/src/observe/cloudflare.ts) handles the translation:

function applyAttributes(span: Span, attributes: Record<string, unknown>): void {
  for (const [key, value] of Object.entries(attributes)) {
    if (value !== undefined) {
      span.setAttribute(key, value);
    }
  }
}

This filtering prevents undefined values from reaching the runtime API, which may reject non-serializable attributes.

Implementing Custom Observers

To adapt a different tracing system (OpenTelemetry, Datadog, etc.), implement WorkspaceObserver:

const myCustomObserver: WorkspaceObserver = {
  span(name, attributes, run) {
    // Start your tracing system's span
    return tracer.startActiveSpan(name, (otelSpan) => {
      // Apply seed attributes
      Object.entries(attributes).forEach(([k, v]) => {
        if (v !== undefined) otelSpan.setAttribute(k, v);
      });
      
      // Wrap in WorkspaceSpan façade
      const workspaceSpan: WorkspaceSpan = {
        setAttribute: (k, v) => otelSpan.setAttribute(k, v)
      };
      
      try {
        return run(workspaceSpan);
      } catch (err) {
        otelSpan.recordException(err);
        throw err;
      } finally {
        otelSpan.end();
      }
    });
  }
};

Pass this implementation wherever the workspace expects an observer. The core library depends only on the contract, never the concrete backend.

Summary

  • The observability hook centers on WorkspaceObserver with a single span method and WorkspaceSpan with setAttribute, defined in observe.ts
  • withSpan is the ergonomic helper that drives instrumentation across the workspace, handling errors and finalization automatically
  • createCloudflareObserver in observe/cloudflare.ts bridges to the runtime's Tracing.enterSpan API with zero-cost fallback when tracing is unavailable
  • The 64-byte name limit is respected by workspace conventions; no truncation logic is required
  • Environment portability is built-in: identical code runs in production Workers and Node tests through graceful degradation

Frequently Asked Questions

What is the WorkspaceObserver interface in @cloudflare/computer?

WorkspaceObserver is a minimal hook interface containing one method: span(name, attributes, run). It starts a named span, seeds it with attributes, executes the provided callback with a WorkspaceSpan façade, and returns the callback's result unchanged. This abstraction lets the workspace core emit traces without depending on any specific tracing backend.

How does the Cloudflare runtime adapter handle missing tracing?

The createCloudflareObserver factory checks if the tracing parameter is undefined. If so, it returns a pass-through observer identical to noopObserver—the callback runs normally with a no-op span, incurring no overhead. This eliminates conditional branching in consuming code between production and test environments.

Can I use the observability hook with OpenTelemetry instead of Cloudflare's native tracing?

Yes. Implement the WorkspaceObserver interface yourself, delegating span calls to your OpenTelemetry tracer's startActiveSpan. Wrap the Otel span in a WorkspaceSpan façade that forwards setAttribute. The workspace core only cares about the contract, so any compliant implementation works interchangeably.

Where are the span names defined in the workspace?

Span names are hardcoded throughout the workspace core using dot-notation conventions like "workspace.runtime.exec", "workspace.sync.push", and "workspace.fs.read". These names stay well under the Cloudflare runtime's 64-byte limit, as verified in the adapter implementation.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →