diff --git a/app/src-tauri/Cargo.toml b/app/src-tauri/Cargo.toml index 7756ee0..37b64de 100644 --- a/app/src-tauri/Cargo.toml +++ b/app/src-tauri/Cargo.toml @@ -12,7 +12,7 @@ name = "triple-c" path = "src/main.rs" [dependencies] -tauri = { version = "2", features = ["image-png"] } +tauri = { version = "2", features = ["image-png", "image-ico"] } tauri-plugin-store = "2" tauri-plugin-dialog = "2" tauri-plugin-opener = "2" @@ -26,7 +26,7 @@ uuid = { version = "1", features = ["v4"] } chrono = { version = "0.4", features = ["serde"] } dirs = "6" log = "0.4" -env_logger = "0.11" +fern = { version = "0.7", features = ["date-based"] } tar = "0.4" reqwest = { version = "0.12", default-features = false, features = ["json", "rustls-tls"] } diff --git a/app/src-tauri/src/lib.rs b/app/src-tauri/src/lib.rs index 80bdcab..130aabe 100644 --- a/app/src-tauri/src/lib.rs +++ b/app/src-tauri/src/lib.rs @@ -1,5 +1,6 @@ mod commands; mod docker; +mod logging; mod models; mod storage; @@ -15,22 +16,42 @@ pub struct AppState { } pub fn run() { - env_logger::init(); + logging::init(); + + let projects_store = match ProjectsStore::new() { + Ok(s) => s, + Err(e) => { + log::error!("Failed to initialize projects store: {}", e); + panic!("Failed to initialize projects store: {}", e); + } + }; + let settings_store = match SettingsStore::new() { + Ok(s) => s, + Err(e) => { + log::error!("Failed to initialize settings store: {}", e); + panic!("Failed to initialize settings store: {}", e); + } + }; tauri::Builder::default() .plugin(tauri_plugin_store::Builder::default().build()) .plugin(tauri_plugin_dialog::init()) .plugin(tauri_plugin_opener::init()) .manage(AppState { - projects_store: ProjectsStore::new().expect("Failed to initialize projects store"), - settings_store: SettingsStore::new().expect("Failed to initialize settings store"), + projects_store, + settings_store, exec_manager: ExecSessionManager::new(), }) .setup(|app| { - let icon = tauri::image::Image::from_bytes(include_bytes!("../icons/icon.ico")) - .expect("Failed to load window icon"); - if let Some(window) = app.get_webview_window("main") { - let _ = window.set_icon(icon); + match tauri::image::Image::from_bytes(include_bytes!("../icons/icon.ico")) { + Ok(icon) => { + if let Some(window) = app.get_webview_window("main") { + let _ = window.set_icon(icon); + } + } + Err(e) => { + log::error!("Failed to load window icon: {}", e); + } } Ok(()) }) diff --git a/app/src-tauri/src/logging.rs b/app/src-tauri/src/logging.rs new file mode 100644 index 0000000..0f89bd1 --- /dev/null +++ b/app/src-tauri/src/logging.rs @@ -0,0 +1,73 @@ +use std::fs; +use std::path::PathBuf; + +/// Returns the log directory path: `/triple-c/logs/` +fn log_dir() -> Option { + dirs::data_dir().map(|d| d.join("triple-c").join("logs")) +} + +/// Initialise logging to both stderr and a log file in the app data directory. +/// +/// Logs are written to `/triple-c/logs/triple-c.log`. +/// A panic hook is also installed so that unexpected crashes are captured in the +/// same log file before the process exits. +pub fn init() { + let log_file_path = log_dir().and_then(|dir| { + fs::create_dir_all(&dir).ok()?; + let path = dir.join("triple-c.log"); + fs::OpenOptions::new() + .create(true) + .append(true) + .open(&path) + .ok() + .map(|file| (path, file)) + }); + + let mut dispatch = fern::Dispatch::new() + .format(|out, message, record| { + out.finish(format_args!( + "[{} {} {}] {}", + chrono::Local::now().format("%Y-%m-%d %H:%M:%S"), + record.level(), + record.target(), + message + )) + }) + .level(log::LevelFilter::Info) + .chain(std::io::stderr()); + + if let Some((_path, file)) = &log_file_path { + dispatch = dispatch.chain(fern::Dispatch::new().chain(file.try_clone().unwrap())); + } + + if let Err(e) = dispatch.apply() { + eprintln!("Failed to initialise logger: {}", e); + } + + // Install a panic hook that writes to the log file so crashes are captured. + let crash_log_dir = log_dir(); + std::panic::set_hook(Box::new(move |info| { + let msg = format!( + "[{} PANIC] {}\nBacktrace:\n{:?}", + chrono::Local::now().format("%Y-%m-%d %H:%M:%S"), + info, + std::backtrace::Backtrace::force_capture(), + ); + eprintln!("{}", msg); + if let Some(ref dir) = crash_log_dir { + let crash_path = dir.join("triple-c.log"); + let _ = fs::OpenOptions::new() + .create(true) + .append(true) + .open(&crash_path) + .and_then(|mut f| { + use std::io::Write; + writeln!(f, "{}", msg) + }); + } + })); + + if let Some((ref path, _)) = log_file_path { + log::info!("Logging to {}", path.display()); + } +}