Observability Hook Structure in @cloudflare/computer: Adapting It for Cloudflare Runtime Tracing
The @cloudflare/computer package provides a span-oriented instrumentation API through the WorkspaceObserver interface that seamlessly bridges to the Cloudflare runtime's native Tracing API using the createCloudflareObserver factory.
The observability hook structure in @cloudflare/computer offers a minimal, façade-based approach to distributed tracing that works across production Workers, Durable Objects, and local test environments without code changes. This article examines the core abstractions, their implementation in the source code, and the exact adapter pattern used for Cloudflare runtime tracing.
Core Observability Abstractions
The foundation lives in [packages/computer/src/observe.ts](https://github.com/cloudflare/computer/blob/main/packages/computer/src/observe.ts), which exports four key constructs:
WorkspaceSpan
A minimal span façade exposing only setAttribute(key, value). This deliberate constraint mirrors the Cloudflare runtime's Span surface, letting adapters forward attributes without importing the full OpenTelemetry API.
WorkspaceObserver
The central hook interface with a single method:
span(name: string, attributes: Record<string, unknown>, run: (span: WorkspaceSpan) => T): T
- Starts a span named
name - Seeds it with
attributes - Executes
runwith the span - Returns the callback's result (or promise) unchanged
noopObserver
The default no-op implementation when no observer is supplied. It invokes the callback directly with a span whose setAttribute is a no-op, ensuring zero overhead when tracing is disabled.
withSpan
The primary helper used throughout the workspace core:
withSpan<T>(
observer: WorkspaceObserver,
name: string,
attributes: Record<string, unknown>,
run: (span: WorkspaceSpan) => T,
finalize?: (span: WorkspaceSpan) => void
): T
This helper:
- Invokes
observer.span(...) - Automatically records error attributes via
recordErrorif the callback throws - Optionally runs
finalizeto add post-hoc attributes (byte counts, exit codes, etc.)
Span Structure and Nesting
Spans follow logical operation nesting. An exec span contains child sync.push and sync.pull spans. The design accepts any context implementation respecting the WorkspaceObserver.span contract—whether the built-in Cloudflare adapter, an OpenTelemetry bridge, or custom implementations.
Cloudflare Runtime Adapter
The bridge to Cloudflare's native tracing lives in [packages/computer/src/observe/cloudflare.ts](https://github.com/cloudflare/computer/blob/main/packages/computer/src/observe/cloudflare.ts). It maps the generic observer to the runtime's Tracing API (tracing.enterSpan(name, callback)).
Adapter Mechanics
| Aspect | Implementation |
|---|---|
| Factory | createCloudflareObserver({ tracing }) returns WorkspaceObserver |
| Graceful degradation | Returns pass-through observer if tracing is undefined |
| Span creation | Wraps tracing.enterSpan with WorkspaceSpan façade |
| Attribute forwarding | applyAttributes copies seed attributes, omitting undefined values |
| Name limits | Runtime enforces 64-byte names; workspace stays within bounds |
Production Usage
// Inside a Worker or Durable Object
import { tracing } from "cloudflare:workers";
import { createCloudflareObserver } from "@cloudflare/computer/observe/cloudflare";
import { withSpan } from "@cloudflare/computer/observe";
const observer = createCloudflareObserver({ tracing });
async function runCommand(cmd: string) {
return withSpan(
observer,
"workspace.runtime.exec",
{ "command": cmd },
async (span) => {
const result = await execCommand(cmd);
span.setAttribute("exitCode", result.code);
span.setAttribute("bytesWritten", result.stdout.length);
return result;
}
);
}
Test Environment Compatibility
The same code runs in Node-based unit tests without modification:
// Tracing unavailable in test environment
const observer = createCloudflareObserver({ tracing: undefined });
// observer behaves like noopObserver; no conditional branching needed
Attribute Forwarding Details
The applyAttributes helper in [cloudflare.ts](https://github.com/cloudflare/computer/blob/main/packages/computer/src/observe/cloudflare.ts) handles the translation:
function applyAttributes(span: Span, attributes: Record<string, unknown>): void {
for (const [key, value] of Object.entries(attributes)) {
if (value !== undefined) {
span.setAttribute(key, value);
}
}
}
This filtering prevents undefined values from reaching the runtime API, which may reject non-serializable attributes.
Implementing Custom Observers
To adapt a different tracing system (OpenTelemetry, Datadog, etc.), implement WorkspaceObserver:
const myCustomObserver: WorkspaceObserver = {
span(name, attributes, run) {
// Start your tracing system's span
return tracer.startActiveSpan(name, (otelSpan) => {
// Apply seed attributes
Object.entries(attributes).forEach(([k, v]) => {
if (v !== undefined) otelSpan.setAttribute(k, v);
});
// Wrap in WorkspaceSpan façade
const workspaceSpan: WorkspaceSpan = {
setAttribute: (k, v) => otelSpan.setAttribute(k, v)
};
try {
return run(workspaceSpan);
} catch (err) {
otelSpan.recordException(err);
throw err;
} finally {
otelSpan.end();
}
});
}
};
Pass this implementation wherever the workspace expects an observer. The core library depends only on the contract, never the concrete backend.
Summary
- The observability hook centers on
WorkspaceObserverwith a singlespanmethod andWorkspaceSpanwithsetAttribute, defined inobserve.ts withSpanis the ergonomic helper that drives instrumentation across the workspace, handling errors and finalization automaticallycreateCloudflareObserverinobserve/cloudflare.tsbridges to the runtime'sTracing.enterSpanAPI with zero-cost fallback when tracing is unavailable- The 64-byte name limit is respected by workspace conventions; no truncation logic is required
- Environment portability is built-in: identical code runs in production Workers and Node tests through graceful degradation
Frequently Asked Questions
What is the WorkspaceObserver interface in @cloudflare/computer?
WorkspaceObserver is a minimal hook interface containing one method: span(name, attributes, run). It starts a named span, seeds it with attributes, executes the provided callback with a WorkspaceSpan façade, and returns the callback's result unchanged. This abstraction lets the workspace core emit traces without depending on any specific tracing backend.
How does the Cloudflare runtime adapter handle missing tracing?
The createCloudflareObserver factory checks if the tracing parameter is undefined. If so, it returns a pass-through observer identical to noopObserver—the callback runs normally with a no-op span, incurring no overhead. This eliminates conditional branching in consuming code between production and test environments.
Can I use the observability hook with OpenTelemetry instead of Cloudflare's native tracing?
Yes. Implement the WorkspaceObserver interface yourself, delegating span calls to your OpenTelemetry tracer's startActiveSpan. Wrap the Otel span in a WorkspaceSpan façade that forwards setAttribute. The workspace core only cares about the contract, so any compliant implementation works interchangeably.
Where are the span names defined in the workspace?
Span names are hardcoded throughout the workspace core using dot-notation conventions like "workspace.runtime.exec", "workspace.sync.push", and "workspace.fs.read". These names stay well under the Cloudflare runtime's 64-byte limit, as verified in the adapter implementation.
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 →