框架

Nitro

Automatic wide events, structured errors, drain adapters, enrichers, and tail sampling in Nitro v2 and v3 applications.

evlog 提供了适用于 Nitro v3 和 Nitro v2(nitropack)的模块。该模块会钩入请求生命周期,创建一个请求范围内的日志记录器,可通过 useLogger(event) 访问,并在响应完成时发出一个广泛事件。

提示
在 Nitro 应用中设置 evlog。

- 安装 evlog:pnpm add evlog
- 在 nitro.config.ts 中导入 evlog 模块(v2 使用 evlog/nitro,v3 使用 evlog/nitro/v3)
- 配置 env.service 为你的应用名称
- 在路由处理程序中使用 useLogger(event) 构建广泛事件
- 使用 log.set() 在请求过程中累积上下文
- 使用 createError({ message, status, why, fix }) 抛出错误
- 请求完成时会自动发出广泛事件

文档:https://www.evlog.dev/frameworks/nitro
适配器:https://www.evlog.dev/adapters

快速开始

1. 安装

pnpm add evlog

2. 添加模块

import { defineConfig } from 'nitro'
import evlog from 'evlog/nitro/v3'

export default defineConfig({
  modules: [
    evlog({
      env: { service: 'my-app' },
    }),
  ],
})

广泛事件

使用 useLogger(event) 在请求过程中逐步构建上下文。evlog 会在请求完成时发出一个单一广泛事件。

import { defineHandler } from 'nitro/h3'
import { useLogger } from 'evlog/nitro/v3'

export default defineHandler(async (event) => {
  const log = useLogger(event)
  const body = await readBody(event)

  log.set({ user: { id: body.userId } })
  log.set({ cart: { items: body.items.length, total: body.total } })

  return { success: true }
})

一个请求,一条日志行,包含所有上下文信息:

终端输出
10:23:45 INFO [my-app] POST /api/checkout 200 in 145ms
  ├─ user: id=usr_123
  ├─ cart: items=3 total=14999
  └─ requestId: a1b2c3d4-...

错误处理

createError 会生成包含 whyfixlink 字段的结构化错误,帮助人类和 AI 代理理解问题所在。

import { defineHandler } from 'nitro/h3'
import { useLogger, createError } from 'evlog/nitro/v3'

export default defineHandler(async (event) => {
  const log = useLogger(event)

  throw createError({
    status: 402,
    message: 'Payment failed',
    why: 'Card declined by issuer',
    fix: 'Try a different payment method',
  })
})
在 Nitro v3 中,请从 evlog/nitro/v3 导入 createError —— 它封装了 Nitro 的错误处理机制。在 Nitro v2 中,请直接从 evlog 导入 createError

配置

请参阅 配置参考 了解所有可用选项(enabledprettysilentsampling 等)。

路由过滤

使用 includeexclude 控制哪些路由被记录,以及使用 routes 为不同的路由组分配不同的服务名称:

import { defineConfig } from 'nitro'
import evlog from 'evlog/nitro/v3'

export default defineConfig({
  modules: [
    evlog({
      include: ['/api/**'],
      exclude: ['/api/health'],
      routes: {
        '/api/auth/**': { service: 'auth-service' },
        '/api/payment/**': { service: 'payment-service' },
      },
    })
  ],
})
排除项优先级更高。 如果路径同时匹配 includeexclude,则会被排除。

排水(Drain)与增强器(Enrichers)

使用 Nitro 插件钩子将日志发送到外部服务并增强上下文信息。

排水插件

server/plugins/evlog-drain.ts
import type { DrainContext } from 'evlog'
import { createAxiomDrain } from 'evlog/axiom'
import { createDrainPipeline } from 'evlog/pipeline'

const pipeline = createDrainPipeline<DrainContext>({
  batch: { size: 50, intervalMs: 5000 },
  retry: { maxAttempts: 3 },
})
const drain = pipeline(createAxiomDrain())

export default defineNitroPlugin((nitroApp) => {
  nitroApp.hooks.hook('evlog:drain', drain)
})
对于独立 Nitro v3,请使用 definePlugin 替代 defineNitroPlugin

增强器插件

server/plugins/evlog-enrich.ts
import { createUserAgentEnricher, createGeoEnricher } from 'evlog/enrichers'

const enrichers = [createUserAgentEnricher(), createGeoEnricher()]

export default defineNitroPlugin((nitroApp) => {
  nitroApp.hooks.hook('evlog:enrich', (ctx) => {
    for (const enricher of enrichers) enricher(ctx)
  })
})
请参阅 适配器增强器 文档获取可用的完整排水和增强器列表。

采样

头部采样

按百分比随机保留各层级的日志。在请求完成前执行。

import { defineConfig } from 'nitro'
import evlog from 'evlog/nitro/v3'

export default defineConfig({
  modules: [
    evlog({
      sampling: {
        rates: { info: 10, warn: 50, debug: 5 },
        keep: [
          { duration: 1000 },
          { status: 400 },
        ],
      },
    })
  ],
})

每个层级都是 0 到 100 的百分比。未配置的层级默认保留 100%(保留全部)。

自定义尾部采样

对于超出状态、时长和路径的条件,使用 evlog:emit:keep 钩子:

server/plugins/evlog-sampling.ts
export default defineNitroPlugin((nitroApp) => {
  nitroApp.hooks.hook('evlog:emit:keep', (ctx) => {
    const user = ctx.context.user as { premium?: boolean } | undefined
    if (user?.premium) ctx.shouldKeep = true
  })
})
错误日志默认总是保留。你必须显式设置 error: 0 才能丢弃它们。

下一步

深入了解 Nitro 集成:

  • 广泛事件:通过上下文分层设计全面的事件
  • 适配器:将日志发送到 Axiom、Sentry、PostHog 等
  • 采样:使用头部和尾部采样控制日志量
  • 结构化错误:使用 whyfixlink 字段抛出错误