How to Use WorkspaceObserver and WorkspaceSpan for Monitoring Workspace Operations in Cloudflare Computer
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. 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:
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:
interface WorkspaceSpan {
setAttribute(key: string, value: boolean | number | string | undefined): void;
}
The generic helper withSpan in 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 and packages/computer/src/shell.ts, the following operations are automatically instrumented:
workspace.connect– Emitted during#handleForand#moduleHandleForwhen establishing DOFS connections.workspace.sync.push– Wrapped around thepush()method inworkspace.ts.workspace.sync.pull– Wrapped around the#pullResolvedprivate method.workspace.runtime.exec– Emitted viaCommandExecutorinshell.tswhen running shell commands.workspace.fs.<operation>– Routed throughWorkspaceStubfor 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:
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:
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 to forward spans directly to the platform's native tracing system:
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 |
Defines WorkspaceObserver, WorkspaceSpan, noopObserver, and withSpan. |
packages/computer/src/observe/cloudflare.ts |
Implements createCloudflareObserver for Cloudflare runtime integration. |
packages/computer/src/workspace.ts |
Core workspace; stores #observer and instruments push(), pull(), and connection handling. |
packages/computer/src/shell.ts |
Wraps command execution; propagates observer to CommandExecutor. |
packages/computer/src/observe.test.ts |
Validates observer behavior with noopObserver. |
packages/computer/src/observe/cloudflare.test.ts |
Tests Cloudflare adapter span creation. |
Summary
- Supply a
WorkspaceObserverwhen constructingWorkspaceto capture operation spans. - Implement
span()to receive operation names, initial attributes, and a callback for executing the wrapped work. - Use
setAttributeon the providedWorkspaceSpanto attach dynamic metadata during execution. - Use
createCloudflareObserverfor zero‑configuration integration with Cloudflare's native tracing. - Reference
noopObserverinobserve.tsif 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.
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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →