Skip to main content

limit_cli/
logging.rs

1use std::env;
2use std::fs::OpenOptions;
3use std::io::Write;
4use std::path::PathBuf;
5use tracing_subscriber::{fmt, EnvFilter};
6
7#[allow(dead_code)]
8pub fn init_logging() {
9    // Create log file in ~/.limit/logs/
10    let home = env::var("HOME").unwrap_or_else(|_| ".".to_string());
11    let log_dir = PathBuf::from(home).join(".limit").join("logs");
12
13    let _ = std::fs::create_dir_all(&log_dir);
14
15    let log_path = log_dir.join("tui.log");
16
17    // Install panic hook to log panics to file before the default handler aborts
18    install_panic_hook(&log_path);
19
20    // Open file in append mode
21    let file = OpenOptions::new().create(true).append(true).open(&log_path);
22
23    match file {
24        Ok(f) => {
25            let default_level = if cfg!(debug_assertions) {
26                "debug,limit_llm=debug,limit_agent=debug,limit_cli=debug,reqwest=warn,hyper=warn,h2=warn,ignore=warn,globset=warn,rustls=warn"
27            } else {
28                "warn,limit_llm=warn,limit_agent=warn,limit_cli=warn,reqwest=warn,hyper=warn,h2=warn,ignore=warn,globset=warn,rustls=warn"
29            };
30            let filter =
31                EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new(default_level));
32
33            fmt()
34                .with_writer(f)
35                .with_target(true)
36                .with_ansi(false)
37                .with_env_filter(filter)
38                .init();
39
40            tracing::info!("=== Logging initialized to {:?} ===", log_path);
41        }
42        Err(e) => {
43            eprintln!("Warning: Could not open log file {:?}: {}", log_path, e);
44            // Fallback to stderr
45            let default_level = if cfg!(debug_assertions) {
46                "debug"
47            } else {
48                "warn"
49            };
50            let filter =
51                EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new(default_level));
52
53            fmt().with_env_filter(filter).init();
54        }
55    }
56}
57
58fn install_panic_hook(log_path: &std::path::Path) {
59    let log_path = log_path.to_path_buf();
60    std::panic::set_hook(Box::new(move |info| {
61        let timestamp = chrono::Local::now().format("%Y-%m-%dT%H:%M:%S%.6fZ");
62        let thread = std::thread::current()
63            .name()
64            .unwrap_or("unnamed")
65            .to_string();
66        let payload = info
67            .payload()
68            .downcast_ref::<&str>()
69            .map(|s| s.to_string())
70            .or_else(|| info.payload().downcast_ref::<String>().cloned())
71            .unwrap_or_else(|| "unknown panic".to_string());
72        let location = info
73            .location()
74            .map(|loc| format!("{}:{}", loc.file(), loc.line()))
75            .unwrap_or_else(|| "unknown location".to_string());
76
77        let msg = format!(
78            "{timestamp} PANIC [{thread}] {payload}\n  at {location}\n",
79            timestamp = timestamp,
80            thread = thread,
81            payload = payload,
82            location = location,
83        );
84
85        // Write to log file
86        if let Ok(mut f) = OpenOptions::new().create(true).append(true).open(&log_path) {
87            let _ = f.write_all(msg.as_bytes());
88        }
89        // Also print to stderr so it's not completely silent
90        eprintln!("{}", msg);
91    }));
92}