nonblocking-logger 0.3.0

A high-performance library with format string support
Documentation
use crate::enums::log_level::LogLevel;
use crate::structs::logger_wasm::LoggerWasm;
use std::cell::RefCell;
use std::io;
use std::sync::OnceLock;

/// A wrapper around RefCell that implements Sync for WASM
/// SAFETY: In WASM, there's only one thread, so RefCell is safe to share
pub struct WasmRefCell<T>(RefCell<T>);

unsafe impl<T> Sync for WasmRefCell<T> {}

impl<T> WasmRefCell<T> {
    fn new(value: T) -> Self {
        Self(RefCell::new(value))
    }

    fn borrow(&self) -> std::cell::Ref<T> {
        self.0.borrow()
    }

    fn borrow_mut(&self) -> std::cell::RefMut<T> {
        self.0.borrow_mut()
    }
}

/// Global mutable logger instance (no sync needed in WASM single-threaded environment)
static GLOBAL_LOGGER: OnceLock<WasmRefCell<LoggerWasm>> = OnceLock::new();

/// Initialize the global WASM logger with default settings
///
/// This function sets up a global logger that writes to browser console with Info level.
/// It's automatically called when you use any of the convenience functions,
/// but you can call it explicitly if you want to ensure initialization.
pub fn init_global_logger() -> &'static WasmRefCell<LoggerWasm> {
    GLOBAL_LOGGER.get_or_init(|| WasmRefCell::new(LoggerWasm::with_level(LogLevel::Info)))
}

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

/// Get a mutable reference to the global WASM logger
///
/// This function returns a `RefMut` that provides mutable access to the global logger.
/// The logger will be initialized with default settings if not already initialized.
pub fn get_global_mut() -> std::cell::RefMut<'static, LoggerWasm> {
    GLOBAL_LOGGER
        .get_or_init(|| WasmRefCell::new(LoggerWasm::with_level(LogLevel::Info)))
        .borrow_mut()
}

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

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

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

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

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

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

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

/// Log with lazy evaluation using the global WASM logger
pub fn error_lazy<F>(message_fn: F) -> io::Result<()>
where
    F: FnOnce() -> String,
{
    get_global_logger().borrow().error_lazy(message_fn)
}

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

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

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

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

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


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

    #[cfg(target_arch = "wasm32")]
    use wasm_bindgen_test::*;

    #[cfg(target_arch = "wasm32")]
    wasm_bindgen_test_configure!(run_in_browser);

    #[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.borrow();
        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(())
    }

    #[cfg(target_arch = "wasm32")]
    mod wasm_tests {
        use super::*;

        #[wasm_bindgen_test]
        fn test_wasm_logger_initialization() {
            let _logger = init_global_logger();
            info("Logger initialization test").expect("Logger should be initialized");
        }

        #[wasm_bindgen_test]
        fn test_wasm_logging_functions() {
            error("Test error message").expect("Error logging should work");
            warning("Test warning message").expect("Warning logging should work");
            info("Test info message").expect("Info logging should work");
            debug("Test debug message").expect("Debug logging should work");
            trace("Test trace message").expect("Trace logging should work");
        }

        #[wasm_bindgen_test]
        fn test_wasm_lazy_logging() {
            let mut called = false;

            info_lazy(|| {
                called = true;
                "Lazy info message".to_string()
            })
            .expect("Lazy info logging should work");

            assert!(called, "Lazy closure should have been called");
        }

        #[wasm_bindgen_test]
        fn test_wasm_log_level() {
            log("Test message with specific level")
                .expect("Logging with specific level should work");
        }
    }
}