@@ -8,16 +8,68 @@ |
| 8 | 8 | pub mod commands; |
| 9 | 9 | pub mod core; |
| 10 | 10 | |
| 11 | +use std::path::PathBuf; |
| 12 | +use std::sync::OnceLock; |
| 13 | + |
| 11 | 14 | use tauri::Manager; |
| 15 | +use tracing_appender::non_blocking::WorkerGuard; |
| 16 | +use tracing_subscriber::layer::SubscriberExt; |
| 17 | +use tracing_subscriber::util::SubscriberInitExt; |
| 18 | +use tracing_subscriber::Layer; |
| 19 | + |
| 20 | +/// Holds the non-blocking appender's worker thread. Dropping the |
| 21 | +/// guard flushes pending log writes, so we stash it in a static |
| 22 | +/// `OnceLock` keyed to process lifetime. |
| 23 | +static LOG_GUARD: OnceLock<WorkerGuard> = OnceLock::new(); |
| 24 | + |
| 25 | +/// Resolve the claudex log directory. macOS convention is |
| 26 | +/// `~/Library/Logs/claudex/`. We create it eagerly so the file |
| 27 | +/// appender has somewhere to write on first boot. |
| 28 | +fn log_dir() -> PathBuf { |
| 29 | + let home = dirs::home_dir().unwrap_or_else(|| PathBuf::from("/tmp")); |
| 30 | + let dir = home.join("Library").join("Logs").join("claudex"); |
| 31 | + let _ = std::fs::create_dir_all(&dir); |
| 32 | + dir |
| 33 | +} |
| 34 | + |
| 35 | +/// Two-layer tracing setup: one `fmt::Layer` writing ANSI to stdout |
| 36 | +/// for `pnpm tauri dev` users, and one writing plain text to a |
| 37 | +/// daily-rotated file under `~/Library/Logs/claudex/claudex.log`. |
| 38 | +/// Both layers share the same env filter (default |
| 39 | +/// `claudex=debug,warn`) so silencing works from either direction. |
| 40 | +fn init_logging() { |
| 41 | + let filter = tracing_subscriber::EnvFilter::try_from_default_env() |
| 42 | + .unwrap_or_else(|_| "claudex=debug,warn".into()); |
| 43 | + |
| 44 | + let file_appender = tracing_appender::rolling::daily(log_dir(), "claudex.log"); |
| 45 | + let (non_blocking, guard) = tracing_appender::non_blocking(file_appender); |
| 46 | + // Keep the worker alive for the process lifetime. |
| 47 | + let _ = LOG_GUARD.set(guard); |
| 48 | + |
| 49 | + let stdout_layer = tracing_subscriber::fmt::layer() |
| 50 | + .with_writer(std::io::stdout) |
| 51 | + .with_target(true) |
| 52 | + .with_filter(filter); |
| 53 | + |
| 54 | + let file_filter = tracing_subscriber::EnvFilter::try_from_default_env() |
| 55 | + .unwrap_or_else(|_| "claudex=debug,warn".into()); |
| 56 | + let file_layer = tracing_subscriber::fmt::layer() |
| 57 | + .with_writer(non_blocking) |
| 58 | + .with_ansi(false) |
| 59 | + .with_target(true) |
| 60 | + .with_filter(file_filter); |
| 61 | + |
| 62 | + tracing_subscriber::registry() |
| 63 | + .with(stdout_layer) |
| 64 | + .with(file_layer) |
| 65 | + .init(); |
| 66 | + |
| 67 | + tracing::info!(log_dir = %log_dir().display(), "claudex log file ready"); |
| 68 | +} |
| 12 | 69 | |
| 13 | 70 | #[cfg_attr(mobile, tauri::mobile_entry_point)] |
| 14 | 71 | pub fn run() { |
| 15 | | - tracing_subscriber::fmt() |
| 16 | | - .with_env_filter( |
| 17 | | - tracing_subscriber::EnvFilter::try_from_default_env() |
| 18 | | - .unwrap_or_else(|_| "claudex=info,warn".into()), |
| 19 | | - ) |
| 20 | | - .init(); |
| 72 | + init_logging(); |
| 21 | 73 | |
| 22 | 74 | tauri::Builder::default() |
| 23 | 75 | .setup(|app| { |
@@ -52,6 +104,7 @@ pub fn run() { |
| 52 | 104 | commands::close_pty, |
| 53 | 105 | commands::get_pty_buffer, |
| 54 | 106 | commands::list_ptys, |
| 107 | + commands::log_frontend, |
| 55 | 108 | ]) |
| 56 | 109 | .run(tauri::generate_context!()) |
| 57 | 110 | .expect("error while running claudex"); |