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)
  },
})
See the Adapters and Enrichers docs for all available drain adapters and enrichers.

Wrangler Configuration

Disable Cloudflare's default invocation logs to avoid duplicates when using evlog:

wrangler.toml
[observability]
enabled = false

Run Locally

wrangler dev