Build on top

Framework integration

Build evlog support for an HTTP framework that doesn't ship a built-in integration — Medusa, AdonisJS, h3 directly, custom dispatchers, queue workers.

When the framework you use doesn't have an evlog/<framework> package yet, you build the integration yourself. evlog ships a small toolkit (createMiddlewareLogger, defineFrameworkIntegration) that handles the request-context plumbing — you only write the framework-specific glue.

Build an evlog integration for a custom framework

What you need to wire

The complete walkthrough lives in Custom integration. The short version:

SurfaceWhat it doesWhen to use
defineFrameworkIntegration()Declaratively wire request extraction + logger attachmentHTTP frameworks (Hono, Express, Fastify, Elysia, NestJS-shaped)
createMiddlewareLogger()Imperative path: create the logger at request start, emit on response endWhen the framework's middleware shape doesn't fit declarative wiring
createRequestLogger()Lower-level: wrap any unit of work in a logger lifecycleNon-HTTP runtimes (queue workers, CLI, cron, durable workflows)

The mental model is always the same: request lifecycle → logger creation → enrich → drain. The toolkit gives you the request-context plumbing; you only write the framework-specific glue.

Non-HTTP runtimes

For queue workers, CLI drivers, cron jobs, or durable execution engines, skip the HTTP-shaped helpers and use createRequestLogger from evlog/toolkit directly:

import { createRequestLogger } from 'evlog/toolkit'

async function processJob(job: Job) {
  const logger = createRequestLogger({
    service: 'jobs',
    context: { jobId: job.id, queue: job.queue },
  })

  try {
    await runJob(job)
    logger.set({ status: 'success' })
  } catch (err) {
    logger.error(err)
    throw err
  } finally {
    await logger.emit()
  }
}

Same enrichers, same drain hook, same identity headers — only the entry point shape changes.