Cloudflare Workers Observability
Production-grade observability for Cloudflare Workers: logging, metrics, tracing, and alerting.
Quick Start
// Structured logging with context
export default {
async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
const requestId = crypto.randomUUID();
const logger = createLogger(requestId, env);
try {
logger.info('Request received', { method: request.method, url: request.url });
const result = await handleRequest(request, env);
logger.info('Request completed', { status: result.status });
return result;
} catch (error) {
logger.error('Request failed', { error: error.message, stack: error.stack });
throw error;
}
}
};
// Simple logger factory
function createLogger(requestId: string, env: Env) {
return {
info: (msg: string, data?: object) => console.log(JSON.stringify({ level: 'info', requestId, msg, ...data, timestamp: Date.now() })),
error: (msg: string, data?: object) => console.error(JSON.stringify({ level: 'error', requestId, msg, ...data, timestamp: Date.now() })),
warn: (msg: string, data?: object) => console.warn(JSON.stringify({ level: 'warn', requestId, msg, ...data, timestamp: Date.now() })),
};
}
Critical Rules
- Always use structured JSON logging - Plain text logs are hard to parse and aggregate
- Include request context - Request ID, method, path in every log entry
- Never log sensitive data - Redact tokens, passwords, PII from logs
- Use appropriate log levels - ERROR for failures, WARN for recoverable issues, INFO for operations
- Sample high-volume logs - Use 1-10% sampling for request logs in production
Observability Components
| Component |
Purpose |
When to Use |
console.log |
Basic logging |
Development, debugging |
| Tail Workers |
Real-time log streaming |
Production log aggregation |
| Analytics Engine |
Custom metrics/analytics |
Business metrics, performance tracking |
| Logpush |
Log export to external services |
Long-term storage, compliance |
| Workers Trace Events |
Distributed tracing |
Request flow debugging |
Top 8 Errors Prevented
| Error |
Symptom |
Prevention |
| Logs not appearing |
No output in dashboard |
Enable "Standard" logging in wrangler.jsonc |
| Log truncation |
Messages cut off at 128KB |
Chunk large payloads, use sampling |
| Tail Worker not receiving |
No events processed |
Check binding name matches wrangler.jsonc |
| Analytics Engine write fails |
Data not recorded |
Verify AE binding, check blobs format |
| PII in logs |
Security/compliance violation |
Implement redaction middleware |
| Missing request context |
Can't correlate logs |
Add requestId to all log entries |
| Log volume explosion |
High costs, noise |
Implement sampling for high-frequency events |
| Alerting gaps |
Incidents not detected |
Configure monitors for error rate thresholds |
Logging Configuration
wrangler.jsonc:
{
"name": "my-worker",
"observability": {
"enabled": true,
"head_sampling_rate": 1 // 0-1, 1 = 100% of requests
},
"tail_consumers": [
{
"service": "log-aggregator", // Tail Worker name
"environment": "production"
}
],
"analytics_engine_datasets": [
{
"binding": "ANALYTICS",
"dataset": "my_worker_metrics"
}
]
}
Structured Logging Pattern
interface LogEntry {
level: 'debug' | 'info' | 'warn' | 'error';
message: string;
requestId: string;
timestamp: number;
// Contextual data
method?: string;
path?: string;
status?: number;
duration?: number;
// Error details
error?: {
name: string;
message: string;
stack?: string;
};
// Custom fields
[key: string]: unknown;
}
class Logger {
constructor(private requestId: string, private baseContext: object = {}) {}
private log(level: LogEntry['level'], message: string, data?: object) {
const entry: LogEntry = {
level,
message,
requestId: this.requestId,
timestamp: Date.now(),
...this.baseContext,
...data,
};
// Redact sensitive fields
const sanitized = this.redact(entry);
const output = JSON.stringify(sanitized);
level === 'error' ? console.error(output) : console.log(output);
}
private redact(entry: LogEntry): LogEntry {
const sensitiveKeys = ['password', 'token', 'secret', 'authorization', 'cookie'];
const redacted = { ...entry };
for (const key of Object.keys(redacted)) {
if (sensitiveKeys.some(s => key.toLowerCase().includes(s))) {
redacted[key] = '[REDACTED]';
}
}
return redacted;
}
info(message: string, data?: object) { this.log('info', message, data); }
warn(message: string, data?: object) { this.log('warn', message, data); }
error(message: string, data?: object) { this.log('error', message, data); }
debug(message: string, data?: object) { this.log('debug', message, data); }
}
Analytics Engine Usage
interface Env {
ANALYTICS: AnalyticsEngineDataset;
}
export default {
async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
const start = Date.now();
const url = new URL(request.url);
try {
const response = await handleRequest(request, env);
// Write success metric
env.ANALYTICS.writeDataPoint({
blobs: [request.method, url.pathname, String(response.status)],
doubles: [Date.now() - start], // Response time in ms
indexes: [url.pathname.split('/')[1] || 'root'], // Index for fast queries
});
return response;
} catch (error) {
// Write error metric
env.ANALYTICS.writeDataPoint({
blobs: [request.method, url.pathname, 'error', error.message],
doubles: [Date.now() - start],
indexes: ['error'],
});
throw error;
}
}
};
Tail Worker Pattern
// tail-worker.ts - Receives logs from other workers
interface TailEvent {
scriptName: string;
event: {
request?: { method: string; url: string };
response?: { status: number };
};
logs: Array<{
level: string;
message: unknown[];
timestamp: number;
}>;
exceptions: Array<{
name: string;
message: string;
timestamp: number;
}>;
outcome: 'ok' | 'exception' | 'exceededCpu' | 'exceededMemory' | 'canceled';
eventTimestamp: number;
}
export default {
async tail(events: TailEvent[], env: Env): Promise<void> {
for (const event of events) {
// Filter and forward logs
const errorLogs = event.logs.filter(l => l.level === 'error');
const exceptions = event.exceptions;
if (errorLogs.length > 0 || exceptions.length > 0) {
// Send to external logging service
await fetch(env.LOGGING_ENDPOINT, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
scriptName: event.scriptName,
timestamp: event.eventTimestamp,
errors: errorLogs,
exceptions,
outcome: event.outcome,
}),
});
}
}
}
};
When to Load References
Load specific references based on the task:
- Setting up logging? → Load
references/logging.md for structured logging patterns, log levels, redaction
- Building custom metrics? → Load
references/analytics-engine.md for Analytics Engine SQL queries, data modeling
- Implementing log aggregation? → Load
references/tail-workers.md for Tail Worker patterns, external service integration
- Creating dashboards/tracking? → Load
references/custom-metrics.md for business metrics, performance tracking
- Setting up alerts? → Load
references/alerting.md for error rate monitoring, PagerDuty/Slack integration
Templates
| Template |
Purpose |
Use When |
templates/logging-setup.ts |
Production logging class |
Setting up new worker with logging |
templates/analytics-worker.ts |
Analytics Engine integration |
Adding custom metrics |
templates/tail-worker.ts |
Complete Tail Worker |
Building log aggregation pipeline |
Scripts
| Script |
Purpose |
Command |
scripts/setup-logging.sh |
Configure logging settings |
./setup-logging.sh |
scripts/analyze-logs.sh |
Query and analyze logs |
./analyze-logs.sh --errors --last 1h |
Resources
1---2name: cloudflare-workers-observability3description: Cloudflare Workers observability with logging, Analytics Engine, Tail Workers, metrics, and alerting. Use for monitoring, debugging, tracing, or encountering log parsing, metric aggregation, alert configuration errors.4license: MIT5---67# Cloudflare Workers Observability89Production-grade observability for Cloudflare Workers: logging, metrics, tracing, and alerting.1011## Quick Start1213```typescript14// Structured logging with context15export default {16 async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {17 const requestId = crypto.randomUUID();18 const logger = createLogger(requestId, env);1920 try {21 logger.info('Request received', { method: request.method, url: request.url });2223 const result = await handleRequest(request, env);2425 logger.info('Request completed', { status: result.status });26 return result;27 } catch (error) {28 logger.error('Request failed', { error: error.message, stack: error.stack });29 throw error;30 }31 }32};3334// Simple logger factory35function createLogger(requestId: string, env: Env) {36 return {37 info: (msg: string, data?: object) => console.log(JSON.stringify({ level: 'info', requestId, msg, ...data, timestamp: Date.now() })),38 error: (msg: string, data?: object) => console.error(JSON.stringify({ level: 'error', requestId, msg, ...data, timestamp: Date.now() })),39 warn: (msg: string, data?: object) => console.warn(JSON.stringify({ level: 'warn', requestId, msg, ...data, timestamp: Date.now() })),40 };41}42```4344## Critical Rules45461. **Always use structured JSON logging** - Plain text logs are hard to parse and aggregate472. **Include request context** - Request ID, method, path in every log entry483. **Never log sensitive data** - Redact tokens, passwords, PII from logs494. **Use appropriate log levels** - ERROR for failures, WARN for recoverable issues, INFO for operations505. **Sample high-volume logs** - Use 1-10% sampling for request logs in production5152## Observability Components5354| Component | Purpose | When to Use |55|-----------|---------|-------------|56| `console.log` | Basic logging | Development, debugging |57| **Tail Workers** | Real-time log streaming | Production log aggregation |58| **Analytics Engine** | Custom metrics/analytics | Business metrics, performance tracking |59| **Logpush** | Log export to external services | Long-term storage, compliance |60| **Workers Trace Events** | Distributed tracing | Request flow debugging |6162## Top 8 Errors Prevented6364| Error | Symptom | Prevention |65|-------|---------|------------|66| Logs not appearing | No output in dashboard | Enable "Standard" logging in wrangler.jsonc |67| Log truncation | Messages cut off at 128KB | Chunk large payloads, use sampling |68| Tail Worker not receiving | No events processed | Check binding name matches wrangler.jsonc |69| Analytics Engine write fails | Data not recorded | Verify AE binding, check blobs format |70| PII in logs | Security/compliance violation | Implement redaction middleware |71| Missing request context | Can't correlate logs | Add requestId to all log entries |72| Log volume explosion | High costs, noise | Implement sampling for high-frequency events |73| Alerting gaps | Incidents not detected | Configure monitors for error rate thresholds |7475## Logging Configuration7677**wrangler.jsonc**:78```jsonc79{80 "name": "my-worker",81 "observability": {82 "enabled": true,83 "head_sampling_rate": 1 // 0-1, 1 = 100% of requests84 },85 "tail_consumers": [86 {87 "service": "log-aggregator", // Tail Worker name88 "environment": "production"89 }90 ],91 "analytics_engine_datasets": [92 {93 "binding": "ANALYTICS",94 "dataset": "my_worker_metrics"95 }96 ]97}98```99100## Structured Logging Pattern101102```typescript103interface LogEntry {104 level: 'debug' | 'info' | 'warn' | 'error';105 message: string;106 requestId: string;107 timestamp: number;108 // Contextual data109 method?: string;110 path?: string;111 status?: number;112 duration?: number;113 // Error details114 error?: {115 name: string;116 message: string;117 stack?: string;118 };119 // Custom fields120 [key: string]: unknown;121}122123class Logger {124 constructor(private requestId: string, private baseContext: object = {}) {}125126 private log(level: LogEntry['level'], message: string, data?: object) {127 const entry: LogEntry = {128 level,129 message,130 requestId: this.requestId,131 timestamp: Date.now(),132 ...this.baseContext,133 ...data,134 };135136 // Redact sensitive fields137 const sanitized = this.redact(entry);138139 const output = JSON.stringify(sanitized);140 level === 'error' ? console.error(output) : console.log(output);141 }142143 private redact(entry: LogEntry): LogEntry {144 const sensitiveKeys = ['password', 'token', 'secret', 'authorization', 'cookie'];145 const redacted = { ...entry };146147 for (const key of Object.keys(redacted)) {148 if (sensitiveKeys.some(s => key.toLowerCase().includes(s))) {149 redacted[key] = '[REDACTED]';150 }151 }152 return redacted;153 }154155 info(message: string, data?: object) { this.log('info', message, data); }156 warn(message: string, data?: object) { this.log('warn', message, data); }157 error(message: string, data?: object) { this.log('error', message, data); }158 debug(message: string, data?: object) { this.log('debug', message, data); }159}160```161162## Analytics Engine Usage163164```typescript165interface Env {166 ANALYTICS: AnalyticsEngineDataset;167}168169export default {170 async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {171 const start = Date.now();172 const url = new URL(request.url);173174 try {175 const response = await handleRequest(request, env);176177 // Write success metric178 env.ANALYTICS.writeDataPoint({179 blobs: [request.method, url.pathname, String(response.status)],180 doubles: [Date.now() - start], // Response time in ms181 indexes: [url.pathname.split('/')[1] || 'root'], // Index for fast queries182 });183184 return response;185 } catch (error) {186 // Write error metric187 env.ANALYTICS.writeDataPoint({188 blobs: [request.method, url.pathname, 'error', error.message],189 doubles: [Date.now() - start],190 indexes: ['error'],191 });192 throw error;193 }194 }195};196```197198## Tail Worker Pattern199200```typescript201// tail-worker.ts - Receives logs from other workers202interface TailEvent {203 scriptName: string;204 event: {205 request?: { method: string; url: string };206 response?: { status: number };207 };208 logs: Array<{209 level: string;210 message: unknown[];211 timestamp: number;212 }>;213 exceptions: Array<{214 name: string;215 message: string;216 timestamp: number;217 }>;218 outcome: 'ok' | 'exception' | 'exceededCpu' | 'exceededMemory' | 'canceled';219 eventTimestamp: number;220}221222export default {223 async tail(events: TailEvent[], env: Env): Promise<void> {224 for (const event of events) {225 // Filter and forward logs226 const errorLogs = event.logs.filter(l => l.level === 'error');227 const exceptions = event.exceptions;228229 if (errorLogs.length > 0 || exceptions.length > 0) {230 // Send to external logging service231 await fetch(env.LOGGING_ENDPOINT, {232 method: 'POST',233 headers: { 'Content-Type': 'application/json' },234 body: JSON.stringify({235 scriptName: event.scriptName,236 timestamp: event.eventTimestamp,237 errors: errorLogs,238 exceptions,239 outcome: event.outcome,240 }),241 });242 }243 }244 }245};246```247248## When to Load References249250Load specific references based on the task:251252- **Setting up logging?** → Load `references/logging.md` for structured logging patterns, log levels, redaction253- **Building custom metrics?** → Load `references/analytics-engine.md` for Analytics Engine SQL queries, data modeling254- **Implementing log aggregation?** → Load `references/tail-workers.md` for Tail Worker patterns, external service integration255- **Creating dashboards/tracking?** → Load `references/custom-metrics.md` for business metrics, performance tracking256- **Setting up alerts?** → Load `references/alerting.md` for error rate monitoring, PagerDuty/Slack integration257258## Templates259260| Template | Purpose | Use When |261|----------|---------|----------|262| `templates/logging-setup.ts` | Production logging class | Setting up new worker with logging |263| `templates/analytics-worker.ts` | Analytics Engine integration | Adding custom metrics |264| `templates/tail-worker.ts` | Complete Tail Worker | Building log aggregation pipeline |265266## Scripts267268| Script | Purpose | Command |269|--------|---------|---------|270| `scripts/setup-logging.sh` | Configure logging settings | `./setup-logging.sh` |271| `scripts/analyze-logs.sh` | Query and analyze logs | `./analyze-logs.sh --errors --last 1h` |272273## Resources274275- Workers Observability: https://developers.cloudflare.com/workers/observability/276- Analytics Engine: https://developers.cloudflare.com/analytics/analytics-engine/277- Tail Workers: https://developers.cloudflare.com/workers/observability/tail-workers/278- Logpush: https://developers.cloudflare.com/logs/get-started/