扩展
自定义 Enricher
编写一个添加部署元数据、租户 ID、功能开关或任何计算值的 enricher。错误隔离和合并由系统处理。
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
每个发出的 event 在到达 drain 之前都会运行 enricher。当你希望每个 event 都包含某个字段,而不想修改每个调用位置时,它就是合适的工具:地理位置、用户代理、trace 上下文、部署 ID、租户 ID、功能开关、性能等级。
从 evlog/toolkit 使用 defineEnricher,并提供一个返回要合并到 event 中的值的 compute() 函数,toolkit 会处理错误隔离、跳过 undefined 以及合并步骤。每个内置 enricher 都基于同一个工厂构建。
编写一个自定义 evlog enricher
EnrichContext
evlog:enrich hook 接收一个 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 等)会自动过滤,绝不会传递给富化器。推荐模式: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 组合
自定义和内置 enrichers 可以自由组合,因为它们都只是 (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 调用,无论使用哪种 framework,都以与基本示例相同的方式接入。
功能开关
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 durationMs = event.durationMs
if (durationMs === undefined) return undefined
if (durationMs < 100) return 'fast'
if (durationMs < 500) return 'normal'
if (durationMs < 2000) return 'slow'
return 'critical'
},
})
什么时候应该改用 plugin
如果你的功能将 enrich 与其他 hook 混合在一起(例如 enrich + 尾部采样 + drain 上的副作用),请改用 plugin:使用一个内聚对象覆盖多个生命周期节点。
下一步
- 内置 Enrichers:User Agent、Geo、Request Size、Trace Context
- 插件:多 hook 扩展(将 drain + enrich + keep 放在同一个对象中)
- Adapters:将 enriched events 发送到外部服务