How to Set Up Observability Hooks with the Span-Based Observer Pattern in Cloudflare Computer

The @cloudflare/computer package provides a lightweight WorkspaceObserver interface that wraps workspace operations in spans, allowing you to inject custom tracing logic via the span method while maintaining zero coupling between the SDK and your specific observability backend.

The @cloudflare/computer SDK implements a span-based observer pattern that enables fine-grained instrumentation of filesystem operations, command execution, and synchronization tasks. By implementing the WorkspaceObserver interface defined in packages/computer/src/observe.ts, you can capture telemetry for every workspace action without modifying the core library code.

Understanding the WorkspaceObserver Interface

The observability system centers on the WorkspaceObserver interface located in packages/computer/src/observe.ts. This interface declares a single method that defines how work gets wrapped and monitored.

The Span Method Signature

The span method accepts three parameters: a logical operation name, initial attributes, and an async callback that receives a span façade:

span<T>(
  name: string, 
  attributes: SpanAttributes, 
  run: (span: WorkspaceSpan) => Promise<T>
): Promise<T>
  • name: A dot-namespaced identifier such as workspace.fs.readFile or workspace.exec.run.
  • attributes: Key-value pairs that seed the span with context (e.g., {"workspace.fs.path": "/config.json"}).
  • run: An async function that receives a WorkspaceSpan and returns a Promise<T>.

The WorkspaceSpan Facade

The WorkspaceSpan type exposed to callbacks is intentionally minimal:

interface WorkspaceSpan {
  setAttribute(name: string, value: unknown): void;
}

This façade exposes only setAttribute, allowing callers to annotate spans with runtime data without depending on specific tracing implementations like OpenTelemetry or Datadog.

Default No-Op Observer

If no observer is provided, the SDK falls back to a default no-op implementation in packages/computer/src/observe.ts that simply executes the callback without instrumentation. This design guarantees that calls to observer.span(...) succeed unconditionally throughout the codebase.

Implementing a Custom Observability Hook

To capture telemetry in your preferred backend, implement the WorkspaceObserver interface and inject it when instantiating a Workspace.

Step 1: Create the Observer Implementation

Create a file that adapts the generic observer contract to your specific tracing library:

// my-observer.ts
import type { WorkspaceObserver, WorkspaceSpan, SpanAttributes } from "@cloudflare/computer";

export const myObserver: WorkspaceObserver = {
  async span<T>(
    name: string, 
    attributes: SpanAttributes, 
    run: (span: WorkspaceSpan) => Promise<T>
  ): Promise<T> {
    // Initialize your tracing library's span
    const customSpan = startMySpan(name, attributes);

    try {
      // Execute the work, providing the façade to the callback
      const result = await run({
        setAttribute: (key: string, value: unknown) => {
          customSpan.setAttribute(key, value);
        },
      });

      // Record success state
      customSpan.setAttribute("status", "ok");
      return result;
    } catch (err) {
      // Capture error details before re-throwing
      customSpan.setAttribute("error.name", err?.name ?? "Error");
      customSpan.setAttribute("error.message", err?.message ?? String(err));
      throw err;
    } finally {
      // Ensure the span always ends
      customSpan.end();
    }
  },
};

Replace startMySpan and customSpan with your specific tracing SDK calls (OpenTelemetry, Jaeger, etc.). The contract requires only that you honor the setAttribute method and manage the span lifecycle from start to end.

Step 2: Inject the Observer into the Workspace

Pass your implementation to the Workspace constructor:

// init-workspace.ts
import { Workspace } from "@cloudflare/computer";
import { myObserver } from "./my-observer";

const ws = new Workspace({ observer: myObserver });

All subsequent operations on ws—including filesystem reads, writes, and command execution—will emit spans through your custom tracer.

Using the Cloudflare Runtime Adapter

For deployments on Cloudflare's edge infrastructure, use the built-in adapter located in packages/computer/src/observe/cloudflare.ts. This implementation wires the generic observer to Cloudflare's native tracing API.

