forked from rootcause-rs/rootcause
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathinspecting_errors.rs
More file actions
124 lines (105 loc) · 3.5 KB
/
inspecting_errors.rs
File metadata and controls
124 lines (105 loc) · 3.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
//! Programmatic error inspection and analysis.
//!
//! Traverse error trees and extract structured data for analytics and
//! monitoring:
//! - `.iter_reports()` - traverse all nodes
//! - `.downcast_current_context::<T>()` - extract typed context
//! - `.downcast_inner::<T>()` - extract typed attachments
use rootcause::{prelude::*, report_collection::ReportCollection};
#[derive(Debug, Clone)]
struct HttpError {
code: u16,
message: &'static str,
}
impl core::fmt::Display for HttpError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "HTTP {}: {}", self.code, self.message)
}
}
impl core::error::Error for HttpError {}
#[derive(Debug, Clone)]
struct RetryMetadata {
attempt: u32,
delay_ms: u64,
}
impl core::fmt::Display for RetryMetadata {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "Attempt {} (waited {}ms)", self.attempt, self.delay_ms)
}
}
// Simulates network requests that fail with different HTTP codes
fn fetch_data(attempt: u32) -> Result<String, Report<HttpError>> {
let error = match attempt {
1 => HttpError {
code: 500,
message: "Internal Server Error",
},
2 => HttpError {
code: 503,
message: "Service Unavailable",
},
_ => HttpError {
code: 504,
message: "Gateway Timeout",
},
};
Err(report!(error).attach(RetryMetadata {
attempt,
delay_ms: attempt as u64 * 100,
}))
}
// Retry logic that collects all failures
fn fetch_with_retries(url: &str) -> Result<String, Report> {
let mut errors = ReportCollection::new();
for attempt in 1..=3 {
match fetch_data(attempt) {
Ok(data) => return Ok(data),
Err(err) => errors.push(err.into_cloneable()),
}
}
Err(errors.context(format!("Failed to fetch {url}")))?
}
// Extract all HTTP status codes from the error tree
fn extract_status_codes(report: &Report) -> Vec<u16> {
report
.iter_reports()
.filter_map(|node| node.downcast_current_context::<HttpError>())
.map(|err| err.code)
.collect()
}
// Calculate total retry time from metadata attachments
fn calculate_total_retry_time(report: &Report) -> u64 {
report
.iter_reports()
.flat_map(|node| node.attachments().iter())
.filter_map(|att| att.downcast_inner::<RetryMetadata>())
.map(|meta| meta.delay_ms)
.sum()
}
// Combine context and attachment data from each node
fn pair_errors_with_delays(report: &Report) -> Vec<(u16, u64)> {
report
.iter_reports()
.filter_map(|node| {
let status = node.downcast_current_context::<HttpError>()?.code;
let delay = node
.attachments()
.iter()
.find_map(|att| att.downcast_inner::<RetryMetadata>())?
.delay_ms;
Some((status, delay))
})
.collect()
}
fn main() {
if let Err(report) = fetch_with_retries("https://api.example.com/data") {
eprintln!("Error tree:\n\n{report}\n");
println!("Programmatic inspection:\n");
let codes = extract_status_codes(&report);
println!("HTTP status codes: {:?}", codes);
let total_time = calculate_total_retry_time(&report);
println!("Total retry time: {}ms", total_time);
let paired = pair_errors_with_delays(&report);
println!("Error/delay pairs: {:?}", paired);
}
}