# How to Use WorkspaceObserver and WorkspaceSpan for Monitoring Workspace Operations in Cloudflare Computer

> Monitor Cloudflare Computer workspace operations using WorkspaceObserver and WorkspaceSpan. Learn how to instrument and forward traces to the Cloudflare runtime.

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

---

**The Cloudflare Computer SDK instruments every workspace operation with hierarchical spans that you can monitor by supplying a custom `WorkspaceObserver` when creating a `Workspace`, or by using the built‑in `createCloudflareObserver` adapter to forward traces to the Cloudflare runtime.**

The Computer SDK provides lightweight, built‑in observability through the `WorkspaceObserver` and `WorkspaceSpan` interfaces defined in [`packages/computer/src/observe.ts`](https://github.com/cloudflare/computer/blob/main/packages/computer/src/observe.ts). These abstractions wrap every significant workspace operation—syncs, command execution, and filesystem calls—in timed, attributed spans that you can route to any telemetry backend.

## What Are WorkspaceObserver and WorkspaceSpan?

The `WorkspaceObserver` interface declares a single method that the Computer SDK calls around each instrumented operation:

```typescript
interface WorkspaceObserver {
  span<T>(
    name: string,
    attrs: Record<string, boolean | number | string | undefined>,
    run: (span: WorkspaceSpan) => Promise<T>
  ): Promise<T>;
}

```

The `WorkspaceSpan` object passed to your `run` callback lets you attach dynamic attributes as operations progress:

```typescript
interface WorkspaceSpan {
  setAttribute(key: string, value: boolean | number | string | undefined): void;
}

```

The generic helper `withSpan` in [`packages/computer/src/observe.ts`](https://github.com/cloudflare/computer/blob/main/packages/computer/src/observe.ts) implements the common wrapping logic: it records error information and supports an optional `finalize` callback for attaching late‑bound attributes such as byte counts or exit codes.

## Operations That Emit Spans

According to the source in [`packages/computer/src/workspace.ts`](https://github.com/cloudflare/computer/blob/main/packages/computer/src/workspace.ts) and [`packages/computer/src/shell.ts`](https://github.com/cloudflare/computer/blob/main/packages/computer/src/shell.ts), the following operations are automatically instrumented:

- **`workspace.connect`** – Emitted during `#handleFor` and `#moduleHandleFor` when establishing DOFS connections.
- **`workspace.sync.push`** – Wrapped around the `push()` method in [`workspace.ts`](https://github.com/cloudflare/computer/blob/main/workspace.ts).
- **`workspace.sync.pull`** – Wrapped around the `#pullResolved` private method.
- **`workspace.runtime.exec`** – Emitted via `CommandExecutor` in [`shell.ts`](https://github.com/cloudflare/computer/blob/main/shell.ts) when running shell commands.
- **`workspace.fs.<operation>`** – Routed through `WorkspaceStub` for filesystem calls.

If no observer is provided, the SDK falls back to `noopObserver`, a no‑op implementation that adds zero overhead.

## Creating a Custom WorkspaceObserver

To capture spans for logging, metrics, or custom tracing, implement the `WorkspaceObserver` interface:

```typescript
import { WorkspaceObserver, WorkspaceSpan } from "@cloudflare/computer/observe";

class ConsoleObserver implements WorkspaceObserver {
  async span<T>(
    name: string,
    attrs: Record<string, boolean | number | string | undefined>,
    run: (span: WorkspaceSpan) => Promise<T>,
  ): Promise<T> {
    console.log(`▶️ span start: ${name}`, attrs);
    const start = Date.now();

    const result = await run({
      setAttribute: (key, value) => {
        if (value !== undefined) console.log(`   ↳ ${key} =`, value);
      },
    });

    console.log(
      `✅ span end: ${name} – duration ${Date.now() - start} ms`,
    );
    return result;
  }
}

```

Wire the observer into your `Workspace` at instantiation:

```typescript
import { Workspace } from "@cloudflare/computer";
import { SQLiteTestStorage } from "@cloudflare/dofs/testing";

const ws = new Workspace({
  storage: new SQLiteTestStorage(),
  observer: new ConsoleObserver(),
});

// Each operation emits a span
await ws.push();  // workspace.sync.push
await ws.pull();  // workspace.sync.pull
await ws.runtime.exec({ source: "echo hello", cwd: "/" });  // workspace.runtime.exec

```

## Using the Cloudflare Runtime Adapter

For deployments on Cloudflare Workers or Durable Objects, use `createCloudflareObserver` from [`packages/computer/src/observe/cloudflare.ts`](https://github.com/cloudflare/computer/blob/main/packages/computer/src/observe/cloudflare.ts) to forward spans directly to the platform's native tracing system:

```typescript
import { createCloudflareObserver } from "@cloudflare/computer/observe/cloudflare";

export default {
  async fetch(request, env, ctx) {
    const observer = createCloudflareObserver({ tracing: ctx.tracing });
    const ws = new Workspace({
      storage: ctx.storage,
      observer,
    });

    // Spans now appear in the Cloudflare dashboard tracing UI
    await ws.push();
    await ws.pull();
    return new Response("OK");
  },
};

```

The adapter maps `WorkspaceObserver` calls onto `ctx.tracing.enterSpan`, preserving span hierarchy and timing automatically.

## Key Source Files

| File | Purpose |
|------|---------|
| [`packages/computer/src/observe.ts`](https://github.com/cloudflare/computer/blob/main/packages/computer/src/observe.ts) | Defines `WorkspaceObserver`, `WorkspaceSpan`, `noopObserver`, and `withSpan`. |
| [`packages/computer/src/observe/cloudflare.ts`](https://github.com/cloudflare/computer/blob/main/packages/computer/src/observe/cloudflare.ts) | Implements `createCloudflareObserver` for Cloudflare runtime integration. |
| [`packages/computer/src/workspace.ts`](https://github.com/cloudflare/computer/blob/main/packages/computer/src/workspace.ts) | Core workspace; stores `#observer` and instruments `push()`, `pull()`, and connection handling. |
| [`packages/computer/src/shell.ts`](https://github.com/cloudflare/computer/blob/main/packages/computer/src/shell.ts) | Wraps command execution; propagates observer to `CommandExecutor`. |
| [`packages/computer/src/observe.test.ts`](https://github.com/cloudflare/computer/blob/main/packages/computer/src/observe.test.ts) | Validates observer behavior with `noopObserver`. |
| [`packages/computer/src/observe/cloudflare.test.ts`](https://github.com/cloudflare/computer/blob/main/packages/computer/src/observe/cloudflare.test.ts) | Tests Cloudflare adapter span creation. |

## Summary

- **Supply a `WorkspaceObserver`** when constructing `Workspace` to capture operation spans.
- **Implement `span()`** to receive operation names, initial attributes, and a callback for executing the wrapped work.
- **Use `setAttribute`** on the provided `WorkspaceSpan` to attach dynamic metadata during execution.
- **Use `createCloudflareObserver`** for zero‑configuration integration with Cloudflare's native tracing.
- **Reference `noopObserver`** in [`observe.ts`](https://github.com/cloudflare/computer/blob/main/observe.ts) if you need a minimal reference implementation.

## Frequently Asked Questions

### What is the default behavior if I don't provide a WorkspaceObserver?

The SDK uses `noopObserver`, a no‑op implementation that immediately invokes the `run` callback without recording timing or attributes. This ensures zero overhead when observability is not needed.

### Can I attach attributes after a span has started?

Yes. The `WorkspaceSpan.setAttribute` method can be called at any point inside your `run` callback. This is useful for values that are only known partway through an operation, such as HTTP response statuses or byte counts written.

### How do I integrate with OpenTelemetry instead of Cloudflare's native tracing?

Implement `WorkspaceObserver` to create Otel spans in your `span()` method, map `setAttribute` calls to `span.setAttribute()`, and handle timing and finalization using Otel's `span.end()`. The interface is deliberately minimal to make such adapters straightforward.

### Is there a performance cost to using a custom observer?

The overhead depends on your implementation. The `noopObserver` path is designed to be free of allocations. For production use, keep your `span()` implementation lightweight—avoid blocking I/O inside the wrapper and delegate heavy work to background queues.