use std::fmt::Arguments; use std::fs::File; use std::io::{Write, stderr}; use std::rc::Rc; use futures::future::LocalBoxFuture; use hashbrown::HashMap; use itertools::Itertools; use orchid_base::{LogWriter, Logger}; use crate::api; pub struct LogWriterImpl(api::LogStrategy); impl LogWriter for LogWriterImpl { fn write_fmt<'a>(&'a self, fmt: Arguments<'a>) -> LocalBoxFuture<'a, ()> { Box::pin(async move { match &self.0 { api::LogStrategy::Discard => (), api::LogStrategy::Default => { stderr().write_fmt(fmt).expect("Could not write to stderr!"); stderr().flush().expect("Could not flush stderr") }, api::LogStrategy::File { path, .. } => { let mut file = (File::options().write(true).create(true).truncate(false).open(path)) .unwrap_or_else(|e| panic!("Could not open {path}: {e}")); file.write_fmt(fmt).unwrap_or_else(|e| panic!("Could not write to {path}: {e}")); }, } }) } } #[derive(Clone, Default)] pub struct LoggerImpl { routing: HashMap, default: Option, } impl LoggerImpl { pub fn to_api(&self) -> api::Logger { api::Logger { default: self.default.clone(), routing: self.routing.iter().map(|(k, v)| (k.clone(), v.clone())).collect(), } } pub fn new( default: Option, strats: impl IntoIterator, ) -> Self { Self { routing: strats.into_iter().collect(), default } } pub fn set_default(&mut self, strat: api::LogStrategy) { self.default = Some(strat) } pub fn clear_default(&mut self) { self.default = None } pub fn set_category(&mut self, category: &str, strat: api::LogStrategy) { self.routing.insert(category.to_string(), strat); } pub fn with_default(mut self, strat: api::LogStrategy) -> Self { self.set_default(strat); self } pub fn with_category(mut self, category: &str, strat: api::LogStrategy) -> Self { self.set_category(category, strat); self } pub async fn log(&self, category: &str, msg: impl AsRef) { writeln!(self.writer(category), "{}", msg.as_ref()).await } pub fn has_category(&self, category: &str) -> bool { self.routing.contains_key(category) } pub async fn log_buf(&self, category: &str, event: impl AsRef, buf: &[u8]) { if std::env::var("ORCHID_LOG_BUFFERS").is_ok_and(|v| !v.is_empty()) { let data = buf.iter().map(|b| format!("{b:02x}")).join(" "); writeln!(self.writer(category), "{}: [{data}]", event.as_ref()).await } } } impl Logger for LoggerImpl { fn writer(&self, category: &str) -> Rc { Rc::new(LogWriterImpl(self.strat(category).clone())) } fn strat(&self, category: &str) -> api::LogStrategy { (self.routing.get(category).cloned().or(self.default.clone())) .expect("Invalid category and catchall logger not set") } }