user_backtrace/
lib.rs

1use std::fmt::{Display, Formatter};
2
3const HIDDEN_PACKAGES: &[&str] = &[
4    "backtrace",
5    "anyhow",
6    "core",
7    "alloc",
8    "std",
9    "test",
10    "tokio",
11    "tracing",
12    "futures",
13    "futures_util",
14];
15
16
17pub struct DecodedFrame {
18    frame: String,
19    location: Option<String>,
20}
21
22/// Represents a best attempt at pulling out only user relevant information from a backtrace frame.
23pub enum DecodedUserBacktrace {
24    Frames(Vec<DecodedFrame>),
25    Disabled,
26}
27
28impl Display for DecodedUserBacktrace {
29    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
30        match self {
31            DecodedUserBacktrace::Frames(frames) => {
32                for frame in frames {
33                    writeln!(f, "{}", frame.frame)?;
34                    if let Some(line2) = &frame.location {
35                        writeln!(f, "{}", line2)?;
36                    }
37                }
38                Ok(())
39            }
40            DecodedUserBacktrace::Disabled => {
41                writeln!(f, "disabled backtrace")
42            }
43        }
44    }
45}
46
47fn decode_backtrace<Backtrace: Display>(b: &Backtrace, hide_packages: &[&str]) -> DecodedUserBacktrace {
48    let s = b.to_string();
49    let mut lines = s.lines().peekable();
50    let mut frames = Vec::new();
51    if lines.peek().map(|&s| s == "disabled backtrace").unwrap_or(true) {
52        return DecodedUserBacktrace::Disabled;
53    }
54
55    loop {
56        let Some(&line) = lines.peek() else {
57            break;
58        };
59        let line = &line[3..];
60        if line.starts_with('1') {
61            break;
62        }
63        lines.next();
64    }
65
66    while let Some(frame) = lines.next() {
67        // skip the "  #: " portion
68        let frame = &frame[6..];
69        if frame.starts_with("__") {
70            continue;
71        }
72        // get location, if its there
73        let mut location = None;
74        if let Some(&l) = lines.peek() {
75            let l = l.trim_start_matches(' ');
76            if l.starts_with("at ") {
77                location = Some(lines.next().unwrap().to_string());
78            }
79        }
80
81        if frame.starts_with("start_thread") || frame.starts_with("clone") {
82            continue;
83        }
84
85        // decode
86        if frame.starts_with('<') {
87            let package1 = frame[1..].splitn(2, "::").next().unwrap();
88            let package2 = frame.splitn(2, " as ").skip(1).next().unwrap().splitn(2, "::").next().unwrap();
89            if hide_packages.contains(&package1) && hide_packages.contains(&package2) {
90                continue;
91            }
92        } else {
93            let package = frame.splitn(2, "::").next().unwrap();
94            if hide_packages.contains(&package) {
95                continue;
96            }
97        };
98        frames.push(DecodedFrame {
99            frame: frame.to_string(),
100            location,
101        });
102    }
103    DecodedUserBacktrace::Frames(frames)
104}
105
106pub trait UserBacktrace {
107    fn user_backtrace(&self) -> DecodedUserBacktrace;
108}
109
110impl UserBacktrace for anyhow::Error {
111    fn user_backtrace(&self) -> DecodedUserBacktrace {
112        decode_backtrace(self.backtrace(), HIDDEN_PACKAGES)
113    }
114}
115
116#[cfg(test)]
117mod tests {
118    use anyhow::{Result, anyhow};
119    use super::*;
120
121    fn nested2() -> Result<()> {
122        Err(anyhow!("Not implemented"))
123    }
124
125    fn nested1() -> Result<()> {
126        nested2()
127    }
128
129    #[test]
130    fn test_anyhow_err() {
131        let Err(e) = nested1() else { panic!("expected error"); };
132        // println!("{:?}", decode_backtrace(e.backtrace()));
133        println!("backtrace: {}", e.backtrace());
134        let user_backtrace = format!("{}", e.user_backtrace());
135        println!("{}", user_backtrace);
136        assert_eq!(user_backtrace.lines().count(), 8);
137    }
138
139    #[test]
140    fn test_parse_backtrace1() {
141        let s = include_str!("../data/backtrace1.txt");
142        let r = decode_backtrace(&s, HIDDEN_PACKAGES);
143        let r = r.to_string();
144        println!("{}", r);
145        assert_eq!(r.lines().count(), 3);
146    }
147}