Build on top
Tail sampling
Decide post-hoc whether to keep an event based on the request outcome — keep all errors, keep slow requests, keep specific paths, drop the rest.
Tail sampling is a decision made after the request runs, with full knowledge of its outcome (status, duration, errors, custom flags). It's how you keep all errors and slow requests while throwing away the bulk of healthy traffic — the opposite of head sampling, which decides up front before knowing what happens.
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
Configure tail sampling on evlog
Canonical guide
Full reference at Sampling, section "Tail sampling":
- Built-in
keeprules (status,duration,path) - Custom predicates via
evlog:emit:keephook - Combining head + tail sampling
This page exists in the build-on-top section as a pointer — same content, classified by axis.
When to write a custom keep hook
The built-in declarative keep rules cover the typical cases. Drop to a custom hook when you need :
- Conditional logic on more than one field (e.g. "keep if
status >= 500ANDuser.plan === 'enterprise'") - Keep based on a derived value (e.g. "keep if
event.audit?.context.actor.role === 'admin'") - Stateful decisions (rare; needs care since sampling runs in the hot path)
nitroApp.hooks.hook('evlog:emit:keep', (ctx) => {
if (ctx.context.user?.plan === 'enterprise' && ctx.status >= 500) {
ctx.shouldKeep = true
}
})
Or as a plugin (preferred for non-trivial logic):
import { definePlugin } from 'evlog/toolkit'
definePlugin({
name: 'keep-enterprise-errors',
keep(ctx) {
if (ctx.context.user?.plan === 'enterprise' && ctx.status >= 500) {
ctx.shouldKeep = true
}
},
})