// Copyright (c) 2025 shibedrill // SPDX-License-Identifier: GPL-3.0-or-later use core::fmt::Write; use crate::constants::{LOG_DEFAULT_LEVEL, NEWLINE}; use crate::memory::alloc; use alloc::boxed::*; use alloc::string::*; use alloc::vec::*; use spin::Mutex; #[macro_export] macro_rules! log_info { ($($arg:tt)*) => { LOGGER.log(LogLevel::Info, &format!($($arg)*), file!(), line!(), column!()) }; } #[macro_export] macro_rules! log_trace { ($($arg:tt)*) => { LOGGER.log(LogLevel::Trace, &format!($($arg)*), file!(), line!(), column!()) }; } #[macro_export] #[allow(unused_macros)] macro_rules! log_warning { ($($arg:tt)*) => { LOGGER.log(LogLevel::Warning, &format!($($arg)*), file!(), line!(), column!()) }; } #[macro_export] #[allow(unused_macros)] macro_rules! log_error { ($($arg:tt)*) => { LOGGER.log(LogLevel::Error, &format!($($arg)*), file!(), line!(), column!()) }; } #[macro_export] #[allow(unused_macros)] macro_rules! log_critical { ($($arg:tt)*) => { LOGGER.log(LogLevel::Critical, &format!($($arg)*), file!(), line!(), column!()) }; } pub struct Logger { pub inner: Mutex, } impl Logger { pub fn log(&self, level: LogLevel, msg: &str, file: &'static str, line: u32, column: u32) { self.inner.lock().log(level, msg, file, line, column); } pub fn add_subscriber(&self, sub: T) { self.inner .lock() .subscriber .push(Mutex::new(alloc::boxed::Box::new(sub))); } pub fn set_level(&self, level: LogLevel) { self.inner.lock().level = level; } } // The logger exists for the entire lifetime of the kernel. pub static LOGGER: Logger = Logger { inner: Mutex::new(LoggerInner::new()), }; pub struct EnumParseError {} pub struct LoggerInner { pub level: LogLevel, pub subscriber: Vec>>, } pub trait LogSubscriber: Send + Sync { fn log_write(&mut self, msg: &str); } impl Default for LoggerInner { fn default() -> Self { Self::new() } } impl LoggerInner { pub const fn new() -> Self { LoggerInner { level: LOG_DEFAULT_LEVEL, subscriber: Vec::new(), } } // Calling log will sequentially acquire lock on all logging subscribers // to write to them with a formatted log message. pub fn log(&self, level: LogLevel, msg: &str, file: &'static str, line: u32, column: u32) { // Nobody is EVER allowed to call log with the Disabled log level. It is a placeholder. if level == LogLevel::Disabled { // Nothing } else if level <= self.level { let level_string = String::from(level); for sub in &self.subscriber { let mut message = String::new(); write!( &mut message, "[{level_string}] {file}:{line},{column} - {msg}{NEWLINE}" ) .unwrap(); sub.lock().log_write(&message); } } } } #[derive(PartialEq, Eq, PartialOrd, Ord, Debug, Clone, Copy)] pub enum LogLevel { Disabled = 0, Critical = 1, Error = 2, Warning = 3, Info = 4, Trace = 5, } impl TryFrom for LogLevel { type Error = EnumParseError; fn try_from(from: u8) -> Result>::Error> { match from { 0 => Ok(Self::Disabled), 1 => Ok(Self::Critical), 2 => Ok(Self::Error), 3 => Ok(Self::Warning), 4 => Ok(Self::Info), 5 => Ok(Self::Trace), _ => Err(EnumParseError {}), } } } impl TryFrom<&str> for LogLevel { type Error = EnumParseError; fn try_from(from: &str) -> Result>::Error> { match from.to_ascii_lowercase().as_ref() { "disabled" => Ok(Self::Disabled), "critical" => Ok(Self::Critical), "error" => Ok(Self::Error), "warning" => Ok(Self::Warning), "info" => Ok(Self::Info), "trace" => Ok(Self::Trace), _ => Err(EnumParseError {}), } } } impl From for String { fn from(from: LogLevel) -> String { match from { LogLevel::Disabled => "Disabled", LogLevel::Critical => "CRIT ", LogLevel::Error => "ERROR", LogLevel::Warning => "WARN ", LogLevel::Info => "INFO ", LogLevel::Trace => "TRACE", } .into() } }