131 lines
4.1 KiB
Rust
131 lines
4.1 KiB
Rust
use std::error::Error;
|
|
use std::fmt::Write as FmtWrite;
|
|
use std::mem;
|
|
|
|
use backtrace::Backtrace;
|
|
use parking_lot::Once;
|
|
use regex::Regex;
|
|
|
|
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 crate::workspace;
|
|
use colored::Colorize;
|
|
use std::io::Write;
|
|
|
|
let log_dir = workspace::get_working_dir()?;
|
|
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, "{}", render_backtrace().sanitize_path())?;
|
|
|
|
let panic_msg = format!(
|
|
"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://github.com/Zenyx-Engine/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());
|
|
println!("{}", panic_msg.red().bold());
|
|
println!(
|
|
"\nFor future reference, the error summary is as follows:\n{}",
|
|
payload_str.red().bold()
|
|
);
|
|
Ok(())
|
|
}
|
|
|
|
fn render_backtrace() -> String {
|
|
const HEX_WIDTH: usize = mem::size_of::<usize>() * 2 + 2;
|
|
const NEXT_SYMBOL_PADDING: usize = HEX_WIDTH + 6;
|
|
|
|
let mut backtrace = String::new();
|
|
let bt = Backtrace::new();
|
|
let symbols = bt
|
|
.frames()
|
|
.iter()
|
|
.flat_map(|frame| {
|
|
let symbols = frame.symbols();
|
|
if symbols.is_empty() {
|
|
vec![(frame, None, "<unresolved>".to_owned())]
|
|
} else {
|
|
symbols
|
|
.iter()
|
|
.map(|s| {
|
|
(
|
|
frame,
|
|
Some(s),
|
|
s.name()
|
|
.map(|n| n.to_string())
|
|
.unwrap_or_else(|| "<unknown>".to_owned()),
|
|
)
|
|
})
|
|
.collect::<Vec<_>>()
|
|
}
|
|
})
|
|
.collect::<Vec<_>>();
|
|
let begin_unwind = "rust_begin_unwind";
|
|
let begin_unwind_start = symbols
|
|
.iter()
|
|
.position(|(_, _, n)| n == begin_unwind)
|
|
.unwrap_or(0);
|
|
for (entry_idx, (frame, symbol, name)) in symbols.iter().skip(begin_unwind_start).enumerate() {
|
|
let ip = frame.ip();
|
|
let _ = writeln!(backtrace, "{entry_idx:4}: {ip:HEX_WIDTH$?} - {name}");
|
|
if let Some(symbol) = symbol {
|
|
if let (Some(file), Some(line)) = (symbol.filename(), symbol.lineno()) {
|
|
let _ = writeln!(
|
|
backtrace,
|
|
"{:3$}at {}:{}",
|
|
"",
|
|
file.display(),
|
|
line,
|
|
NEXT_SYMBOL_PADDING
|
|
);
|
|
}
|
|
}
|
|
}
|
|
backtrace
|
|
}
|
|
|
|
trait Sanitize {
|
|
fn sanitize_path(&self) -> String;
|
|
}
|
|
|
|
impl Sanitize for str {
|
|
fn sanitize_path(&self) -> String {
|
|
let username_pattern = r"(?i)(/home/|/Users/|\\Users\\)([^/\\]+)";
|
|
let re = Regex::new(username_pattern).expect("Failed to compile regex for sanitization");
|
|
re.replace_all(self, "${1}<USER>").to_string()
|
|
}
|
|
}
|