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

> Learn to set up observability hooks with the span-based observer pattern in Cloudflare Computer. Inject custom tracing logic with WorkspaceObserver for zero coupling to your observability backend.

- Repository: [Cloudflare/computer](https://github.com/cloudflare/computer)
- Tags: how-to-guide
- Published: 2026-08-08

---

**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`](https://github.com/cloudflare/computer/blob/main/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`](https://github.com/cloudflare/computer/blob/main/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:

```typescript
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:

```typescript
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`](https://github.com/cloudflare/computer/blob/main/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:

```typescript
// 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:

```typescript
// 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`](https://github.com/cloudflare/computer/blob/main/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:

```typescript
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:

```typescript
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`](https://github.com/cloudflare/computer/blob/main/packages/computer/src/workspace.ts). High-level operations wrap their implementation in spans automatically:

```typescript
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`](https://github.com/cloudflare/computer/blob/main/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`](https://github.com/cloudflare/computer/blob/main/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`](https://github.com/cloudflare/computer/blob/main/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`](https://github.com/cloudflare/computer/blob/main/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`](https://github.com/cloudflare/computer/blob/main/packages/computer/src/observe/cloudflare.ts) explicitly propagates context so that nested spans become children of the active parent span, maintaining proper trace hierarchy.