1use backtrace::Backtrace;
7use serde_derive::Serialize;
8use std::error::Error;
9use std::fmt::Write as FmtWrite;
10use std::mem;
11use std::{env, path::Path, path::PathBuf};
12use uuid::Uuid;
13
14#[derive(Debug, Serialize, Clone, Copy)]
16#[non_exhaustive]
17pub enum Method {
18 Panic,
20}
21
22#[derive(Debug, Serialize)]
27pub struct Report {
28 name: String,
29 operating_system: String,
30 crate_version: String,
31 explanation: String,
32 cause: String,
33 method: Method,
34 backtrace: String,
35}
36
37impl Report {
38 pub fn new(
40 name: &str,
41 version: &str,
42 method: Method,
43 explanation: String,
44 cause: String,
45 ) -> Self {
46 let cpu_arch = sysinfo::System::cpu_arch();
47 let operating_system =
48 sysinfo::System::long_os_version().unwrap_or_else(|| "unknown".to_owned());
49 let operating_system = format!("{operating_system} [{cpu_arch}]");
50 let backtrace = render_backtrace();
51
52 Self {
53 crate_version: version.into(),
54 name: name.into(),
55 operating_system,
56 method,
57 explanation,
58 cause,
59 backtrace,
60 }
61 }
62
63 pub fn serialize(&self) -> Option<String> {
65 toml::to_string_pretty(&self).ok()
66 }
67
68 pub fn persist(&self) -> Result<PathBuf, Box<dyn Error + 'static>> {
70 let uuid = Uuid::new_v4().hyphenated().to_string();
71 let tmp_dir = env::temp_dir();
72 let file_name = format!("report-{}.toml", &uuid);
73 let file_path = Path::new(&tmp_dir).join(file_name);
74 let toml = self.serialize().expect("only using toml-compatible types");
75 std::fs::write(&file_path, toml.as_bytes())?;
76 Ok(file_path)
77 }
78}
79
80fn render_backtrace() -> String {
81 #[allow(unused_qualifications)] const HEX_WIDTH: usize = mem::size_of::<usize>() * 2 + 2;
85 const NEXT_SYMBOL_PADDING: usize = HEX_WIDTH + 6;
87
88 let mut backtrace = String::new();
89
90 let bt = Backtrace::new();
96 let symbols = bt
97 .frames()
98 .iter()
99 .flat_map(|frame| {
100 let symbols = frame.symbols();
101 if symbols.is_empty() {
102 vec![(frame, None, "<unresolved>".to_owned())]
103 } else {
104 symbols
105 .iter()
106 .map(|s| {
107 (
108 frame,
109 Some(s),
110 s.name()
111 .map(|n| n.to_string())
112 .unwrap_or_else(|| "<unknown>".to_owned()),
113 )
114 })
115 .collect::<Vec<_>>()
116 }
117 })
118 .collect::<Vec<_>>();
119 let begin_unwind = "rust_begin_unwind";
120 let begin_unwind_start = symbols
121 .iter()
122 .position(|(_, _, n)| n == begin_unwind)
123 .unwrap_or(0);
124 for (entry_idx, (frame, symbol, name)) in symbols.iter().skip(begin_unwind_start).enumerate() {
125 let ip = frame.ip();
126 let _ = writeln!(backtrace, "{entry_idx:4}: {ip:HEX_WIDTH$?} - {name}");
127 if let Some(symbol) = symbol {
128 if let (Some(file), Some(line)) = (symbol.filename(), symbol.lineno()) {
130 let _ = writeln!(
131 backtrace,
132 "{:3$}at {}:{}",
133 "",
134 file.display(),
135 line,
136 NEXT_SYMBOL_PADDING
137 );
138 }
139 }
140 }
141
142 backtrace
143}