Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Tracing Conventions

Catenary uses the tracing crate for all logging and telemetry. LoggingServer subscribes to every tracing event and dispatches to its sinks: the per-root-sharded JSONL firehose (catenary query, everything), the desktop-notification sink (error severity, the urgent interrupt), and — in the daemon — the state.json snapshot writer (the TUI health surface). The store-and-forward systemMessage notification queue retired in the TUI rework: warns no longer interrupt a transcript, they persist as health findings on the dashboard.

Severity guidelines

Severity chooses the delivery channel:

  • error!() — reaches the desktop-notification sink (the OS-level urgent interrupt, deduped per daemon lifetime, suppressible via [notifications] desktop = false or CATENARY_NOTIFY=0) and the TUI health surface and the firehose.
  • warn!() — reaches the TUI health surface (a warn is a health finding — stale hooks, version skew, 027 degradation) and the firehose. It no longer interrupts.
  • info!() / debug!() — the firehose only.

Choose severity by asking:

User cares?Actionable?Interrupt-worthy?Severity
Nodebug!()
YesNoinfo!()
YesYesYes (systemic failure)error!()
YesYesNo (recoverable / a health finding)warn!()

Use error!() only for conditions that indicate a systemic failure (e.g., root resolution failed, critical I/O error) — an error fires a desktop notification, so it must earn the interrupt. Use warn!() for degradation that the user should know about but that Catenary can recover from (e.g., server died, roots/list failed); it surfaces durably on the dashboard rather than interrupting.

Server-forwarded events are firehose-only

Events forwarded verbatim from an LSP server’s window/logMessage or window/showMessage are tagged source = lsp.logging at the forwarding site. A server’s showMessage type 1 maps to error, but it is still just that server’s own chatter about itself — not Catenary’s own user-actionable event — so the maintainer ruled (CatenaryInternal misc 125) it is firehose-only and belongs on the TUI’s secondary Activity/Alerts surface, never the desktop interrupt. It stays fully queryable in the JSONL firehose; a genuinely broken server surfaces where it matters (a routed-but-broken server is a health finding).

Reserved structured fields

kind       — "lsp" | "mcp" | "hook" — marks a protocol event in the firehose
method     — Protocol method name (LSP/MCP method)
server     — LSP server name ("rust-analyzer", "pylsp", ...)
client     — Client identifier ("claude-code", "antigravity")
request_id — In-process correlation id (i64)
parent_id  — Correlation id of the causing event (i64)
source     — Subsystem that emitted the event (see taxonomy below)
language   — Language id ("rust", "python", ...)
payload    — Raw protocol JSON string (for kind = lsp|mcp|hook)

warn!()/error!() events should carry source, server, and language where applicable: they key the firehose query surface (catenary query) and the health finding an event maps to, so an event with a stable identity groups cleanly instead of scrolling by as noise.

Source taxonomy

The source field uses a fixed two-level subsystem.concern taxonomy. The Source enum in src/source.rs is the single source of truth; convenience constants are derived from it for use in tracing macros.

Subsystems

SubsystemScope
configConfiguration loading and validation
daemonDaemon process (socket listeners, connection management)
hookHook layer (pre/post tool hooks)
loggingLogging infrastructure itself
lspLSP client layer (server communication, lifecycle, routing)
mcpMCP server layer (host communication, dispatch)

Concerns

ConcernMeaning
bootstrapStartup sequencing
dispatchMessage routing, method dispatch, capability checks
lifecycleSpawn, init, crash, recovery, shutdown
loggingForwarded server window messages (window/logMessage, window/showMessage)
parseParsing and deserialization
stderrRaw server process stderr output
validationSemantic correctness checks

Valid combinations

SourceDescriptionConstant
config.parseConfig loading errors (TOML parsing, deserialization)ConfigParse
config.validationSemantic config errors (orphan servers, unsupported keys)ConfigValidation
daemon.dispatchConnection accept, correlation, session routingDaemonDispatch
daemon.lifecycleDaemon startup, shutdown, signal handlingDaemonLifecycle
hook.dispatchHook request routing and dispatchHookDispatch
logging.bootstrapLogging infrastructure startup sequencingLoggingBootstrap
lsp.dispatchLSP message routing, method dispatch, capability checksLspDispatch
lsp.lifecycleServer spawn, init, crash, recovery, shutdownLspLifecycle
lsp.loggingServer window/logMessage / window/showMessage telemetry (firehose-only, never a desktop interrupt)LspLogging
lsp.stderrRaw server process stderr outputLspStderr
mcp.dispatchMCP message dispatch and roots handlingMcpDispatch

Not every subsystem uses every concern. Only the combinations listed above are valid. New values must be added as variants to the Source enum in src/source.rs.

Protocol events

Protocol boundary components (McpServer, Connection/LspServer, HookServer) emit structured tracing::info!() events with kind, method, request_id, parent_id, and payload fields. At info severity they land in the firehose only — never a desktop interrupt.