---
title: "Enrichers"
description: "Add derived context to every wide event, from user agent and geo to your own computed fields."
canonical_url: "https://www.evlog.dev/use-cases/enrichers"
---
# 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](https://www.evlog.dev/extend/custom-enrichers).

<prompt :actions="["copy","cursor","claude"]" description="Add all built-in evlog enrichers" icon="i-lucide-puzzle">

Add all built-in enrichers to my evlog setup.

1. Identify which framework I'm using and follow its evlog integration pattern
2. Import createUserAgentEnricher, createGeoEnricher, createRequestSizeEnricher, and createTraceContextEnricher from 'evlog/enrichers'
3. Wire the enrichers into my framework's enrich configuration
4. Enrichers add userAgent, geo, requestSize, and traceContext fields to wide events
5. All enrichers accept { overwrite?: boolean } - defaults to false to preserve user-set data

Enricher docs: [https://www.evlog.dev/use-cases/enrichers](https://www.evlog.dev/use-cases/enrichers)
Framework setup: [https://www.evlog.dev/integrate/frameworks/overview](https://www.evlog.dev/integrate/frameworks/overview)

</prompt>

```typescript [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:

<framework-tabs>

```typescript [Nuxt]server/plugins/evlog.ts
import { createDefaultEnrichers } from 'evlog/enrichers'

const enrich = createDefaultEnrichers()

export default defineNitroPlugin((nitroApp) => {
  nitroApp.hooks.hook('evlog:enrich', enrich)
})
```

```typescript [Nitro]server/plugins/evlog.ts
import { definePlugin } from 'nitro'
import { createDefaultEnrichers } from 'evlog/enrichers'

const enrich = createDefaultEnrichers()

export default definePlugin((nitroApp) => {
  nitroApp.hooks.hook('evlog:enrich', enrich)
})
```

```typescript [Next.js]lib/evlog.ts
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,
})
```

```typescript [SvelteKit]src/hooks.server.ts
import { createEvlogHooks } from 'evlog/sveltekit'
import { createDefaultEnrichers } from 'evlog/enrichers'

const enrich = createDefaultEnrichers()
export const { handle, handleError } = createEvlogHooks({ enrich: enrich })
```

```typescript [TanStack Start]server/plugins/evlog.ts
import { definePlugin } from 'nitro'
import { createDefaultEnrichers } from 'evlog/enrichers'

const enrich = createDefaultEnrichers()

export default definePlugin((nitroApp) => {
  nitroApp.hooks.hook('evlog:enrich', enrich)
})
```

```typescript [React Router]app/root.tsx
import { evlog } from 'evlog/react-router'
import { createDefaultEnrichers } from 'evlog/enrichers'

const enrich = createDefaultEnrichers()
export const middleware: Route.MiddlewareFunction[] = [
  evlog({ enrich: enrich }),
]
```

```typescript [Hono]src/index.ts
import { evlog } from 'evlog/hono'
import { createDefaultEnrichers } from 'evlog/enrichers'

const enrich = createDefaultEnrichers()
app.use(evlog({ enrich: enrich }))
```

```typescript [Express]src/index.ts
import { evlog } from 'evlog/express'
import { createDefaultEnrichers } from 'evlog/enrichers'

const enrich = createDefaultEnrichers()
app.use(evlog({ enrich: enrich }))
```

```typescript [Fastify]src/index.ts
import { evlog } from 'evlog/fastify'
import { createDefaultEnrichers } from 'evlog/enrichers'

const enrich = createDefaultEnrichers()
await app.register(evlog, { enrich: enrich })
```

```typescript [Elysia]src/index.ts
import { evlog } from 'evlog/elysia'
import { createDefaultEnrichers } from 'evlog/enrichers'

const enrich = createDefaultEnrichers()
app.use(evlog({ enrich: enrich }))
```

```typescript [NestJS]src/app.module.ts
import { EvlogModule } from 'evlog/nestjs'
import { createDefaultEnrichers } from 'evlog/enrichers'

const enrich = createDefaultEnrichers()
EvlogModule.forRoot({ enrich: enrich })
```

```typescript [oRPC]server/orpc.ts
import { withEvlog } from 'evlog/orpc'
import { createDefaultEnrichers } from 'evlog/enrichers'

const enrich = createDefaultEnrichers()
const handler = withEvlog(new RPCHandler(router), { enrich: enrich })
```

```typescript [Cloudflare Workers]src/worker.ts
import { withEvlog } from 'evlog/workers'
import { createDefaultEnrichers } from 'evlog/enrichers'

