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
22pub 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 let frame = &frame[6..];
69 if frame.starts_with("__") {
70 continue;
71 }
72 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 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!("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}