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
28pub 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 let frame = &frame[6..];
82 if frame.starts_with("__") || frame == "<unknown>" || frame == "clone" || frame == "start_thread" {
83 continue;
84 }
85 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 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!("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}