penna 0.1.0

Structured JSON logging for tracing, in one line per event, without a regex engine underneath
Documentation
//! Where the lines go.

use std::io;

/// Supplies a writer for each line.
///
/// One line is written with a single `write_all`, so a writer that locks —
/// which [`Stdout`] and [`Stderr`] do — keeps lines whole when threads log at
/// once.
pub trait MakeWriter: Send + Sync + 'static {
    /// The writer this hands out.
    type Writer: io::Write;

    /// Makes a writer for one line.
    fn make_writer(&self) -> Self::Writer;
}

/// Writes to standard output, locked per line.
#[derive(Debug, Clone, Copy, Default)]
pub struct Stdout;

impl MakeWriter for Stdout {
    type Writer = io::StdoutLock<'static>;

    fn make_writer(&self) -> Self::Writer {
        io::stdout().lock()
    }
}

/// Writes to standard error, locked per line.
#[derive(Debug, Clone, Copy, Default)]
pub struct Stderr;

impl MakeWriter for Stderr {
    type Writer = io::StderrLock<'static>;

    fn make_writer(&self) -> Self::Writer {
        io::stderr().lock()
    }
}

/// Anything shared and lockable works, which is how a test collects lines:
///
/// ```
/// use std::sync::{Arc, Mutex};
///
/// let lines: Arc<Mutex<Vec<u8>>> = Arc::default();
/// let subscriber = penna::json().with_writer(lines.clone()).filter("info").finish();
/// tracing::subscriber::with_default(subscriber, || tracing::info!("hello"));
/// assert!(String::from_utf8(lines.lock().unwrap().clone()).unwrap().contains("hello"));
/// ```
impl MakeWriter for std::sync::Arc<std::sync::Mutex<Vec<u8>>> {
    type Writer = SharedBuffer;

    fn make_writer(&self) -> Self::Writer {
        SharedBuffer(self.clone())
    }
}

/// The writer [`MakeWriter`] hands out for a shared buffer.
#[derive(Debug)]
pub struct SharedBuffer(std::sync::Arc<std::sync::Mutex<Vec<u8>>>);

impl io::Write for SharedBuffer {
    fn write(&mut self, buffer: &[u8]) -> io::Result<usize> {
        let mut out = self.0.lock().unwrap_or_else(|poison| poison.into_inner());
        out.extend_from_slice(buffer);
        Ok(buffer.len())
    }

    fn flush(&mut self) -> io::Result<()> {
        Ok(())
    }
}