The adapter performs three critical functions:

  1. Starts a runtime span via tracing.startSpan(name, attributes).
  2. Propagates the active context so nested spans automatically become children of the current span.
  3. Ends the span when the callback resolves or throws, forwarding error information via setAttribute calls.

Import and use it directly:

import { cloudflareObserver } from "@cloudflare/computer/observe/cloudflare";

const ws = new Workspace({ observer: cloudflareObserver });

Composing Multiple Observability Backends

When you need both Cloudflare-specific telemetry and external monitoring, compose observers by nesting their span implementations:

const combinedObserver: WorkspaceObserver = {
  async span(name, attrs, run) {
    return cloudflareObserver.span(name, attrs, async (cfSpan) => {
      return myObserver.span(name, attrs, async (mySpan) => {
        // Forward initial attributes to both spans
        Object.entries(attrs).forEach(([k, v]) => {
          cfSpan.setAttribute(k, v);
          mySpan.setAttribute(k, v);
        });
        
        return run({
          setAttribute: (k, v) => {
            cfSpan.setAttribute(k, v);
            mySpan.setAttribute(k, v);
          },
        });
      });
    });
  },
};

This pattern ensures that setAttribute calls in your business logic propagate to both telemetry systems simultaneously.

Real-World Usage in Workspace Operations

The observer pattern integrates deeply into packages/computer/src/workspace.ts. High-level operations wrap their implementation in spans automatically:

await observer.span(
  "workspace.fs.readFile",
  { "workspace.fs.path": path },
  async (span) => {
    // Actual file read implementation here
    const content = await fs.promises.readFile(path, "utf-8");
    span.setAttribute("workspace.fs.size", content.length);
    return content;
  }
);

The span automatically records the operation name, initial path attribute, and any additional metadata set via setAttribute during execution.

Summary

  • The WorkspaceObserver interface in packages/computer/src/observe.ts defines a single span method that wraps work in logical units with attributes.
  • The WorkspaceSpan façade provides only setAttribute, keeping the SDK agnostic to tracing implementations.
  • A default no-op observer ensures safe execution when no observability backend is configured.
  • The Cloudflare adapter in packages/computer/src/observe/cloudflare.ts connects spans to the native edge runtime tracing API.
  • Custom implementations require only implementing the span method and managing the lifecycle around the provided callback.
  • Observer composition allows simultaneous emission to multiple telemetry systems by nesting span calls.

Frequently Asked Questions

What is the minimum implementation required for a custom observer?

You must implement the WorkspaceObserver interface with a span method that accepts a name, attributes, and a callback function. The method must execute the callback, passing an object with at least a setAttribute method, and return the callback's result. Error handling and span lifecycle management (start/end) are your responsibility within that wrapper.

How do I access the span inside my workspace operations?

The span parameter passed to your callback in the run function is a WorkspaceSpan façade. You can call span.setAttribute(key, value) to annotate the current span with runtime data. All high-level Workspace methods in packages/computer/src/workspace.ts automatically wrap their implementation in observer spans, so attributes set within your operation callbacks automatically attach to the active span.

Can I use OpenTelemetry with the Cloudflare Computer SDK?

Yes. Implement the WorkspaceObserver interface to wrap callbacks in OpenTelemetry spans. Create spans using your OTel tracer, expose setAttribute as a wrapper around span.setAttribute(), and ensure you end spans in a finally block. Then inject your implementation via new Workspace({ observer: myOtelObserver }).

Does the observer pattern support nested spans?

Yes. The observer implementation in packages/computer/src/observe.ts handles nested calls automatically. When you invoke observer.span within the callback of another span, your implementation controls whether these become child spans in your specific backend. The Cloudflare runtime adapter in packages/computer/src/observe/cloudflare.ts explicitly propagates context so that nested spans become children of the active parent span, maintaining proper trace hierarchy.

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 →