sampling decision· tail → head
tail rules: status ≥ 400 duration ≥ 1000 path: /api/payments/**
#1POST/api/users·200·45ms
dropped
#2POST/api/users·500·45ms
force-keptkept
#3GET/api/products·200·2300ms
force-keptkept
#4POST/api/payments/charge·200·120ms
force-keptkept
#5POST/api/checkout·200·120ms
head (0.07)kept
#6GET/api/health·200·12ms
dropped
tail-kept0 / 0
head-kept0 / 0
dropped0
尾部采样是在请求执行之后做出的决定,并且对其结果(状态、持续时间、错误、自定义标志)有完整了解。这就是你保留所有错误和慢请求,同时丢弃大部分健康流量的方式——与头部采样相反,头部采样是在事先还不知道会发生什么时就做出决定。
完整的理论和配置参考——内置 keep 规则、通过 evlog:emit:keep 的自定义谓词、头部 + 尾部采样的组合——请参见 采样。本页介绍的是扩展接口:如何将你自己的保留逻辑接入管道。
在 evlog 上配置尾部采样
当内置规则还不够用时
内置的声明式 keep 规则覆盖了典型场景(状态码阈值、持续时间阈值、路径匹配、级别匹配)。当你需要以下能力时,请切换到自定义钩子:
- 对多个字段进行条件逻辑判断(例如:“如果
status >= 500且user.plan === 'enterprise',则保留”) - 基于派生值保留(例如:“如果
event.audit?.context.actor.role === 'admin',则保留”) - 有状态的决策(较少见;需要谨慎,因为采样运行在热路径上)
自定义保留钩子
无论框架如何,钩子签名都是相同的。具体接线方式取决于你的运行时。
nitroApp.hooks.hook('evlog:emit:keep', (ctx) => {
if (ctx.context.user?.plan === 'enterprise' && ctx.status >= 500) {
ctx.shouldKeep = true
}
})
import { definePlugin } from 'evlog/toolkit'
export const keepEnterpriseErrors = definePlugin({
name: 'keep-enterprise-errors',
keep(ctx) {
if (ctx.context.user?.plan === 'enterprise' && ctx.status >= 500) {
ctx.shouldKeep = true
}
},
})
// 然后:initLogger({ plugins: [keepEnterpriseErrors] })
// 或:app.use(evlog({ plugins: [keepEnterpriseErrors] }))
对于非平凡逻辑,优先使用插件形式——它会与 evlog 的其他配置(drains、enrichers)一起携带,并且可在不同框架之间复用。
组合多个保留谓词
使用 evlog/toolkit 中的 composeKeep 将多个谓词组合成一个钩子。每个谓词独立运行,如果其中任意一个将最终的 shouldKeep 设为 true,则结果为 true:
import { composeKeep } from 'evlog/toolkit'
const keep = composeKeep([
({ duration, shouldKeep }) => duration && duration > 2000 ? true : shouldKeep,
({ event }) => event.level === 'error',
({ context, status }) => context.user?.plan === 'enterprise' && status >= 500,
])
单个谓词中的错误是隔离的(会以 [evlog/keep] 前缀记录),因此有缺陷的谓词不会在不知不觉中丢弃合法事件。
你将收到什么
保留钩子会接收一个 TailSamplingContext:
interface TailSamplingContext {
/** 事件级别(debug | info | warn | error) */
level: string
/** HTTP 响应状态(如果已知) */
status?: number
/** 请求持续时间(毫秒),如果已测量 */
duration?: number
/** 完整的累计上下文(所有 log.set 的内容) */
context: Record<string, unknown>
/** 完全丰富后的、准备排出的事件 */
event: WideEvent
/** 可变:设为 true 以强制保留此事件 */
shouldKeep: boolean
}
将 shouldKeep = true 会强制让事件通过。将 shouldKeep = false 则无效(其他谓词仍可能保留它;默认由头部采样器决定)。