chiark / gitweb /
server: route wip, do sending
[hippotat.git] / src / reporter.rs
1 // Copyright 2021 Ian Jackson, yaahc and contributors to Hippotat and Eyre
2 // SPDX-License-Identifier: GPL-3.0-or-later
3 // There is NO WARRANTY.
4
5 use crate::prelude::*;
6
7 #[derive(StructOpt,Debug)]
8 pub struct LogOpts {
9   /// Increase debug level
10   #[structopt(long, short="D", parse(from_occurrences))]
11   debug: usize,
12 }
13
14 impl LogOpts {
15   #[throws(AE)]
16   pub fn log_init(&self) {
17     let env = env_logger::Env::new()
18       .filter("HIPPOTAT_LOG")
19       .write_style("HIPPOTAT_LOG_STYLE");
20   
21     let mut logb = env_logger::Builder::new();
22     logb.filter(Some("hippotat"),
23                 *[ log::LevelFilter::Info,
24                    log::LevelFilter::Debug ]
25                 .get(self.debug)
26                 .unwrap_or(
27                   &log::LevelFilter::Trace
28                 ));
29     logb.parse_env(env);
30     logb.init();
31   }
32 }
33
34 pub struct OptionPrefixColon<T>(pub Option<T>);
35 impl<T:Display> Display for OptionPrefixColon<T> {
36   #[throws(fmt::Error)]
37   fn fmt(&self, f: &mut fmt::Formatter) {
38     if let Some(x) = &self.0 { write!(f, "{}: ", x)? }
39   }
40 }
41
42 // For clients only, really.
43 pub struct Reporter<'r> {
44   ic: &'r InstanceConfig,
45   successes: u64,
46   last_report: Option<Report>,
47 }
48
49 #[derive(Debug)]
50 struct Report {
51   when: Instant,
52   ok: Result<(),()>,
53 }         
54
55 // Reporting strategy
56 //   - report all errors
57 //   - report first success after a period of lack of messages
58 //   - if error, report last success
59
60 impl<'r> Reporter<'r> {
61   pub fn new(ic: &'r InstanceConfig) -> Self { Reporter {
62     ic,
63     successes: 0,
64     last_report: None,
65   } }
66   
67   pub fn success(&mut self) {
68     self.successes += 1;
69     let now = Instant::now();
70     if let Some(rep) = &self.last_report {
71       if now - rep.when < match rep.ok {
72         Ok(()) => match self.ic.success_report_interval {
73           z if z == Duration::default() => return,
74           nonzero => nonzero,
75         },
76         Err(()) => self.ic.effective_http_timeout,
77       } {
78         return
79       }
80     }
81     
82     info!(target:"hippotat", "{} ({}ok): running", self.ic, self.successes);
83     self.last_report = Some(Report { when: now, ok: Ok(()) });
84   }
85
86   pub fn filter<T>(&mut self, req_num: Option<ReqNum>, r: Result<T,AE>)
87                    -> Option<T> {
88     let now = Instant::now();
89     match r {
90       Ok(t) => {
91         Some(t)
92       },
93       Err(e) => {
94         let m = (||{
95           let mut m = self.ic.to_string();
96           if let Some(req_num) = req_num {
97             write!(m, " #{}", req_num)?;
98           }
99           if self.successes > 0 {
100             write!(m, " ({}ok)", self.successes)?;
101             self.successes = 0;
102           }
103           write!(m, ": {}", e)?;
104           Ok::<_,fmt::Error>(m)
105         })().unwrap();
106         warn!(target:"hippotat", "{}", m);
107         self.last_report = Some(Report { when: now, ok: Err(()) });
108         None
109       },
110     }
111   }
112 }
113
114 use backtrace::Backtrace;
115 use eyre::Chain;
116 use indenter::indented;
117
118 #[derive(Debug)]
119 struct EyreDedupHandler {
120   backtrace: Option<Arc<parking_lot::Mutex<Backtrace>>>,
121 }
122
123 type EyreDynError<'r> = &'r (dyn std::error::Error + 'static);
124
125 impl eyre::EyreHandler for EyreDedupHandler {
126   #[throws(fmt::Error)]
127   fn display(&self, error: EyreDynError, f: &mut fmt::Formatter) {
128     let mut last: Option<String> = None;
129     let mut error = Some(error);
130     while let Some(e) = error {
131       let m = e.to_string();
132       match last {
133         None => write!(f, "{}", m)?,
134         Some(l) if l.contains(&m) => { },
135         Some(_) => write!(f, ": {}", m)?,
136       }
137       last = Some(m);
138       error = e.source();
139     }
140   }
141
142   #[throws(fmt::Error)]
143   fn debug(&self, error: EyreDynError, f: &mut fmt::Formatter) {
144     if f.alternate() {
145       return core::fmt::Debug::fmt(error, f)?;
146     }
147
148     write!(f, "{}", error)?;
149
150     if let Some(cause) = error.source() {
151       write!(f, "\n\nCaused by:")?;
152       let multiple = cause.source().is_some();
153
154       for (n, error) in Chain::new(cause).enumerate() {
155         writeln!(f)?;
156         if multiple {
157           write!(indented(f).ind(n), "{}", error)?;
158         } else {
159           write!(indented(f), "{}", error)?;
160         }
161       }
162     }
163
164     if let Some(bt) = &self.backtrace {
165       let mut bt = bt.lock();
166       bt.resolve();
167       write!(f, "\n\nStack backtrace:\n{:?}", bt)?;
168     }
169   }
170 }
171
172 #[throws(AE)]
173 pub fn dedup_eyre_setup() {
174   eyre::set_hook(Box::new(|_error| {
175     lazy_static! {
176       static ref BACKTRACE: bool = {
177         match env::var("RUST_BACKTRACE") {
178           Ok(s) if s.starts_with("1") => true,
179           Ok(s) if s == "0" => false,
180           Err(env::VarError::NotPresent) => false,
181           x => {
182             eprintln!("warning: RUST_BACKTRACE not understood: {:?}", x);
183             false
184           },
185         }
186       };
187     }
188     let backtrace = if *BACKTRACE {
189       let bt = Backtrace::new_unresolved();
190       let bt = Arc::new(bt.into());
191       Some(bt)
192     } else {
193       None
194     };
195     Box::new(EyreDedupHandler { backtrace })
196   }))
197     .context("set error handler")?;
198 }
199
200 const MAX_WARNINGS: usize = 15;
201
202 #[derive(Debug,Default)]
203 pub struct Warnings {
204   pub warnings: Vec<String>,
205 }
206
207 #[derive(Debug,Error)]
208 #[error("too many warnings")]
209 pub struct TooManyWarnings;
210
211 impl Warnings {
212   #[throws(TooManyWarnings)]
213   pub fn add(&mut self, e: &dyn Display) {
214     if self.warnings.len() >= MAX_WARNINGS { throw!(TooManyWarnings) }
215     self.warnings.push(e.to_string());
216   }
217 }