Without a config file, the CI gate is a flag on every evlog map run, a check you decided not to care about is disabled file by file, and sampling and redaction live in whichever file calls initLogger. evlog.config.ts holds all of it, and a preset carries it from one repository to the next. The CLI reads the file without running it, and the app imports it like any other module.
Here an app builds on a shared preset, turns a check back on, and leaves its dev routes out of the map:
import { defineEvlog } from 'evlog'
import { createAxiomDrain } from 'evlog/axiom'
import preset from './evlog.preset'
export default defineEvlog({
extends: preset,
service: 'checkout',
drain: createAxiomDrain(),
sampling: { rates: { info: 25 } },
map: {
rules: { 'audit-coverage': 'on' },
ignore: ['src/routes/_dev/**'],
},
logs: { limit: 100 },
})
import { defineEvlog } from 'evlog'
export default defineEvlog({
sampling: { rates: { info: 10, debug: 0 } },
redact: { paths: ['user.password', 'card.number'] },
map: { rules: { 'error-catalog': 'off', 'audit-coverage': 'off' }, minScore: 70 },
})
evlog config prints the merged result, split by who applies each setting, with the line it is written on:
evlog config
evlog.config.ts · extends ./evlog.preset → evlog.preset.ts
CLI · applied by evlog map and evlog logs
map.rules.error-catalog 'off' evlog.preset.ts:6
map.rules.audit-coverage 'on' evlog.config.ts:11
map.minScore 70 evlog.preset.ts:6
map.ignore ['src/routes/_dev/**'] evlog.config.ts:12
logs.limit 100 evlog.config.ts:14
APP · applied where the app imports the config
sampling.rates.info 25 evlog.config.ts:9
sampling.rates.debug 0 evlog.preset.ts:4
redact.paths ['user.password', 'card.number'] evlog.preset.ts:5
service 'checkout' evlog.config.ts:7
drain createAxiomDrain() evlog.config.ts:8
A value the file computes, like createAxiomDrain(), is shown as the code that produces it. --json returns the same settings as cli and app lists of { path, value, source }, with a computed value written as { "runtime": "createAxiomDrain()" }.
Where the CLI finds the file
The CLI looks for evlog.config.ts, evlog.config.mts, evlog.config.js and evlog.config.mjs, in that order, starting in the package it runs on and walking up to the workspace root. The first file found applies on its own. Configs do not cascade, so an app with its own evlog.config.ts ignores the one at the root unless it extends it.
Paths in map.ignore, map.baseline and logs.dir are relative to the package being mapped or read, not to the config file. One config at the root of a monorepo therefore fits every app in it.
Gate the map from the config
evlog map reads the map section, and a flag passed on the command line wins over the same setting.
| Setting | Accepts | Flag | What it does |
|---|---|---|---|
map.rules | { [id]: 'on' | 'off' } | none | Turns a check off for every entry point, or back on when the preset turned it off |
map.ignore | list of globs | none | Leaves the entry points whose file matches out of the map |
map.minScore | whole number from 0 to 100 | --min-score | Exits 1 when the global score is below it |
map.baseline | true, a path, or git:<ref> | --baseline | Exits 1 on a regression against the committed map, true meaning evlog.map.json |
The ids are the ones on Rules. Every check can be turned off except wide-event and context, because the map sorts entry points into instrumented, partial and dark by them. Leave those entry points out with map.ignore instead.
A check turned off in the config becomes n/a on every entry point, with turned off in evlog.config as its message in --json. The report says what the config changed above the score, and names the setting the gate came from:
evlog.config.ts: error-catalog off, 1 entry point ignored
█▀█ ▀▀█ score /100 checkout · Hono
█▀█ ▀█ ▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▱▱▱ 2 entry points scanned
▀▀▀ ▀▀▀ good ▆█
GATE score 83 meets map.minScore 70 — exit code 0
evlog map --min-score 95 on the same project gates on 95 and says --min-score 95. To turn a check off for one handler rather than the whole project, keep using a disable comment next to the code.
Read logs from another directory
evlog logs reads the logs section, and its flags win the same way.
| Setting | Accepts | Flag | What it does |
|---|---|---|---|
logs.dir | a path | --dir | Reads this directory instead of .evlog/logs |
logs.limit | whole number of 1 or more | --limit | Shows at most this many events |
--format, --verbose, and --limit on evlog map stay flags only. They describe one run, not the project.
Write values the CLI can read
The CLI parses evlog.config.ts and never runs it, so every value under map and logs has to be a literal, a const, or a value imported from a local file. A call, an environment variable, or a value imported from a package stops the command:
logs.limit in evlog.config.ts:14 is computed at runtime
→ Write the value inline, as a const, or import it from a local file
The rest of the file is for the app and can compute anything: a drain, an enrich function, a sampling rate read from process.env. The CLI lists those values without evaluating them.
Share settings with extends
extends takes another config, imported from a local file or from a package. A preset published to npm is an ordinary module whose default export is defineEvlog({ ... }), so a team installs it and extends it:
import { defineEvlog } from 'evlog'
import preset from '@acme/evlog-preset'
export default defineEvlog({
extends: preset,
service: 'checkout',
})
The CLI follows the package's exports to the file it ships and reads it the same way, so the preset's map and logs have to be literals too.
Settings merge by kind:
| In the config | Result |
|---|---|
A scalar or a function: service, drain, enrich, keep | The config's value replaces the preset's |
An object: sampling.rates, routes, env, map.rules | Merged key by key, the config winning on each key |
redact.paths, redact.patterns, sampling.keep | The preset's entries, then the config's |
Any other list: map.ignore, include, exclude | The config's list replaces the preset's |
plugins | Merged by name, a config plugin replacing the preset plugin of the same name |
redact: false | Redaction off |
redact: true | The preset's redact settings, unchanged |
Redaction paths and kept events add up rather than being replaced, so an app that lists its own paths cannot drop the ones the preset redacts.
A config extends one level only. When evlog.preset.ts itself extends a config, extending it fails and names both files:
./evlog.preset extends another config, so evlog.config.ts:6 cannot extend it
→ Extend the config it extends directly, or copy the settings you need into one of the two files
Every setting is then at most one file away from where it applies, and evlog config names that file.
Use the config in your app
Nothing loads evlog.config.ts at runtime: the app imports it. toLoggerConfig keeps the options initLogger takes, toMiddlewareOptions keeps the ones a framework middleware takes, and both leave map and logs out:
import { Hono } from 'hono'
import { initLogger, toLoggerConfig, toMiddlewareOptions } from 'evlog'
import { evlog, type EvlogVariables } from 'evlog/hono'
import config from '../evlog.config'
initLogger(toLoggerConfig(config))
const app = new Hono<EvlogVariables>()
app.use(evlog(toMiddlewareOptions(config)))
The Nuxt and Nitro modules do not read evlog.config.ts. Their options stay in nuxt.config.ts or nitro.config.ts, and the file there carries the map and logs settings the CLI applies.
When the config cannot be read
evlog map and evlog config exit 1 on a config they cannot read, and evlog logs exits 2. evlog doctor reports the same error as a failing config check. Each error carries a code from the CLI's catalog:
| Code | Raised when |
|---|---|
cli.CONFIG_PARSE_FAILED | The file has a syntax error |
cli.CONFIG_NO_EXPORT | There is no default export of an object or defineEvlog({ ... }) |
cli.CONFIG_NOT_STATIC | The default export, extends, or a map or logs value is computed at runtime |
cli.CONFIG_INVALID | A setting is misspelt, has the wrong type, or turns off wide-event or context |
cli.CONFIG_EXTENDS_NOT_FOUND | The extends import does not lead to a file |
cli.CONFIG_EXTENDS_DEPTH | The extended config extends another one |
A misspelt key is an error rather than a setting quietly ignored:
logs.limt in evlog.config.ts:14 is not a setting; expected dir, limit
→ Use a setting and a value the config reference lists