Logging
This page is for the curious, or for anyone hacking on rt itself: how logging is structured across the codebase, and where to find the log files.
The seams model
Logging in rt is structural, not per-feature: outcomes are logged at a small number of central seams, and feature code only logs domain events invisible at those seams. Concretely:
- CLI commands.
dispatch()(inlib/command-tree.ts) logs every command's outcome, and CLI-wide crash/error capture covers everyprocess.exit()path and persists crash stacks. A new command gets usage, error, and crash logging for free, with no logging code of its own. - Daemon commands. Every IPC/REST command funnels through a single
handleCommanddispatcher inlib/daemon.ts, which logs ok/rejected/threw with duration for each call. A new handler inherits this automatically. - Daemon crashes. Dedicated crash handlers capture uncaught exceptions, unhandled rejections, JS-side stderr, and native Bun panics.
- Menu bar app. mattstack.app's own logging API is the only way it logs (never a bare system log call), and it logs every non-2xx reply it sends.
Because outcomes are already logged at these seams, feature code almost never needs to add logging itself. What feature code should log is domain events: things invisible at the seams, like a sync fast-forwarding or a watcher being rewired. Those go through a per-module logger, generally at debug level (noisy/periodic events) since the default log level is info.
The file convention
Every surface (daemon, CLI, the menu bar app's tray surface, and any future surface that follows the same convention) appends JSON lines to:
~/.mattstack/rt/logs/<surface>.YYYY-MM-DD[.N].log
For example, cli.2026-07-17.log, tray.2026-07-17.log, or daemon.2026-07-17.1.log (the daemon's rotating writer always numbers its files, and starts a new one when a file passes 50 MB).
rt daemon logs auto-discovers surfaces by matching this filename pattern in ~/.mattstack/rt/logs/ and following the newest file per surface. That means a new surface which adopts the convention shows up in the log viewer automatically, with nothing to register.
Why this matters when adding a feature
Before adding logging to a new feature, check whether the seam above already covers it. Almost always, it does. The one thing to avoid: swallowing errors below a logged seam. An empty catch is fine only for genuinely expected conditions (a socket already closed, a file already gone); anything else should still be logged as a warning so it's visible in rt daemon logs (or the CLI log) later.