Examples

Express

Source Code
Using evlog with Express — automatic wide events, structured errors, drain adapters, enrichers, and tail sampling in Express applications.

Practical patterns for using evlog with Express. The evlog/express middleware auto-creates a request-scoped logger on req.log and emits a wide event when the response finishes.

Setup

1. Install dependencies

npm install evlog express

2. Initialize and register the middleware

src/index.ts
import express from 'express'
import { initLogger } from 'evlog'
import { evlog } from 'evlog/express'

initLogger({
  env: { service: 'my-api' },
})

const app = express()

app.use(evlog())

app.get('/health', (req, res) => {
  req.log.set({ route: 'health' })
  res.json({ ok: true })
})

app.listen(3000)

The logger is available on req.log with full TypeScript support via module augmentation — no extra type annotations needed.

Wide Events

Build up context progressively through your handler. One request = one wide event:

src/index.ts
app.get('/users/:id', (req, res) => {
  const userId = req.params.id

  req.log.set({ user: { id: userId } })

  const user = await db.findUser(userId)
  req.log.set({ user: { name: user.name, plan: user.plan } })

  const orders = await db.findOrders(userId)
  req.log.set({ orders: { count: orders.length, totalRevenue: sum(orders) } })

  res.json({ user, orders })
})

All fields are merged into a single wide event emitted when the response finishes:

Terminal output
14:58:15 INFO [my-api] GET /users/usr_123 200 in 12ms
  ├─ orders: count=2 totalRevenue=6298
  ├─ user: id=usr_123 name=Alice plan=pro
  └─ requestId: 4a8ff3a8-...

useLogger()

Use useLogger() to access the request-scoped logger from anywhere in the call stack — no need to pass req through your service layer:

src/services/user.ts
import { useLogger } from 'evlog/express'

export async function findUser(id: string) {
  const log = useLogger()
  log.set({ user: { id } })

  const user = await db.findUser(id)
  log.set({ user: { name: user.name, plan: user.plan } })

  return user
}
src/index.ts
import { findUser } from './services/user'

app.get('/users/:id', async (req, res) => {
  const user = await findUser(req.params.id)
  res.json(user)
})

Both req.log and useLogger() return the same logger instance. useLogger() uses AsyncLocalStorage to propagate the logger across async boundaries.

Error Handling

Use createError for structured errors with why, fix, and link fields. Express uses a 4-argument error handler middleware:

src/index.ts
import { createError, parseError } from 'evlog'

app.get('/checkout', () => {
  throw createError({
    message: 'Payment failed',
    status: 402,
    why: 'Card declined by issuer',
    fix: 'Try a different payment method',
    link: 'https://docs.example.com/payments/declined',
  })
})

app.use((err, req, res, next) => {
  req.log.error(err)
  const parsed = parseError(err)

  res.status(parsed.status).json({
    message: parsed.message,
    why: parsed.why,
    fix: parsed.fix,
    link: parsed.link,
  })
})

The error is captured and logged with both the custom context and structured error fields:

Terminal output
14:58:20 ERROR [my-api] GET /checkout 402 in 3ms
  ├─ error: name=EvlogError message=Payment failed status=402
  ├─ cart: items=3 total=9999
  └─ requestId: 880a50ac-...

Drain & Enrichers

Configure drain adapters and enrichers directly in the middleware options:

src/index.ts
import { createAxiomDrain } from 'evlog/axiom'
import { createUserAgentEnricher } from 'evlog/enrichers'

const userAgent = createUserAgentEnricher()

app.use(evlog({
  drain: createAxiomDrain(),
  enrich: (ctx) => {
    userAgent(ctx)
    ctx.event.region = process.env.FLY_REGION
  },
}))

Like Hono, Express passes drain and enrich as options to the middleware. Enrich runs first, then drain.

Pipeline (Batching & Retry)

Without a pipeline, the drain is called once per request with a single event. For production, wrap your adapter with createDrainPipeline to batch events and retry on failure:

src/index.ts
import type { DrainContext } from 'evlog'
import { createAxiomDrain } from 'evlog/axiom'
import { createDrainPipeline } from 'evlog/pipeline'

const pipeline = createDrainPipeline<DrainContext>({
  batch: { size: 50, intervalMs: 5000 },
  retry: { maxAttempts: 3 },
})
const drain = pipeline(createAxiomDrain())

app.use(evlog({ drain }))
Call drain.flush() on server shutdown to ensure all buffered events are sent. See the Pipeline docs for all options.

Tail Sampling

Use keep to force-retain specific events regardless of head sampling:

src/index.ts
app.use(evlog({
  drain: createAxiomDrain(),
  keep: (ctx) => {
    if (ctx.duration && ctx.duration > 2000) ctx.shouldKeep = true
  },
}))

Route Filtering

Control which routes are logged with include and exclude patterns:

src/index.ts
app.use(evlog({
  include: ['/api/**'],
  exclude: ['/_internal/**', '/health'],
  routes: {
    '/api/auth/**': { service: 'auth-service' },
    '/api/payment/**': { service: 'payment-service' },
  },
}))

Client-Side Logging

Use evlog/browser to send structured logs from any frontend to your Express server. This works with any client framework (React, Vue, Svelte, vanilla JS).

Browser setup

client.ts
import { initLogger, log } from 'evlog'
import { createBrowserLogDrain } from 'evlog/browser'

const drain = createBrowserLogDrain({
  drain: { endpoint: '/v1/ingest' },
})
initLogger({ drain })

log.info({ action: 'page_view', path: location.pathname })

Ingest endpoint

Add a POST route to receive batched DrainContext[] from the browser:

src/index.ts
import type { DrainContext } from 'evlog'

app.post('/v1/ingest', express.json(), (req, res) => {
  const batch = req.body as DrainContext[]
  for (const ctx of batch) {
    console.log('[BROWSER]', JSON.stringify(ctx.event))
  }
  res.sendStatus(204)
})
See the full Browser Drain adapter docs for batching, retry, sendBeacon fallback, and authentication options.

Run Locally

git clone https://github.com/HugoRCD/evlog.git
cd evlog
bun install
bun run example:express

Open http://localhost:3000 to explore the interactive test UI.

Source Code

Browse the complete Express example source on GitHub.