225 lines
6.6 KiB
Rust
225 lines
6.6 KiB
Rust
use std::str::FromStr;
|
|
use std::{env, error::Error, path::PathBuf, thread};
|
|
|
|
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(1);
|
|
}));
|
|
});
|
|
}
|
|
|
|
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, "Panic Occurred: {}", payload_str)?;
|
|
|
|
if let Some(location) = info.location() {
|
|
writeln!(file, "Panic Location: {}", location)?;
|
|
}
|
|
|
|
writeln!(file, "{}", capture_backtrace().sanitize_path())?;
|
|
|
|
// Add more contextual information
|
|
writeln!(file, "\n--- Additional Information ---")?;
|
|
|
|
// Rust Version
|
|
if let Ok(rust_version) = rust_version() {
|
|
writeln!(file, "Rust Version: {}", rust_version)?;
|
|
}
|
|
|
|
// Command-line Arguments
|
|
writeln!(file, "Command-line Arguments:")?;
|
|
for arg in env::args() {
|
|
writeln!(file, " {}", arg)?;
|
|
}
|
|
|
|
// Environment Variables (consider filtering sensitive ones)
|
|
writeln!(file, "\nEnvironment Variables (selected):")?;
|
|
let interesting_env_vars = ["PATH", "RUST_VERSION", "CARGO_TARGET_DIR", "HOME", "USER"];
|
|
for (key, value) in env::vars() {
|
|
if interesting_env_vars.contains(&key.as_str()) {
|
|
writeln!(file, " {}: {}", key, value)?;
|
|
}
|
|
}
|
|
|
|
// Current Working Directory
|
|
if let Ok(cwd) = env::current_dir() {
|
|
writeln!(file, "\nCurrent Working Directory: {}", cwd.display())?;
|
|
}
|
|
|
|
// Thread Information
|
|
if let Some(thread) = thread::current().name() {
|
|
writeln!(file, "\nThread Name: {}", thread)?;
|
|
} else {
|
|
writeln!(file, "\nThread ID: {:?}", thread::current().id())?;
|
|
}
|
|
|
|
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 detailed 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:
|
|
{}
|
|
|
|
More details can be found in the crash report file."#,
|
|
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 rust_version() -> Result<String, Box<dyn Error>> {
|
|
let version = env!("CARGO_PKG_RUST_VERSION");
|
|
Ok(version.to_string())
|
|
}
|
|
|
|
fn capture_backtrace() -> String {
|
|
let mut backtrace = String::new();
|
|
let sysinfo = crate::metadata::SystemMetadata::current();
|
|
backtrace.push_str(&format!(
|
|
"--- System Information ---\n{}\n",
|
|
sysinfo.verbose_summary()
|
|
));
|
|
|
|
let trace = std::backtrace::Backtrace::force_capture();
|
|
let message = format!("\n--- Backtrace ---\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()
|
|
);
|
|
}
|
|
}
|