reovim-kernel 0.14.4

Core kernel mechanisms for reovim (Linux kernel/ equivalent)
Documentation
//! Logger trait and global logger storage.
//!
//! Linux equivalent: `struct console` and `register_console()` in `kernel/printk/`
//!
//! This module defines the `Logger` trait that drivers implement to provide
//! actual log output. The kernel only defines the interface (mechanism),
//! while drivers implement the policy (where and how to log).

use std::{error::Error, fmt, sync::OnceLock};

use super::{level::Level, record::Record};

/// Logger trait - the kernel mechanism for logging.
///
/// Drivers implement this trait to provide actual log output. The kernel
/// only defines the interface, not the policy (where logs go, formatting, etc.).
///
/// # Thread Safety
///
/// Implementations must be `Send + Sync` as the logger is accessed from
/// multiple threads concurrently. Implementations should be lock-free
/// or use minimal locking to avoid blocking the caller.
///
/// # Example
///
/// ```
/// use reovim_kernel::api::v1::*;
///
/// struct StderrLogger;
///
/// impl Logger for StderrLogger {
///     fn log(&self, record: &Record) {
///         eprintln!("[{}] {}: {}",
///             record.level(),
///             record.file(),
///             record.message());
///     }
///
///     fn flush(&self) {
///         // stderr is unbuffered by default
///     }
///
///     fn enabled(&self, level: Level) -> bool {
///         level <= Level::Debug  // Log everything except Trace
///     }
/// }
/// ```
pub trait Logger: Send + Sync {
    /// Log a record.
    ///
    /// This method should be fast and non-blocking. If the logger
    /// buffers output, it should do so efficiently.
    fn log(&self, record: &Record);

    /// Flush any buffered output.
    ///
    /// Called to ensure all pending logs are written. May be called
    /// before program exit or when immediate output is needed.
    fn flush(&self);

    /// Check if a level is enabled for logging.
    ///
    /// This method is called before formatting the log message,
    /// allowing early exit if the level is not enabled. This avoids
    /// the cost of string formatting when logging is disabled.
    fn enabled(&self, level: Level) -> bool;
}

/// No-op logger used when no logger is set.
///
/// All operations are no-ops. `enabled()` returns `false` for all levels,
/// causing the logging macros to skip message formatting entirely.
///
/// This is the default logger if `set_logger()` is never called.
#[derive(Debug, Clone, Copy, Default)]
pub struct NopLogger;

impl Logger for NopLogger {
    #[inline]
    fn log(&self, _record: &Record) {
        // No-op
    }

    #[inline]
    fn flush(&self) {
        // No-op
    }

    #[inline]
    fn enabled(&self, _level: Level) -> bool {
        false
    }
}

/// Error returned when attempting to set the logger more than once.
///
/// The global logger can only be set once. This error is returned
/// if `set_logger()` is called after a logger has already been set.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct SetLoggerError;

// LLVM coverage artifact: unit struct Display impl closing brace marked DA:0
// despite being exercised by test_set_logger_error_display.
#[cfg_attr(coverage_nightly, coverage(off))]
impl fmt::Display for SetLoggerError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "logger already set")
    }
}

impl Error for SetLoggerError {}

// =============================================================================
// Global Logger State
// =============================================================================

/// Global logger storage.
///
/// Uses `OnceLock` for thread-safe one-time initialization.
/// This is the only global state in the printk module.
static LOGGER: OnceLock<&'static dyn Logger> = OnceLock::new();

/// Static no-op logger instance used as default.
static NOP_LOGGER: NopLogger = NopLogger;

/// Sets the global logger.
///
/// This function can only be called once. Subsequent calls will
/// return `Err(SetLoggerError)`.
///
/// # Errors
///
/// Returns `Err(SetLoggerError)` if a logger has already been set.
/// The global logger can only be set once.
///
/// # Thread Safety
///
/// This function is thread-safe. If multiple threads call `set_logger()`
/// concurrently, only one will succeed (and return `Ok`), while the
/// others will return `Err(SetLoggerError)`.
///
/// # Example
///
/// ```
/// use reovim_kernel::api::v1::*;
///
/// struct MyLogger;
///
/// impl Logger for MyLogger {
///     fn log(&self, record: &Record) {
///         eprintln!("{}", record.message());
///     }
///     fn flush(&self) {}
///     fn enabled(&self, _level: Level) -> bool { true }
/// }
///
/// static MY_LOGGER: MyLogger = MyLogger;
///
/// // First call succeeds
/// // Note: This would succeed, but we can't actually run it in doctests
/// // because other tests might have already set the logger.
/// // assert!(set_logger(&MY_LOGGER).is_ok());
/// ```
#[cfg_attr(coverage_nightly, coverage(off))]
pub fn set_logger(logger: &'static dyn Logger) -> Result<(), SetLoggerError> {
    LOGGER.set(logger).map_err(|_| SetLoggerError)
}

/// Returns the global logger.
///
/// If no logger has been set via `set_logger()`, returns the no-op logger
/// which silently discards all log messages.
///
/// # Example
///
/// ```
/// use reovim_kernel::api::v1::*;
///
/// // Before set_logger() is called, returns NopLogger
/// assert!(!logger().enabled(Level::Error));
/// ```
#[must_use]
pub fn logger() -> &'static dyn Logger {
    LOGGER.get().copied().unwrap_or(&NOP_LOGGER)
}

/// Internal helper for logging macros.
///
/// This function is called by the `pr_*` macros after the level check passes.
/// It formats the message and passes it to the logger.
///
/// # Note
///
/// This function is marked `#[doc(hidden)]` because it's an implementation
/// detail of the logging macros. Users should use the macros instead.
#[doc(hidden)]
pub fn __log(
    level: Level,
    module_path: &'static str,
    file: &'static str,
    line: u32,
    args: fmt::Arguments,
) {
    // Format the message (this is the allocation we want to avoid
    // when logging is disabled - hence the level check in macros)
    let message = args.to_string();

    let record = Record::builder(level)
        .message(&message)
        .module_path(module_path)
        .file(file)
        .line(line)
        .build();

    logger().log(&record);
}

/// Flushes the global logger.
///
/// Ensures all buffered log messages are written out. Useful before
/// program exit or when immediate output is required.
pub fn flush() {
    logger().flush();
}