use std::process::Command; use log::{Level, Log, Metadata, Record}; use reqwest::blocking; mod message; pub use message::{LogMsg, Severity}; pub struct SnitchLogger { wrapped: L, url: String, service_name: String, hostname: String, } impl SnitchLogger { pub fn new(wrapped: L, url: impl Into, service_name: impl Into) -> Self { SnitchLogger { wrapped, url: url.into(), service_name: service_name.into(), hostname: probe_hostname(), } } } impl Log for SnitchLogger { fn enabled(&self, metadata: &Metadata) -> bool { self.wrapped.enabled(metadata) || metadata.level() <= Level::Warn } fn log(&self, record: &Record) { self.wrapped.log(record); let severity = match record.metadata().level() { Level::Error => Severity::Error, Level::Warn => Severity::Warning, _ => return, }; let record = LogMsg { severity, file: record.file().map(String::from), line: record.line(), ..LogMsg::new( self.service_name.clone(), record.args().to_string(), self.hostname.clone(), ) }; let client = blocking::Client::new(); if let Err(e) = client.post(&self.url).json(&record).send() { // TODO: log error (without sending it) eprintln!("failed to send log record: {e:?}"); } } fn flush(&self) { self.wrapped.flush(); } } pub fn probe_hostname() -> String { let output = Command::new("uname").arg("-n").output().ok(); output .as_ref() .and_then(|output| std::str::from_utf8(&output.stdout).ok()) .unwrap_or("") .to_string() } impl Drop for SnitchLogger { fn drop(&mut self) { self.flush(); } }