Skip to content

Logging

View Markdown~1,310 tokens

Caudra writes one structured JSON record per line to a rotating file. It is on by default, stays on your machine, and is the first thing to read when a run does something you did not expect.

Read it three ways:

  • /logs in the TUI, for a live view with fuzzy filtering and expansion.
  • caudra logs in a shell, for piping into jq or pasting into a bug report.
  • Any file reader, since each line is plain JSON.

In /logs, the wheel scrolls the page and clicking the level in the footer cycles it. / opens a filter field where each term matches as a subsequence against the message, the target, the level, and each field on its own. Space separates terms and a record has to match all of them, so provider retry finds a retry from the provider.

A record shows every field it carries, so a wide one runs past the right margin. w wraps it onto as many rows as it needs and the arrow keys pan across it, the same pair of answers the Workbench editor gives a long line. The arrow hint appears only while there is something out there to reach.

Tab on a selected record keeps only the records sharing its narrowest id, which turns a scattered turn or tool call into a readable sequence. A hint row lists the rest. See Commands for the keys.

PlatformPath
Linux~/.local/state/caudra/logs/caudra.log
macOS~/Library/Application Support/caudra/logs/caudra.log
Windows%APPDATA%\caudra\logs\caudra.log

Rotated files sit next to it as caudra.1.log, caudra.2.log, and so on, with the highest number being the oldest. storage.max_log_bytes_mb sets when rotation happens and storage.max_log_files sets how many to keep. Both are in Configuration.

Every Caudra process on the machine appends to the same file, so a record from a background session can appear between two records from the one in front of you. The session_id field tells them apart.

{
"timestamp": "2026-09-09T14:22:07.418123Z",
"level": "WARN",
"target": "caudra::provider",
"fields": {
"message": "request failed, retrying",
"event": "retry",
"attempt": 3,
"delay_ms": 4000,
"status": 529
},
"spans": [{ "name": "turn", "session_id": "01J8...", "turn_id": 4 }]
}

target says which part of Caudra spoke. Events Caudra raises on purpose use one of caudra::agent, caudra::provider, caudra::tool, caudra::mcp, caudra::permission, or caudra::session, and those are the ones worth filtering on. Anything else, including records from dependencies, carries the Rust module path it came from, such as isahc::handler.

fields.event names the specific thing that happened, and fields.outcome is one of ok, error, cancelled, or timeout.

spans carries the correlation ids. A record produced while a turn was running has session_id and turn_id, and a record produced inside a tool call adds tool and tool_use_id. To follow one tool call from dispatch to result, filter on its tool_use_id.

A line that is not valid JSON, such as a panic backtrace from a dependency, is kept as written and treated as an error, so a level filter never hides it.

The default level is info. Set storage.log_level in caudra.toml to trace, debug, info, warn, or error.

[storage]
log_level = "debug"

RUST_LOG overrides the config for one run, and takes the full tracing filter syntax, so you can raise one target without raising the rest:

Terminal window
RUST_LOG=caudra::provider=debug caudra

RUST_LOG never affects telemetry export, which has its own filter. See Telemetry.

Prompt text, model output, and tool input are not written. A prompt is recorded as prompt_length and a tool call as its name, its source, and how long it took.

Provider error messages are written in full, because they are usually the only thing that explains a failed run and they stay on your machine. A provider that echoes the request back in an error body puts that body in the log, so read the file before pasting it into an issue. The collector only ever receives the status code.

The opt-ins live under telemetry: log_user_prompts adds the prompt and log_tool_details adds the tool input, to the log file and to the collector together. Both require telemetry.enabled, so with telemetry off the text is never written anywhere. Turn them on while debugging and off afterwards. Telemetry lists what each one adds.

The writer runs on its own thread and the agent never waits for it. When a burst outruns the disk, the newest records are dropped and a warning records how many, which keeps a slow disk from slowing a run. Caudra flushes the queue on a clean exit and on a panic, so the record that explains a crash reaches the file.

Lua plugins write to the same file through caudra.log.info|warn|error. They run only when experimental.lua_plugins is on. See Plugins.

Website privacy and analytics