not_so_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, fs::File, io::Write, path::Path, path::PathBuf};
12use uuid::Uuid;
13
14/// Method of failure.
15#[derive(Debug, Serialize, Clone, Copy)]
16pub enum Method {
17    /// Failure caused by a panic.
18    Panic,
19}
20
21/// Contains metadata about the crash like the backtrace and
22/// information about the crate and operating system. Can
23/// be used to be serialized and persisted or printed as
24/// information to the user.
25#[derive(Debug, Serialize)]
26pub struct Report {
27    name: String,
28    operating_system: String,
29    crate_version: String,
30    explanation: String,
31    cause: String,
32    method: Method,
33    backtrace: String,
34}
35
36impl Report {
37    /// Create a new instance.
38    pub fn new(
39        name: &str,
40        version: &str,
41        method: Method,
42        explanation: String,
43        cause: String,
44    ) -> Self {
45        let operating_system = os_info::get().to_string();
46
47        //We skip 3 frames from backtrace library
48        //Then we skip 3 frames for our own library
49        //(including closure that we set as hook)
50        //Then we skip 2 functions from Rust's runtime
51        //that calls panic hook
52        const SKIP_FRAMES_NUM: usize = 8;
53        //We take padding for address and extra two letters
54        //to padd after index.
55        const HEX_WIDTH: usize = mem::size_of::<usize>() + 2;
56        //Padding for next lines after frame's address
57        const NEXT_SYMBOL_PADDING: usize = HEX_WIDTH + 6;
58
59        let mut backtrace = String::new();
60
61        //Here we iterate over backtrace frames
62        //(each corresponds to function's stack)
63        //We need to print its address
64        //and symbol(e.g. function name),
65        //if it is available
66        for (idx, frame) in Backtrace::new()
67            .frames()
68            .iter()
69            .skip(SKIP_FRAMES_NUM)
70            .enumerate()
71        {
72            let ip = frame.ip();
73            let _ = write!(backtrace, "\n{idx:4}: {ip:HEX_WIDTH$?}");
74
75            let symbols = frame.symbols();
76            if symbols.is_empty() {
77                let _ = write!(backtrace, " - <unresolved>");
78                continue;
79            }
80
81            for (idx, symbol) in symbols.iter().enumerate() {
82                //Print symbols from this address,
83                //if there are several addresses
84                //we need to put it on next line
85                if idx != 0 {
86                    let _ = write!(backtrace, "\n{:1$}", "", NEXT_SYMBOL_PADDING);
87                }
88
89                if let Some(name) = symbol.name() {
90                    let _ = write!(backtrace, " - {name}");
91                } else {
92                    let _ = write!(backtrace, " - <unknown>");
93                }
94
95                //See if there is debug information with file name and line
96                if let (Some(file), Some(line)) = (symbol.filename(), symbol.lineno()) {
97                    let _ = write!(
98                        backtrace,
99                        "\n{:3$}at {}:{}",
100                        "",
101                        file.display(),
102                        line,
103                        NEXT_SYMBOL_PADDING
104                    );
105                }
106            }
107        }
108
109        Self {
110            crate_version: version.into(),
111            name: name.into(),
112            operating_system,
113            method,
114            explanation,
115            cause,
116            backtrace,
117        }
118    }
119
120    /// Serialize the `Report` to a TOML string.
121    pub fn serialize(&self) -> Option<String> {
122        toml::to_string_pretty(&self).ok()
123    }
124
125    /// Write a file to disk.
126    pub fn persist(&self) -> Result<PathBuf, Box<dyn Error + 'static>> {
127        let uuid = Uuid::new_v4().hyphenated().to_string();
128        let tmp_dir = env::temp_dir();
129        let file_name = format!("report-{}.toml", &uuid);
130        let file_path = Path::new(&tmp_dir).join(file_name);
131        let mut file = File::create(&file_path)?;
132        let toml = self.serialize().unwrap();
133        file.write_all(toml.as_bytes())?;
134        Ok(file_path)
135    }
136}