1 // Copyright 2021-2022 Ian Jackson, yaahc and contributors to Hippotat and Eyre
2 // SPDX-License-Identifier: GPL-3.0-or-later
3 // There is NO WARRANTY.
7 #[derive(StructOpt,Debug)]
9 /// Increase debug level
11 /// May be repeated for more verbosity.
13 /// When using syslog, one `-D` this arranges to send to syslog even
14 /// trace messages (mapped onto syslog level `DEBUG`);
15 /// and two -`D` means to send to syslog even messages from lower layers
16 /// (normally just the hippotat modules log to
18 #[structopt(long, short="D", parse(from_occurrences))]
21 /// Syslog facility to use
22 #[structopt(long, parse(try_from_str=parse_syslog_facility))]
23 syslog_facility: Option<syslog::Facility>,
27 fn parse_syslog_facility(s: &str) -> syslog::Facility {
28 s.parse().map_err(|()| anyhow!("unrecognised syslog facility: {:?}", s))?
37 impl<T> LogWrapper<T> {
38 fn wanted(&self, md: &log::Metadata<'_>) -> bool {
39 let first = |mod_path| {
40 let mod_path: &str = mod_path; // can't do in args as breaks lifetimes
41 mod_path.split_once("::").map(|s| s.0).unwrap_or(mod_path)
43 self.debug >= 2 || first(md.target()) == first(module_path!())
46 fn set_max_level(&self) {
47 log::set_max_level(if self.debug < 1 {
48 log::LevelFilter::Debug
50 log::LevelFilter::Trace
55 impl<T> log::Log for LogWrapper<T> where T: log::Log {
56 fn enabled(&self, md: &log::Metadata<'_>) -> bool {
57 self.wanted(md) && self.output.enabled(md)
60 fn log(&self, record: &log::Record<'_>) {
61 if self.wanted(record.metadata()) {
62 let mut wrap = log::Record::builder();
64 macro_rules! copy { { $( $f:ident ),* $(,)? } => {
65 $( wrap.$f(record.$f()); )*
68 level, target, module_path, file, line
70 match format_args!("{}: {}",
71 heck::AsKebabCase(record.level().as_str()),
75 self.output.log(&wrap.build());
88 pub fn log_init(&self) {
89 if let Some(facility) = self.syslog_facility {
90 let f = syslog::Formatter3164 {
93 process: "hippotatd".into(),
94 pid: std::process::id(),
96 let l = syslog::unix(f)
97 // syslog::Error is not Sync.
98 // https://github.com/Geal/rust-syslog/issues/65
99 .map_err(|e| anyhow!(e.to_string()))
100 .context("set up syslog logger")?;
101 let l = syslog::BasicLogger::new(l);
102 let l = LogWrapper { output: l, debug: self.debug };
104 let l = Box::new(l) as _;
105 log::set_boxed_logger(l).context("install syslog logger")?;
107 let env = env_logger::Env::new()
108 .filter("HIPPOTAT_LOG")
109 .write_style("HIPPOTAT_LOG_STYLE");
111 let mut logb = env_logger::Builder::new();
112 logb.filter(Some("hippotat"),
113 *[ log::LevelFilter::Info,
114 log::LevelFilter::Debug ]
117 &log::LevelFilter::Trace
125 pub struct OptionPrefixColon<T>(pub Option<T>);
126 impl<T:Display> Display for OptionPrefixColon<T> {
127 #[throws(fmt::Error)]
128 fn fmt(&self, f: &mut fmt::Formatter) {
129 if let Some(x) = &self.0 { write!(f, "{}: ", x)? }
133 // For clients only, really.
134 pub struct Reporter<'r> {
135 ic: &'r InstanceConfig,
137 last_report: Option<Report>,
146 // Reporting strategy
147 // - report all errors
148 // - report first success after a period of lack of messages
149 // - if error, report last success
151 impl<'r> Reporter<'r> {
152 pub fn new(ic: &'r InstanceConfig) -> Self { Reporter {
158 pub fn success(&mut self) {
160 let now = Instant::now();
161 if let Some(rep) = &self.last_report {
162 if now - rep.when < match rep.ok {
163 Ok(()) => match self.ic.success_report_interval {
164 z if z == Duration::default() => return,
167 Err(()) => self.ic.effective_http_timeout,
173 info!(target:"hippotat", "{} ({}ok): running", self.ic, self.successes);
174 self.last_report = Some(Report { when: now, ok: Ok(()) });
177 pub fn filter<T>(&mut self, req_num: Option<ReqNum>, r: Result<T,AE>)
179 let now = Instant::now();
186 let mut m = self.ic.to_string();
187 if let Some(req_num) = req_num {
188 write!(m, " #{}", req_num)?;
190 if self.successes > 0 {
191 write!(m, " ({}ok)", self.successes)?;
194 write!(m, ": {}", e)?;
195 Ok::<_,fmt::Error>(m)
197 warn!(target:"hippotat", "{}", m);
198 self.last_report = Some(Report { when: now, ok: Err(()) });
205 use backtrace::Backtrace;
207 use indenter::indented;
210 struct EyreDedupHandler {
211 backtrace: Option<Arc<parking_lot::Mutex<Backtrace>>>,
214 type EyreDynError<'r> = &'r (dyn std::error::Error + 'static);
216 impl eyre::EyreHandler for EyreDedupHandler {
217 #[throws(fmt::Error)]
218 fn display(&self, error: EyreDynError, f: &mut fmt::Formatter) {
219 let mut last: Option<String> = None;
220 let mut error = Some(error);
221 while let Some(e) = error {
222 let m = e.to_string();
224 None => write!(f, "{}", m)?,
225 Some(l) if l.contains(&m) => { },
226 Some(_) => write!(f, ": {}", m)?,
233 #[throws(fmt::Error)]
234 fn debug(&self, error: EyreDynError, f: &mut fmt::Formatter) {
236 return core::fmt::Debug::fmt(error, f)?;
239 write!(f, "{}", error)?;
241 if let Some(cause) = error.source() {
242 write!(f, "\n\nCaused by:")?;
243 let multiple = cause.source().is_some();
245 for (n, error) in Chain::new(cause).enumerate() {
248 write!(indented(f).ind(n), "{}", error)?;
250 write!(indented(f), "{}", error)?;
255 if let Some(bt) = &self.backtrace {
256 let mut bt = bt.lock();
258 write!(f, "\n\nStack backtrace:\n{:?}", bt)?;
264 pub fn dedup_eyre_setup() {
265 eyre::set_hook(Box::new(|_error| {
267 static ref BACKTRACE: bool = {
268 match env::var("RUST_BACKTRACE") {
269 Ok(s) if s.starts_with("1") => true,
270 Ok(s) if s == "0" => false,
271 Err(env::VarError::NotPresent) => false,
273 eprintln!("warning: RUST_BACKTRACE not understood: {:?}", x);
279 let backtrace = if *BACKTRACE {
280 let bt = Backtrace::new_unresolved();
281 let bt = Arc::new(bt.into());
286 Box::new(EyreDedupHandler { backtrace })
288 .context("set error handler")?;
291 const MAX_WARNINGS: usize = 15;
293 #[derive(Debug,Default)]
294 pub struct Warnings {
295 pub warnings: Vec<String>,
298 #[derive(Debug,Error)]
299 #[error("too many warnings")]
300 pub struct TooManyWarnings;
303 #[throws(TooManyWarnings)]
304 pub fn add(&mut self, e: &dyn Display) {
305 if self.warnings.len() >= MAX_WARNINGS { throw!(TooManyWarnings) }
306 self.warnings.push(e.to_string());