Custom Enrichers
Write custom enrichers to add any derived context to your wide events. The recommended way is defineEnricher from evlog/toolkit — provide a single compute() function returning the value you want to merge into the event, and the toolkit handles error isolation, undefined skipping, and the merge step.
Write a custom evlog enricher
Basic Example
Add deployment metadata to every event. The enricher is the same function everywhere — only the wiring step differs per framework.
// server/plugins/evlog-enrich.ts
export default defineNitroPlugin((nitroApp) => {
nitroApp.hooks.hook('evlog:enrich', (ctx) => {
ctx.event.deploymentId = process.env.DEPLOYMENT_ID
ctx.event.deployedBy = process.env.DEPLOYED_BY
})
})
// lib/evlog.ts
import { createEvlog } from 'evlog/next'
export const { withEvlog, useLogger, log, createError } = createEvlog({
service: 'my-app',
enrich: (ctx) => {
ctx.event.deploymentId = process.env.DEPLOYMENT_ID
ctx.event.deployedBy = process.env.DEPLOYED_BY
},
})
import type { EnrichContext } from 'evlog'
const deployment = (ctx: EnrichContext) => {
ctx.event.deploymentId = process.env.DEPLOYMENT_ID
ctx.event.deployedBy = process.env.DEPLOYED_BY
}
app.use(evlog({ enrichers: [deployment] })) // Hono / Express / Elysia
// await app.register(evlog, { enrichers: [deployment] }) // Fastify
// EvlogModule.forRoot({ enrichers: [deployment] }) // NestJS
// index.ts
import type { EnrichContext } from 'evlog'
import { initLogger } from 'evlog'
const deployment = (ctx: EnrichContext) => {
ctx.event.deploymentId = process.env.DEPLOYMENT_ID
ctx.event.deployedBy = process.env.DEPLOYED_BY
}
initLogger({ enrichers: [deployment] })
EnrichContext
The evlog:enrich hook receives an EnrichContext:
interface EnrichContext {
/** The emitted wide event (mutable) */
event: WideEvent
/** Request metadata */
request?: {
method?: string
path?: string
requestId?: string
}
/** Safe HTTP request headers (sensitive headers filtered out) */
headers?: Record<string, string>
/** Response metadata */
response?: {
status?: number
headers?: Record<string, string>
}
}
Recommended pattern — defineEnricher
Every built-in enricher uses this same factory. Provide compute() and you're done:
import { defineEnricher, getHeader, type EnricherOptions } from 'evlog/toolkit'
interface TenantInfo {
id: string
org?: string
}
export function createTenantEnricher(options: EnricherOptions & { headerName?: string } = {}) {
const headerName = options.headerName ?? 'x-tenant-id'
return defineEnricher<TenantInfo>({
name: 'tenant',
field: 'tenant',
compute: ({ headers }) => {
const id = getHeader(headers, headerName)
if (!id) return undefined
return { id }
},
}, options)
}
defineEnricher automatically:
- skips when
compute()returnsundefined - merges the result into
ctx.event[field]viamergeEventField(respectingoptions.overwrite) - catches errors and logs them as
[evlog/<name>]instead of breaking the pipeline
// server/plugins/evlog-enrich.ts
import { createTenantEnricher } from '~/server/utils/enrichers'
export default defineNitroPlugin((nitroApp) => {
const enrichTenant = createTenantEnricher({ headerName: 'x-org-id' })
nitroApp.hooks.hook('evlog:enrich', enrichTenant)
})
// lib/evlog.ts
import { createEvlog } from 'evlog/next'
import { createTenantEnricher } from './enrichers'
const enrichTenant = createTenantEnricher({ headerName: 'x-org-id' })
export const { withEvlog, useLogger, log, createError } = createEvlog({
service: 'my-app',
enrich: enrichTenant,
})
import { createTenantEnricher } from './enrichers'
const enrichTenant = createTenantEnricher({ headerName: 'x-org-id' })
app.use(evlog({ enrichers: [enrichTenant] }))
// await app.register(evlog, { enrichers: [enrichTenant] }) // Fastify
// EvlogModule.forRoot({ enrichers: [enrichTenant] }) // NestJS
import { initLogger } from 'evlog'
import { createTenantEnricher } from './enrichers'
initLogger({
enrichers: [createTenantEnricher({ headerName: 'x-org-id' })],
})
Combining with Built-in Enrichers
Custom and built-in enrichers compose freely — they're all just (ctx: EnrichContext) => void functions. Use composeEnrichers from evlog/toolkit to combine them into a single callable:
import { composeEnrichers, defineEnricher } from 'evlog/toolkit'
import { createDefaultEnrichers } from 'evlog/enrichers'
const region = defineEnricher({
name: 'region',
field: 'region',
compute: () => process.env.FLY_REGION ?? process.env.AWS_REGION,
})
export const enrich = composeEnrichers([
createDefaultEnrichers(), // userAgent + geo + requestSize + traceContext
region,
])
// Wire `enrich` to your framework — see the Basic Example above for tabs per framework.
More Examples
Each example below is a plain (ctx: EnrichContext) => void function — wire it the same way as the Basic Example, regardless of framework.
Feature Flags
import { defineEnricher } from 'evlog/toolkit'
export const featureFlags = defineEnricher({
name: 'feature-flags',
field: 'featureFlags',
compute: () => ({
newCheckout: isEnabled('new-checkout'),
betaApi: isEnabled('beta-api'),
}),
})
Response Time Classification
import { defineEnricher } from 'evlog/toolkit'
export const performanceTier = defineEnricher<string>({
name: 'performance-tier',
field: 'performanceTier',
compute: ({ event }) => {
const duration = event.duration as number | undefined
if (duration === undefined) return undefined
if (duration < 100) return 'fast'
if (duration < 500) return 'normal'
if (duration < 2000) return 'slow'
return 'critical'
},
})
Next Steps
- Built-in Enrichers - See all available built-in enrichers
- Adapters - Send enriched events to external services