chiark / gitweb /
eyre: wip experiment, needs some work
[hippotat.git] / src / reporter.rs
1 // Copyright 2021 Ian Jackson and contributors to Hippotat
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 // For clients only, really.
35 pub struct Reporter<'r> {
36   ic: &'r InstanceConfig,
37   successes: u64,
38   last_report: Option<Report>,
39 }
40
41 #[derive(Debug)]
42 struct Report {
43   when: Instant,
44   ok: Result<(),()>,
45 }         
46
47 // Reporting strategy
48 //   - report all errors
49 //   - report first success after a period of lack of messages
50 //   - if error, report last success
51
52 impl<'r> Reporter<'r> {
53   pub fn new(ic: &'r InstanceConfig) -> Self { Reporter {
54     ic,
55     successes: 0,
56     last_report: None,
57   } }
58   
59   pub fn success(&mut self) {
60     self.successes += 1;
61     let now = Instant::now();
62     if let Some(rep) = &self.last_report {
63       if now - rep.when < match rep.ok {
64         Ok(()) => match self.ic.success_report_interval {
65           z if z == Duration::default() => return,
66           nonzero => nonzero,
67         },
68         Err(()) => self.ic.effective_http_timeout,
69       } {
70         return
71       }
72     }
73     
74     info!(target:"hippotat", "{} ({}ok): running", self.ic, self.successes);
75     self.last_report = Some(Report { when: now, ok: Ok(()) });
76   }
77
78   pub fn filter<T>(&mut self, req_num: Option<ReqNum>, r: Result<T,AE>)
79                    -> Option<T> {
80     let now = Instant::now();
81     match r {
82       Ok(t) => {
83         Some(t)
84       },
85       Err(e) => {
86         let m = (||{
87           let mut m = self.ic.to_string();
88           if let Some(req_num) = req_num {
89             write!(m, " #{}", req_num)?;
90           }
91           if self.successes > 0 {
92             write!(m, " ({}ok)", self.successes)?;
93             self.successes = 0;
94           }
95           write!(m, ": {}", e)?;
96           Ok::<_,fmt::Error>(m)
97         })().unwrap();
98         warn!(target:"hippotat", "{:?}", m);
99         self.last_report = Some(Report { when: now, ok: Err(()) });
100         None
101       },
102     }
103   }
104 }
105
106 struct EyreDedupHandler;
107 type EyreDynError<'r> = &'r (dyn std::error::Error + 'static);
108
109 impl eyre::EyreHandler for EyreDedupHandler {
110   #[throws(fmt::Error)]
111   fn debug(&self, error: EyreDynError, f: &mut fmt::Formatter) {
112     Debug::fmt(error, f)?;
113   }
114   #[throws(fmt::Error)]
115   fn display(&self, error: EyreDynError, f: &mut fmt::Formatter) {
116     Display::fmt(error, f)?;
117   }
118 }
119
120 #[throws(AE)]
121 pub fn dedup_eyre_setup() {
122   eyre::set_hook(Box::new(|_error| Box::new(EyreDedupHandler)))
123     .context("set error handler")?;
124 }