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 operating_system = os_info::get().to_string();
47 let backtrace = render_backtrace();
48
49 Self {
50 crate_version: version.into(),
51 name: name.into(),
52 operating_system,
53 method,
54 explanation,
55 cause,
56 backtrace,
57 }
58 }
59
60 pub fn serialize(&self) -> Option<String> {
62 toml::to_string_pretty(&self).ok()
63 }
64
65 pub fn persist(&self) -> Result<PathBuf, Box<dyn Error + 'static>> {
67 let uuid = Uuid::new_v4().hyphenated().to_string();
68 let tmp_dir = env::temp_dir();
69 let file_name = format!("report-{}.toml", &uuid);
70 let file_path = Path::new(&tmp_dir).join(file_name);
71 let toml = self.serialize().expect("only using toml-compatible types");
72 std::fs::write(&file_path, toml.as_bytes())?;
73 Ok(file_path)
74 }
75}
76
77fn render_backtrace() -> String {
78 #[allow(unused_qualifications)] const HEX_WIDTH: usize = mem::size_of::<usize>() * 2 + 2;
82 const NEXT_SYMBOL_PADDING: usize = HEX_WIDTH + 6;
84
85 let mut backtrace = String::new();
86
87 let bt = Backtrace::new();
93 let symbols = bt
94 .frames()
95 .iter()
96 .flat_map(|frame| {
97 let symbols = frame.symbols();
98 if symbols.is_empty() {
99 vec![(frame, None, "<unresolved>".to_owned())]
100 } else {
101 symbols
102 .iter()
103 .map(|s| {
104 (
105 frame,
106 Some(s),
107 s.name()
108 .map(|n| n.to_string())
109 .unwrap_or_else(|| "<unknown>".to_owned()),
110 )
111 })
112 .collect::<Vec<_>>()
113 }
114 })
115 .collect::<Vec<_>>();
116 let begin_unwind = "rust_begin_unwind";
117 let begin_unwind_start = symbols
118 .iter()
119 .position(|(_, _, n)| n == begin_unwind)
120 .unwrap_or(0);
121 for (entry_idx, (frame, symbol, name)) in symbols.iter().skip(begin_unwind_start).enumerate() {
122 let ip = frame.ip();
123 let _ = writeln!(backtrace, "{entry_idx:4}: {ip:HEX_WIDTH$?} - {name}");
124 if let Some(symbol) = symbol {
125 if let (Some(file), Some(line)) = (symbol.filename(), symbol.lineno()) {
127 let _ = writeln!(
128 backtrace,
129 "{:3$}at {}:{}",
130 "",
131 file.display(),
132 line,
133 NEXT_SYMBOL_PADDING
134 );
135 }
136 }
137 }
138
139 backtrace
140}