kairoi 0.2.0

Another application-level tracing library
Documentation
use crate::channel::rx;
use crate::error::SendSyncError;
use crate::{Event, Span, SpanRef};
use crossbeam_channel::TryRecvError;
use parking_lot::Mutex;
use std::error::Error;
use std::fmt::{Debug, Display, Formatter};
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use std::thread;
use std::thread::JoinHandle;
use std::time::{Duration, Instant};

pub trait Handler: Send {
    fn handle(&self, event: &Event) -> Result<(), SendSyncError>;
    fn tick(&self, root: &SpanRef) -> Result<(), SendSyncError>;
}

pub struct GlobalHandlerBuilder {
    fps: u32,
    handlers: Vec<Box<dyn Handler>>,
}

impl GlobalHandlerBuilder {
    fn new() -> Self {
        Self {
            fps: 15,
            handlers: vec![],
        }
    }

    pub fn fps(mut self, fps: u32) -> Self {
        self.fps = fps;
        self
    }

    pub fn handler(mut self, handler: Box<dyn Handler>) -> Self {
        self.handlers.push(handler);
        self
    }

    pub fn build(self) -> GlobalHandler {
        GlobalHandler::new(self.fps, self.handlers)
    }
}

pub struct GlobalHandler {
    token: Arc<AtomicBool>,
    handle: JoinHandle<Result<(), SendSyncError>>,
}

struct AggregatedError(Mutex<Vec<SendSyncError>>);

impl Debug for AggregatedError {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        let lock = self.0.lock();

        writeln!(f, "Aggregated error(s) (total {})", lock.len())?;
        for i in 0..lock.len() {
            writeln!(f, "- {}/{}: {}", i + 1, lock.len(), lock[i])?;
        }

        Ok(())
    }
}

impl Display for AggregatedError {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        let lock = self.0.lock();

        writeln!(f, "Aggregated error(s) (total {})", lock.len())?;
        for i in 0..lock.len() {
            writeln!(
                f,
                "=== Error Dump {}/{} ===\n{:?}",
                i + 1,
                lock.len(),
                lock[i]
            )?;
        }

        Ok(())
    }
}

impl Error for AggregatedError {}

impl GlobalHandler {
    fn foreach<T, F: Fn(&T) -> Result<(), SendSyncError>>(
        handlers: &mut Vec<T>,
        f: F,
    ) -> Result<(), AggregatedError> {
        let mut begin = 0;

        let mut errors: Vec<SendSyncError> = Vec::new();
        while begin < handlers.len() {
            'a: for i in begin..handlers.len() {
                if let Err(e) = f(&handlers[i]) {
                    errors.push(e);
                    handlers.remove(i);
                    #[allow(unused_assignments)]
                    begin = i;
                    break 'a;
                }
            }

            begin = handlers.len();
        }
        if !errors.is_empty() {
            return Err(AggregatedError(Mutex::new(errors)));
        }

        Ok(())
    }

    fn thread_loop(
        fps: u32,
        token: Arc<AtomicBool>,
        mut handlers: Vec<Box<dyn Handler>>,
    ) -> Result<(), SendSyncError> {
        let mut last_update = Instant::now();
        let frame_duration = Duration::from_millis(1000 / fps as u64);

        while token.load(Ordering::Acquire) {
            loop {
                match rx().try_recv() {
                    Ok(event) => {
                        Self::foreach(&mut handlers, |handler| handler.handle(&event))?;
                    }
                    Err(TryRecvError::Disconnected) => {
                        return Err("Channel has been disconnected".into());
                    }
                    Err(TryRecvError::Empty) => {
                        break;
                    }
                };
            }
            if last_update.elapsed() >= frame_duration {
                let root = Span::root();
                Self::foreach(&mut handlers, |handler| handler.tick(&root))?;
                last_update = Instant::now();
            }

            thread::sleep(Duration::from_millis(10));
        }

        Ok(())
    }

    fn new(fps: u32, handlers: Vec<Box<dyn Handler>>) -> Self {
        let token = Arc::new(AtomicBool::new(true));

        let token_clone = token.clone();
        let handle = thread::spawn(move || -> Result<(), SendSyncError> {
            match Self::thread_loop(fps, token_clone, handlers) {
                Ok(_) => Ok(()),
                Err(e) => {
                    const MSG: &str = "[kairoi] logging thread panic; memory consumption with event channel won't be held";
                    #[cfg(debug_assertions)]
                    eprintln!("{MSG}\n{e:?}");
                    #[cfg(not(debug_assertions))]
                    eprintln!("{MSG}\n{e}");
                    Err(e)
                }
            }
        });

        Self { token, handle }
    }

    pub fn join(self) {
        self.token.store(false, Ordering::Release);
        if let Err(e) = self.handle.join() {
            std::panic::resume_unwind(e);
        }
    }

    pub fn builder() -> GlobalHandlerBuilder {
        GlobalHandlerBuilder::new()
    }
}