Frameworks
Cloudflare Workers
Wide events, structured errors, and logging in Cloudflare Workers and Durable Objects.
The evlog/workers adapter provides factory functions for creating request-scoped loggers with Cloudflare-specific context. Unlike framework integrations, Workers require manual log.emit() calls since there is no middleware lifecycle to hook into.
Prompt
Set up evlog in my Cloudflare Worker.
- Install evlog: pnpm add evlog
- Import initLogger and createRequestLogger from 'evlog'
- Call initLogger({ service: 'my-worker' }) at the top level
- In the fetch handler, create a logger with createRequestLogger({ method, path })
- Use log.set() to accumulate context throughout the request
- Call log.emit() manually before returning the response (no middleware lifecycle)
Docs: https://www.evlog.dev/frameworks/cloudflare-workers
Adapters: https://www.evlog.dev/adapters
Quick Start
1. Install
bun add evlog
2. Initialize and create request loggers
src/worker.ts
import { initWorkersLogger, createWorkersLogger } from 'evlog/workers'
initWorkersLogger({
env: { service: 'my-worker' },
})
export default {
async fetch(request: Request, env: Env): Promise<Response> {
const log = createWorkersLogger(request)
log.set({ action: 'handle_request' })
// ... your handler logic
log.emit()
return Response.json({ ok: true })
},
}
createWorkersLogger(request) automatically extracts method, path, and cf-ray from the request.
You must call
log.emit() manually before returning a response. Workers don't have a request lifecycle hook to auto-emit.Wide Events
Build up context progressively, then emit at the end:
src/worker.ts
export default {
async fetch(request: Request, env: Env): Promise<Response> {
const log = createWorkersLogger(request)
const url = new URL(request.url)
log.set({ route: url.pathname })
const user = await env.DB.prepare('SELECT * FROM users WHERE id = ?').bind(url.searchParams.get('userId')).first()
log.set({ user: { id: user.id, plan: user.plan } })
const orders = await env.DB.prepare('SELECT COUNT(*) as count FROM orders WHERE user_id = ?').bind(user.id).first()
log.set({ orders: { count: orders.count } })
log.emit()
return Response.json({ user, orders })
},
}
Terminal output
14:58:15 INFO [my-worker] GET /api/users 200 in 12ms
├─ orders: count=5
├─ user: id=usr_123 plan=pro
├─ route: /api/users
└─ requestId: 4a8ff3a8-...
Error Handling
Use createError for structured errors and handle them with try/catch:
src/worker.ts
import { createError, parseError } from 'evlog'
export default {
async fetch(request: Request, env: Env): Promise<Response> {
const log = createWorkersLogger(request)
try {
const body = await request.json()
log.set({ payment: { amount: body.amount } })
if (body.amount <= 0) {
throw createError({
status: 400,
message: 'Invalid payment amount',
why: 'The amount must be a positive number',
fix: 'Pass a positive integer in cents',
})
}
log.emit()
return Response.json({ success: true })
} catch (error) {
log.error(error instanceof Error ? error : new Error(String(error)))
log.emit()
const parsed = parseError(error)
return Response.json({
message: parsed.message,
why: parsed.why,
fix: parsed.fix,
}, { status: parsed.status })
}
},
}
Configuration
See the Configuration reference for all available options (initLogger, middleware options, sampling, silent mode, etc.).
Drain & Enrichers
Configure drain and enrichers via initWorkersLogger options:
src/worker.ts
import { initWorkersLogger, createWorkersLogger } from 'evlog/workers'
import { createAxiomDrain } from 'evlog/axiom'
import { createUserAgentEnricher } from 'evlog/enrichers'
import { createDrainPipeline } from 'evlog/pipeline'
import type { DrainContext } from 'evlog'
const pipeline = createDrainPipeline<DrainContext>({
batch: { size: 50, intervalMs: 5000 },
})
const drain = pipeline(createAxiomDrain())
const userAgent = createUserAgentEnricher()
initWorkersLogger({
env: { service: 'my-worker' },
drain,
enrich: (ctx) => {
userAgent(ctx)
},
})
Wrangler Configuration
Disable Cloudflare's default invocation logs to avoid duplicates when using evlog:
wrangler.toml
[observability]
enabled = false
Run Locally
wrangler dev