tor_log_ratelim/
logstate.rs1use std::{error::Error as StdError, fmt, time::Duration};
4
5type DynError = Box<dyn StdError + Send + 'static>;
7
8pub struct LogState {
21 n_fail: usize,
23 n_ok: usize,
25 activity: String,
27 error_message: Option<String>,
29 error: Option<DynError>,
31}
32impl LogState {
33 pub fn new(activity: String) -> Self {
35 Self {
36 n_fail: 0,
37 n_ok: 0,
38 activity,
39 error_message: None,
40 error: None,
41 }
42 }
43 pub fn reset(&mut self) {
45 *self = Self::new(std::mem::take(&mut self.activity));
46 }
47 pub fn note_fail(&mut self, msg_fn: impl FnOnce() -> (Option<String>, Option<DynError>)) {
53 if self.n_fail == 0 {
54 let (m, e) = msg_fn();
55 self.error_message = m;
56 self.error = e;
57 }
58 self.n_fail = self.n_fail.saturating_add(1);
59 }
60 pub fn note_ok(&mut self) {
62 self.n_ok = self.n_ok.saturating_add(1);
63 }
64 pub fn activity(&self) -> crate::Activity {
66 if self.n_fail == 0 {
67 crate::Activity::Dormant
68 } else {
69 crate::Activity::Active
70 }
71 }
72 pub fn display_problem(&self, dur: Duration) -> impl fmt::Display + '_ {
75 DispProblem(self, dur)
76 }
77 pub fn display_recovery(&self, dur: Duration) -> impl fmt::Display + '_ {
80 DispWorking(self, dur)
81 }
82}
83
84struct DispProblem<'a>(&'a LogState, Duration);
86impl<'a> fmt::Display for DispProblem<'a> {
87 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
88 write!(f, "{}: error", self.0.activity)?;
89 let n_total = self.0.n_fail.saturating_add(self.0.n_ok);
90 write!(
91 f,
92 " (problem occurred {}/{} times in the last {})",
93 self.0.n_fail,
94 n_total,
95 humantime::format_duration(self.1)
96 )?;
97 if let Some(msg) = self.0.error_message.as_ref() {
98 write!(f, ": {}", msg)?;
99 }
100 if let Some(err) = self.0.error.as_ref() {
101 let err = Adaptor(err);
102 write!(f, ": {}", tor_error::Report(&err))?;
103 }
104 Ok(())
105 }
106}
107struct DispWorking<'a>(&'a LogState, Duration);
109impl<'a> fmt::Display for DispWorking<'a> {
110 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
111 write!(f, "{}: now working", self.0.activity)?;
112 write!(
113 f,
114 " (problem occurred 0/{} times in the last {})",
115 self.0.n_ok,
116 humantime::format_duration(self.1)
117 )?;
118 Ok(())
119 }
120}
121#[derive(Debug)]
125struct Adaptor<'a>(&'a DynError);
126impl<'a> AsRef<dyn StdError + 'static> for Adaptor<'a> {
127 fn as_ref(&self) -> &(dyn StdError + 'static) {
128 self.0.as_ref()
129 }
130}
131
132#[cfg(test)]
133mod tests {
134
135 #![allow(clippy::redundant_closure)]
136 use super::*;
137 use crate::Activity;
138 use std::time::Duration;
139 use thiserror::Error;
140
141 #[derive(Debug, Error)]
142 #[error("TestError is here!")]
143 struct TestError {
144 source: TestErrorBuddy,
145 }
146
147 #[derive(Debug, Error)]
148 #[error("TestErrorBuddy is here!")]
149 struct TestErrorBuddy;
150
151 #[test]
152 fn display_problem() {
153 let duration = Duration::from_millis(10);
154 let mut ls: LogState = LogState::new("test".to_string());
155 let mut activity: Activity = ls.activity();
156 assert_eq!(Activity::Dormant, activity);
157 assert_eq!(ls.n_fail, 0);
158 fn err_msg() -> (Option<String>, Option<DynError>) {
159 (
160 Some("test".to_string()),
161 Some(Box::new(TestError {
162 source: TestErrorBuddy,
163 })),
164 )
165 }
166 ls.note_fail(|| err_msg());
167 assert_eq!(ls.n_fail, 1);
168 let problem = ls.display_problem(duration);
169 let expected_problem = String::from(
170 "test: error (problem occurred 1/1 times in the last 10ms): test: error: TestError is here!: TestErrorBuddy is here!"
171 );
172 let str_problem = format!("{problem}");
173 assert_eq!(expected_problem, str_problem);
174 activity = ls.activity();
175 assert_eq!(Activity::Active, activity);
176 }
177
178 #[test]
179 fn display_recovery() {
180 let duration = Duration::from_millis(10);
181 let mut ls = LogState::new("test".to_string());
182 ls.note_ok();
183 assert_eq!(ls.n_ok, 1);
184 {
185 let recovery = ls.display_recovery(duration);
186 let expected_recovery =
187 String::from("test: now working (problem occurred 0/1 times in the last 10ms)");
188 let str_recovery = format!("{recovery}");
189 assert_eq!(expected_recovery, str_recovery);
190 }
191 ls.reset();
192 assert_eq!(ls.n_ok, 0);
193 }
194}