Files
routstrd/docs/logging.md
redshift 95378e793e fix(daemon): log one line per refresh pass instead of one per phase
A scheduled refresh runs every 21 minutes and most of what it printed was
identical every pass. On a real daemon (20,746 log lines/day) the refresh block
was ~10% of the file: four INFO phase lines per pass plus one WARN per
unreachable provider, with the same 26-provider failure set repeated across
consecutive passes.

Three changes, one per source of noise:

1. `ROUTSTRD_LOG_LEVEL` (default `info`) makes the level meaningful. `writeLog`
   wrote every level unconditionally, so the SDK's `debug` channel was not
   actually filterable — which is what blocked moving recurring detail down a
   level. `ROUTSTRD_LOG_LEVEL=debug` restores everything; an unparseable value
   falls back to the default instead of muting the log. `error` is never
   suppressed. The Cashu wallet-engine sink keeps `debug` under its own
   `ROUTSTRD_COCO_LOG_LEVEL`, because it is ~87% debug by design and an explicit
   `ROUTSTRD_LOG_LEVEL=info` must not silently drop wallet detail.

2. The refresh pass collapses into one line:

   [INFO] Scheduled refresh: 214 models, 3 client integration(s) in 33.4s

   The phase markers ("Running scheduled Nostr event refresh...", "Running
   scheduled model refresh...", "Refreshing 3 client integration(s)...", "Client
   integrations refreshed.") move to `debug`. The caller owns the line and
   measures the whole pass (Nostr events + models + integrations), so
   `refreshModelsAndIntegrations` now returns a result instead of logging.
   Integration failures are counted into the line, and `runIntegrationsForClients`
   reports them via `logger.error` instead of `console.error` so they land in the
   dated log with a level.

3. `Using existing API key for <client>` moves to `debug`. That is the steady
   state for an already-configured client, and the `*/5 * * * * routstrd clients
   add --pi-agent` cron printed it 290 times/day. `Created new API key` stays at
   `info`.

The provider warnings themselves still come from @routstr/sdk 0.4.9 one per
provider; Routstr/routstr-sdk#89 replaces them with a transition-based summary,
and this level filter is what lets that detail stay available at `debug`.

Nothing parsed these strings: `routstrd logs` only tails and filters lines by
request id.

Tests: src/utils/logger.test.ts (level parsing, filtering, argument passthrough,
error never suppressed) and src/integrations/index.test.ts (summary formatting).
Verified end-to-end against a temp ROUTSTRD_DIR that each level writes exactly
the expected lines.
2026-10-09 11:34:46 +08:00

2.8 KiB

Logging

The daemon appends to dated files under ~/.routstrd/logs/<YYYY-MM-DD>.log (one per local day) and to ~/.routstrd/coco-logs/ for Cashu wallet-engine diagnostics. Writes are synchronous on purpose: the daemon calls process.exit() right after logger.error(...) on fatal paths, and async writes were silently dropped, making startup failures invisible.

Levels

ROUTSTRD_LOG_LEVEL sets the minimum level written to disk:

Value Written
debug everything, including per-provider and per-pass diagnostics
info (default) info, warn, error
warn warn, error
error error

An unparseable value falls back to info rather than muting the log.

The daemon repeats work on timers, so anything that would otherwise print once per pass is emitted at debug and summarized into a single info line. A scheduled refresh therefore costs one line by default:

[2026-10-05T15:42:27.044Z] [INFO] Scheduled refresh: 214 models, 3 client integration(s) in 33.4s

Set ROUTSTRD_LOG_LEVEL=debug to see the phases (Running scheduled model refresh...), the per-provider detail behind a failure summary, and the Nostr-discovery markers. The daemon inherits the variable from whatever starts it (routstrd start, pm2, systemd), so set it there, not per request.

coco-logs is the Cashu wallet-engine sink and writes debug regardless of ROUTSTRD_LOG_LEVEL, because those diagnostics are verbose by design (roughly 87% of those lines are debug) and live in their own directory precisely so they stay out of the main log. ROUTSTRD_COCO_LOG_LEVEL is its own knob, using the same values, if you do want it quieter (it is ~19k lines/day on a busy daemon).

What a healthy pass looks like

$ ROUTSTRD_LOG_LEVEL=info
[INFO] Routstr daemon listening on http://127.0.0.1:8008/v1
[INFO] Initial refresh: 214 models, 3 client integration(s) in 41.2s
[INFO] Model refresh: 32/58 providers ok, 26 unavailable (0 new, 26 unchanged) in 9.8s — maebarai.xyz(530), api.voltai.top(connect), +20 more
[INFO] Scheduled refresh: 214 models, 3 client integration(s) in 33.4s

The Model refresh: line comes from the SDK (@routstr/sdk): it reports reachability as state transitions plus one bounded summary, so a provider that has been down for days is a count, not a repeated warning. A provider that newly fails — or starts failing differently — still gets its own [WARN] Provider <url> unreachable: ... line, so anything worth alerting on stays greppable.

Inspecting

routstrd logs -n 50          # last 50 lines
routstrd logs -f             # follow (from debug.log, the daemon's stdout)
routstrd logs -c             # Cashu wallet-engine log instead
ROUTSTRD_LOG_LEVEL=debug routstrd start
ROUTSTRD_COCO_LOG_LEVEL=info routstrd start