---
title: "插件"
description: "definePlugin 是 evlog 的规范扩展点：通过一个统一的对象，可选择接入任意生命周期钩子子集。"
canonical_url: "https://evlog.zhcndoc.com/extend/plugins"
---
# 插件

> definePlugin 是 evlog 的规范扩展点：通过一个统一的对象，可选择接入任意生命周期钩子子集。

<lifecycle-flow>



</lifecycle-flow>

`definePlugin()` 是 evlog 的**规范扩展点**。Drain 和 Enricher 是插件的特殊情况，但单个插件可以同时接入多个钩子，这正适合任何混合多种职责的复杂扩展（例如，在每个事件上进行 enrich＋在 drain 时执行副作用＋在尾部采样时做保留决策，并且所有操作都读取同一份共享状态）。

当扩展只做一件事时，优先使用单一用途的 [`enricherPlugin()`](https://evlog.zhcndoc.com/extend/custom-enrichers) / [`drainPlugin()`](https://evlog.zhcndoc.com/extend/custom-drains) 包装器。只有当多个钩子共享状态时，才使用 `definePlugin`。

<prompt :actions="["copy","cursor","claude"]" description="构建一个多钩子的 evlog 插件" icon="i-lucide-blocks">

构建一个会挂钩到多个生命周期阶段的 evlog 插件。

- 从 `evlog` 导入 `definePlugin`，并选择我需要的钩子（`onRequestStart`、`enrich`、`drain`、`extendLogger`、`keep`、`onClientLog`、`onRequestFinish`）
- 保持 `enrich` 纯粹（无 I/O、不抛出错误，并在内部使用 try/catch）
- 对于 drain，进行批处理并发送到目标位置；如果 drain 速度较慢，请遵守背压机制
- 通过框架配置注册插件：Next/standalone 使用 `initLogger({ plugins: [myPlugin] })`，Hono/Express/Fastify/Elysia 使用中间件选项 `{ plugins: [myPlugin] }`。对于 Nitro，直接注册各个钩子（`nitroApp.hooks.hook('evlog:enrich' | 'evlog:drain' | …)`）
- 对于简单扩展，优先使用单一用途的 `enricherPlugin()` / `drainPlugin()` 包装器；只有在需要多个钩子时才使用 `definePlugin`

文档：[https://www.evlog.dev/extend/plugins](https://www.evlog.dev/extend/plugins)

</prompt>

## 最小示例

```ts
import { definePlugin } from 'evlog'

export const tenantPlugin = definePlugin({
  name: 'tenant',
  onRequestStart({ logger, headers }) {
    const tenantId = headers?.['x-tenant-id']
    if (tenantId) logger.set({ tenant: { id: tenantId } })
  },
  enrich({ event }) {
    event.region = process.env.REGION
  },
})
```

在你引导 evlog 的地方注册该插件。具体形态取决于你的运行时：

<framework-tabs>

```ts [Nuxt]server/plugins/evlog.ts
import { tenantPlugin } from './plugins/tenant'

export default defineNitroPlugin((nitroApp) => {
  nitroApp.hooks.hook('evlog:enrich', tenantPlugin.enrich!)
})
```

```ts [Nitro]server/plugins/evlog.ts
import { definePlugin } from 'nitro'
import { tenantPlugin } from './plugins/tenant'

export default definePlugin((nitroApp) => {
  nitroApp.hooks.hook('evlog:enrich', tenantPlugin.enrich!)
})
```

```ts [Next.js]lib/evlog.ts
import { createEvlog } from 'evlog/next'
import { tenantPlugin } from './plugins/tenant'

export const { withEvlog, useLogger, log, createError } = createEvlog({
  service: 'my-app',
  plugins: [tenantPlugin],
})
```

```ts [SvelteKit]src/hooks.server.ts
import { createEvlogHooks } from 'evlog/sveltekit'
import { tenantPlugin } from './plugins/tenant'

export const { handle, handleError } = createEvlogHooks({ plugins: [tenantPlugin] })
```

```ts [TanStack Start]server/plugins/evlog.ts
import { definePlugin } from 'nitro'
import { tenantPlugin } from './plugins/tenant'

export default definePlugin((nitroApp) => {
  nitroApp.hooks.hook('evlog:enrich', tenantPlugin.enrich!)
})
```

```ts [React Router]app/root.tsx
import { evlog } from 'evlog/react-router'
import { tenantPlugin } from './plugins/tenant'

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

```ts [Hono]src/index.ts
import { evlog } from 'evlog/hono'
import { tenantPlugin } from './plugins/tenant'

app.use(evlog({ plugins: [tenantPlugin] }))
```

```ts [Express]src/index.ts
import { evlog } from 'evlog/express'
import { tenantPlugin } from './plugins/tenant'

app.use(evlog({ plugins: [tenantPlugin] }))
```

```ts [Fastify]src/index.ts
import { evlog } from 'evlog/fastify'
import { tenantPlugin } from './plugins/tenant'

await app.register(evlog, { plugins: [tenantPlugin] })
```

```ts [Elysia]src/index.ts
import { evlog } from 'evlog/elysia'
import { tenantPlugin } from './plugins/tenant'

app.use(evlog({ plugins: [tenantPlugin] }))
```

```ts [NestJS]src/app.module.ts
import { EvlogModule } from 'evlog/nestjs'
import { tenantPlugin } from './plugins/tenant'

EvlogModule.forRoot({ plugins: [tenantPlugin] })
```

```ts [oRPC]server/orpc.ts
import { withEvlog } from 'evlog/orpc'
import { tenantPlugin } from './plugins/tenant'

const handler = withEvlog(new RPCHandler(router), { plugins: [tenantPlugin] })
```

```ts [Cloudflare Workers]src/worker.ts
import { withEvlog } from 'evlog/workers'
import { tenantPlugin } from './plugins/tenant'

export default withEvlog(handler, { plugins: [tenantPlugin] })
```

```ts [Standalone]src/index.ts
import { initLogger } from 'evlog'
import { tenantPlugin } from './plugins/tenant'

initLogger({ plugins: [tenantPlugin] })
```

</framework-tabs>

## 钩子

<table>
<thead>
  <tr>
    <th>
      钩子
    </th>
    
    <th>
      何时
    </th>
    
    <th>
      用途
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        setup(ctx)
      </code>
    </td>
    
    <td>
      注册时只执行一次
    </td>
    
    <td>
      读取 <code>
        env
      </code>
      
      ，建立共享状态
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        onRequestStart(ctx)
      </code>
    </td>
    
    <td>
      每个请求，在任何处理程序运行之前
    </td>
    
    <td>
      将请求头中的值提取到 <code>
        logger
      </code>
      
       中
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        enrich(ctx)
      </code>
    </td>
    
    <td>
      每个事件，在 drain 之前
    </td>
    
    <td>
      添加派生字段（地理位置、部署 ID……）
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        keep(ctx)
      </code>
    </td>
    
    <td>
      尾部采样决策
    </td>
    
    <td>
      基于结果强制保留（<code>
        status >= 400
      </code>
      
      、<code>
        duration > 500
      </code>
      
      ，……）
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        drain(ctx)
      </code>
    </td>
    
    <td>
      每个已发出的事件
    </td>
    
    <td>
      副作用：告警、镜像到队列等
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        onRequestFinish(ctx)
      </code>
    </td>
    
    <td>
      响应后
    </td>
    
    <td>
      每请求后的后处理
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        onClientLog(ctx)
      </code>
    </td>
    
    <td>
      浏览器提交的事件命中摄取端点时
    </td>
    
    <td>
      观察 / 拒绝客户端流量
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        extendLogger(logger)
      </code>
    </td>
    
    <td>
      每个请求
    </td>
    
    <td>
      添加自定义方法（例如 <code>
        logger.audit.refund()
      </code>
      
      ）
    </td>
  </tr>
</tbody>
</table>

每个钩子都是**可选的**。插件可以实现任意子集。完整类型位于 [`packages/evlog/src/shared/plugin.ts`](https://github.com/evloghq/evlog/blob/main/packages/evlog/src/shared/plugin.ts)。

## 一个多钩子示例

当多个职责共享状态时，插件就会大放异彩。这里，单个 `request-metrics` 插件通过 `setup`、`onRequestStart` 和 `drain` 跟踪每个请求的时序：

```ts
import { definePlugin } from 'evlog/toolkit'

export const requestMetricsPlugin = definePlugin({
  name: 'request-metrics',

  setup({ env }) {
    statsd.init({ service: env.service })
  },

  enrich({ event }) {
    if (event.durationMs !== undefined) event.tier = event.durationMs > 1000 ? 'slow' : 'fast'
  },

  drain({ event }) {
    if (event.durationMs !== undefined) {
      statsd.timing('http.request', event.durationMs, { path: event.path as string })
    }
  },

  onRequestStart({ logger, request }) {
    logger.set({ trace: { startedAt: Date.now() } })
  },

  onRequestFinish({ event, durationMs }) {
    if (event && (event.level === 'error' || durationMs > 5000)) {
      // 告警 / 转发 / 等等。
    }
  },
})
```

## 便捷插件

对于单钩子扩展，工具包提供了 `drainPlugin()` 和 `enricherPlugin()` 包装器：

```typescript
import { drainPlugin, enricherPlugin } from 'evlog/toolkit'

const drainOnly = drainPlugin('axiom', createAxiomDrain())
const enricherOnly = enricherPlugin('user-agent', createUserAgentEnricher())
```

当意图很明显时，这些写法等价于 `definePlugin({ name, drain | enrich })` 的形式，但可读性更强。

## 常见陷阱

- **不要从钩子中抛出错误。** 插件运行器会捕获错误，并使用插件名称记录日志，但 `enrich` 中抛出的错误不会将事件继续传递到下游。请让钩子具备防御性。
- **drain 会为每个事件运行**：而不只是每个请求运行。如果你只关注每请求的生命周期，请改用 `onRequestFinish`。
- **extendLogger 会修改 logger 对象**：在 `.d.ts` 中扩展 `RequestLogger`，这样 `useLogger(event)` 才会让新方法对 TypeScript 可用。请参阅[类型化字段](https://evlog.zhcndoc.com/learn/typed-fields)。
- **插件会根据 name 去重**。使用相同的 `name` 重新注册时，会替换之前的版本（后注册的版本优先）。

## 下一步

- [自定义 Enricher](https://evlog.zhcndoc.com/extend/custom-enrichers)：单钩子 enrich
- [自定义 Drain](https://evlog.zhcndoc.com/extend/custom-drains)：单目标输出
- [尾部采样](https://evlog.zhcndoc.com/extend/tail-sampling)：基于结果的保留决策
- [身份标识请求头](https://evlog.zhcndoc.com/extend/identity-headers)：为每个 drain 请求添加标签。

---

- [自定义 Enricher](https://evlog.zhcndoc.com/extend/custom-enrichers)
- [自定义 Drain](https://evlog.zhcndoc.com/extend/custom-drains)
- [尾部采样](https://evlog.zhcndoc.com/extend/tail-sampling)


## Sitemap

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