-
Notifications
You must be signed in to change notification settings - Fork 2.3k
feat: log rotation #5561
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: log rotation #5561
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull Request Overview
This PR reorganizes the logging structure by moving LLM request logs into a dedicated llm subdirectory and implementing automated cleanup of old log files. The changes establish better log organization and prevent log directory bloat by removing files older than two weeks.
Key changes:
- LLM request logs are now stored in
logs/llm/instead of directly inlogs/ - New
cleanup_old_logs()function removes logs older than 14 days - Log rotation changed from
NEVERtoDAILYfor both CLI and server
Reviewed Changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
| crates/goose/src/providers/utils.rs | Updates LLM request log paths to use logs/llm subdirectory |
| crates/goose/src/logging.rs | Adds cleanup_old_logs() function for removing logs older than 14 days |
| crates/goose-server/src/logging.rs | Integrates log cleanup calls and switches to daily log rotation |
| crates/goose-cli/src/logging.rs | Integrates log cleanup calls and switches to daily log rotation |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
crates/goose/src/logging.rs
Outdated
| // Get the metadata to check the modification time | ||
| if let Ok(metadata) = entry.metadata() { | ||
| if let Ok(modified) = metadata.modified() { | ||
| // If the file/directory is older than one week, remove it |
Copilot
AI
Nov 4, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The comment incorrectly states 'older than one week', but the code on line 41 implements a two-week threshold (14 * 24 * 60 * 60 seconds). Update the comment to match the actual implementation.
| // If the file/directory is older than one week, remove it | |
| // If the file/directory is older than two weeks, remove it |
b595d84 to
f90b1c9
Compare
|
I think it solves this #4365 one too... i was working on this then saw new requirements.. so I put it on hold.. |
crates/goose-server/src/logging.rs
Outdated
| let log_dir = get_log_directory()?; | ||
| let _ = goose::logging::cleanup_old_logs("server"); | ||
| let _ = goose::logging::cleanup_old_logs("llm"); | ||
| let log_dir = goose::logging::get_log_directory("server", true)?; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we actually have two setup_logging methods, one for the server and one for the cli. the get_log_directory is shared though. I think the right move would be to either just have one and use the name here (don't put it in a server or cli folder, just have a server.log, cli.log and an mcp.log) but that's a rather bigger refactor. or we can just rename this function to prepare_log_directory that also does the clean up? definitely need to set up the log rotation also for the cli though
crates/goose-server/src/logging.rs
Outdated
| // Create daily rolling file appender for detailed logs | ||
| let file_appender = | ||
| tracing_appender::rolling::RollingFileAppender::new(Rotation::NEVER, log_dir, log_filename); | ||
| tracing_appender::rolling::RollingFileAppender::new(Rotation::DAILY, log_dir, log_filename); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think this works - we were previously doing our own timestamping, so this is just going to produce log files of the form:
20251104_143052.log.2025-11-04
so we can use the rotation, but then just get rid of the old timestamp code or just leave it as is
crates/goose/src/logging.rs
Outdated
| } | ||
|
|
||
| let two_weeks = SystemTime::now() - Duration::from_secs(14 * 24 * 60 * 60); | ||
| let entries = fs::read_dir(&component_dir).context("Failed to read log directory")?; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd drop the context
crates/goose/src/logging.rs
Outdated
| if let Ok(metadata) = entry.metadata() { | ||
| if let Ok(modified) = metadata.modified() { | ||
| if modified < two_weeks { | ||
| if path.is_dir() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would only remove dirs here - that's what we expect and if there are files, maybe somebody put them there for a reason? i.e. copied a log file out of the sub folder for save keeping? also not sure about logging inside of a logger
f90b1c9 to
843029b
Compare
843029b to
a318728
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull Request Overview
Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.
Comments suppressed due to low confidence (1)
crates/goose/src/providers/utils.rs:521
- Calling
prepare_log_directoryin thefinishmethod triggers log cleanup on every request completion. This could cause unnecessary filesystem operations and performance overhead for high-frequency requests. Consider caching the logs directory path in theRequestLogstruct during initialization in thestartmethod, or implementing a rate-limiting mechanism for cleanup operations.
self.write_json(&serde_json::json!({
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| pub fn prepare_log_directory(component: &str, use_date_subdir: bool) -> Result<PathBuf> { | ||
| let base_log_dir = Paths::in_state_dir("logs"); | ||
|
|
||
| let _ = cleanup_old_logs(component); |
Copilot
AI
Nov 4, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The error result from cleanup_old_logs is silently discarded. While this may be intentional to prevent cleanup failures from blocking log directory preparation, it would be better to log the error for debugging purposes. Consider using if let Err(e) = cleanup_old_logs(component) { tracing::warn!(\"Failed to cleanup old logs: {}\", e); } to provide visibility into cleanup failures.
| let _ = cleanup_old_logs(component); | |
| if let Err(e) = cleanup_old_logs(component) { | |
| tracing::warn!("Failed to cleanup old logs: {}", e); | |
| } |
|
|
||
| Ok(log_dir) | ||
| } | ||
|
|
Copilot
AI
Nov 4, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The cleanup_old_logs function is missing documentation. Add a doc comment explaining its purpose, parameters, and behavior (e.g., that it removes log files and directories older than 14 days).
| /// Removes log files and directories older than 14 days for the specified component. | |
| /// | |
| /// # Arguments | |
| /// | |
| /// * `component` - The component name (e.g., "cli", "server", "debug") whose log directory will be cleaned. | |
| /// | |
| /// # Behavior | |
| /// | |
| /// Scans the log directory for the given component and removes any files or subdirectories | |
| /// whose last modification time is older than 14 days. Ignores missing directories. | |
| /// | |
| /// Returns `Ok(())` on success, or an error if the log directory cannot be read. |
a318728 to
e0883ab
Compare
|
@DOsinga Addressed all feedback. Reaping now works the same way for all three of The content of the llm ones is a little different in the |
e0883ab to
a76840d
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull Request Overview
Copilot reviewed 4 out of 4 changed files in this pull request and generated 3 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| pub fn prepare_log_directory(component: &str, use_date_subdir: bool) -> Result<PathBuf> { | ||
| let base_log_dir = Paths::in_state_dir("logs"); | ||
|
|
||
| let _ = cleanup_old_logs(component); |
Copilot
AI
Nov 4, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Errors from cleanup_old_logs are silently ignored. This could hide issues like permission problems or I/O errors. Consider logging a warning when cleanup fails or propagating the error if it's critical. Example: cleanup_old_logs(component).ok() with a log message, or remove the let _ = if the error truly doesn't matter.
crates/goose/src/logging.rs
Outdated
| if let Ok(modified) = metadata.modified() { | ||
| if modified < two_weeks { | ||
| if path.is_dir() { | ||
| let _ = fs::remove_dir_all(&path); |
Copilot
AI
Nov 4, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The cleanup logic only removes directories but ignores files. For components like 'llm' that create .jsonl files directly in date-based subdirectories, old files will never be cleaned up. Either remove the is_dir() check to also clean up files, or add an else branch to handle files: else { let _ = fs::remove_file(&path); }
| let _ = fs::remove_dir_all(&path); | |
| let _ = fs::remove_dir_all(&path); | |
| } else { | |
| let _ = fs::remove_file(&path); |
crates/goose/src/logging.rs
Outdated
| if let Ok(modified) = metadata.modified() { | ||
| if modified < two_weeks { | ||
| if path.is_dir() { | ||
| let _ = fs::remove_dir_all(&path); |
Copilot
AI
Nov 4, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Errors from fs::remove_dir_all are silently ignored. If a directory cannot be removed (e.g., due to permissions or a file being in use), the cleanup will silently fail and the old logs will accumulate. Consider logging a warning or debug message when removal fails.
| let _ = fs::remove_dir_all(&path); | |
| if let Err(e) = fs::remove_dir_all(&path) { | |
| eprintln!("Warning: Failed to remove directory {:?}: {}", path, e); | |
| } |
a76840d to
564aa4c
Compare
…est-and-fix * 'main' of github.com:block/goose: improve linux tray icon support (#5425) feat: log rotation (#5561) use app.isPackaged instead of checking for node env development (#5465) disable RPM build-ID generation to prevent package conflicts (#5563) Add Diagnostics Info to Q&A and Bug Report Templates (#5565) fix: improve server error messages to include HTTP status code (#5532) improvement: add useful error message when attempting to use unauthenticated cursor-agent (#5300) fix: unblock acp via databricks (#5562) feat: add --output-format json flag to goose run command (#5525)
* main: (85 commits) improve linux tray icon support (#5425) feat: log rotation (#5561) use app.isPackaged instead of checking for node env development (#5465) disable RPM build-ID generation to prevent package conflicts (#5563) Add Diagnostics Info to Q&A and Bug Report Templates (#5565) fix: improve server error messages to include HTTP status code (#5532) improvement: add useful error message when attempting to use unauthenticated cursor-agent (#5300) fix: unblock acp via databricks (#5562) feat: add --output-format json flag to goose run command (#5525) Sessions required (#5548) feat: add grouped extension loading notification (#5529) we should run this on main and also test open models at least via ope… (#5556) info: print location of sessions.db via goose info (#5557) chore: remove yarn usage from documentation (#5555) cli: adjust default theme to address #1905 (#5552) Manual compaction counting fix + cli cleanup (#5480) chore(deps): bump prismjs and react-syntax-highlighter in /ui/desktop (#5549) fix: remove qwen3-coder from provider/mcp smoke tests (#5551) fix: do not build unsigned desktop app bundles on every PR in ci. add manual option. (#5550) fix: update Husky prepare script to v9 format (#5522) ...
* main: (54 commits) add clippy warning for string_slice (#5422) improve linux tray icon support (#5425) feat: log rotation (#5561) use app.isPackaged instead of checking for node env development (#5465) disable RPM build-ID generation to prevent package conflicts (#5563) Add Diagnostics Info to Q&A and Bug Report Templates (#5565) fix: improve server error messages to include HTTP status code (#5532) improvement: add useful error message when attempting to use unauthenticated cursor-agent (#5300) fix: unblock acp via databricks (#5562) feat: add --output-format json flag to goose run command (#5525) Sessions required (#5548) feat: add grouped extension loading notification (#5529) we should run this on main and also test open models at least via ope… (#5556) info: print location of sessions.db via goose info (#5557) chore: remove yarn usage from documentation (#5555) cli: adjust default theme to address #1905 (#5552) Manual compaction counting fix + cli cleanup (#5480) chore(deps): bump prismjs and react-syntax-highlighter in /ui/desktop (#5549) fix: remove qwen3-coder from provider/mcp smoke tests (#5551) fix: do not build unsigned desktop app bundles on every PR in ci. add manual option. (#5550) ...
| if let Some(mut writer) = self.writer.take() { | ||
| writer.flush()?; | ||
| let logs_dir = Paths::in_state_dir("logs"); | ||
| let logs_dir = crate::logging::prepare_log_directory("llm", true)?; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we should undo this; the llm logs are special and huge; keep only 10 of them and we send them out with the diagnostics
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
will do
* main: (41 commits) Add pending extension indicator to extension panel (#5493) Add environment subsition for auth blocks (#5439) acp: ToolCallLocations and working cancellation (#5588) feat(providers): add Mistral AI provider (#5009) Listen for ctrl-c during provider request (#5585) Also accept null as description, not just missing (#5589) Document missing recipe param types (#5584) docs: description required for "Add Extension" in cli (#5573) fix: Add schema-aware numeric coercion for MCP tool arguments (#5478) Add uv for uvx in Justfile (#5581) Keep llm logs in place (#5577) bump to 1.12.0 (#5580) automate more of the release process (#5409) add clippy warning for string_slice (#5422) improve linux tray icon support (#5425) feat: log rotation (#5561) use app.isPackaged instead of checking for node env development (#5465) disable RPM build-ID generation to prevent package conflicts (#5563) Add Diagnostics Info to Q&A and Bug Report Templates (#5565) fix: improve server error messages to include HTTP status code (#5532) ...
* main: (53 commits) acp: ToolCallLocations and working cancellation (#5588) feat(providers): add Mistral AI provider (#5009) Listen for ctrl-c during provider request (#5585) Also accept null as description, not just missing (#5589) Document missing recipe param types (#5584) docs: description required for "Add Extension" in cli (#5573) fix: Add schema-aware numeric coercion for MCP tool arguments (#5478) Add uv for uvx in Justfile (#5581) Keep llm logs in place (#5577) bump to 1.12.0 (#5580) automate more of the release process (#5409) add clippy warning for string_slice (#5422) improve linux tray icon support (#5425) feat: log rotation (#5561) use app.isPackaged instead of checking for node env development (#5465) disable RPM build-ID generation to prevent package conflicts (#5563) Add Diagnostics Info to Q&A and Bug Report Templates (#5565) fix: improve server error messages to include HTTP status code (#5532) improvement: add useful error message when attempting to use unauthenticated cursor-agent (#5300) fix: unblock acp via databricks (#5562) ...
* origin/main: (75 commits) fix: customised recipe to yaml string to avoid minininjia parsing error (#5494) Add pending extension indicator to extension panel (#5493) Add environment subsition for auth blocks (#5439) acp: ToolCallLocations and working cancellation (#5588) feat(providers): add Mistral AI provider (#5009) Listen for ctrl-c during provider request (#5585) Also accept null as description, not just missing (#5589) Document missing recipe param types (#5584) docs: description required for "Add Extension" in cli (#5573) fix: Add schema-aware numeric coercion for MCP tool arguments (#5478) Add uv for uvx in Justfile (#5581) Keep llm logs in place (#5577) bump to 1.12.0 (#5580) automate more of the release process (#5409) add clippy warning for string_slice (#5422) improve linux tray icon support (#5425) feat: log rotation (#5561) use app.isPackaged instead of checking for node env development (#5465) disable RPM build-ID generation to prevent package conflicts (#5563) Add Diagnostics Info to Q&A and Bug Report Templates (#5565) ...
Signed-off-by: fbalicchia <[email protected]>
Signed-off-by: Blair Allan <[email protected]>
Simple log rotation to prevent runaway log file storage. Will fix #4965 and #4365
Approach:
llmsubdirectory forllm-requestlogs so they can use the same cleanup strategyNotes:
cliandserverdirsrmof logs at the top level of the log dir, as I moved llm-requests to a new subdir, but after that should work for everything automatically