Learn evlog
This section is the mental model of evlog. By the end, you'll know exactly what evlog does, when each API fits, and how an event flows from your code to your drain.
If you're new, read it in order. If you've already shipped with evlog, jump to the page that matches your question.
The three logging modes
Simple Logging
console.log, consola, pino, or winston with log.info, log.error, log.warn, log.debug — same level filtering, drain pipeline, redaction, and pretty/JSON output.Quick comparison
Simple Logging (log)
One event per call. No accumulation, no lifecycle management.
import { log } from 'evlog'
log.info('auth', 'User logged in')
log.error({ action: 'payment', error: 'card_declined', userId: 42 })
Wide Events (createLogger / createRequestLogger)
One event per unit of work. Accumulate context progressively, emit when done.
import { createLogger } from 'evlog'
const log = createLogger({ jobId: 'sync-001', queue: 'emails' })
log.set({ batch: { size: 50, processed: 50 } })
log.emit()
import { createRequestLogger } from 'evlog'
const log = createRequestLogger({ method: 'POST', path: '/api/checkout' })
log.set({ user: { id: 1, plan: 'pro' } })
log.emit()
createRequestLogger is a thin wrapper around createLogger that pre-populates method, path, and requestId.
Request Logging (framework middleware)
Framework integrations create a wide event logger automatically on each request. useLogger(event) retrieves the logger that's already attached to the request context:
import { useLogger } from 'evlog'
export default defineEventHandler(async (event) => {
const log = useLogger(event)
log.set({ user: { id: 1, plan: 'pro' } })
return { success: true }
// auto-emitted on response end
})
useLogger(event) doesn't create a logger, it retrieves the one the framework middleware already attached to the event. Each framework has its own way to access it (useLogger, req.log, c.get('log'), etc.). In Nuxt, useLogger is auto-imported.When to use what
log | createLogger / createRequestLogger | Framework middleware | |
|---|---|---|---|
| Use case | Quick one-off events | Scripts, jobs, workers, queues, HTTP without a framework | API routes with a framework integration |
| Context | Single call | Accumulate with set() | Accumulate with set() |
| Emit | Immediate | Manual emit() | Automatic on response end |
| Lifecycle | None | You manage it | Framework manages it |
| Output | Console + drain | Console + drain | Console + drain + enrich |
By context
| Context | Best fit | Why |
|---|---|---|
| HTTP route in Nuxt / Next / Hono / Express / … | useLogger(event) via framework integration | One wide event per request, auto-emitted on response end |
| HTTP handler without a framework | createRequestLogger({ method, path }) | Same shape as framework middleware, manual emit |
| CLI tool / one-shot script | log.* for steps + createLogger for the run summary — see Standalone | Pretty in dev, structured in CI, one summary event for the whole run |
| Published library | createLogger only — never initLogger — see Standalone | Don't pollute the host app's global config or force a drain on consumers |
| Background job / queue worker / cron | createLogger({ jobId, queue }) per invocation — see Standalone | One wide event per job run, perfect for retry analysis |
| Cloudflare Worker / edge function | createWorkersLogger(req) or createRequestLogger — see Cloudflare Workers | Per-request event, no process globals required |
| AWS Lambda | initLogger once + createLogger per invocation — see AWS Lambda | Cold-start init, per-event scope, drain flush in the handler |
| Batch / pipeline step | createLogger({ step }) per stage | One event per stage with inputs and outputs side by side |
| AI agent / LLM call | createLogger + createAILogger | Token usage, tool calls, streaming metrics on the same wide event |
| Library function called inside a request | useLogger(event) from caller, or accept a logger as argument | Inherit the parent's request context, contribute to the same wide event |
| Shared workspace package | Treat it like a library — see Standalone | Host app owns initLogger / drain; packages use createLogger or accept a logger |
log and createLogger in the same file when it makes sense — they share the global drain, redaction, and types.Shared foundation
All three modes share the same foundation:
- Pretty output in development, JSON in production (default, no configuration needed)
- Drain pipeline to send events to Axiom, Sentry, PostHog, and more — see Integrate / Adapters
- Structured errors with
why,fix, andlink, plus optional backend-onlyinternalfor logs - Sampling (head + tail) to control log volume in production
- Redaction that wipes secrets before they ever leave the process
- Zero dependencies, ~6 kB gzip
The rest of this section
After the three modes, the rest of Learn covers the concepts that show up across every mode:
- Structured Errors —
why,fix,link,internal, and howcreateErrordiffers fromthrow new Error - Catalogs — typed error / audit catalogs that survive refactors
- Lifecycle — exactly what happens between
emit()and your drain - Sampling — keep all errors and slow requests; drop healthy noise
- Typed Fields — augment
RequestLoggersolog.setis autocompleted - Redaction — the rules that strip
authorization,password,token, etc. before drain
When you're done with Learn, head to Integrate to wire evlog into your stack.
Quick Start
Get up and running with evlog in minutes. Learn the log API, createLogger for wide events, useLogger for requests, and structured errors.
Simple Logging
evlog's general-purpose logger. A drop-in for console.log, pino, or consola, with the same level filtering, drain pipeline, redaction, and pretty/JSON output as wide events.