扩展
自定义 Enricher
编写自定义 enricher,为你的宽事件添加派生上下文。添加部署元数据、租户 ID、功能开关、地理位置或任何计算得出的数据——工具包会处理错误隔离、跳过 undefined,以及合并步骤。
enrich pipeline·idle
UserAgent
+3 fields
Geo
+3 fields
RequestSize
+2 fields
TraceContext
+2 fields
wide event·4 fields
{
method:"POST",
path:"/api/checkout",
status:200,
duration:234,
userAgent.browser:"chrome 142",+UserAgent
userAgent.os:"macOS 26",+UserAgent
userAgent.device:"desktop",+UserAgent
geo.country:"FR",+Geo
geo.city:"Paris",+Geo
geo.region:"Île-de-France",+Geo
request.size:1248,+RequestSize
response.size:8412,+RequestSize
trace.traceId:"4bf92f3577b34da6a3ce…",+TraceContext
trace.spanId:"00f067aa0ba902b7",+TraceContext
}
base fields4
enriched fields+0
app code touched0 lines
enricher 会在每个发出的事件到达 drain 之前运行。当你希望每个事件都拥有某个字段,而不必修改每个调用点时,这就是合适的工具——例如地理位置、用户代理、trace 上下文、部署 ID、租户 ID、功能开关、性能等级。
使用 evlog/toolkit 中的 defineEnricher——只需提供一个返回你想合并到事件中的值的 compute() 函数,工具包会处理错误隔离、跳过 undefined,以及合并步骤。所有内置 enricher 都是基于这个相同的工厂构建的。
编写一个自定义 evlog enricher
基本示例
为每个事件添加部署元数据。这个 enricher 在任何地方都是同一个函数——只是不同框架的接入步骤不同。
// server/plugins/evlog-enrich.ts
export default defineNitroPlugin((nitroApp) => {
nitroApp.hooks.hook('evlog:enrich', (ctx) => {
ctx.event.deploymentId = process.env.DEPLOYMENT_ID
ctx.event.deployedBy = process.env.DEPLOYED_BY
})
})
// lib/evlog.ts
import { createEvlog } from 'evlog/next'
export const { withEvlog, useLogger, log, createError } = createEvlog({
service: 'my-app',
enrich: (ctx) => {
ctx.event.deploymentId = process.env.DEPLOYMENT_ID
ctx.event.deployedBy = process.env.DEPLOYED_BY
},
})
import type { EnrichContext } from 'evlog'
const deployment = (ctx: EnrichContext) => {
ctx.event.deploymentId = process.env.DEPLOYMENT_ID
ctx.event.deployedBy = process.env.DEPLOYED_BY
}
app.use(evlog({ enrichers: [deployment] })) // Hono / Express / Elysia
// await app.register(evlog, { enrichers: [deployment] }) // Fastify
// EvlogModule.forRoot({ enrichers: [deployment] }) // NestJS
// index.ts
import type { EnrichContext } from 'evlog'
import { initLogger } from 'evlog'
const deployment = (ctx: EnrichContext) => {
ctx.event.deploymentId = process.env.DEPLOYMENT_ID
ctx.event.deployedBy = process.env.DEPLOYED_BY
}
initLogger({ enrichers: [deployment] })
EnrichContext
evlog:enrich 钩子接收一个 EnrichContext:
enrich-context.ts
interface EnrichContext {
/** 发出的宽事件(可变) */
event: WideEvent
/** 请求元数据 */
request?: {
method?: string
path?: string
requestId?: string
}
/** 安全的 HTTP 请求头(已过滤敏感请求头) */
headers?: Record<string, string>
/** 响应元数据 */
response?: {
status?: number
headers?: Record<string, string>
}
}
安全性: 敏感请求头(
authorization、cookie、x-api-key 等)会自动过滤,绝不会传递给 enrichers。推荐模式 — defineEnricher
每个内置 enricher 都使用同一个工厂。提供 compute() 就完成了:
server/utils/enrichers.ts
import { defineEnricher, getHeader, type EnricherOptions } from 'evlog/toolkit'
interface TenantInfo {
id: string
org?: string
}
export function createTenantEnricher(options: EnricherOptions & { headerName?: string } = {}) {
const headerName = options.headerName ?? 'x-tenant-id'
return defineEnricher<TenantInfo>({
name: 'tenant',
field: 'tenant',
compute: ({ headers }) => {
const id = getHeader(headers, headerName)
if (!id) return undefined
return { id }
},
}, options)
}
defineEnricher 会自动:
- 当
compute()返回undefined时跳过 - 通过
mergeEventField将结果合并到ctx.event[field]中(遵循options.overwrite) - 捕获错误并将其记录为
[evlog/<name>],而不是中断管道
像任何其他 enricher 一样接入它:
// server/plugins/evlog-enrich.ts
import { createTenantEnricher } from '~/server/utils/enrichers'
export default defineNitroPlugin((nitroApp) => {
const enrichTenant = createTenantEnricher({ headerName: 'x-org-id' })
nitroApp.hooks.hook('evlog:enrich', enrichTenant)
})
// lib/evlog.ts
import { createEvlog } from 'evlog/next'
import { createTenantEnricher } from './enrichers'
const enrichTenant = createTenantEnricher({ headerName: 'x-org-id' })
export const { withEvlog, useLogger, log, createError } = createEvlog({
service: 'my-app',
enrich: enrichTenant,
})
import { createTenantEnricher } from './enrichers'
const enrichTenant = createTenantEnricher({ headerName: 'x-org-id' })
app.use(evlog({ enrichers: [enrichTenant] }))
// await app.register(evlog, { enrichers: [enrichTenant] }) // Fastify
// EvlogModule.forRoot({ enrichers: [enrichTenant] }) // NestJS
import { initLogger } from 'evlog'
import { createTenantEnricher } from './enrichers'
initLogger({
enrichers: [createTenantEnricher({ headerName: 'x-org-id' })],
})
与内置 enrichers 组合
自定义 enricher 和内置 enricher 可以自由组合——它们本质上都只是 (ctx: EnrichContext) => void 函数。使用 evlog/toolkit 中的 composeEnrichers 将它们组合成一个可调用对象:
enrichers.ts
import { composeEnrichers, defineEnricher } from 'evlog/toolkit'
import { createDefaultEnrichers } from 'evlog/enrichers'
const region = defineEnricher({
name: 'region',
field: 'region',
compute: () => process.env.FLY_REGION ?? process.env.AWS_REGION,
})
export const enrich = composeEnrichers([
createDefaultEnrichers(), // 用户代理 + 地理位置 + 请求大小 + trace 上下文
region,
])
更多示例
下面的每个示例都是一次普通的 defineEnricher 调用——无论使用什么框架,接入方式都与基本示例相同。
功能开关
enricher-feature-flags.ts
import { defineEnricher } from 'evlog/toolkit'
export const featureFlags = defineEnricher({
name: 'feature-flags',
field: 'featureFlags',
compute: () => ({
newCheckout: isEnabled('new-checkout'),
betaApi: isEnabled('beta-api'),
}),
})
响应时间分级
enricher-perf-tier.ts
import { defineEnricher } from 'evlog/toolkit'
export const performanceTier = defineEnricher<string>({
name: 'performance-tier',
field: 'performanceTier',
compute: ({ event }) => {
const duration = event.duration as number | undefined
if (duration === undefined) return undefined
if (duration < 100) return 'fast'
if (duration < 500) return 'normal'
if (duration < 2000) return 'slow'
return 'critical'
},
})
什么时候应该改用 plugin
如果你的功能把 enrichment 与其他钩子混合在一起(例如 enrich + tail-sample + 在 drain 上做副作用),请改用 plugin —— 一个统一对象即可覆盖多个生命周期点。
下一步
- 内置 Enrichers — 用户代理、地理位置、请求大小、trace 上下文
- Plugins — 多钩子扩展(把 drain + enrich + keep 放在一个对象中)
- Adapters — 将已丰富的事件发送到外部服务