timber_rust 2.0.2

A high-performance, asynchronous logging library with support for Grafana Loki and AWS CloudWatch.
Documentation
// SPDX-License-Identifier: Apache-2.0
// Copyright 2026 Dante Doménech Martinez dante19031999@gmail.com

use crate::service::ServiceError;
use crate::service::fallback::Fallback;
use crate::service::write::{MessageFormatter, StandardMessageFormatter};
use crate::{LoggerStatus, Message, Service};
use std::any::Any;
use std::io::BufWriter;
use std::sync::Mutex;

/// A private synchronization container for [`IoWrite`].
///
/// This struct groups the writer and formatter into a single unit. This ensures
/// **atomicity**: the formatter state and writer output are synchronized.
/// By placing both in a single [`Mutex`], we guarantee that log interleaving
/// is impossible even if the formatter holds internal state.
struct IoData<W, F>
where
    W: std::io::Write + Send + Sync,
    F: MessageFormatter,
{
    /// The byte-oriented output destination.
    writer: W,
    /// The logic used to transform a [`Message`] into bytes.
    formatter: F,
}

/// A thread-safe [`Service`] for byte-stream logging destinations.
///
/// [`IoWrite`] is the primary workhorse for file-based, socket-based, or
/// console-based logging. It implements the [`Service`] trait by wrapping its
/// internal data in a [`Mutex`].
///
/// ### Performance Note
/// This service does not explicitly call `flush()` after every write. If low-latency
/// is required with guaranteed persistence, wrap your writer in [`std::io::BufWriter`].
pub struct IoWrite<W, F>
where
    W: std::io::Write + Send + Sync,
    F: MessageFormatter,
{
    /// The mutex-protected destination and formatting logic.
    writer: Mutex<IoData<W, F>>,
}

impl<W, F> IoWrite<W, F>
where
    W: std::io::Write + Send + Sync,
    F: MessageFormatter,
{
    /// Creates a new [`IoWrite`] on the heap.
    ///
    /// # Parameters
    /// - `writer`: A type implementing [`std::io::Write`].
    /// - `formatter`: The [`MessageFormatter`] implementation.
    pub fn new(writer: W) -> Box<Self> {
        Box::new(Self {
            writer: Mutex::new(IoData {
                writer,
                formatter: Default::default(),
            }),
        })
    }

    /// Creates a new [`IoWrite`] on the heap with a custom [formatter][`MessageFormatter`].
    ///
    /// # Parameters
    /// - `writer`: A type implementing [`std::io::Write`].
    /// - `formatter`: The [`MessageFormatter`] implementation.
    pub fn with_formatter(writer: W, formatter: F) -> Box<Self> {
        Box::new(Self {
            writer: Mutex::new(IoData { writer, formatter }),
        })
    }
}

impl<W, F> Service for IoWrite<W, F>
where
    W: std::io::Write + Send + Sync + 'static,
    F: MessageFormatter + 'static,
{
    fn status(&self) -> LoggerStatus {
        LoggerStatus::Running
    }

    /// Acquires the lock and streams the formatted message to the writer.
    ///
    /// # Errors
    /// - [`ServiceError::LockPoisoned`]: If the internal [`Mutex`] is poisoned.
    /// - [`ServiceError`]: If the formatter fails or the writer encounters an I/O error.
    fn work(&self, msg: &Message) -> Result<(), ServiceError> {
        let mut guard = self.writer.lock()?;

        // Destructuring allows simultaneous mutable access to both fields.
        let IoData {
            formatter, writer, ..
        } = &mut *guard;

        formatter.format_io(msg, writer)?;
        Ok(())
    }

    fn as_any(&self) -> &dyn Any {
        self
    }
}

impl<W, F> Fallback for IoWrite<W, F>
where
    W: std::io::Write + Send + Sync + 'static,
    F: MessageFormatter + 'static,
{
    /// Best-effort fallback. Skips writing if the mutex is locked or poisoned
    /// to prevent cascading failures in the logging pipeline.
    fn fallback(&self, error: &ServiceError, msg: &Message) {
        if let Ok(mut guard) = self.writer.lock() {
            let mut out = std::io::stdout();
            let _ = guard.formatter.format_io(msg, &mut out);
            let _ = eprintln!("IoWriteService Fallback [Error: {}]", error);
        }
    }
}

/// A type alias for an [`IoWrite`] service using a dynamic trait object.
///
/// This is particularly useful when you need to change the logging destination
/// at runtime (e.g., switching from a File to a Network stream).
///
/// **Bound Requirements:** The inner writer must be [`Send`] + [`Sync`] + `'static`.
#[allow(type_alias_bounds)]
pub type BoxedIo<F: MessageFormatter> = IoWrite<Box<dyn std::io::Write + Send + Sync>, F>;

/// A type alias for an [`IoWrite`] service writing specifically to a [`std::fs::File`].
#[allow(type_alias_bounds)]
pub type File<F: MessageFormatter> = IoWrite<std::fs::File, F>;

/// A type alias for an [`IoWrite`] service writing specifically to a [`std::io::BufWriter<std::fs::File>`][`BufWriter`].
#[allow(type_alias_bounds)]
pub type BufferedFile<F: MessageFormatter> = IoWrite<BufWriter<std::fs::File>, F>;

/// A pre-configured [`BoxedIo`] service using the crate's [`StandardMessageFormatter`].
pub type StandardBoxedIo = BoxedIo<StandardMessageFormatter>;

/// A pre-configured [`File`] using the crate's [`StandardMessageFormatter`].
pub type StandardFile = File<StandardMessageFormatter>;

/// A pre-configured [`BufferedFile`] using the crate's [`StandardMessageFormatter`].
pub type StandardBufferedFile = BufferedFile<StandardMessageFormatter>;