Scenarios
Tenant-aware logging
Every wide event automatically carries the right tenant id, drawn from the request — no per-call-site setup, no risk of forgetting.
The problem
You run a multi-tenant app. Every request is "for" a specific tenant (header, JWT claim, subdomain). You want every wide event from a tenant's request to carry their id, so you can:
- Filter logs by tenant when debugging a customer issue
- Group analytics by tenant
- Apply tail sampling differently per tier (
enterprisealways kept,freeheavily sampled)
You don't want to add logger.set({ tenant: { id } }) at every handler — error-prone.
The full code
1. The plugin
A single plugin handles three concerns: extract the id, attach it on onRequestStart, force-keep enterprise errors via tail sampling.
server/plugins/evlog-tenant.ts
import { definePlugin, getGlobalPluginRunner } from 'evlog/toolkit'
interface TenantPlanMap {
[id: string]: 'free' | 'pro' | 'enterprise'
}
// In a real app this would come from a cache / DB lookup.
// Keeping it inline for the recipe.
const TENANT_PLANS: TenantPlanMap = {
acme: 'enterprise',
pied_piper: 'pro',
}
export default definePlugin({
name: 'tenant',
onRequestStart({ logger, headers }) {
const tenantId = headers?.['x-tenant-id']
if (!tenantId) return
const plan = TENANT_PLANS[tenantId] ?? 'free'
logger.set({ tenant: { id: tenantId, plan } })
},
keep(ctx) {
// Always keep errors from enterprise tenants
if (ctx.context.tenant?.plan === 'enterprise' && ctx.status >= 500) {
ctx.shouldKeep = true
}
},
})
// Register on app boot
getGlobalPluginRunner().add(plugin)
2. Type augmentation (so logger.set({ tenant }) autocompletes)
types/evlog.d.ts
import 'evlog'
declare module 'evlog' {
interface BaseWideEvent {
tenant?: {
id: string
plan: 'free' | 'pro' | 'enterprise'
}
}
}
3. Adjust sampling in your evlog config
nuxt.config.ts
evlog: {
sampling: {
rates: { info: 5 }, // aggressive: 5% of info events
keep: [{ status: 400 }], // keep 4xx + 5xx (the plugin force-keeps enterprise 5xx)
},
}
That's it. Every wide event from a request with x-tenant-id: acme carries tenant: { id: 'acme', plan: 'enterprise' }, and 5xx from enterprise tenants are always recorded regardless of sampling.
What it gives you
- Zero per-call-site code — handlers just call
logger.error(err)or whatever, the tenant context rides along automatically - Type-safe —
logger.set({ tenant: { ... } })autocompletes the right shape - Compatible with sampling — heavy sampling for free tier, full retention for enterprise errors
Variants
- JWT claim instead of header — read
getRequestHeader('authorization')inonRequestStart, decode, extract claim - Subdomain-based tenant — read
getRequestHost(), parse the leftmost label - Resolved against a DB — make
onRequestStartasync (note: blocks the request until resolved; cache aggressively)
Where to go next
- Plugins — the canonical contract used here
- Tail sampling — the
keephook in detail - Enrichers as packages — publish this as
@my-org/evlog-tenant-enricherfor use across services