---
title: "采样"
description: "两级采样：头部采样按级别丢弃噪声，尾部采样在事件结果已知后保留事件。"
canonical_url: "https://evlog.zhcndoc.com/learn/sampling"
---
# 采样

> 两级采样：头部采样按级别丢弃噪声，尾部采样在事件结果已知后保留事件。

在规模较大时，记录所有内容会迅速增加成本。采样可以让你在不丢失对重要内容的可观测性的同时控制成本。evlog 使用两级方法：头部采样在源头丢弃噪声，尾部采样事后恢复关键事件。

<prompt :actions="["copy","cursor","claude"]" description="启用头部和尾部采样" icon="i-lucide-filter">

在我的 evlog 生产配置中启用头部和尾部采样。

- 识别我的框架并定位 evlog 配置（nuxt.config.ts、lib/evlog.ts、initLogger 等）
- 配置每个级别的 sampling.rates：{ info: 10, warn: 50, debug: 0, error: 100 } 作为起点
- 添加 sampling.keep 规则以强制保留关键事件：<span>

{ status: 400 }, { duration: 1000 }, { path: '/api/critical/**' }

</span>
- 对于业务特定的保留逻辑（例如付费用户），添加自定义 keep 回调或 evlog:emit:keep 钩子
- 将采样包裹在 $production 覆盖中，以便开发环境保留完整日志
- 确认除非我明确设置 error: 0，否则错误默认总是保留

