diff --git a/desktop/src-tauri/Cargo.toml b/desktop/src-tauri/Cargo.toml index 7485168..73de985 100644 --- a/desktop/src-tauri/Cargo.toml +++ b/desktop/src-tauri/Cargo.toml @@ -63,6 +63,7 @@ dashmap = { workspace = true } uuid = { workspace = true } base64 = { workspace = true } tracing = { workspace = true } +tracing-subscriber = { workspace = true } secrecy = { workspace = true } # Browser automation (existing) diff --git a/desktop/src-tauri/src/lib.rs b/desktop/src-tauri/src/lib.rs index f92fe40..7bd5c2f 100644 --- a/desktop/src-tauri/src/lib.rs +++ b/desktop/src-tauri/src/lib.rs @@ -49,6 +49,45 @@ mod dev_server; #[cfg_attr(mobile, tauri::mobile_entry_point)] pub fn run() { + // Initialize tracing subscriber for structured logging + tracing_subscriber::fmt() + .with_env_filter( + tracing_subscriber::EnvFilter::try_from_default_env() + .unwrap_or_else(|_| tracing_subscriber::EnvFilter::new("info")), + ) + .with_thread_ids(true) + .with_file(true) + .with_line_number(true) + .init(); + + // Install panic hook to capture crash info for diagnostics + std::panic::set_hook(Box::new(|info| { + let location = info.location().map(|l| format!("{}:{}", l.file(), l.line())) + .unwrap_or_else(|| "unknown".into()); + let payload = if let Some(s) = info.payload().downcast_ref::<&str>() { + s.to_string() + } else if let Some(s) = info.payload().downcast_ref::() { + s.clone() + } else { + "unknown panic payload".into() + }; + tracing::error!(location = %location, payload = %payload, "PANIC — application crashed"); + + // Write crash log to disk for post-mortem analysis + if let Some(app_dir) = dirs::data_local_dir() + .or_else(|| dirs::data_dir()) + { + let crash_dir = app_dir.join("zclaw").join("crash-reports"); + let _ = std::fs::create_dir_all(&crash_dir); + let timestamp = chrono::Local::now().format("%Y%m%d_%H%M%S"); + let crash_file = crash_dir.join(format!("crash_{}.log", timestamp)); + let _ = std::fs::write(&crash_file, format!( + "ZCLAW Crash Report\n==================\nTime: {}\nLocation: {}\nPayload: {}\n\n{:?}", + chrono::Local::now().to_rfc3339(), location, payload, info, + )); + } + })); + // Start development server when dev-server feature is enabled #[cfg(feature = "dev-server")] { diff --git a/desktop/src/constants/api-urls.ts b/desktop/src/constants/api-urls.ts index 230ec08..48b0718 100644 --- a/desktop/src/constants/api-urls.ts +++ b/desktop/src/constants/api-urls.ts @@ -50,8 +50,8 @@ export const LLM_PROVIDER_URLS = { * ZCLAW Gateway default URLs */ export const GATEWAY_URLS = { - DEFAULT_HTTP: 'http://127.0.0.1:50051', - DEFAULT_WS: 'ws://127.0.0.1:50051/ws', + DEFAULT_HTTP: import.meta.env.VITE_GATEWAY_HTTP || 'http://127.0.0.1:50051', + DEFAULT_WS: import.meta.env.VITE_GATEWAY_WS || 'ws://127.0.0.1:50051/ws', FALLBACK_HTTP: 'http://127.0.0.1:4200', FALLBACK_WS: 'ws://127.0.0.1:4200/ws', } as const; diff --git a/desktop/src/lib/gateway-storage.ts b/desktop/src/lib/gateway-storage.ts index f43d7ab..2903f32 100644 --- a/desktop/src/lib/gateway-storage.ts +++ b/desktop/src/lib/gateway-storage.ts @@ -48,7 +48,7 @@ export function isLocalhost(url: string): boolean { // ZCLAW endpoints (port 50051 - actual running port) // Note: REST API uses relative path to leverage Vite proxy for CORS bypass -export const DEFAULT_GATEWAY_URL = `${DEFAULT_WS_PROTOCOL}127.0.0.1:50051/ws`; +export const DEFAULT_GATEWAY_URL = import.meta.env.VITE_GATEWAY_WS || `${DEFAULT_WS_PROTOCOL}127.0.0.1:50051/ws`; export const REST_API_URL = ''; // Empty = use relative path (Vite proxy) export const FALLBACK_GATEWAY_URLS = [ DEFAULT_GATEWAY_URL,