nonblocking-logger 0.3.0

A high-performance library with format string support
Documentation
use crate::enums::log_level::LogLevel;
use crate::structs::logger::Logger;
use std::io;
use std::sync::{OnceLock, RwLock};

/// Global mutable logger instance wrapped in RwLock for thread-safe mutable access
static GLOBAL_LOGGER: OnceLock<RwLock<Logger>> = OnceLock::new();

fn init_global_logger() -> &'static RwLock<Logger> {
    GLOBAL_LOGGER.get_or_init(|| RwLock::new(Logger::from_env()))
}

/// Get the global logger instance
///
/// This will initialize the logger with default settings if not already initialized.
pub fn get_global_logger() -> &'static RwLock<Logger> {
    init_global_logger()
}

/// Get a mutable reference to the global logger
///
/// This function returns a `RwLockWriteGuard` that provides mutable access to the global logger.
/// The logger will be initialized with default settings if not already initialized.
///
/// # Examples
///
/// ```rust
/// use nonblocking_logger::{get_global_mut, LogLevel};
///
/// fn main() -> Result<(), Box<dyn std::error::Error>> {
///     let mut logger = get_global_mut();
///     logger.set_level(LogLevel::Debug);
///     logger.set_time_format("%Y-%m-%d %H:%M:%S%.3f");
///     logger.set_format_for_level(LogLevel::Error, "[ERROR] {time} - {message}");
///     Ok(())
/// }
/// ```
pub fn get_global_mut() -> std::sync::RwLockWriteGuard<'static, Logger> {
    GLOBAL_LOGGER
        .get_or_init(|| RwLock::new(Logger::from_env()))
        .write()
        .unwrap()
}

/// Log an error message using the global logger
///
/// # Examples
///
/// ```rust
/// use nonblocking_logger::error;
///
/// fn main() -> Result<(), Box<dyn std::error::Error>> {
///     error("Something went wrong")?;
///     Ok(())
/// }
/// ```
pub fn error(message: &str) -> io::Result<()> {
    get_global_logger().read().unwrap().error(message)
}

/// Log a warning message using the global logger
pub fn warning(message: &str) -> io::Result<()> {
    get_global_logger().read().unwrap().warning(message)
}

/// Log an info message using the global logger
pub fn info(message: &str) -> io::Result<()> {
    get_global_logger().read().unwrap().info(message)
}

/// Log a debug message using the global logger
pub fn debug(message: &str) -> io::Result<()> {
    get_global_logger().read().unwrap().debug(message)
}

/// Log a trace message using the global logger
pub fn trace(message: &str) -> io::Result<()> {
    get_global_logger().read().unwrap().trace(message)
}

/// Log a message using the global logger (always outputs, no level filtering)
pub fn log(message: &str) -> io::Result<()> {
    get_global_logger().read().unwrap().log(message)
}

/// Log a message with a specific level using the global logger (with filtering)
pub fn log_with_level(level: LogLevel, message: &str) -> io::Result<()> {
    get_global_logger().read().unwrap().log_with_level(level, message)
}

/// Log with lazy evaluation using the global logger
///
/// The closure is only executed if the log level is sufficient.
///
/// # Examples
///
/// ```rust
/// use nonblocking_logger::debug_lazy;
///
/// fn expensive_function() -> String {
///     "result".to_string()
/// }
///
/// fn main() -> Result<(), Box<dyn std::error::Error>> {
///     // This expensive computation only runs if debug logging is enabled
///     debug_lazy(|| {
///         format!("Expensive computation: {}", expensive_function())
///     })?;
///     Ok(())
/// }
/// ```
pub fn error_lazy<F>(message_fn: F) -> io::Result<()>
where
    F: FnOnce() -> String,
{
    get_global_logger().read().unwrap().error_lazy(message_fn)
}

pub fn warning_lazy<F>(message_fn: F) -> io::Result<()>
where
    F: FnOnce() -> String,
{
    get_global_logger().read().unwrap().warning_lazy(message_fn)
}

pub fn info_lazy<F>(message_fn: F) -> io::Result<()>
where
    F: FnOnce() -> String,
{
    get_global_logger().read().unwrap().info_lazy(message_fn)
}

pub fn debug_lazy<F>(message_fn: F) -> io::Result<()>
where
    F: FnOnce() -> String,
{
    get_global_logger().read().unwrap().debug_lazy(message_fn)
}

pub fn trace_lazy<F>(message_fn: F) -> io::Result<()>
where
    F: FnOnce() -> String,
{
    get_global_logger().read().unwrap().trace_lazy(message_fn)
}

pub fn log_lazy<F>(message_fn: F) -> io::Result<()>
where
    F: FnOnce() -> String,
{
    get_global_logger()
        .read()
        .unwrap()
        .log_lazy(message_fn)
}


#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_convenience_functions() -> io::Result<()> {
        error("Test error")?;
        warning("Test warning")?;
        info("Test info")?;
        debug("Test debug")?;
        trace("Test trace")?;

        let global_logger = get_global_logger();
        let logger = global_logger.read().unwrap();
        assert_eq!(logger.level(), LogLevel::Info);

        Ok(())
    }

    #[test]
    fn test_lazy_convenience_functions() -> io::Result<()> {
        let mut called = false;

        debug_lazy(|| {
            called = true;
            "Debug message".to_string()
        })?;

        called = false;

        info_lazy(|| {
            called = true;
            "Info message".to_string()
        })?;

        assert!(
            called,
            "Lazy closure should have been called for info level"
        );

        Ok(())
    }
}