Enrichers
Enrichers add derived context to your wide events after they are emitted, before they are printed and before they reach your drain adapters. Use them to automatically extract useful information from request headers without cluttering your application code.
All built-in enrichers are exported from evlog/enrichers. Each enricher is a factory function that returns an (ctx: EnrichContext) => void callback. To write your own, see Custom Enrichers.
Add all built-in evlog enrichers
import {
createUserAgentEnricher,
createGeoEnricher,
createRequestSizeEnricher,
createTraceContextEnricher,
} from 'evlog/enrichers'
All built-in enrichers
Use createDefaultEnrichers() to compose user agent, geo, request size, and trace context in one call:
import { createDefaultEnrichers } from 'evlog/enrichers'
const enrich = createDefaultEnrichers()
export default defineNitroPlugin((nitroApp) => {
nitroApp.hooks.hook('evlog:enrich', enrich)
})
import { definePlugin } from 'nitro'
import { createDefaultEnrichers } from 'evlog/enrichers'
const enrich = createDefaultEnrichers()
export default definePlugin((nitroApp) => {
nitroApp.hooks.hook('evlog:enrich', enrich)
})
import { createEvlog } from 'evlog/next'
import { createDefaultEnrichers } from 'evlog/enrichers'
const enrich = createDefaultEnrichers()
export const { withEvlog, useLogger, log, createError } = createEvlog({
service: 'my-app',
enrich: enrich,
})
import { createEvlogHooks } from 'evlog/sveltekit'
import { createDefaultEnrichers } from 'evlog/enrichers'
const enrich = createDefaultEnrichers()
export const { handle, handleError } = createEvlogHooks({ enrich: enrich })
import { definePlugin } from 'nitro'
import { createDefaultEnrichers } from 'evlog/enrichers'
const enrich = createDefaultEnrichers()
export default definePlugin((nitroApp) => {
nitroApp.hooks.hook('evlog:enrich', enrich)
})
import { evlog } from 'evlog/react-router'
import { createDefaultEnrichers } from 'evlog/enrichers'
const enrich = createDefaultEnrichers()
export const middleware: Route.MiddlewareFunction[] = [
evlog({ enrich: enrich }),
]
import { evlog } from 'evlog/hono'
import { createDefaultEnrichers } from 'evlog/enrichers'
const enrich = createDefaultEnrichers()
app.use(evlog({ enrich: enrich }))
import { evlog } from 'evlog/express'
import { createDefaultEnrichers } from 'evlog/enrichers'
const enrich = createDefaultEnrichers()
app.use(evlog({ enrich: enrich }))
import { evlog } from 'evlog/fastify'
import { createDefaultEnrichers } from 'evlog/enrichers'
const enrich = createDefaultEnrichers()
await app.register(evlog, { enrich: enrich })
import { evlog } from 'evlog/elysia'
import { createDefaultEnrichers } from 'evlog/enrichers'
const enrich = createDefaultEnrichers()
app.use(evlog({ enrich: enrich }))
import { EvlogModule } from 'evlog/nestjs'
import { createDefaultEnrichers } from 'evlog/enrichers'
const enrich = createDefaultEnrichers()
EvlogModule.forRoot({ enrich: enrich })
import { withEvlog } from 'evlog/orpc'
import { createDefaultEnrichers } from 'evlog/enrichers'
const enrich = createDefaultEnrichers()
const handler = withEvlog(new RPCHandler(router), { enrich: enrich })
import { withEvlog } from 'evlog/workers'
import { createDefaultEnrichers } from 'evlog/enrichers'
const enrich = createDefaultEnrichers()
export default withEvlog(handler, { enrich: enrich })
import { initLogger } from 'evlog'
import { createDefaultEnrichers } from 'evlog/enrichers'
const enrich = createDefaultEnrichers()
initLogger({ enrich: enrich })
Each enricher accepts { overwrite?: boolean } (default false) so user-set fields are preserved.
userAgentEnricher()
Parse browser, OS, and device type from the User-Agent header.
Sets: event.userAgent
const enrich = createUserAgentEnricher()
Output shape:
interface UserAgentInfo {
raw: string // Original User-Agent string
browser?: { name: string; version?: string } // Chrome, Firefox, Safari, Edge
os?: { name: string; version?: string } // Windows, macOS, iOS, Android, Linux
device?: { type: 'mobile' | 'tablet' | 'desktop' | 'bot' | 'unknown' }
}
Example output:
{
"userAgent": {
"raw": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 Chrome/120.0.0.0",
"browser": { "name": "Chrome", "version": "120.0.0.0" },
"os": { "name": "macOS", "version": "10.15.7" },
"device": { "type": "desktop" }
}
}
Detected browsers: Edge, Chrome, Firefox, Safari (checked in order, Edge before Chrome to avoid false matches).
Detected devices: Bot (crawlers, spiders), Tablet (iPad), Mobile (iPhone, Android phones), Desktop (fallback).
geoEnricher()
Extract geographic data from platform-injected headers.
Sets: event.geo
const enrich = createGeoEnricher()
Output shape:
interface GeoInfo {
country?: string // ISO country code (e.g., "US", "FR")
region?: string // Region/state name
regionCode?: string // Region code
city?: string // City name
latitude?: number // Decimal latitude
longitude?: number // Decimal longitude
}
Supported platforms:
| Platform | Headers | Coverage |
|---|---|---|
| Vercel | x-vercel-ip-country, x-vercel-ip-country-region, x-vercel-ip-city, x-vercel-ip-latitude, x-vercel-ip-longitude | Full |
| Cloudflare | cf-ipcountry | Country only |
cf-ipcountry is a standard Cloudflare HTTP header. Other geo fields (city, region, latitude, etc.) are properties of request.cf, which is not exposed as headers. For full Cloudflare geo data, write a custom enricher that reads request.cf, or use a Workers middleware to copy cf properties into custom headers.requestSizeEnricher()
Capture request and response payload sizes from Content-Length headers.
Sets: event.requestSize
const enrich = createRequestSizeEnricher()
Output shape:
interface RequestSizeInfo {
requestBytes?: number // Request Content-Length
responseBytes?: number // Response Content-Length
}
Example output:
{
"requestSize": {
"requestBytes": 1234,
"responseBytes": 5678
}
}
Content-Length header from both the request and response. If the header is missing (e.g., for chunked transfer encoding), the corresponding field will be undefined.traceContextEnricher()
Extract W3C trace context from the traceparent and tracestate headers.
Sets: event.traceContext, event.traceId, event.parentSpanId
const enrich = createTraceContextEnricher()
Output shape:
interface TraceContextInfo {
traceparent?: string // Full traceparent header value
tracestate?: string // Full tracestate header value
traceId?: string // 32-char hex trace ID (parsed from traceparent)
spanId?: string // Span of this request, copied from event.spanId when already set
parentSpanId?: string // 16-char hex span ID of the caller (parsed from traceparent)
}
Example output:
{
"traceContext": {
"traceparent": "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01",
"traceId": "4bf92f3577b34da6a3ce929d0e0e4736",
"parentSpanId": "00f067aa0ba902b7"
},
"traceId": "4bf92f3577b34da6a3ce929d0e0e4736",
"parentSpanId": "00f067aa0ba902b7"
}
traceId and parentSpanId are also set at the top level of the event for querying and correlation.
The span id in an incoming traceparent belongs to the service that made the call, not to the request being handled, so the enricher records it as parentSpanId and leaves event.spanId alone. Set event.spanId from your tracer's active span (trace.getActiveSpan()?.spanContext().spanId with OpenTelemetry) to link the event to the server span.
{version}-{traceId}-{spanId}-{flags}.Wire all four at once
Use all built-in enrichers together. The list of enrichers is identical across frameworks. Only the wiring changes.
import {
createUserAgentEnricher,
createGeoEnricher,
createRequestSizeEnricher,
createTraceContextEnricher,
} from 'evlog/enrichers'
import { composeEnrichers } from 'evlog/toolkit'
export default defineNitroPlugin((nitroApp) => {
nitroApp.hooks.hook('evlog:enrich', composeEnrichers([
createUserAgentEnricher(),
createGeoEnricher(),
createRequestSizeEnricher(),
createTraceContextEnricher(),
]))
})
import { definePlugin } from 'nitro'
import {
createUserAgentEnricher,
createGeoEnricher,
createRequestSizeEnricher,
createTraceContextEnricher,
} from 'evlog/enrichers'
import { composeEnrichers } from 'evlog/toolkit'
export default definePlugin((nitroApp) => {
nitroApp.hooks.hook('evlog:enrich', composeEnrichers([
createUserAgentEnricher(),
createGeoEnricher(),
createRequestSizeEnricher(),
createTraceContextEnricher(),
]))
})
import { createEvlog } from 'evlog/next'
import {
createUserAgentEnricher,
createGeoEnricher,
createRequestSizeEnricher,
createTraceContextEnricher,
} from 'evlog/enrichers'
import { composeEnrichers } from 'evlog/toolkit'
export const { withEvlog, useLogger, log, createError } = createEvlog({
service: 'my-app',
enrich: composeEnrichers([
createUserAgentEnricher(),
createGeoEnricher(),
createRequestSizeEnricher(),
createTraceContextEnricher(),
]),
})
import { createEvlogHooks } from 'evlog/sveltekit'
import { composeEnrichers } from 'evlog/toolkit'
import {
createUserAgentEnricher,
createGeoEnricher,
createRequestSizeEnricher,
createTraceContextEnricher,
} from 'evlog/enrichers'
const enrich = composeEnrichers([
createUserAgentEnricher(),
createGeoEnricher(),
createRequestSizeEnricher(),
createTraceContextEnricher(),
])
export const { handle, handleError } = createEvlogHooks({ enrich })
import { definePlugin } from 'nitro'
import {
createUserAgentEnricher,
createGeoEnricher,
createRequestSizeEnricher,
createTraceContextEnricher,
} from 'evlog/enrichers'
import { composeEnrichers } from 'evlog/toolkit'
export default definePlugin((nitroApp) => {
nitroApp.hooks.hook('evlog:enrich', composeEnrichers([
createUserAgentEnricher(),
createGeoEnricher(),
createRequestSizeEnricher(),
createTraceContextEnricher(),
]))
})
import { evlog } from 'evlog/react-router'
import { composeEnrichers } from 'evlog/toolkit'
import {
createUserAgentEnricher,
createGeoEnricher,
createRequestSizeEnricher,
createTraceContextEnricher,
} from 'evlog/enrichers'
const enrich = composeEnrichers([
createUserAgentEnricher(),
createGeoEnricher(),
createRequestSizeEnricher(),
createTraceContextEnricher(),
])
export const middleware: Route.MiddlewareFunction[] = [
evlog({ enrich }),
]
import { evlog } from 'evlog/hono'
import { composeEnrichers } from 'evlog/toolkit'
import {
createUserAgentEnricher,
createGeoEnricher,
createRequestSizeEnricher,
createTraceContextEnricher,
} from 'evlog/enrichers'
const enrich = composeEnrichers([
createUserAgentEnricher(),
createGeoEnricher(),
createRequestSizeEnricher(),
createTraceContextEnricher(),
])
app.use(evlog({ enrich }))
import { evlog } from 'evlog/express'
import { composeEnrichers } from 'evlog/toolkit'
import {
createUserAgentEnricher,
createGeoEnricher,
createRequestSizeEnricher,
createTraceContextEnricher,
} from 'evlog/enrichers'
const enrich = composeEnrichers([
createUserAgentEnricher(),
createGeoEnricher(),
createRequestSizeEnricher(),
createTraceContextEnricher(),
])
app.use(evlog({ enrich }))
import { evlog } from 'evlog/fastify'
import { composeEnrichers } from 'evlog/toolkit'
import {
createUserAgentEnricher,
createGeoEnricher,
createRequestSizeEnricher,
createTraceContextEnricher,
} from 'evlog/enrichers'
const enrich = composeEnrichers([
createUserAgentEnricher(),
createGeoEnricher(),
createRequestSizeEnricher(),
createTraceContextEnricher(),
])
await app.register(evlog, { enrich })
import { evlog } from 'evlog/elysia'
import { composeEnrichers } from 'evlog/toolkit'
import {
createUserAgentEnricher,
createGeoEnricher,
createRequestSizeEnricher,
createTraceContextEnricher,
} from 'evlog/enrichers'
const enrich = composeEnrichers([
createUserAgentEnricher(),
createGeoEnricher(),
createRequestSizeEnricher(),
createTraceContextEnricher(),
])
app.use(evlog({ enrich }))
import { EvlogModule } from 'evlog/nestjs'
import { composeEnrichers } from 'evlog/toolkit'
import {
createUserAgentEnricher,
createGeoEnricher,
createRequestSizeEnricher,
createTraceContextEnricher,
} from 'evlog/enrichers'
const enrich = composeEnrichers([
createUserAgentEnricher(),
createGeoEnricher(),
createRequestSizeEnricher(),
createTraceContextEnricher(),
])
EvlogModule.forRoot({ enrich })
import { withEvlog } from 'evlog/orpc'
import { composeEnrichers } from 'evlog/toolkit'
import {
createUserAgentEnricher,
createGeoEnricher,
createRequestSizeEnricher,
createTraceContextEnricher,
} from 'evlog/enrichers'
const enrich = composeEnrichers([
createUserAgentEnricher(),
createGeoEnricher(),
createRequestSizeEnricher(),
createTraceContextEnricher(),
])
const handler = withEvlog(new RPCHandler(router), { enrich })
import { withEvlog } from 'evlog/workers'
import { composeEnrichers } from 'evlog/toolkit'
import {
createUserAgentEnricher,
createGeoEnricher,
createRequestSizeEnricher,
createTraceContextEnricher,
} from 'evlog/enrichers'
const enrich = composeEnrichers([
createUserAgentEnricher(),
createGeoEnricher(),
createRequestSizeEnricher(),
createTraceContextEnricher(),
])
export default withEvlog(handler, { enrich })
import { initLogger } from 'evlog'
import { composeEnrichers, enricherPlugin } from 'evlog/toolkit'
import {
createUserAgentEnricher,
createGeoEnricher,
createRequestSizeEnricher,
createTraceContextEnricher,
} from 'evlog/enrichers'
initLogger({
plugins: [
enricherPlugin('defaults', composeEnrichers([
createUserAgentEnricher(),
createGeoEnricher(),
createRequestSizeEnricher(),
createTraceContextEnricher(),
])),
],
})
Next Steps
- Custom Enrichers - Write your own enricher
- Adapters - Send enriched events to external services