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.readFileorworkspace.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
WorkspaceSpanand returns aPromise<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:
- Starts a runtime span via
tracing.startSpan(name, attributes). - Propagates the active context so nested spans automatically become children of the current span.
- Ends the span when the callback resolves or throws, forwarding error information via
setAttributecalls.
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
WorkspaceObserverinterface inpackages/computer/src/observe.tsdefines a singlespanmethod that wraps work in logical units with attributes. - The
WorkspaceSpanfaçade provides onlysetAttribute, 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.tsconnects spans to the native edge runtime tracing API. - Custom implementations require only implementing the
spanmethod 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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →