human_panic/
report.rs

1//! This module encapsulates the report of a failure event.
2//!
3//! A `Report` contains the metadata collected about the event
4//! to construct a helpful error message.
5
6use 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/// Method of failure.
15#[derive(Debug, Serialize, Clone, Copy)]
16#[non_exhaustive]
17pub enum Method {
18    /// Failure caused by a panic.
19    Panic,
20}
21
22/// Contains metadata about the crash like the backtrace and
23/// information about the crate and operating system. Can
24/// be used to be serialized and persisted or printed as
25/// information to the user.
26#[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    /// Create a new instance.
39    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    /// Serialize the `Report` to a TOML string.
64    pub fn serialize(&self) -> Option<String> {
65        toml::to_string_pretty(&self).ok()
66    }
67
68    /// Write a file to disk.
69    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    //We take padding for address and extra two letters
82    //to pad after index.
83    #[allow(unused_qualifications)] // needed for pre-1.80 MSRV
84    const HEX_WIDTH: usize = mem::size_of::<usize>() * 2 + 2;
85    //Padding for next lines after frame's address
86    const NEXT_SYMBOL_PADDING: usize = HEX_WIDTH + 6;
87
88    let mut backtrace = String::new();
89
90    //Here we iterate over backtrace frames
91    //(each corresponds to function's stack)
92    //We need to print its address
93    //and symbol(e.g. function name),
94    //if it is available
95    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            //See if there is debug information with file name and line
129            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}