zenyx-engine-telemetry/engine/src/core/panic.rs

177 lines
5.1 KiB
Rust

use std::fmt::Write as FmtWrite;
use std::mem;
use std::str::FromStr;
use std::{error::Error, path::PathBuf};
use backtrace::Backtrace;
use native_dialog::{MessageDialog, MessageType};
use parking_lot::Once;
use tracing::error;
static INIT: parking_lot::Once = Once::new();
pub fn set_panic_hook() {
INIT.call_once(|| {
let default_hook = std::panic::take_hook();
std::panic::set_hook(Box::new(move |info| {
if let Err(e) = process_panic(info) {
eprintln!("Error in panic hook: {}", e);
default_hook(info);
}
std::process::exit(0);
}));
});
}
fn process_panic(info: &std::panic::PanicHookInfo<'_>) -> Result<(), Box<dyn Error>> {
use std::io::Write;
use colored::Colorize;
let log_dir = PathBuf::from_str("./").expect("wtf, The current directory no longer exists?");
if !log_dir.exists() {
std::fs::create_dir_all(&log_dir)?;
}
let log_path = log_dir.join("panic.log");
let mut file = std::fs::File::create(&log_path)?;
let payload = info.payload();
let payload_str = if let Some(s) = payload.downcast_ref::<&str>() {
*s
} else if let Some(s) = payload.downcast_ref::<String>() {
s
} else {
"<non-string panic payload>"
};
writeln!(file, "{}", payload_str)?;
writeln!(file, "{}", capture_backtrace().sanitize_path())?;
let panic_msg = format!(
r#"Zenyx had a problem and crashed. To help us diagnose the problem you can send us a crash report.
We have generated a report file at '{}'. Submit an issue or email with the subject of 'Zenyx Crash Report' and include the report as an attachment.
To submit the crash report:
https://codeberg.org/Caznix/Zenyx/issues
We take privacy seriously, and do not perform any automated error collection. In order to improve the software, we rely on people to submit reports.
Thank you kindly!"#,
log_path.display()
);
let final_msg = format!(
r#"{}
For future reference, the error summary is as follows:
{}"#,
panic_msg, payload_str
);
println!("{}", final_msg.red().bold());
if let Err(e) = MessageDialog::new()
.set_type(MessageType::Error)
.set_title("A fatal error in Zenyx has occurred")
.set_text(&final_msg)
.show_confirm()
{
error!("Failed to show message dialog: {e}")
}
Ok(())
}
fn capture_backtrace() -> String {
let mut backtrace = String::new();
let sysinfo = crate::metadata::SystemMetadata::current();
backtrace.push_str(&sysinfo.verbose_summary());
let trace = backtrace::Backtrace::new();
let message = format!("\nBacktrace:\n\n");
backtrace.push_str(&message);
backtrace.push_str(&format!("{trace:?}"));
backtrace
}
trait Sanitize {
fn sanitize_path(&self) -> String;
}
impl Sanitize for str {
fn sanitize_path(&self) -> String {
let prefixes = ["/home/", "/Users/", "\\Users\\", "/opt/home/"];
let mut result = String::from(self);
for prefix in prefixes {
if let Some(start_index) = result.find(prefix) {
let start_of_user = start_index + prefix.len();
let mut end_of_user = result[start_of_user..]
.find(|c| c == '/' || c == '\\')
.map(|i| start_of_user + i)
.unwrap_or(result.len());
if end_of_user == start_of_user && start_of_user < result.len() {
end_of_user = result.len();
}
result.replace_range(start_of_user..end_of_user, "<USER>");
break;
}
}
result
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_sanitize_home() {
assert_eq!(
"/home/<USER>/documents",
"/home/john.doe/documents".sanitize_path()
);
assert_eq!("/home/<USER>", "/home/jane".sanitize_path());
assert_eq!("/opt/home/<USER>", "/opt/home/user".sanitize_path());
}
#[test]
fn test_sanitize_users_unix() {
assert_eq!(
"/Users/<USER>/desktop",
"/Users/alice/desktop".sanitize_path()
);
assert_eq!("/Users/<USER>/", "/Users/bob/".sanitize_path());
assert_eq!("/user/Users/<USER>", "/user/Users/name".sanitize_path());
}
#[test]
fn test_sanitize_users_windows() {
assert_eq!(
"\\Users\\<USER>\\documents",
"\\Users\\charlie\\documents".sanitize_path()
);
assert_eq!("\\Users\\<USER>", "\\Users\\david".sanitize_path());
assert_eq!(
"C:\\Other\\Users\\<USER>",
"C:\\Other\\Users\\folder".sanitize_path()
);
}
#[test]
fn test_no_match() {
assert_eq!("/opt/data/file.txt", "/opt/data/file.txt".sanitize_path());
}
#[test]
fn test_mixed_separators() {
assert_eq!(
"/home/<USER>\\documents",
"/home/eve\\documents".sanitize_path()
);
assert_eq!(
"\\Users\\<USER>/desktop",
"\\Users\\frank/desktop".sanitize_path()
);
}
}