This project demonstrates how to use Cloudflare Workers' Streaming Tail API to export worker execution traces to OpenTelemetry (OTEL) and visualize them using Jaeger.
- Real-time trace streaming from Cloudflare Workers
- Complete OTEL conversion of Cloudflare trace events
- Jaeger visualization with comprehensive span relationships
- Type-safe implementation using Cloudflare's official types
- Local development friendly setup with Docker
The solution consists of:
- tailStream handler - Converts Cloudflare trace events to OTEL format
- Jaeger - OTEL-compatible trace visualizer
- Docker setup - Simple local development environment
npm installdocker compose up -dThis starts Jaeger with:
- UI: http://localhost:16686
- OTLP HTTP: http://localhost:4318
npx wrangler dev- Make requests to your worker: http://localhost:8787
- View traces in Jaeger UI: http://localhost:16686
- Select "cloudflare-worker" service to see traces
The tailStream handler processes all Cloudflare trace events:
onset- Worker invocation start with contextspanOpen- New span creationspanClose- Span completion with outcomeattributes- Dynamic span attribute additionlog- Console log captureexception- Error and exception trackingreturn- Response informationoutcome- Final execution metricsdiagnosticChannel- Diagnostic events
Each Cloudflare event is mapped to OTEL spans with:
- Proper span relationships (parent-child)
- Rich metadata (HTTP details, performance metrics)
- Structured logging (console logs as span events)
- Error tracking (exceptions with stack traces)
- Fetch events: HTTP method, URL, status codes, CF properties
- Scheduled events: Cron expressions, execution times
- Queue events: Queue names, batch sizes
- Performance: CPU time, wall time metrics
- Script metadata: Version, tags, execution model
| Variable | Description | Default | Required |
|---|---|---|---|
OTEL_ENDPOINT |
OTEL traces endpoint URL | http://localhost:4318/v1/traces |
No |
# Replace Jaeger with Zipkin
docker run -d -p 9411:9411 --name zipkin openzipkin/zipkin
# Update environment
OTEL_ENDPOINT=http://localhost:9411/api/v2/spans# docker-compose.yml with OTEL Collector
version: '3.9'
services:
otel-collector:
image: otel/opentelemetry-collector:latest
command: ["--config=/etc/otel-collector-config.yml"]
volumes:
- ./otel-collector-config.yml:/etc/otel-collector-config.yml
ports:
- "4317:4317" # OTLP gRPC receiver
- "4318:4318" # OTLP HTTP receiver
jaeger:
image: jaegertracing/all-in-one:latest
ports:
- "16686:16686"Modify the converter to use dynamic service names:
// In extractTags method
tags['service.name'] = onset.scriptName || 'cloudflare-worker';
tags['service.version'] = onset.scriptVersion?.id || 'unknown';
tags['service.namespace'] = onset.dispatchNamespace || 'default';