Skip to content

feat(logging): keep this session's logs in memory and on disk - #5566

Open
aodhanroche wants to merge 1 commit into
mainfrom
Aodhan/260901/diagnostics-1-logging
Open

aodhanroche wants to merge 1 commit into
mainfrom
Aodhan/260901/diagnostics-1-logging

Conversation

@aodhanroche

@aodhanroche aodhanroche commented Sep 16, 2026

Copy link
Copy Markdown
Contributor

Issue

Engine logs are gone by the time someone asks for them, so every bug report starts with "can you run it again with logging on".

Fix

Two sinks on the griptape_nodes logger, both on by default: a 5,000-line ring buffer in memory, and a rotating file (10 MB × 5, aged out at 7 days). Console output unchanged, log_level still gates both.

Two gotchas: secret expansion is off, because secrets_manager doesn't exist yet inside ConfigManager.__init__. And settings get type-coerced, because "log_to_file": "false" would otherwise be a truthy string.

🤖 Generated with Claude Code


📚 Documentation preview 📚: https://griptape-nodes--5566.org.readthedocs.build/en/5566/

Reporting a problem currently means reproducing it: whatever the engine logged
on the way to the failure is gone as soon as the console scrolls, so the first
answer to any bug report is "can you run it again with more logging on".

Attaches two sinks to the shared `griptape_nodes` logger:

- A ring buffer of the most recent records, on by default at 5000 lines. It
  holds what just happened, so a report can carry the run that actually failed
  rather than a second one staged for the occasion.
- A rotating log file, on by default, so logs outlive the process. One file per
  engine process, rolling at 10 MB and keeping 5 rollovers, so a long-running
  engine cannot fill a disk. Files unwritten for `log_retention_days` are aged
  out when the engine starts and whenever a logging setting changes; the file
  being written is never deleted, however old it is.

Both sit at DEBUG and add no filtering, so `log_level` still decides what
reaches them. Both are process-global, because the logger they attach to is
shared by every `Engine` in the process, which makes
`configure_diagnostic_logging` idempotent and last-call-wins -- the same shape
`ConfigManager` already uses for the shared log level.

`ConfigManager` applies them at the end of every config load, so no caller has
to remember to, and skips the work when nothing relevant changed rather than
re-scanning the log directory on every config write. The settings are read with
secret expansion off: expansion resolves through `secrets_manager`, and this
runs inside `ConfigManager.__init__`, before `Engine` has one. They are also
coerced to their declared types, because `load_configs` keeps values as written
-- so `"log_to_file": "false"` from a config file is a truthy string that would
otherwise leave file logging on for someone who turned it off.

The settings reference now expands nested settings one level, so
`logging.log_to_file` gets its own row carrying the
`GTN_CONFIG_LOGGING__LOG_TO_FILE` spelling instead of only appearing as
"(nested object)".

Test isolation is set up in `pytest_configure` rather than a fixture: the
`ConfigManager` built at import time by `agent_manager` and `servers.mcp`
applies logging settings, so merely collecting those modules wrote into the
developer's real log directory and pruned week-old files out of it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@codecov

codecov Bot commented Sep 16, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@aodhanroche
aodhanroche added this pull request to stack #5574 September 16, 2026 17:06

@collindutter collindutter left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've got mixed feelings about the engine writing logs to a file. If anything, this seems like it should live in the app, not the engine. But even then, is there a precedent for CLI applications like this writing their logs to a file instead of stdout/stderr?

CC @alex-rumsey-foundry

@aodhanroche

Copy link
Copy Markdown
Contributor Author

@collindutter @alex-rumsey-foundry

AFAIK npm, pip, gcloud and Homebrew all keep a rolling file
alongside console output. And it's in addition to stdout here, not instead of:
console output is unchanged.

On app vs engine, We could split also it. The ring buffer stays in the engine, since the
engine assembles the bundle. The file is negotiable — but if the desktop app
owned it, the bundle has to read it back across repos.

Given we're steering people to the desktop app, is that the trade you want?

@alex-rumsey-foundry

alex-rumsey-foundry commented Sep 21, 2026

Copy link
Copy Markdown
Contributor

Hey @collindutter! Thanks for sharing your concern.

AFAIR Python's native logging module lets one logger fan out to multiple handlers at once, e.g. StreamHandler for stdout/stderr + RotatingFileHandler for the file, each with its own level/formatter. Engine can default to a stream and optionally support file handler, app can point file handler to the setting from the app. So it's composable IMO rather than "engine vs app."

And yeah, plenty of precedent: pip logs verbosely to a file while keeping stdout clean, lots of click/typer CLIs support --log-file alongside normal stdout output. Pattern's usually "stdout for the human, file for the postmortem," fed from the same log calls.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants