---
title: "自定义 Enricher"
description: "编写一个添加部署元数据、租户 ID、功能开关或任何计算值的 enricher。错误隔离和合并由系统处理。"
canonical_url: "https://evlog.zhcndoc.com/extend/custom-enrichers"
---
# 自定义 Enricher

> 编写一个添加部署元数据、租户 ID、功能开关或任何计算值的 enricher。错误隔离和合并由系统处理。

<enricher-chain>



</enricher-chain>

每个发出的 event 在打印之前、到达 drain 之前都会运行 **enricher**。当你希望每个 event 都包含某个字段，而不想修改每个调用位置时，它就是合适的工具：地理位置、用户代理、trace 上下文、部署 ID、租户 ID、功能开关、性能等级。

从 `evlog/toolkit` 使用 `defineEnricher`，并提供一个返回要合并到 event 中的值的 `compute()` 函数，toolkit 会处理错误隔离、跳过 `undefined` 以及合并步骤。每个内置 enricher 都基于同一个工厂构建。

<prompt :actions="["copy","cursor","claude"]" description="编写一个自定义 evlog enricher" icon="i-lucide-code">

编写一个自定义 evlog enricher，为每个宽事件添加派生上下文。

- 从 `evlog/toolkit` 使用 `defineEnricher`，不要手动编写合并／错误／undefined 逻辑
- 将 `{ name, field, compute }` 传递给 `defineEnricher`
- `compute(ctx)` 从 `ctx.headers`／`ctx.request`／`ctx.response`／`ctx.event` 中读取数据，并返回要合并的值（或返回 `undefined` 以跳过）
- 保持 `compute` 纯粹且快速：不要在热路径中执行可等待的 I/O；将任何开销较大的内容缓存在模块作用域
- `defineEnricher` 已经处理：错误隔离（记录错误，但绝不抛出）、单字段合并、覆盖选项
- 通过 `enrich` 选项（middleware）或 `initLogger.enrichers`（standalone）将 enricher 接入我的 framework
- 对于多个 enricher，使用 `evlog/toolkit` 中的 `composeEnrichers([...])`
- 对于多 hook 功能（enrich + drain 副作用等），改用 `definePlugin`

