atomcode_telemetry/
scrub.rs1use std::path::Path;
4
5pub const HEAD_MAX: usize = 200;
6
7pub fn scrub_path(s: &str, home: Option<&Path>, cwd: Option<&Path>) -> String {
8 let mut out = s.to_string();
9 if let Some(h) = home.and_then(|p| p.to_str()) {
10 if !h.is_empty() {
11 out = out.replace(h, "<HOME>");
12 }
13 }
14 if let Some(c) = cwd.and_then(|p| p.to_str()) {
15 if !c.is_empty() {
16 out = out.replace(c, "<CWD>");
17 }
18 }
19 out
20}
21
22pub fn truncate_head(s: &str, max_chars: usize) -> String {
23 if s.chars().count() <= max_chars {
24 return s.to_string();
25 }
26 s.chars().take(max_chars).collect()
27}
28
29pub fn backtrace_top_k(bt: &str, k: usize, home: Option<&Path>, cwd: Option<&Path>) -> Vec<String> {
30 bt.lines()
31 .take(k)
32 .map(|line| {
33 let scrubbed = scrub_path(line, home, cwd);
34 if let Some(idx) = scrubbed.find(" at ") {
35 let (head, rest) = scrubbed.split_at(idx + 4);
36 let short = match rest.rsplit_once('/') {
37 Some((_, tail)) => tail.to_string(),
38 None => rest.to_string(),
39 };
40 format!("{}{}", head, short)
41 } else {
42 scrubbed
43 }
44 })
45 .collect()
46}
47
48#[cfg(test)]
49mod tests {
50 use super::*;
51 use std::path::PathBuf;
52
53 #[test]
54 fn scrubs_home_path() {
55 let home = PathBuf::from("/Users/lichao");
56 let s = "panic at /Users/lichao/project/foo.rs:10";
57 assert_eq!(
58 scrub_path(s, Some(&home), None),
59 "panic at <HOME>/project/foo.rs:10"
60 );
61 }
62
63 #[test]
64 fn scrubs_cwd_path() {
65 let cwd = PathBuf::from("/tmp/proj");
66 let s = "error in /tmp/proj/src/main.rs:3";
67 assert_eq!(
68 scrub_path(s, None, Some(&cwd)),
69 "error in <CWD>/src/main.rs:3"
70 );
71 }
72
73 #[test]
74 fn truncate_head_respects_char_boundary() {
75 let s = "中文字串";
76 let out = truncate_head(s, 3);
77 assert_eq!(out, "中文字");
78 assert!(out.is_char_boundary(out.len()));
79 }
80
81 #[test]
82 fn truncate_head_returns_full_when_short() {
83 assert_eq!(truncate_head("hi", 200), "hi");
84 }
85
86 #[test]
87 fn backtrace_strips_abs_paths_keeps_basename() {
88 let bt = "\
89 0: my_crate::fn_a
90 at /Users/lichao/p/src/a.rs:10
91 1: my_crate::fn_b
92 at /Users/lichao/p/src/b.rs:20
93 2: my_crate::fn_c
94 at /Users/lichao/p/src/c.rs:30
95 3: frame4
96 4: frame5
97 5: frame6_should_be_dropped";
98 let frames = backtrace_top_k(bt, 5, Some(Path::new("/Users/lichao")), None);
99 assert_eq!(frames.len(), 5);
100 assert!(frames[1].ends_with("a.rs:10"), "got {:?}", frames[1]);
101 assert!(!frames[1].contains("/Users/lichao"));
102 }
103}