Use Cases

Enrichers

Add derived context to every wide event, from user agent and geo to your own computed fields.

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

server/plugins/evlog-enrich.ts
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:

server/plugins/evlog.tsserver/plugins/evlog.tslib/evlog.tssrc/hooks.server.tsserver/plugins/evlog.tsapp/root.tsxsrc/index.tssrc/index.tssrc/index.tssrc/index.tssrc/app.module.tsserver/orpc.tssrc/worker.tssrc/index.ts
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

user-agent-enricher.ts
const enrich = createUserAgentEnricher()

Output shape:

user-agent-types.ts
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:

Example wide event: userAgent
{
  "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

geo-enricher.ts
const enrich = createGeoEnricher()

Output shape:

geo-types.ts
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:

PlatformHeadersCoverage
Vercelx-vercel-ip-country, x-vercel-ip-country-region, x-vercel-ip-city, x-vercel-ip-latitude, x-vercel-ip-longitudeFull
Cloudflarecf-ipcountryCountry only
Cloudflare note: 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

request-size-enricher.ts
const enrich = createRequestSizeEnricher()

Output shape:

request-size-types.ts
interface RequestSizeInfo {
  requestBytes?: number    // Request Content-Length
  responseBytes?: number   // Response Content-Length
}

Example output:

Example wide event: requestSize
{
  "requestSize": {
    "requestBytes": 1234,
    "responseBytes": 5678
  }
}
This enricher reads the 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

trace-context-enricher.ts
const enrich = createTraceContextEnricher()

Output shape:

trace-context-types.ts
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:

Example wide event: traceContext
{
  "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.

The traceparent format follows the W3C Trace Context specification: {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.

server/plugins/evlog-enrich.tsserver/plugins/evlog-enrich.tslib/evlog.tssrc/hooks.server.tsserver/plugins/evlog-enrich.tsapp/root.tsxsrc/index.tssrc/index.tssrc/index.tssrc/index.tssrc/app.module.tsserver/orpc.tssrc/worker.tsindex.ts
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