文档：[https://www.evlog.dev/learn/sampling](https://www.evlog.dev/learn/sampling)
最佳实践：[https://www.evlog.dev/reference/best-practices](https://www.evlog.dev/reference/best-practices)

</prompt>

## 在构建之前丢弃噪声

头部采样按每个级别随机保留一定百分比的日志。它在**请求完成之前**运行，就像在发射时抛硬币一样。

<head-sampling-plinko>



</head-sampling-plinko>

<framework-tabs>

```typescript [Nuxt]nuxt.config.ts
export default defineNuxtConfig({
  modules: ['evlog/nuxt'],
  evlog: {
    sampling: {
      rates: {
        info: 10,    // 保留 10% 的 info 日志
        warn: 50,    // 保留 50% 的警告
        debug: 0,    // 丢弃所有 debug 日志
        error: 100,  // 始终保留错误（默认）
      },
    },
  },
})
```

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

export const { withEvlog, useLogger } = createEvlog({
  service: 'my-app',
  sampling: {
    rates: {
      info: 10,
      warn: 50,
      debug: 0,
      error: 100,
    },
  },
})
```

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

initLogger({
  env: { service: 'my-app' },
  sampling: {
    rates: {
      info: 10,
      warn: 50,
      debug: 0,
      error: 100,
    },
  },
})
```

</framework-tabs>

每个级别都是 0 到 100 的百分比。你未配置的级别默认保留 100%（保留所有内容）。即使配置了其他级别，错误默认也保留 100%，因此必须显式设置 `error: 0` 才能丢弃错误。

相同的速率也适用于通过[客户端接收端点](https://evlog.zhcndoc.com/integrate/frameworks/nuxt#client-transport)收到的浏览器日志：被头部采样丢弃的客户端事件会在 enrich 和 drain 之前被丢弃。尾部采样不适用于客户端事件，因为它们不携带状态或耗时。

<callout color="info" icon="i-lucide-info">

头部采样是随机的。`10%` 的比率意味着大约每 10 条 info 日志保留 1 条，而不是精确的每 10 条保留 1 条。

</callout>

## 知道结果后保留事件

头部采样是盲目的：它不知道请求是否变慢、失败或命中了关键路径。尾部采样通过在请求完成后评估条件来修复这一点，并强制保留匹配特定条件的日志。

```typescript [nuxt.config.ts]
// 采样配置，在所有框架中工作方式相同
evlog: {
  sampling: {
    rates: { info: 10 },
    keep: [
      { status: 400 },              // HTTP 状态 >= 400
      { duration: 1000 },           // 请求耗时 >= 1 秒
      { path: '/api/payments/**' }, // 关键路径（通配符）
    ],
  },
}
```

条件对 `status` 和 `duration` 使用 **>=** 比较，对 `path` 使用通配符匹配。如果**任意**条件匹配，无论头部采样如何都会保留日志（OR 逻辑）。

### 可匹配的条件

<table>
<thead>
  <tr>
    <th>
      条件
    </th>
    
    <th>
      类型
    </th>
    
    <th>
      描述
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        status
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      如果 HTTP 状态 >= 值则保留（例如 <code>
        400
      </code>
      
       会捕获所有 4xx 和 5xx）
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        duration
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      如果请求耗时 >= 指定毫秒数则保留
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        path
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      如果请求路径匹配通配符模式（例如 <code>
        '/api/critical/**'
      </code>
      
      ）则保留
    </td>
  </tr>
</tbody>
</table>

## 结合头部和尾部采样

这两个级别相互补充：

1. **请求完成** - evlog 已知状态、耗时和路径
2. **尾部采样评估** - 如果任何 `keep` 条件匹配，则强制保留日志
3. **头部采样应用** - 仅当尾部采样未强制保留时，才会执行随机百分比检查
4. **日志发出或丢弃** - 保留的日志按正常流程进行增强和传输

这意味着对 `/api/payments/charge` 的请求如果返回 500 且耗时 2 秒，无论 `info` 设置为 1% 都会被记录，尾部条件会将其保留。

<tail-sample-decision>



</tail-sample-decision>

<code-group>

```typescript [配置]
sampling: {
  rates: { info: 10 },
  keep: [
    { status: 400 },
    { duration: 1000 },
  ],
}
```

```bash [记录内容]
POST /api/users     200  45ms   → 10% 机会（头部采样）
POST /api/users     500  45ms   → 总是保留（状态 >= 400）
GET  /api/products  200  2300ms → 总是保留（耗时 >= 1000）
POST /api/checkout  200  120ms  → 10% 机会（头部采样）
```

</code-group>

## 编写自己的保留规则

对于超出状态、耗时和路径的条件，在 Nuxt/Nitro 中使用 `evlog:emit:keep` 钩子，在其他框架中使用 `keep` 回调。

<framework-tabs>

```typescript [Nuxt]server/plugins/sampling.ts
export default defineNitroPlugin((nitroApp) => {
  nitroApp.hooks.hook('evlog:emit:keep', (ctx) => {
    if (ctx.context.user?.plan === 'enterprise') {
      ctx.shouldKeep = true
    }
  })
})
```

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

export const { withEvlog, useLogger } = createEvlog({
  service: 'my-app',
  sampling: {
    rates: { info: 10 },
    keep: [{ status: 400 }],
  },
  keep(ctx) {
    if (ctx.context.user?.plan === 'enterprise') {
      ctx.shouldKeep = true
    }
  },
})
```

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

export const { handle, handleError } = createEvlogHooks({
  keep(ctx) {
    if (ctx.context.user?.plan === 'enterprise') {
      ctx.shouldKeep = true
    }
  },
})
```

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

export default definePlugin((nitroApp) => {
  nitroApp.hooks.hook('evlog:emit:keep', (ctx) => {
    if (ctx.context.user?.plan === 'enterprise') {
      ctx.shouldKeep = true
    }
  })
})
```

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

export const middleware: Route.MiddlewareFunction[] = [
  evlog({
    keep(ctx) {
      if (ctx.context.user?.plan === 'enterprise') {
        ctx.shouldKeep = true
      }
    },
  }),
]
```

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

app.use(evlog({
  keep(ctx) {
    if (ctx.context.user?.plan === 'enterprise') {
      ctx.shouldKeep = true
    }
  },
}))
```

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

app.use(evlog({
  keep(ctx) {
    if (ctx.context.user?.plan === 'enterprise') {
      ctx.shouldKeep = true
    }
  },
}))
```

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

await app.register(evlog, {
  keep(ctx) {
    if (ctx.context.user?.plan === 'enterprise') {
      ctx.shouldKeep = true
    }
  },
})
```

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

app.use(evlog({
  keep(ctx) {
    if (ctx.context.user?.plan === 'enterprise') {
      ctx.shouldKeep = true
    }
  },
}))
```

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

EvlogModule.forRoot({
  keep(ctx) {
    if (ctx.context.user?.plan === 'enterprise') {
      ctx.shouldKeep = true
    }
  },
})
```

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

const handler = withEvlog(new RPCHandler(router), {
  keep(ctx) {
    if (ctx.context.user?.plan === 'enterprise') {
      ctx.shouldKeep = true
    }
  },
})
```

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

export default withEvlog(handler, {
  keep(ctx) {
    if (ctx.context.user?.plan === 'enterprise') {
      ctx.shouldKeep = true
    }
  },
})
```

</framework-tabs>

`ctx` 对象包含：

<table>
<thead>
  <tr>
    <th>
      字段
    </th>
    
    <th>
      类型
    </th>
    
    <th>
      描述
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        status
      </code>
    </td>
    
    <td>
      <code>
        number | undefined
      </code>
    </td>
    
    <td>
      HTTP 响应状态
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        duration
      </code>
    </td>
    
    <td>
      <code>
        number | undefined
      </code>
    </td>
    
    <td>
      请求耗时（毫秒）
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        path
      </code>
    </td>
    
    <td>
      <code>
        string | undefined
      </code>
    </td>
    
    <td>
      请求路径
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        method
      </code>
    </td>
    
    <td>
      <code>
        string | undefined
      </code>
    </td>
    
    <td>
      HTTP 方法
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        context
      </code>
    </td>
    
    <td>
      <code>
        Record<string, unknown>
      </code>
    </td>
    
    <td>
      通过 <code>
        log.set()
      </code>
      
       设置的所有字段
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        shouldKeep
      </code>
    </td>
    
    <td>
      <code>
        boolean
      </code>
    </td>
    
    <td>
      设置为 <code>
        true
      </code>
      
       以强制保留
    </td>
  </tr>
</tbody>
</table>

## 可投入生产的配置

平衡成本与可见性的典型生产配置：

<framework-tabs>

```typescript [Nuxt]nuxt.config.ts
export default defineNuxtConfig({
  modules: ['evlog/nuxt'],
  evlog: {
    env: { service: 'my-app' },
  },
  $production: {
    evlog: {
      sampling: {
        rates: {
          info: 10,
          warn: 50,
          debug: 0,
          error: 100,
        },
        keep: [
          { status: 400 },
          { duration: 1000 },
          { path: '/api/payments/**' },
          { path: '/api/auth/**' },
        ],
      },
    },
  },
})
```

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

export const { withEvlog, useLogger } = createEvlog({
  service: 'my-app',
  sampling: {
    rates: {
      info: 10,
      warn: 50,
      debug: 0,
      error: 100,
    },
    keep: [
      { status: 400 },
      { duration: 1000 },
      { path: '/api/payments/**' },
      { path: '/api/auth/**' },
    ],
  },
})
```

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

initLogger({
  env: { service: 'my-app' },
  sampling: {
    rates: {
      info: 10,
      warn: 50,
      debug: 0,
      error: 100,
    },
    keep: [
      { status: 400 },
      { duration: 1000 },
      { path: '/api/payments/**' },
      { path: '/api/auth/**' },
    ],
  },
})
```

</framework-tabs>

<callout color="warning" icon="i-lucide-lightbulb">

在 Nuxt 中，使用 `$production` 覆盖配置可以在开发环境中保留完整日志，在生产环境中启用采样。在其他框架中，请使用自己的环境检查或配置系统。

</callout>

## 下一步

- [最佳实践](https://evlog.zhcndoc.com/reference/best-practices) - 安全和生产检查清单
- [宽事件](https://evlog.zhcndoc.com/learn/wide-events) - 设计有效的宽事件。

---

- [最佳实践](https://evlog.zhcndoc.com/reference/best-practices)
- [宽事件](https://evlog.zhcndoc.com/learn/wide-events)


## Sitemap

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