You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
131 lines
3.6 KiB
131 lines
3.6 KiB
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<R> AddableResource for R where R: 'static + Resource + Debug {}
|
|
|
|
pub type AddResult<R> = Result<(<R as ToArtifact>::Artifact, bool), Box<dyn Error>>;
|
|
|
|
#[async_trait(?Send)]
|
|
pub trait Add<R: AddableResource> {
|
|
async fn add(&self, logger: &Rc<Logger>, resource: Rc<R>, force_run: bool) -> AddResult<R>;
|
|
}
|
|
|
|
#[async_trait(?Send)]
|
|
pub trait AddGeneric<X: ToArtifact> {
|
|
async fn add_generic(&self, logger: &Rc<Logger>, x: X, force_run: bool) -> AddResult<X>;
|
|
}
|
|
|
|
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<Logger>, ($($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<R: AddableResource + Debug, S: Add<R>> AddGeneric<R> for S {
|
|
async fn add_generic(&self, logger: &Rc<Logger>, r: R, force_run: bool) -> AddResult<R> {
|
|
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<W>(Rc<RefCell<W>>);
|
|
|
|
impl<W: Write> Output<W> {
|
|
pub fn new(w: W) -> Self {
|
|
Self(Rc::new(RefCell::new(w)))
|
|
}
|
|
}
|
|
|
|
impl<W: Write> Write for Output<W> {
|
|
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
|
|
self.0.borrow_mut().write(buf)
|
|
}
|
|
|
|
fn flush(&mut self) -> io::Result<()> {
|
|
self.0.borrow_mut().flush()
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Default)]
|
|
pub struct Recorder(Arc<Mutex<Vec<AsyncRecord>>>);
|
|
|
|
impl Drain for Recorder {
|
|
type Ok = ();
|
|
type Err = slog::Never;
|
|
|
|
fn log(&self, record: &Record<'_>, logger_values: &OwnedKVList) -> Result<Self::Ok, Self::Err> {
|
|
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), "");
|
|
}
|
|
}
|