use crate::async_utils::join; use crate::resources::Resource; use crate::to_artifact::ToArtifact; use async_trait::async_trait; use slog::{Drain, Filter, Logger, OwnedKVList, Record}; use slog_async::AsyncRecord; use std::cell::RefCell; use std::error::Error; use std::fmt::Debug; use std::io::{self, Write}; use std::rc::Rc; use std::sync::{Arc, Mutex}; pub trait AddableResource: 'static + Resource + Debug {} impl AddableResource for R where R: 'static + Resource + Debug {} pub type AddResult = Result<(::Artifact, bool), Box>; #[async_trait(?Send)] pub trait Add { async fn add(&self, logger: &Rc, resource: Rc, force_run: bool) -> AddResult; } #[async_trait(?Send)] pub trait AddGeneric { async fn add_generic(&self, logger: &Rc, x: X, force_run: bool) -> AddResult; } macro_rules! add_generic { ( $($name:ident)* ) => ( #[async_trait(?Send)] #[allow(non_snake_case)] impl<_S, $($name: AddableResource,)*> AddGeneric<($($name,)*)> for _S where $( _S: AddGeneric<$name> ),* { #[allow(unused, clippy::shadow_unrelated)] async fn add_generic(&self, logger: &Rc, ($($name,)*): ($($name,)*), force_run: bool) -> AddResult<($($name,)*)> { let ($($name,)*) = join!($(self.add_generic(logger, $name, force_run),)*); let mut did_run_any = false; $( let (artifact, did_run) = $name?; did_run_any = did_run_any || did_run; let $name = artifact; )* Ok((($($name,)*), did_run_any)) } } ); } for_each_tuple!(add_generic); #[async_trait(?Send)] impl> AddGeneric for S { async fn add_generic(&self, logger: &Rc, r: R, force_run: bool) -> AddResult { self.add(logger, Rc::new(r), force_run).await } } // From https://users.rust-lang.org/t/how-to-send-a-writer-into-a-thread/4965/10 #[derive(Clone)] struct Output(Rc>); impl Output { pub fn new(w: W) -> Self { Self(Rc::new(RefCell::new(w))) } } impl Write for Output { fn write(&mut self, buf: &[u8]) -> io::Result { self.0.borrow_mut().write(buf) } fn flush(&mut self) -> io::Result<()> { self.0.borrow_mut().flush() } } #[derive(Clone, Default)] pub struct Recorder(Arc>>); impl Drain for Recorder { type Ok = (); type Err = slog::Never; fn log(&self, record: &Record<'_>, logger_values: &OwnedKVList) -> Result { self .0 .lock() .unwrap() .push(AsyncRecord::from(record, logger_values)); Ok(()) } } impl Recorder { pub fn into_string(self, filter_level: slog::Level) -> String { let output = Output::new(vec![]); { let decorator = slog_term::PlainDecorator::new(output.clone()); let drain = Filter::new( slog_term::CompactFormat::new(decorator).build(), move |record| record.level().is_at_least(filter_level), ); let Ok(mutex) = Arc::try_unwrap(self.0) else { panic!("cannot unwrap Arc") }; // AsyncRecord does not implement Debug, so we cannot unwrap for record in mutex.into_inner().unwrap() { record.log_to(&drain).unwrap(); } } String::from_utf8(Rc::try_unwrap(output.0).unwrap().into_inner()) .expect("Record output should be valid UTF-8") } } #[cfg(test)] mod test { use super::Recorder; use slog::Level; #[test] fn records_no_output() { let recorder = Recorder::default(); assert_eq!(recorder.into_string(Level::Trace), ""); } }