自定义框架集成
当你使用的框架还没有 evlog/<framework> 包时,就需要自己来构建集成。evlog/toolkit 提供了支撑所有内置集成(Hono、Express、Fastify、Elysia、NestJS)的相同基础构件——你只需要编写框架相关的胶水代码。
其核心思路始终相同:请求生命周期 → 创建 logger → enrich → drain。工具包会处理请求上下文的传递。
| Surface | 它的作用 | 何时使用 |
|---|---|---|
defineFrameworkIntegration() | 声明式地连接请求提取 + logger 挂载 | 具有 (ctx, next) 中间件形状的 HTTP 框架(Hono、Express、Fastify、Elysia、NestJS 这类形状) |
createMiddlewareLogger() | 命令式路径:在请求开始时创建 logger,在响应结束时发出 | 生命周期不适合 (ctx, next) 的框架(NestJS 拦截器、Next.js App Router、SvelteKit handle) |
createRequestLogger() | 将任意工作单元包裹在 logger 生命周期中 | 非 HTTP 运行时(队列 worker、CLI、cron、durable workflows) |
为自定义框架构建 evlog 集成
安装
pnpm add evlog
bun add evlog
yarn add evlog
npm install evlog
工具包里有什么
| 导出 | 用途 |
|---|---|
defineFrameworkIntegration(spec) | 清单工厂——提取请求、创建 logger、挂载、使用 ALS 运行 |
createMiddlewareLogger(opts) | 更底层的生命周期(自定义模式) |
createRequestLogger(opts) | 将非 HTTP 工作单元包裹在 logger 生命周期中 |
BaseEvlogOptions | 面向用户的基础选项——drain、enrich、keep、include、exclude、routes、plugins |
MiddlewareLoggerResult | 返回类型:{ logger, finish, skipped } |
extractSafeHeaders(headers) | 从 Web API Headers 对象中过滤敏感 headers |
extractSafeNodeHeaders(headers) | 从 Node.js IncomingHttpHeaders 中过滤敏感 headers |
createLoggerStorage(hint) | 返回 { storage, useLogger } 的工厂,基于 AsyncLocalStorage |
attachForkToLogger(storage, parent, opts) | 将 log.fork(label, fn) 绑定到请求 logger 上,使消费者可以发起相关联的后台工作——在清单模式下自动使用;在自定义模式中,需要在 createMiddlewareLogger 返回 logger 之后、生命周期结束之前手动调用 |
defineEvlog(config) | 标准化配置对象——适用于 initLogger 和中间件选项 |
definePlugin(plugin) | 插件契约——可选择接入 setup、enrich、drain、keep、onRequestStart、onRequestFinish、onClientLog、extendLogger 中的任意子集 |
composeEnrichers / composeDrains / composeKeep / composePlugins | 将多个扩展组合为一个 |
像 RequestLogger、DrainContext、EnrichContext、WideEvent 和 TailSamplingContext 这样的类型都从主 evlog 包中导出。
清单模式(推荐)
大多数框架都适合 (ctx, next) 中间件形状。对于这类框架,编写一个清单来描述如何提取请求并挂载 logger——剩下的交给 defineFrameworkIntegration。
import type { IncomingMessage, ServerResponse } from 'node:http'
import {
createLoggerStorage,
defineFrameworkIntegration,
type BaseEvlogOptions,
} from 'evlog/toolkit'
import type { RequestLogger } from 'evlog'
export type MyFrameworkEvlogOptions = BaseEvlogOptions
const { storage, useLogger } = createLoggerStorage(
'无法在中间件上下文之外访问 logger。请确保在路由之前注册了 evlog 中间件。',
)
export { useLogger }
const integration = defineFrameworkIntegration<IncomingMessage>({
name: 'my-framework',
extractRequest: (req) => ({
method: req.method || 'GET',
path: req.url || '/',
headers: req.headers,
requestId: typeof req.headers['x-request-id'] === 'string'
? req.headers['x-request-id']
: undefined,
}),
attachLogger: (req, logger) => {
(req as IncomingMessage & { log: RequestLogger }).log = logger
},
storage,
})
export function evlog(options: MyFrameworkEvlogOptions = {}) {
return async (req: IncomingMessage, res: ServerResponse, next: () => Promise<void>) => {
const { skipped, finish, runWith } = integration.start(req, options)
if (skipped) {
await next()
return
}
try {
await runWith(() => next())
await finish({ status: res.statusCode })
} catch (error) {
await finish({ error: error as Error })
throw error
}
}
}
就是这样。这个中间件可以免费获得所有功能:路由过滤、drain 适配器、enricher、尾部采样、错误捕获、插件生命周期钩子、log.fork() 和持续时间跟踪。
defineFrameworkIntegration 的作用
基于上面的清单,这个辅助函数会:
- 规范化 headers(自动识别
Headers与IncomingHttpHeaders)。 - 如果
extractRequest没有返回requestId,则自动生成一个。 - 使用合并后的选项调用
createMiddlewareLogger。 - 调用
attachLogger(ctx, logger)。 - 当提供了
storage时,为 logger 附加log.fork()(这样用户就能发起相关联的后台工作)。 - 暴露
runWith(fn)——如果配置了storage,就会在storage.run(logger, …)中运行fn(),否则直接调用fn()。
你最终只需要处理框架相关的胶水代码:从哪里读取请求、在哪里附加 logger,以及如何计算响应状态。
自定义模式
如果你的框架生命周期不适合清晰的 (ctx, next) 结构(NestJS 拦截器、Next.js App Router、SvelteKit handle),那就再往下一层,直接调用 createMiddlewareLogger:
import { createMiddlewareLogger, extractSafeNodeHeaders } from 'evlog/toolkit'
const { logger, finish, skipped } = createMiddlewareLogger({
method,
path,
requestId,
headers: extractSafeNodeHeaders(rawHeaders),
...options,
})
你需要负责 ALS 包装(storage.run)、log.fork() 附加(通过 attachForkToLogger),以及完成生命周期——但你仍然可以免费获得完整管道(路由过滤、采样、emit、enrich、drain、插件)。
非 HTTP 运行时
对于队列 worker、CLI 驱动、cron 作业或持久化执行引擎,跳过 HTTP 形状的辅助函数,直接使用 evlog/toolkit 中的 createRequestLogger:
import { createRequestLogger } from 'evlog/toolkit'
async function processJob(job: Job) {
const logger = createRequestLogger({
service: 'jobs',
context: { jobId: job.id, queue: job.queue },
})
try {
await runJob(job)
logger.set({ status: 'success' })
} catch (err) {
logger.error(err)
throw err
} finally {
await logger.emit()
}
}
相同的 enrichers、相同的 drain 钩子、以及相同的身份 headers都会用于向外发出的 HTTP drain 请求——只是入口形状不同。
参考实现
研究这些内置集成,了解不同框架的模式:
| 框架 | 行数 | 模式 | 源码 |
|---|---|---|---|
| Hono | ~50 | manifest | hono/index.ts |
| Express | ~50 | manifest + ALS | express/index.ts |
| Fastify | ~70 | manifest + Fastify hooks | fastify/index.ts |
| Elysia | ~80 | manifest + 自定义 ALS 作用域 | elysia/index.ts |
| NestJS | ~120 | 自定义(拦截器) | nestjs/ |
| SvelteKit | ~90 | 自定义(handle 钩子) | sveltekit/ |