diff --git a/Cargo.lock b/Cargo.lock index 025aafb2..f4ccd4dc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1572,6 +1572,7 @@ dependencies = [ "tokio", "toml 0.8.2", "tracing", + "tracing-appender", "tracing-subscriber", "uuid", "which", @@ -10263,6 +10264,18 @@ dependencies = [ "tracing-core", ] +[[package]] +name = "tracing-appender" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "786d480bce6247ab75f005b14ae1624ad978d3029d9113f0a22fa1ac773faeaf" +dependencies = [ + "crossbeam-channel", + "thiserror 2.0.18", + "time", + "tracing-subscriber", +] + [[package]] name = "tracing-attributes" version = "0.1.31" diff --git a/cortex-cli/Cargo.toml b/cortex-cli/Cargo.toml index 72444c7d..678cd329 100644 --- a/cortex-cli/Cargo.toml +++ b/cortex-cli/Cargo.toml @@ -46,6 +46,7 @@ tokio = { workspace = true, features = ["full"] } anyhow = { workspace = true } tracing = { workspace = true } tracing-subscriber = { workspace = true, features = ["env-filter"] } +tracing-appender = { workspace = true } dirs = { workspace = true } serde_json = { workspace = true } chrono = { workspace = true } diff --git a/cortex-cli/src/main.rs b/cortex-cli/src/main.rs index fffbe8f7..e18dfdda 100644 --- a/cortex-cli/src/main.rs +++ b/cortex-cli/src/main.rs @@ -286,8 +286,10 @@ struct InteractiveArgs { )] log_level: LogLevel, - /// Enable debug logging (deprecated: use --log-level debug instead) - #[arg(long, hide = true)] + /// Enable debug mode: writes ALL trace-level logs to ./debug.txt + /// This captures all Rust logs, tracing events, and internal diagnostics + /// to help troubleshoot issues when the agent doesn't return expected output. + #[arg(long = "debug", help_heading = "Debugging")] debug: bool, /// Initial prompt (if no subcommand) @@ -672,6 +674,62 @@ fn pre_main_hardening() { cortex_process_hardening::pre_main_hardening(); } +/// Guard that ensures debug log file is properly flushed when dropped. +struct DebugLogGuard { + _guard: tracing_appender::non_blocking::WorkerGuard, +} + +/// Set up debug file logging that writes ALL trace-level logs to ./debug.txt. +/// +/// This function configures tracing to write comprehensive debug information +/// to a file in the current working directory. It captures: +/// - All Rust log levels (trace, debug, info, warn, error) +/// - Tracing spans and events from all crates +/// - Internal diagnostics for troubleshooting +/// +/// Returns a guard that must be kept alive for the duration of logging. +fn setup_debug_file_logging() -> Result { + use std::fs::File; + use tracing_subscriber::layer::SubscriberExt; + use tracing_subscriber::util::SubscriberInitExt; + + let debug_file_path = std::env::current_dir()?.join("debug.txt"); + + // Create/truncate the debug file + let file = File::create(&debug_file_path).map_err(|e| { + anyhow::anyhow!( + "Failed to create debug.txt: {}. Check write permissions.", + e + ) + })?; + + // Use non-blocking writer for performance + let (non_blocking, guard) = tracing_appender::non_blocking(file); + + // Create a formatting layer that writes to the file + let file_layer = tracing_subscriber::fmt::layer() + .with_writer(non_blocking) + .with_ansi(false) // No ANSI colors in file + .with_target(true) // Include module path + .with_thread_ids(true) // Include thread IDs + .with_thread_names(true) // Include thread names + .with_file(true) // Include source file + .with_line_number(true); // Include line numbers + + // Initialize the subscriber with trace level (captures everything) + tracing_subscriber::registry() + .with(tracing_subscriber::EnvFilter::new("trace")) + .with(file_layer) + .init(); + + eprintln!( + "Debug mode enabled: logging to {}", + debug_file_path.display() + ); + + Ok(DebugLogGuard { _guard: guard }) +} + #[tokio::main] async fn main() -> Result<()> { // Install Ctrl+C handler to restore terminal state before exiting @@ -679,14 +737,20 @@ async fn main() -> Result<()> { let cli = Cli::parse(); - // Initialize logging only for non-TUI commands + // Initialize debug file logging if --debug flag is passed + // This writes ALL trace-level logs to ./debug.txt for troubleshooting + let _debug_guard = if cli.interactive.debug { + Some(setup_debug_file_logging()?) + } else { + None + }; + + // Initialize logging only for non-TUI commands (when not in debug mode) // TUI mode has its own file-based logging to avoid stdout pollution - if cli.command.is_some() { - // Determine log level with precedence: --debug flag > --log-level > CORTEX_LOG_LEVEL env > default (info) - let log_level = if cli.interactive.debug { - // Deprecated --debug flag takes precedence for backward compatibility - LogLevel::Debug - } else if let Ok(env_level) = std::env::var("CORTEX_LOG_LEVEL") { + // Debug mode uses file logging configured above + if cli.command.is_some() && !cli.interactive.debug { + // Determine log level with precedence: --log-level > CORTEX_LOG_LEVEL env > default (info) + let log_level = if let Ok(env_level) = std::env::var("CORTEX_LOG_LEVEL") { // Check environment variable LogLevel::from_str_loose(&env_level).unwrap_or(cli.interactive.log_level) } else {