Configuration
evlog has two configuration surfaces, and this page documents both: global options set once at startup, and middleware options set per framework integration.
Global options (initLogger)
These options apply to all frameworks. Call initLogger() once at application startup for standalone frameworks (Hono, Express, Fastify, Elysia, NestJS, SvelteKit, Cloudflare Workers). For Nuxt and Nitro, these are set via module config and passed through automatically.
import { initLogger } from 'evlog'
import { createAxiomDrain } from 'evlog/axiom'
initLogger({
enabled: true,
env: { service: 'my-api', environment: 'production' },
pretty: false,
silent: false,
stringify: true,
minLevel: 'info',
sampling: { rates: { info: 10 }, keep: [{ status: 400 }] },
drain: createAxiomDrain(),
})
| Option | Type | Default | Description |
|---|---|---|---|
enabled | boolean | true | Enable/disable all logging globally. When false, all operations become no-ops |
env | Partial<EnvironmentContext> | Auto-detected | Environment context overrides (see below) |
pretty | boolean | true in dev | Pretty print with tree formatting. Auto-detected based on NODE_ENV |
dev | 'evlog' | 'nitro' | 'both' | object | 'evlog' in pretty dev | Dev terminal presets or { frameworkOverlay, prettyError }, see Tune the dev terminal output |
silent | boolean | false | Suppress console output. Events are still built, sampled, and passed to drains |
stringify | boolean | true | Emit JSON strings when pretty is disabled. Set to false for Cloudflare Workers |
minLevel | 'debug' | 'info' | 'warn' | 'error' | 'debug' | Minimum severity for the global log API only (not createLogger / request wide events). Order: debug < info < warn < error |
sampling | SamplingConfig | undefined | Head and tail sampling configuration. See Sampling |
redact | boolean | RedactConfig | true in production | Enabled by default in production. false to disable. Object for fine-grained control. See Auto-Redaction |
drain | (ctx: DrainContext) => void | undefined | Drain callback for sending events to external services |
RedactConfig fields (when redact is an object): paths (dot-notation with globs), patterns (regex on string values), builtins, replacement (string, or a function computing it from the matched value), transform (hook for conditional policies). Full table in Auto-Redaction.
minLevel vs sampling
minLevelis a hard threshold on the simplelog.*API: levels below the threshold are never emitted. It does not apply to wide events fromuseLogger/createLogger().emit(), usesampling.rates(and tailkeep) for request volume.- Head sampling (
sampling.rates) is probabilistic on what is already allowed byminLevelfor simple logs.
Evaluation order for log.info / log.debug / etc.: enabled → minLevel → head sampling → output.
Tune the dev terminal output
Pretty error blocks run only when pretty: true (default in development). Production always emits JSON wide events, with no stack snippets and no disk reads.
Use dev to control two independent axes: whether Nitro's Youch overlay runs, and how much stack detail evlog prints inside the wide event.
Presets (recommended):
| Preset | Nitro overlay | evlog error block |
|---|---|---|
'evlog' (default in pretty dev) | Off | Full — location, snippet, stack tail, Why/Fix |
'nitro' | On | Guidance only — message + Why/Fix/link (stack from Nitro) |
'both' | On | Full — evlog block + Nitro overlay (debug) |
export default defineNuxtConfig({
modules: ['evlog/nuxt'],
evlog: {
pretty: true,
dev: 'evlog', // or 'nitro' | 'both'
},
})
Explicit object (fine-grained):
evlog: {
dev: {
frameworkOverlay: true,
prettyError: {
snippet: false,
stackDepth: 0,
compact: true,
detail: 'guidance', // 'full' | 'guidance'
},
},
}
See Development terminal output in Structured Errors for an example of the pretty error tree.
Stamp the environment on every event
The env option controls the fields included in every log event, and the table below names the variable each one is read from when you leave it unset.
| Field | Type | Default | Auto-detected from |
|---|---|---|---|
service | string | 'app' | SERVICE_NAME |
environment | string | 'development' | NODE_ENV |
version | string | undefined | APP_VERSION |
commitHash | string | undefined | COMMIT_SHA, GITHUB_SHA, VERCEL_GIT_COMMIT_SHA, CF_PAGES_COMMIT_SHA |
region | string | undefined | VERCEL_REGION, AWS_REGION, FLY_REGION, CF_REGION |
Silence the terminal
Use silent when your deployment platform captures stdout as its primary log ingestion (GCP Cloud Run, AWS Lambda, Fly.io, Railway, etc.) and you want a drain adapter to control the output format.
import { initLogger } from 'evlog'
import { createAxiomDrain } from 'evlog/axiom'
initLogger({
silent: process.env.NODE_ENV === 'production',
drain: createAxiomDrain(),
})
silent is enabled without a drain, events are built and sampled but never output anywhere, which evlog warns about at startup.Middleware options
These options are passed to the framework middleware/plugin. They control per-request behavior: which routes to log, how to drain and enrich events, and custom tail sampling logic.
// lib/evlog.ts
import { createEvlog } from 'evlog/next'
import { createAxiomDrain } from 'evlog/axiom'
export const { withEvlog, useLogger, log, createError } = createEvlog({
service: 'my-app',
include: ['/api/**'],
exclude: ['/api/health'],
routes: { '/api/auth/**': { service: 'auth' } },
drain: createAxiomDrain(),
enrich: (ctx) => { ctx.event.region = process.env.FLY_REGION },
keep: (ctx) => { if (ctx.duration > 2000) ctx.shouldKeep = true },
})
app.use(evlog({
include: ['/api/**'],
exclude: ['/api/health'],
routes: { '/api/auth/**': { service: 'auth' } },
drain: createAxiomDrain(),
enrich: (ctx) => { ctx.event.region = process.env.FLY_REGION },
keep: (ctx) => { if (ctx.duration > 2000) ctx.shouldKeep = true },
}))
app.use(evlog({
include: ['/api/**'],
drain: createAxiomDrain(),
enrich: (ctx) => { ctx.event.region = process.env.FLY_REGION },
}))
await app.register(evlog, {
include: ['/api/**'],
drain: createAxiomDrain(),
})
| Option | Type | Default | Description |
|---|---|---|---|
include | string[] | undefined | Route glob patterns to log. If not set, all routes are logged |
exclude | string[] | undefined | Route patterns to exclude. Exclusions take precedence over inclusions |
routes | Record<string, { service: string }> | undefined | Route-specific service name overrides |
drain | (ctx: DrainContext) => void | undefined | Drain callback called with every emitted event |
enrich | (ctx: EnrichContext) => void | undefined | Enrich callback called after emit, before drain |
keep | (ctx: TailSamplingContext) => void | undefined | Custom tail sampling callback |
evlog:drain, evlog:enrich, evlog:emit:keep) instead of middleware options. See the Nuxt and Nitro pages.Middleware drain vs global drain
When a middleware drain is set, it takes precedence over the global drain from initLogger(). If no middleware drain is set, the global drain is used as fallback, with the benefit of receiving the full enriched event with request context (method, path, headers).
import { initLogger } from 'evlog'
import { createAxiomDrain } from 'evlog/axiom'
initLogger({
env: { service: 'my-api' },
drain: createAxiomDrain(), // fallback: used by singleton log API AND middleware (if no middleware drain)
})
app.use(evlog({
// no drain here - falls back to globalDrain from initLogger, with full request context
}))
Framework-specific options
Some frameworks have additional options beyond the shared config:
Nuxt
The Nuxt module accepts all global options and middleware options in nuxt.config.ts under the evlog key, plus:
| Option | Type | Default | Description |
|---|---|---|---|
console | boolean | true | Enable/disable browser console output (client-side only) |
transport.enabled | boolean | false | Send client logs to the server via API endpoint |
transport.endpoint | string | '/api/_evlog/ingest' | Custom transport endpoint |
transport.credentials | RequestCredentials | 'same-origin' | Fetch credentials mode ('include' for cross-origin endpoints) |
See the full Nuxt configuration.
Nitro
The Nitro module accepts enabled, env, pretty, silent, sampling, include, exclude, and routes in nitro.config.ts, and leaves drain and enrichment to the Nitro hooks.