From 359e09f7cd4093bf9c49a47f859bdaeae4697ec9 Mon Sep 17 00:00:00 2001 From: Mike Mueller Date: Wed, 6 May 2026 21:21:01 +0200 Subject: [PATCH] Implement panic logging for better debugging --- src/common.rs | 64 ++++++++++++++++++++++++++++++++++++++++++++++++++ src/service.rs | 1 + 2 files changed, 65 insertions(+) diff --git a/src/common.rs b/src/common.rs index 0b0b8bebf..e71615b24 100644 --- a/src/common.rs +++ b/src/common.rs @@ -122,6 +122,7 @@ impl Drop for SimpleCallOnReturn { } pub fn global_init() -> bool { + install_panic_hook(); #[cfg(target_os = "linux")] { if !crate::platform::linux::is_x11() { @@ -131,6 +132,69 @@ pub fn global_init() -> bool { true } +/// Capture Rust panics so they survive the windows-subsystem build. +/// +/// In release we have `panic = 'abort'` and `windows_subsystem = "windows"`, +/// which means a panic prints to stderr (which is null when no console is +/// attached) and immediately calls `abort()`. The flexi_logger file never +/// sees the panic, and on Windows the abort doesn't reliably surface in the +/// Application event log either, so the process appears to vanish. +/// +/// This hook routes every panic to two places before the abort: +/// 1. `log::error!` — works once `init_log` has set up flexi_logger. +/// 2. `Config::log_path()/panic.log` — fallback for panics that fire before +/// `init_log` runs (e.g. inside `load_custom_client` or `bootstrap`). +/// The log dir is per-user/per-app on every platform and is writable by +/// the user-session `--server` process; the install dir is not. +fn install_panic_hook() { + use std::sync::Once; + static INIT: Once = Once::new(); + INIT.call_once(|| { + let prev = std::panic::take_hook(); + std::panic::set_hook(Box::new(move |info| { + let payload: &str = if let Some(s) = info.payload().downcast_ref::<&'static str>() { + s + } else if let Some(s) = info.payload().downcast_ref::() { + s.as_str() + } else { + "" + }; + let location = info + .location() + .map(|l| format!("{}:{}:{}", l.file(), l.line(), l.column())) + .unwrap_or_else(|| "".into()); + let backtrace = std::backtrace::Backtrace::force_capture(); + let exe = std::env::current_exe() + .ok() + .and_then(|p| p.to_str().map(|s| s.to_owned())) + .unwrap_or_default(); + let pid = std::process::id(); + let args: Vec = std::env::args().collect(); + let header = format!( + "PANIC pid={pid} exe={exe} args={args:?}\n at {location}\n payload: {payload}" + ); + log::error!("{header}\n backtrace:\n{backtrace}"); + let mut path = config::Config::log_path(); + let _ = std::fs::create_dir_all(&path); + path.push("panic.log"); + if let Ok(mut f) = std::fs::OpenOptions::new() + .create(true) + .append(true) + .open(&path) + { + use std::io::Write; + let ts = std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .map(|d| d.as_secs()) + .unwrap_or(0); + let _ = writeln!(f, "[unix={ts}] {header}\n backtrace:\n{backtrace}\n"); + let _ = f.flush(); + } + prev(info); + })); + }); +} + pub fn global_clean() {} #[inline] diff --git a/src/service.rs b/src/service.rs index ce1855bdb..4a3c82430 100644 --- a/src/service.rs +++ b/src/service.rs @@ -5,6 +5,7 @@ fn main() {} #[cfg(target_os = "macos")] fn main() { + crate::common::global_init(); crate::common::load_custom_client(); hbb_common::init_log(false, "service"); crate::start_os_service();