文档：[https://www.evlog.dev/extend/custom-enrichers](https://www.evlog.dev/extend/custom-enrichers)
内置：[https://www.evlog.dev/use-cases/enrichers](https://www.evlog.dev/use-cases/enrichers)

::。

## 基本示例

为每个 event 添加部署元数据。无论在哪里，enricher 都是同一个函数。只有接入步骤会因 framework 而异。

<framework-tabs>

```typescript [Nuxt]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
  })
})
```

```typescript [Nitro]server/plugins/evlog-enrich.ts
import { definePlugin } from 'nitro'
export default definePlugin((nitroApp) => {
  nitroApp.hooks.hook('evlog:enrich', (ctx) => {
    ctx.event.deploymentId = process.env.DEPLOYMENT_ID
    ctx.event.deployedBy = process.env.DEPLOYED_BY
  })
})
```

```typescript [Next.js]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
  },
})
```

```typescript [SvelteKit]src/hooks.server.ts
import { createEvlogHooks } from 'evlog/sveltekit'
import type { EnrichContext } from 'evlog'

const deployment = (ctx: EnrichContext) => {
  ctx.event.deploymentId = process.env.DEPLOYMENT_ID
  ctx.event.deployedBy = process.env.DEPLOYED_BY
}

export const { handle, handleError } = createEvlogHooks({ enrich: deployment })
```

```typescript [TanStack Start]server/plugins/evlog-enrich.ts
import { definePlugin } from 'nitro'

export default definePlugin((nitroApp) => {
  nitroApp.hooks.hook('evlog:enrich', (ctx) => {
    ctx.event.deploymentId = process.env.DEPLOYMENT_ID
    ctx.event.deployedBy = process.env.DEPLOYED_BY
  })
})
```

```typescript [React Router]app/root.tsx
import { evlog } from 'evlog/react-router'
import type { EnrichContext } from 'evlog'

const deployment = (ctx: EnrichContext) => {
  ctx.event.deploymentId = process.env.DEPLOYMENT_ID
  ctx.event.deployedBy = process.env.DEPLOYED_BY
}

export const middleware: Route.MiddlewareFunction[] = [
  evlog({ enrich: deployment }),
]
```

```typescript [Hono]src/index.ts
import { evlog } from 'evlog/hono'
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({ enrich: deployment }))
```

```typescript [Express]src/index.ts
import { evlog } from 'evlog/express'
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({ enrich: deployment }))
```

```typescript [Fastify]src/index.ts
import { evlog } from 'evlog/fastify'
import type { EnrichContext } from 'evlog'

const deployment = (ctx: EnrichContext) => {
  ctx.event.deploymentId = process.env.DEPLOYMENT_ID
  ctx.event.deployedBy = process.env.DEPLOYED_BY
}

await app.register(evlog, { enrich: deployment })
```

```typescript [Elysia]src/index.ts
import { evlog } from 'evlog/elysia'
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({ enrich: deployment }))
```

```typescript [NestJS]src/app.module.ts
import { EvlogModule } from 'evlog/nestjs'
import type { EnrichContext } from 'evlog'

const deployment = (ctx: EnrichContext) => {
  ctx.event.deploymentId = process.env.DEPLOYMENT_ID
  ctx.event.deployedBy = process.env.DEPLOYED_BY
}

EvlogModule.forRoot({ enrich: deployment })
```

```typescript [oRPC]server/orpc.ts
import { withEvlog } from 'evlog/orpc'
import type { EnrichContext } from 'evlog'

const deployment = (ctx: EnrichContext) => {
  ctx.event.deploymentId = process.env.DEPLOYMENT_ID
  ctx.event.deployedBy = process.env.DEPLOYED_BY
}

const handler = withEvlog(new RPCHandler(router), { enrich: deployment })
```

```typescript [Cloudflare Workers]src/worker.ts
import { withEvlog } from 'evlog/workers'
import type { EnrichContext } from 'evlog'

const deployment = (ctx: EnrichContext) => {
  ctx.event.deploymentId = process.env.DEPLOYMENT_ID
  ctx.event.deployedBy = process.env.DEPLOYED_BY
}

export default withEvlog(handler, { enrich: deployment })
```

```typescript [Standalone]index.ts
import type { EnrichContext } from 'evlog'
import { initLogger } from 'evlog'
import { enricherPlugin } from 'evlog/toolkit'

const deployment = (ctx: EnrichContext) => {
  ctx.event.deploymentId = process.env.DEPLOYMENT_ID
  ctx.event.deployedBy = process.env.DEPLOYED_BY
}

initLogger({ plugins: [enricherPlugin('deployment', deployment)] })
```

</framework-tabs>
</prompt>

## EnrichContext

`evlog:enrich` hook 接收一个 `EnrichContext`：

```typescript [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>
  }
}
```

<callout color="success" icon="i-lucide-shield-check">

**安全性：** 敏感请求头（`authorization`、`cookie`、`x-api-key` 等）会自动过滤，绝不会传递给富化器。

</callout>

## 推荐模式：`defineEnricher`

每个内置 enricher 都使用同一个工厂。提供 `compute()` 就完成了：

```typescript [server/utils/enrichers.ts]
import { defineEnricher, type EnricherOptions } from 'evlog/toolkit'

interface TenantInfo {
  id: string
}

// `event.user` is set by the auth layer with log.set({ user }), never by the
// client. A request header would let the caller choose its own tenant.
export function createTenantEnricher(options: EnricherOptions = {}) {
  return defineEnricher<TenantInfo>({
    name: 'tenant',
    field: 'tenant',
    compute: ({ event }) => {
      const user = event.user as { orgId?: string } | undefined
      if (!user?.orgId) return undefined
      return { id: user.orgId }
    },
  }, options)
}
```

`defineEnricher` 会自动：

- 当 `compute()` 返回 `undefined` 时跳过
- 通过 `mergeEventField` 将结果合并到 `ctx.event[field]` 中（遵循 `options.overwrite`）
- 捕获错误并将其记录为 `[evlog/<name>]`，而不是中断管道

像任何其他 enricher 一样接入它：

<framework-tabs>

```typescript [Nuxt]server/plugins/evlog-enrich.ts
import { createTenantEnricher } from '~/server/utils/enrichers'

export default defineNitroPlugin((nitroApp) => {
  const enrichTenant = createTenantEnricher()
  nitroApp.hooks.hook('evlog:enrich', enrichTenant)
})
```

```typescript [Nitro]server/plugins/evlog-enrich.ts
import { definePlugin } from 'nitro'
import { createTenantEnricher } from '~/server/utils/enrichers'

export default definePlugin((nitroApp) => {
  const enrichTenant = createTenantEnricher()
  nitroApp.hooks.hook('evlog:enrich', enrichTenant)
})
```

```typescript [Next.js]lib/evlog.ts
import { createEvlog } from 'evlog/next'
import { createTenantEnricher } from './enrichers'

const enrichTenant = createTenantEnricher()

export const { withEvlog, useLogger, log, createError } = createEvlog({
  service: 'my-app',
  enrich: enrichTenant,
})
```

```typescript [SvelteKit]src/hooks.server.ts
import { createEvlogHooks } from 'evlog/sveltekit'
import { createTenantEnricher } from './enrichers'

const enrichTenant = createTenantEnricher()

export const { handle, handleError } = createEvlogHooks({ enrich: enrichTenant })
```

```typescript [TanStack Start]server/plugins/evlog-enrich.ts
import { definePlugin } from 'nitro'
import { createTenantEnricher } from '~/server/utils/enrichers'

export default definePlugin((nitroApp) => {
  const enrichTenant = createTenantEnricher()
  nitroApp.hooks.hook('evlog:enrich', enrichTenant)
})
```

```typescript [React Router]app/root.tsx
import { evlog } from 'evlog/react-router'
import { createTenantEnricher } from './enrichers'

const enrichTenant = createTenantEnricher()

export const middleware: Route.MiddlewareFunction[] = [
  evlog({ enrich: enrichTenant }),
]
```

```typescript [Hono]src/index.ts
import { evlog } from 'evlog/hono'
import { createTenantEnricher } from './enrichers'

const enrichTenant = createTenantEnricher()

app.use(evlog({ enrich: enrichTenant }))
```

```typescript [Express]src/index.ts
import { evlog } from 'evlog/express'
import { createTenantEnricher } from './enrichers'

const enrichTenant = createTenantEnricher()

app.use(evlog({ enrich: enrichTenant }))
```

```typescript [Fastify]src/index.ts
import { evlog } from 'evlog/fastify'
import { createTenantEnricher } from './enrichers'

const enrichTenant = createTenantEnricher()

await app.register(evlog, { enrich: enrichTenant })
```

```typescript [Elysia]src/index.ts
import { evlog } from 'evlog/elysia'
import { createTenantEnricher } from './enrichers'

const enrichTenant = createTenantEnricher()

app.use(evlog({ enrich: enrichTenant }))
```

```typescript [NestJS]src/app.module.ts
import { EvlogModule } from 'evlog/nestjs'
import { createTenantEnricher } from './enrichers'

const enrichTenant = createTenantEnricher()

EvlogModule.forRoot({ enrich: enrichTenant })
```

```typescript [oRPC]server/orpc.ts
import { withEvlog } from 'evlog/orpc'
import { createTenantEnricher } from './enrichers'

const enrichTenant = createTenantEnricher()

const handler = withEvlog(new RPCHandler(router), { enrich: enrichTenant })
```

```typescript [Cloudflare Workers]src/worker.ts
import { withEvlog } from 'evlog/workers'
import { createTenantEnricher } from './enrichers'

const enrichTenant = createTenantEnricher()

export default withEvlog(handler, { enrich: enrichTenant })
```

```typescript [Standalone]
import { initLogger } from 'evlog'
import { enricherPlugin } from 'evlog/toolkit'
import { createTenantEnricher } from './enrichers'

initLogger({
  plugins: [enricherPlugin('tenant', createTenantEnricher())],
})
```

</framework-tabs>

## 与内置 enrichers 组合

自定义和内置 enrichers 可以自由组合，因为它们都只是 `(ctx: EnrichContext) => void` 函数。使用 `evlog/toolkit` 中的 `composeEnrichers` 将它们组合成一个可调用函数：

```typescript [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，都以与基本示例相同的方式接入。

### 功能开关

```typescript [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'),
  }),
})
```

### 响应时间分级

```typescript [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](https://evlog.zhcndoc.com/extend/plugins)：使用一个内聚对象覆盖多个生命周期节点。

## 下一步

- [内置 Enrichers](https://evlog.zhcndoc.com/use-cases/enrichers)：User Agent、Geo、Request Size、Trace Context
- [插件](https://evlog.zhcndoc.com/extend/plugins)：多 hook 扩展（将 drain + enrich + keep 放在同一个对象中）
- [Adapters](https://evlog.zhcndoc.com/integrate/adapters/overview)：将 enriched events 发送到外部服务

---

- [内置 Enricher](https://evlog.zhcndoc.com/use-cases/enrichers)
- [插件](https://evlog.zhcndoc.com/extend/plugins)


## Sitemap

See the full [sitemap](https://evlog.zhcndoc.com/sitemap.md) for all pages.