const enrich = createDefaultEnrichers()
export default withEvlog(handler, { enrich: enrich })
```

```typescript [Standalone]src/index.ts
import { initLogger } from 'evlog'
import { createDefaultEnrichers } from 'evlog/enrichers'

const enrich = createDefaultEnrichers()
initLogger({ enrich: enrich })
```

</framework-tabs>

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`

```typescript [user-agent-enricher.ts]
const enrich = createUserAgentEnricher()
```

**Output shape:**

```typescript [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:**

```json [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`

```typescript [geo-enricher.ts]
const enrich = createGeoEnricher()
```

**Output shape:**

```typescript [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:**

<table>
<thead>
  <tr>
    <th>
      Platform
    </th>
    
    <th>
      Headers
    </th>
    
    <th>
      Coverage
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      Vercel
    </td>
    
    <td>
      <code>
        x-vercel-ip-country
      </code>
      
      , <code>
        x-vercel-ip-country-region
      </code>
      
      , <code>
        x-vercel-ip-city
      </code>
      
      , <code>
        x-vercel-ip-latitude
      </code>
      
      , <code>
        x-vercel-ip-longitude
      </code>
    </td>
    
    <td>
      Full
    </td>
  </tr>
  
  <tr>
    <td>
      Cloudflare
    </td>
    
    <td>
      <code>
        cf-ipcountry
      </code>
    </td>
    
    <td>
      Country only
    </td>
  </tr>
</tbody>
</table>

<callout color="info" icon="i-lucide-info">

**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](https://www.evlog.dev/extend/custom-enrichers) that reads `request.cf`, or use a Workers middleware to copy `cf` properties into custom headers.

</callout>

## `requestSizeEnricher()`

Capture request and response payload sizes from `Content-Length` headers.

**Sets:** `event.requestSize`

```typescript [request-size-enricher.ts]
const enrich = createRequestSizeEnricher()
```

**Output shape:**

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

**Example output:**

```json [Example wide event: requestSize]
{
  "requestSize": {
    "requestBytes": 1234,
    "responseBytes": 5678
  }
}
```

<callout color="info" icon="i-lucide-info">

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`.

</callout>

## `traceContextEnricher()`

Extract W3C trace context from the `traceparent` and `tracestate` headers.

**Sets:** `event.traceContext`, `event.traceId`, `event.parentSpanId`

```typescript [trace-context-enricher.ts]
const enrich = createTraceContextEnricher()
```

**Output shape:**

```typescript [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:**

```json [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.

<callout color="info" icon="i-lucide-info">

The traceparent format follows the [W3C Trace Context](https://www.w3.org/TR/trace-context/) specification: `{version}-{traceId}-{spanId}-{flags}`.

</callout>

## Wire all four at once

Use all built-in enrichers together. The list of enrichers is identical across frameworks. Only the wiring changes.

<framework-tabs>

```typescript [Nuxt]server/plugins/evlog-enrich.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(),
  ]))
})
```

```typescript [Nitro]server/plugins/evlog-enrich.ts
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(),
  ]))
})
```

```typescript [Next.js]lib/evlog.ts
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(),
  ]),
})
```

```typescript [SvelteKit]src/hooks.server.ts
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 })
```

```typescript [TanStack Start]server/plugins/evlog-enrich.ts
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(),
  ]))
})
```

```typescript [React Router]app/root.tsx
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 }),
]
```

```typescript [Hono]src/index.ts
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 }))
```

```typescript [Express]src/index.ts
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 }))
```

```typescript [Fastify]src/index.ts
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 })
```

```typescript [Elysia]src/index.ts
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 }))
```

```typescript [NestJS]src/app.module.ts
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 })
```

```typescript [oRPC]server/orpc.ts
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 })
```

```typescript [Cloudflare Workers]src/worker.ts
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 })
```

```typescript [Standalone]index.ts
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(),
    ])),
  ],
})
```

</framework-tabs>

## Next Steps

- [Custom Enrichers](https://www.evlog.dev/extend/custom-enrichers) - Write your own enricher
- [Adapters](https://www.evlog.dev/integrate/adapters/overview) - Send enriched events to external services

---

- [Custom Enrichers](https://www.evlog.dev/extend/custom-enrichers)


## Sitemap

See the full [sitemap](https://www.evlog.dev/sitemap.md) for all pages.
