user_backtrace/
lib.rs

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