框架

Cloudflare Workers

宽事件、结构化错误和日志记录,适用于 Cloudflare Workers 和持久对象。

evlog/workers 适配器提供了用于创建具有 Cloudflare 特定上下文的请求范围日志记录器的工厂函数。与框架集成不同,Workers 需要手动调用 log.emit(),因为没有可以钩入的中间件生命周期。

提示
在我的 Cloudflare Worker 中设置 evlog:

- 安装 evlog:pnpm add evlog
- 从 'evlog/workers' 导入 initWorkersLogger 和 createWorkersLogger
- 在顶层调用 initWorkersLogger({ env: { service: 'my-worker' } })
- 在 fetch 处理程序中,使用 createWorkersLogger(request) 创建日志记录器
- 使用 log.set() 在整个请求过程中累积上下文
- 在返回响应前手动调用 log.emit()(没有中间件生命周期)

文档:https://www.evlog.dev/frameworks/cloudflare-workers
适配器:https://www.evlog.dev/adapters

快速开始

1. 安装

终端
bun add evlog

2. 初始化并创建请求日志记录器

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' })

    // ... 你的处理逻辑

    log.emit()
    return Response.json({ ok: true })
  },
}

createWorkersLogger(request) 会自动从请求中提取 methodpathcf-ray

你必须在返回响应前手动调用 log.emit()。Workers 没有用于自动发射的中间件生命周期钩子。

宽事件

逐步构建上下文,最后发射:

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 })
  },
}
终端输出
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-...

错误处理

使用 createError 创建结构化错误,并使用 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 })
    }
  },
}

配置

请参阅 配置参考 了解所有可用选项(initLogger、中间件选项、采样、静默模式等)。

排水和增强器

通过 initWorkersLogger 选项配置排水和增强器:

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 配置

禁用 Cloudflare 的默认调用日志以避免重复:

wrangler.toml
[observability]
enabled = false

本地运行

终端
wrangler dev

下一步

  • 宽事件:设计具有上下文分层的综合事件
  • 适配器:将日志发送到 Axiom、Sentry、PostHog 等
  • 采样:使用头部和尾部采样控制日志量
  • 结构化错误:抛出带有 whyfixlink 字段的错误