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 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    /// Serialize the `Report` to a TOML string.
61    pub fn serialize(&self) -> Option<String> {
62        toml::to_string_pretty(&self).ok()
63    }
64
65    /// Write a file to disk.
66    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    //We take padding for address and extra two letters
79    //to pad after index.
80    #[allow(unused_qualifications)] // needed for pre-1.80 MSRV
81    const HEX_WIDTH: usize = mem::size_of::<usize>() * 2 + 2;
82    //Padding for next lines after frame's address
83    const NEXT_SYMBOL_PADDING: usize = HEX_WIDTH + 6;
84
85    let mut backtrace = String::new();
86
87    //Here we iterate over backtrace frames
88    //(each corresponds to function's stack)
89    //We need to print its address
90    //and symbol(e.g. function name),
91    //if it is available
92    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            //See if there is debug information with file name and line
126            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}