Config & Logging
Configuration answers “what settings apply to this run”; logging answers “what the run tells you about itself”. Both are designed around one guarantee: the rest of the code only ever sees resolved values, and everything the tool says about itself goes to stderr. Configuration documents every key; this page documents the machinery.
Layers and precedence
Section titled “Layers and precedence”Four layers resolve, highest first: flags → env vars → config file → built-in defaults.
- Flags shadow config in
src/cli/apply-user-config.ts: a config value is copied onto a command option only when that option was not set on the command line. - Env vars shadow config in
src/config/runtime-overrides.ts: a runtime config reader checks its env var first, then the config key, then the exported default constant. Only some settings have an env var: the event store (LLM_USAGE_EVENT_STORE,LLM_USAGE_EVENT_STORE_PATH), the worker pool (LLM_USAGE_PARSE_WORKERS,LLM_USAGE_PARSE_WORKER_MIN_BYTES), and the update check (LLM_USAGE_SKIP_UPDATE_CHECK). The pricing cache TTL and fetch timeout,parseMaxParallel, and the update check’s TTL and timeout come from config or the default only. - Consumers never read raw layers — they call the runtime-config readers and get final values.
A worked example with the event-store path: LLM_USAGE_EVENT_STORE_PATH=/tmp/scratch.db wins over eventStore.path = "/data/events.db" in config.toml, which wins over the default events.db under the platform data root. Same shape for a flag-backed key: --timezone Europe/Paris wins over timezone = "Africa/Casablanca".
The default constants live in runtime-overrides.ts as exports, and the config init template interpolates them — so the template’s documented defaults cannot drift from the real ones by construction.
Loading and validation
Section titled “Loading and validation”src/config/user-config.ts loads the file once per run:
- The default path is
<config-root>/llm-usage-metrics/config.toml, where the config root isXDG_CONFIG_HOME, then%APPDATA%on Windows, then~/.config(src/utils/config-root-dir.ts).LLM_USAGE_CONFIG_PATHpoints at an alternate file. - A missing file is silent: the run proceeds with an empty config.
- Malformed TOML fails with
Failed to parse config file <path>: <reason>; a file whose root is not a TOML table fails with its own message. - Unknown keys (top-level and nested, including
[sourceDirs]) produce onestderrwarning listing the sorted key names; the run continues. - If the default TOML file is missing but a legacy
config.jsonsits next to it, loading fails with an actionable migration error telling you the format is now TOML, to runllm-usage config initfor a commented template, and to remove the old file. The probe is skipped whenLLM_USAGE_CONFIG_PATHis set.
Transparency blocks
Section titled “Transparency blocks”Reports print what configuration actually applied, on stderr. The default summary line names the config file and counts env overrides; --verbose prints the full blocks:
Active config: <path>lists the config-file values that took effect this run. The shadowing rule is load-bearing: a config value overridden by a flag or an env var is not listed, so the block never claims credit for a value that came from somewhere else.Active environment overrides:separately lists every recognizedLLM_USAGE_*env var that is set, with its value.
The template and the schema
Section titled “The template and the schema”config path prints the resolved config file path, and config show prints the effective configuration with where each value comes from.
llm-usage config init (src/cli/create-config-command.ts) writes a fully commented template at the active config path, creating parent directories as needed. It refuses to overwrite an existing file (Config file already exists: <path> (use --force to overwrite)); --force replaces it. The template is documentation that cannot drift: every key ships commented out with its real default interpolated from the exported constants — including the machine’s absolute default event-store path — and covers all keys, including every supported [sourceDirs] entry.
The first line is a #:schema https://ayagmar.github.io/llm-usage-metrics/config-schema.json directive, which TOML editors (e.g. Taplo) use for completion and validation. The schema is hand-maintained at schema/config.schema.json, and unit tests keep it honest: one test asserts the schema’s key paths equal the loader’s known-key set, and another asserts the published copy at site/public/config-schema.json is byte-identical to it.
Adding a config key therefore touches: the known-key set in user-config.ts, both schema copies, the config init template, and the key tables in Configuration — the tests fail if the schema half of that list is forgotten.
Logging levels
Section titled “Logging levels”src/utils/logger.ts is a leveled stderr logger with a module-level setLogLevel and the ladder silent < warn < info < debug. The default level is info. Message kinds map to levels as follows: warn messages show at warn and above; info and dim messages show at info and above; debug messages only at debug.
- Startup applies the
logLevelconfig key (setLogLevel(config.logLevel ?? 'info')insrc/cli/index.ts). --quietmaps towarnand--verbosetodebugfor a single run via a CommanderpreActionhook increate-cli.ts; either wins over the configured level for that run, and passing both is an error.- Report diagnostics (
src/cli/emit-diagnostics.ts) print oneinfosummary line: files scanned per source, the pricing origin, the config file, and the env-override count. Real problems arewarnlines: source failures, files that failed to parse, rows that could not be read, a source whose files yield no usage at all (a likely log-format change), and pricing and event-store warnings. Informational notes such as the--historytotals areinfo. Routine skips (rows that carry no usage by design:no_token_usage,missing_usage_signal,synthetic_message,non_gemini_message; and goose’sinvalid_model_config, which keeps the event), per-source event counts, and the config/env blocks aredebug. - Report bodies on
stdoutare never gated. Levels only controlstderrdiagnostics;--jsonoutput atsilentis byte-identical to--jsonoutput atdebug. - The fatal error handler in
src/cli/index.tsis a rawconsole.errorwith exit code 1, outside the logger — failures always print, even atsilent.
Related pages
Section titled “Related pages”- Configuration — the key tables, source path overrides, and env vars.
- CLI Reference — the
--quietand--verboseflags and per-command options. - Architecture overview — where config and logging sit in the runtime.