chiark / gitweb /
syslog: Add syslog support
[hippotat.git] / src / reporter.rs
index 6f4fb0d4cb17f073391dc171aea8d7e4520b015e..49c26a2f5fbced321209b31eed66494b94fb0e63 100644 (file)
@@ -7,27 +7,99 @@ use crate::prelude::*;
 #[derive(StructOpt,Debug)]
 pub struct LogOpts {
   /// Increase debug level
+  ///
+  /// May be repeated for more verbosity.
+  ///
+  /// When using syslog, one `-D` this arranges to send to syslog even
+  /// trace messages (mapped onto syslog level `DEBUG`);
+  /// and two -`D` means to send to syslog even messages from lower layers
+  /// (normally just the hippotat modules log to
+  /// syslog).
   #[structopt(long, short="D", parse(from_occurrences))]
   debug: usize,
+
+  /// Syslog facility to use
+  #[structopt(long, parse(try_from_str=parse_syslog_facility))]
+  syslog_facility: Option<syslog::Facility>,
+}
+
+#[throws(AE)]
+fn parse_syslog_facility(s: &str) -> syslog::Facility {
+  s.parse().map_err(|()| anyhow!("unrecognised syslog facility: {:?}", s))?
+}
+
+#[derive(Debug)]
+struct FilteringLogWrapper<T>(T);
+
+impl<T> FilteringLogWrapper<T> {
+  fn wanted(&self, md: &log::Metadata<'_>) -> bool {
+    let first = |mod_path| {
+      let mod_path: &str = mod_path; // can't do in args as breaks lifetimes
+      mod_path.split_once("::").map(|s| s.0).unwrap_or(mod_path)
+    };
+    first(md.target()) == first(module_path!())
+  }
+}
+
+impl<T> log::Log for FilteringLogWrapper<T> where T: log::Log {
+  fn enabled(&self, md: &log::Metadata<'_>) -> bool {
+    self.wanted(md) && self.0.enabled(md)
+  }
+
+  fn log(&self, record: &log::Record<'_>) {
+    if self.wanted(record.metadata()) {
+      self.0.log(record)
+    }
+  }
+
+  fn flush(&self) {
+    self.0.flush()
+  }
 }
 
 impl LogOpts {
   #[throws(AE)]
   pub fn log_init(&self) {
-    let env = env_logger::Env::new()
-      .filter("HIPPOTAT_LOG")
-      .write_style("HIPPOTAT_LOG_STYLE");
+    if let Some(facility) = self.syslog_facility {
+      let f = syslog::Formatter3164 {
+        facility,
+        hostname: None,
+        process: "hippotatd".into(),
+        pid: std::process::id(),
+      };
+      let l = syslog::unix(f)
+        // syslog::Error is not Sync.
+        // https://github.com/Geal/rust-syslog/issues/65
+        .map_err(|e| anyhow!(e.to_string()))
+        .context("set up syslog logger")?;
+      let l = syslog::BasicLogger::new(l);
+      let l = if self.debug < 2 {
+        Box::new(FilteringLogWrapper(l)) as _
+      } else {
+        Box::new(l) as _
+      };
+      log::set_boxed_logger(l).context("install syslog logger")?;
+      log::set_max_level(if self.debug < 1 {
+        log::LevelFilter::Debug
+      } else {
+        log::LevelFilter::Trace
+      });
+    } else {
+      let env = env_logger::Env::new()
+        .filter("HIPPOTAT_LOG")
+        .write_style("HIPPOTAT_LOG_STYLE");
   
-    let mut logb = env_logger::Builder::new();
-    logb.filter(Some("hippotat"),
-                *[ log::LevelFilter::Info,
-                   log::LevelFilter::Debug ]
-                .get(self.debug)
-                .unwrap_or(
-                  &log::LevelFilter::Trace
-                ));
-    logb.parse_env(env);
-    logb.init();
+      let mut logb = env_logger::Builder::new();
+      logb.filter(Some("hippotat"),
+                  *[ log::LevelFilter::Info,
+                     log::LevelFilter::Debug ]
+                  .get(self.debug)
+                  .unwrap_or(
+                    &log::LevelFilter::Trace
+                  ));
+      logb.parse_env(env);
+      logb.init();
+    }
   }
 }