Skip to content

Conversation

@alexhancock
Copy link
Collaborator

@alexhancock alexhancock commented Nov 4, 2025

Simple log rotation to prevent runaway log file storage. Will fix #4965 and #4365

Approach:

  • Rotates log content to new files daily
  • Deletes log files after two weeks
  • Sets up a new llm subdirectory for llm-request logs so they can use the same cleanup strategy

Notes:

  • There wasn't a super natural place to call the cleanup function for llm requests, so I coupled calls to it when we do cleanup for cli and server dirs
  • This will require a one time manual rm of 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

Copy link
Contributor

Copilot AI left a 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 in logs/
  • New cleanup_old_logs() function removes logs older than 14 days
  • Log rotation changed from NEVER to DAILY for 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.

// 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
Copy link

Copilot AI Nov 4, 2025

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.

Suggested change
// If the file/directory is older than one week, remove it
// If the file/directory is older than two weeks, remove it

Copilot uses AI. Check for mistakes.
@alexhancock alexhancock force-pushed the alexhancock/log-rotation branch from b595d84 to f90b1c9 Compare November 4, 2025 15:12
@sheikhlimon
Copy link
Contributor

I think it solves this #4365 one too... i was working on this then saw new requirements.. so I put it on hold..

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)?;
Copy link
Collaborator

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

// 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);
Copy link
Collaborator

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

}

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")?;
Copy link
Collaborator

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

if let Ok(metadata) = entry.metadata() {
if let Ok(modified) = metadata.modified() {
if modified < two_weeks {
if path.is_dir() {
Copy link
Collaborator

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

Copilot AI review requested due to automatic review settings November 4, 2025 18:01
@alexhancock alexhancock force-pushed the alexhancock/log-rotation branch from f90b1c9 to 843029b Compare November 4, 2025 18:01
@alexhancock alexhancock force-pushed the alexhancock/log-rotation branch from 843029b to a318728 Compare November 4, 2025 18:01
Copy link
Contributor

Copilot AI left a 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_directory in the finish method 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 the RequestLog struct during initialization in the start method, 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);
Copy link

Copilot AI Nov 4, 2025

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.

Suggested change
let _ = cleanup_old_logs(component);
if let Err(e) = cleanup_old_logs(component) {
tracing::warn!("Failed to cleanup old logs: {}", e);
}

Copilot uses AI. Check for mistakes.

Ok(log_dir)
}

Copy link

Copilot AI Nov 4, 2025

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).

Suggested change
/// 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.

Copilot uses AI. Check for mistakes.
@alexhancock alexhancock force-pushed the alexhancock/log-rotation branch from a318728 to e0883ab Compare November 4, 2025 18:40
@alexhancock
Copy link
Collaborator Author

@DOsinga Addressed all feedback. Reaping now works the same way for all three of cli, server, llm using a unified prepare_log_directory which also does cleanup. File structure now looks like:

13:38:55 ~/.local/state/goose $ tree logs
logs
├── cli
│   └── 2025-11-04
│       ├── 20251104_132253.log
│       └── 20251104_132300.log
├── llm
│   └── 2025-11-04
│       ├── llm_request.0.jsonl
│       ├── llm_request.1.jsonl
│       ├── llm_request.2.jsonl
│       └── llm_request.3.jsonl
└── server
    └── 2025-11-04
        ├── 20251104_133833-goosed.log
        └── 20251104_133844-mcp-developer.log

The content of the llm ones is a little different in the .jsonl format so I left the files themselves alone for that, but made the directory structure nicer.

Copilot AI review requested due to automatic review settings November 4, 2025 18:51
@alexhancock alexhancock force-pushed the alexhancock/log-rotation branch from e0883ab to a76840d Compare November 4, 2025 18:51
Copy link
Contributor

Copilot AI left a 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);
Copy link

Copilot AI Nov 4, 2025

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.

Copilot uses AI. Check for mistakes.
if let Ok(modified) = metadata.modified() {
if modified < two_weeks {
if path.is_dir() {
let _ = fs::remove_dir_all(&path);
Copy link

Copilot AI Nov 4, 2025

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); }

Suggested change
let _ = fs::remove_dir_all(&path);
let _ = fs::remove_dir_all(&path);
} else {
let _ = fs::remove_file(&path);

Copilot uses AI. Check for mistakes.
if let Ok(modified) = metadata.modified() {
if modified < two_weeks {
if path.is_dir() {
let _ = fs::remove_dir_all(&path);
Copy link

Copilot AI Nov 4, 2025

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.

Suggested change
let _ = fs::remove_dir_all(&path);
if let Err(e) = fs::remove_dir_all(&path) {
eprintln!("Warning: Failed to remove directory {:?}: {}", path, e);
}

Copilot uses AI. Check for mistakes.
@alexhancock alexhancock force-pushed the alexhancock/log-rotation branch from a76840d to 564aa4c Compare November 4, 2025 19:40
@alexhancock alexhancock merged commit cd2ea77 into main Nov 4, 2025
14 checks passed
katzdave added a commit that referenced this pull request Nov 4, 2025
…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)
wpfleger96 added a commit that referenced this pull request Nov 4, 2025
* 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)
  ...
wpfleger96 added a commit that referenced this pull request Nov 5, 2025
* 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)?;
Copy link
Collaborator

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

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

will do

michaelneale added a commit that referenced this pull request Nov 6, 2025
* 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)
  ...
michaelneale added a commit that referenced this pull request Nov 6, 2025
* 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)
  ...
aharvard added a commit that referenced this pull request Nov 6, 2025
* 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)
  ...
fbalicchia pushed a commit to fbalicchia/goose that referenced this pull request Nov 7, 2025
BlairAllan pushed a commit to BlairAllan/goose that referenced this pull request Nov 29, 2025
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.

goosed runaway logging with no rotation under ~/.local/state/goose/logs/server on macOS — logs up to 630 GB per file

4 participants