框架

Cloudflare Workers

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

evlog/workers 适配器为 Cloudflare Workers 和 Durable Objects 添加检测功能,并提供携带 Cloudflare 特定上下文的请求范围日志记录器。

使用 withEvlog 获取与其他框架集成相同的中间件管道:路由过滤、脱敏、增强、尾部采样、插件、排水和自动发射。如果你希望自行控制发射,则使用 defineWorkerFetchcreateWorkersLogger

在我的 Cloudflare Worker 中设置 evlog

快速开始

1. 安装

pnpm add evlog

2. 包装你的 fetch 处理程序

src/worker.ts
import { initWorkersLogger, withEvlog } from 'evlog/workers'

initWorkersLogger({
  env: { service: 'my-worker' },
})

export default withEvlog(async (request, _env, _ctx, log) => {
  log.set({ action: 'handle_request' })

  // ... 你的处理程序逻辑

  return Response.json({ ok: true })
})

withEvlog 会在处理程序返回时为每个请求发射一个宽事件,无需手动调用 log.emit()。它会从第三个参数中读取 ExecutionContext,因此异步的 drain 调用(PostHog、Axiom、……)会通过 waitUntil 注册,并在响应返回后保持活动状态。流式响应会将发射推迟到正文完成之后。

当调用方发送 x-request-id 时,requestId 会从中获取;否则回退到 cf-raymethodpathcf-raytraceparent 以及 request.cf 中的安全子集会被自动捕获。

选项

withEvlog 接受与其他框架集成相同的选项:

src/worker.ts
import { initWorkersLogger, withEvlog } from 'evlog/workers'
import { createAxiomDrain } from 'evlog/axiom'

initWorkersLogger({ env: { service: 'my-worker' } })

export default withEvlog(
  async (request, env, ctx, log) => {
    log.set({ route: 'checkout' })
    return Response.json({ ok: true })
  },
  {
    drain: createAxiomDrain(),
    exclude: ['/health'],
    routes: { '/api/**': { service: 'api' } },
    redact: true,
    enrich: (ctx) => {
      ctx.event.colo = ctx.event.colo ?? 'unknown'
    },
    keep: (ctx) => {
      if (ctx.duration > 1000) ctx.shouldKeep = true
    },
  },
)

手动发送

想要自行控制事件发送?defineWorkerFetch 会为你连接 ExecutionContext,但将 log.emit() 留给你手动调用:

src/worker.ts
import { defineWorkerFetch, initWorkersLogger } from 'evlog/workers'

initWorkersLogger({ env: { service: 'my-worker' } })

export default defineWorkerFetch(async (request, _env, _ctx, log) => {
  log.set({ action: 'handle_request' })
  log.emit()
  return Response.json({ ok: true })
})
defineWorkerFetchcreateWorkersLogger 是底层路径:它们会创建日志记录器,并将生命周期管理留给你,因此 include / excluderoutesredactenrichkeepplugins不会生效。使用 withEvlog 即可获得这些功能。

宽事件

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

src/worker.ts
import { defineWorkerFetch, initWorkersLogger } from 'evlog/workers'

initWorkersLogger({
  env: { service: 'my-worker' },
})

export default defineWorkerFetch(async (request, env, _ctx, log) => {
  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 耗时 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'
import { defineWorkerFetch, initWorkersLogger } from 'evlog/workers'

initWorkersLogger({ env: { service: 'my-worker' } })

export default defineWorkerFetch(async (request, env, _ctx, log) => {
  try {
    const body = await request.json()
    log.set({ payment: { amount: body.amount } })

    if (body.amount <= 0) {
      throw createError({
        status: 400,
        message: '无效的支付金额',
        why: '金额必须是正数',
        fix: '以分为单位传入一个正整数',
      })
    }

    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 字段的错误。