use std::any::Any; use std::cell::RefCell; use std::fmt::Arguments; use std::io::Write; use std::rc::Rc; use futures::future::LocalBoxFuture; use task_local::task_local; use crate::api; task_local! { static DEFAULT_WRITER: RefCell> } /// Set the stream used for [api::LogStrategy::Default]. If not set, /// [std::io::stderr] will be used. pub async fn with_default_stream(stderr: impl Write + 'static, fut: F) -> F::Output { DEFAULT_WRITER.scope(RefCell::new(Box::new(stderr)), fut).await } pub trait LogWriter { fn write_fmt<'a>(&'a self, fmt: Arguments<'a>) -> LocalBoxFuture<'a, ()>; } pub trait Logger: Any { fn writer(&self, category: &str) -> Rc; fn strat(&self, category: &str) -> api::LogStrategy; fn is_active(&self, category: &str) -> bool { !matches!(self.strat(category), api::LogStrategy::Discard) } } task_local! { static LOGGER: Rc; } pub async fn with_logger(logger: impl Logger + 'static, fut: F) -> F::Output { LOGGER.scope(Rc::new(logger), fut).await } pub fn log(category: &str) -> Rc { LOGGER.try_with(|l| l.writer(category)).expect("Logger not set!") } pub fn get_logger() -> Rc { LOGGER.try_with(|l| l.clone()).expect("Logger not set!") } pub mod test { use std::fmt::Arguments; use std::rc::Rc; use futures::future::LocalBoxFuture; use crate::clone; use crate::logging::{LogWriter, Logger}; #[derive(Clone)] pub struct TestLogger(Rc LocalBoxFuture<'static, ()>>); impl LogWriter for TestLogger { fn write_fmt<'a>(&'a self, fmt: Arguments<'a>) -> LocalBoxFuture<'a, ()> { (self.0)(fmt.to_string()) } } impl Logger for TestLogger { fn strat(&self, _category: &str) -> orchid_api::LogStrategy { orchid_api::LogStrategy::Default } fn writer(&self, _category: &str) -> std::rc::Rc { Rc::new(self.clone()) } } impl TestLogger { pub fn new(f: impl AsyncFn(String) + 'static) -> Self { let f = Rc::new(f); Self(Rc::new(move |s| clone!(f; Box::pin(async move { f(s).await })))) } } impl Default for TestLogger { fn default() -> Self { TestLogger::new(async |s| eprint!("{s}")) } } }