在规模较大时,记录所有内容会迅速增加成本。采样可以让你在不丢失对重要内容的可观测性的同时控制成本。evlog 使用两级方法:头部采样在源头丢弃噪声,尾部采样事后恢复关键事件。
头部采样
头部采样按每个级别随机保留一定百分比的日志。它在请求完成之前运行,就像在发射时抛硬币一样。
export default defineNuxtConfig({
modules: ['evlog/nuxt'],
evlog: {
sampling: {
rates: {
info: 10, // 保留 10% 的 info 日志
warn: 50, // 保留 50% 的警告
debug: 0, // 丢弃所有 debug 日志
error: 100, // 始终保留错误(默认)
},
},
},
})
import { createEvlog } from 'evlog/next'
export const { withEvlog, useLogger } = createEvlog({
service: 'my-app',
sampling: {
rates: {
info: 10,
warn: 50,
debug: 0,
error: 100,
},
},
})
import { initLogger } from 'evlog'
initLogger({
env: { service: 'my-app' },
sampling: {
rates: {
info: 10,
warn: 50,
debug: 0,
error: 100,
},
},
})
每个级别都是 0 到 100 的百分比。你未配置的级别默认保留 100%(保留所有内容)。即使配置了其他级别,错误默认也保留 100%,因此必须显式设置 error: 0 才能丢弃错误。
头部采样是随机的。
10% 的比率意味着大约每 10 条 info 日志保留 1 条,而不是精确的每 10 条保留 1 条。尾部采样
头部采样是盲目的:它不知道请求是否变慢、失败或命中了关键路径。尾部采样通过在请求完成后评估条件来修复这一点,并强制保留匹配特定条件的日志。
nuxt.config.ts
// 采样配置,在所有框架中工作方式相同
evlog: {
sampling: {
rates: { info: 10 },
keep: [
{ status: 400 }, // HTTP 状态 >= 400
{ duration: 1000 }, // 请求耗时 >= 1 秒
{ path: '/api/payments/**' }, // 关键路径(通配符)
],
},
}
条件对 status 和 duration 使用 >= 比较,对 path 使用通配符匹配。如果任意条件匹配,无论头部采样如何都会保留日志(OR 逻辑)。
可用条件
| 条件 | 类型 | 描述 |
|---|---|---|
status | number | 如果 HTTP 状态 >= 值则保留(例如 400 会捕获所有 4xx 和 5xx) |
duration | number | 如果请求耗时 >= 指定毫秒数则保留 |
path | string | 如果请求路径匹配通配符模式(例如 '/api/critical/**')则保留 |
它们如何协同工作
这两个级别相互补充:
- 请求完成 - evlog 已知状态、耗时和路径
- 尾部采样评估 - 如果任何
keep条件匹配,则强制保留日志 - 头部采样应用 - 仅当尾部采样未强制保留时,才会执行随机百分比检查
- 日志发出或丢弃 - 保留的日志按正常流程进行增强和传输
这意味着对 /api/payments/charge 的请求如果返回 500 且耗时 2 秒,无论 info 设置为 1% 都会被记录,尾部条件会将其保留。
sampling: {
rates: { info: 10 },
keep: [
{ status: 400 },
{ duration: 1000 },
],
}
POST /api/users 200 45ms → 10% 机会(头部采样)
POST /api/users 500 45ms → 总是保留(状态 >= 400)
GET /api/products 200 2300ms → 总是保留(耗时 >= 1000)
POST /api/checkout 200 120ms → 10% 机会(头部采样)
自定义尾部采样
对于超出状态、耗时和路径的条件,在 Nuxt/Nitro 中使用 evlog:emit:keep 钩子,在其他框架中使用 keep 回调。
export default defineNitroPlugin((nitroApp) => {
nitroApp.hooks.hook('evlog:emit:keep', (ctx) => {
if (ctx.context.user?.plan === 'enterprise') {
ctx.shouldKeep = true
}
})
})
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
}
},
})
import { evlog } from 'evlog/hono'
app.use(evlog({
keep(ctx) {
if (ctx.context.user?.plan === 'enterprise') {
ctx.shouldKeep = true
}
},
}))
ctx 对象包含:
| 字段 | 类型 | 描述 |
|---|---|---|
status | number | undefined | HTTP 响应状态 |
duration | number | undefined | 请求耗时(毫秒) |
path | string | undefined | 请求路径 |
method | string | undefined | HTTP 方法 |
context | Record<string, unknown> | 通过 log.set() 设置的所有字段 |
shouldKeep | boolean | 设置为 true 以强制保留 |
生产环境示例
平衡成本与可见性的典型生产配置:
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/**' },
],
},
},
},
})
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/**' },
],
},
})
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/**' },
],
},
})
在 Nuxt 中,使用
$production 覆盖配置可以在开发环境中保留完整日志,在生产环境中启用采样。在其他框架中,请使用自己的环境检查或配置系统。