框架

自定义集成

使用工具包 API 构建自定义的 evlog 框架集成,包含 createMiddlewareLogger、头部提取、AsyncLocalStorage 以及完整的 drain/enrich/keep 管道。

没有看到你使用的框架?evlog/toolkit 包暴露了与每个内置集成(Hono、Express、Fastify、Elysia、nestJS、SvelteKit)相同的构建模块。使用大约 50 行代码即可为任意 HTTP 框架构建功能完整的 evlog 中间件。

工具包 API 标记为 beta。其接口是稳定的(所有内置集成都在使用),但可能会根据社区反馈进行演进。

安装

pnpm add evlog

工具包包含什么

导出用途
createMiddlewareLogger(opts)完整管道:创建日志器、路由过滤、尾部采样、发送、增强、排水
BaseEvlogOptions基础用户选项类型,包含 drainenrichkeepincludeexcluderoutes
MiddlewareLoggerOptions内部选项,在 BaseEvlogOptions 基础上扩展了 methodpathrequestIdheaders
MiddlewareLoggerResult返回类型:{ logger, finish, skipped }
extractSafeHeaders(headers)从 Web API 的 Headers 对象中过滤敏感头部(适用于 Hono、Elysia、Deno、Bun)
extractSafeNodeHeaders(headers)从 Node.js 的 IncomingHttpHeaders 中过滤敏感头部(适用于 Express、Fastify、nestJS)
createLoggerStorage(hint)返回 { storage, useLogger } 的工厂函数,基于 AsyncLocalStorage
extractErrorStatus(error)从任意错误对象中提取 HTTP 状态码(支持 statusstatusCode 字段)
shouldLog(path, include, exclude)路由过滤逻辑(支持通配符模式)
getServiceForPath(path, routes)根据路径解析对应的服务名称

RequestLoggerDrainContextEnrichContextWideEventTailSamplingContext 等类型可从主包 evlog 中导出。

架构

每个 evlog 框架集成都遵循相同的 5 步模式:

请求 → createMiddlewareLogger() → 存储日志器 → 处理请求 → finish()
  1. 提取 methodpathrequestIdheaders,从框架请求对象中获取
  2. 调用 createMiddlewareLogger(),传入这些字段以及用户选项
  3. 检查 skipped —— 若为 true,表示路由被过滤,跳过后续中间件
  4. 存储 logger 到框架的上下文(如 req.logc.set('log') 等)
  5. 调用 finish({ status })(成功)或 finish({ error })(失败),处理发送、增强与排水

createMiddlewareLogger 负责其余所有工作:路由过滤、服务覆盖、耗时统计、尾部采样、事件发送、数据增强与排水。

最小示例

以下是一个适用于通用 Node.js HTTP 框架的完整集成:

my-framework-evlog.ts
import type { IncomingMessage, ServerResponse } from 'node:http'
import type { RequestLogger } from 'evlog'
import {
  createMiddlewareLogger,
  extractSafeNodeHeaders,
  createLoggerStorage,
  type BaseEvlogOptions,
} from 'evlog/toolkit'

export type MyFrameworkEvlogOptions = BaseEvlogOptions

const { storage, useLogger } = createLoggerStorage(
  '中间件上下文。确保在路由注册之前挂载 evlog 中间件。',
)

export { useLogger }

export function evlog(options: MyFrameworkEvlogOptions = {}) {
  return async (req: IncomingMessage, res: ServerResponse, next: () => Promise<void>) => {
    const { logger, finish, skipped } = createMiddlewareLogger({
      method: req.method || 'GET',
      path: req.url || '/',
      requestId: (req.headers['x-request-id'] as string) || crypto.randomUUID(),
      headers: extractSafeNodeHeaders(req.headers),
      ...options,
    })

    if (skipped) {
      await next()
      return
    }

    ;(req as IncomingMessage & { log: RequestLogger }).log = logger

    try {
      await storage.run(logger, () => next())
      await finish({ status: res.statusCode })
    } catch (error) {
      await finish({ error: error as Error })
      throw error
    }
  }
}

仅此而已。该中间件将免费获得所有功能:路由过滤、排水适配器、数据增强、尾部采样、错误捕获与耗时统计。

关键规则

  1. 始终使用 createMiddlewareLogger —— 不要直接调用 createRequestLogger
  2. 使用正确的头部提取器 —— extractSafeHeaders 用于 Web API 的 Headers(Hono、Elysia、Deno),extractSafeNodeHeaders 用于 Node.js 的 IncomingHttpHeaders(Express、Fastify)
  3. 展开用户选项 —— ...options 会自动将 drainenrichkeepincludeexclude 传递给管道
  4. 在成功和错误路径中都调用 finish() —— 它会处理发送、增强与排水
  5. 重新抛出错误,以确保框架的错误处理器仍能生效
  6. 导出 useLogger() —— 消费者需要它来从服务函数中访问日志器
  7. 导出扩展 BaseEvlogOptions 的选项类型 —— 为 IDE 提供 drainenrichkeep 的自动补全

使用方式

构建完成后,你的集成可以像其他集成一样使用:

src/index.ts
import { initLogger } from 'evlog'
import { evlog, useLogger } from './my-framework-evlog'
import { createAxiomDrain } from 'evlog/axiom'

initLogger({ env: { service: 'my-api' } })

app.use(evlog({
  include: ['/api/**'],
  drain: createAxiomDrain(),
  enrich: (ctx) => {
    ctx.event.region = process.env.FLY_REGION
  },
  keep: (ctx) => {
    if (ctx.duration && ctx.duration > 2000) ctx.shouldKeep = true
  },
}))

app.get('/api/users', (req, res) => {
  req.log.set({ users: { count: 42 } })
  res.json({ users: [] })
})

// 从调用栈中的任意位置访问日志器
function findUsers() {
  const log = useLogger()
  log.set({ db: { query: 'SELECT * FROM users' } })
}

参考实现

研究以下内置集成,了解框架相关的特定模式:

框架行数模式源码
Hono~40Web API 头部提取、c.set()、try/catchhono/index.ts
Express~60Node.js 头部、req.logres.on('finish')express/index.ts
Elysia~70插件 API、derive()onAfterHandle/onErrorelysia/index.ts
Fastify~70插件、decorateRequestonRequest/onResponse 钩子fastify/index.ts
为尚未支持的框架编写了集成?提交 PR —— 社区会感谢你的。

后续步骤

  • 广泛事件:设计包含多层上下文信息的综合事件
  • 适配器:将日志发送到 Axiom、Sentry、PostHog 等平台
  • 采样:使用头部和尾部采样控制日志数量
  • 结构化错误:抛出包含 whyfixlink 字段的错误对象