//! Utilities for debug information logging use alloc::{ string::{String, ToString}, sync::Arc, }; use core::{fmt, str::FromStr}; use ring::RingLoggerSink; use libk_util::{sync::IrqSafeSpinlock, OneTimeInit}; use sink::DEBUG_SINKS; use yggdrasil_abi::error::Error; use crate::{ fs::sysfs::{ attribute::{StringAttribute, StringAttributeOps}, object::KObject, }, task::{process::Process, thread::Thread}, }; mod panic; mod ring; mod sink; pub use panic::{panic_log, PanicLoggerSink}; pub use ring::add_kernel_log_file; pub use sink::{add_early_sink, add_serial_sink, add_sink, DebugSink}; static DEBUG_LOCK: IrqSafeSpinlock<()> = IrqSafeSpinlock::new(()); struct KernelLoggerSink; /// Defines the severity of the message #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] pub enum LogLevel { /// Very verbose low-level debugging information Trace, /// Debugging and verbose information Debug, /// General information about transitions in the system state Info, /// Non-critical abnormalities or notices Warning, /// Failures of non-essential components Error, /// Irrecoverable errors which result in kernel panic Fatal, } impl LogLevel { fn log_prefix(self) -> &'static str { match self { LogLevel::Trace => "", LogLevel::Debug => "", LogLevel::Info => "\x1b[36m\x1b[1m", LogLevel::Warning => "\x1b[33m\x1b[1m", LogLevel::Error => "\x1b[31m\x1b[1m", LogLevel::Fatal => "\x1b[38;2;255;0;0m\x1b[1m", } } fn log_suffix(self) -> &'static str { match self { LogLevel::Trace => "", LogLevel::Debug => "", LogLevel::Info => "\x1b[0m", LogLevel::Warning => "\x1b[0m", LogLevel::Error => "\x1b[0m", LogLevel::Fatal => "\x1b[0m", } } } impl From for LogLevel { fn from(value: log::Level) -> Self { match value { log::Level::Trace => Self::Trace, log::Level::Debug => Self::Debug, log::Level::Info => Self::Info, log::Level::Warn => Self::Warning, log::Level::Error => Self::Error, } } } impl log::Log for KernelLoggerSink { fn enabled(&self, metadata: &log::Metadata) -> bool { metadata.target() != "io" } fn log(&self, record: &log::Record) { if !self.enabled(record.metadata()) { return; } RingLoggerSink.log(record); let _guard = DEBUG_LOCK.lock(); for sink in DEBUG_SINKS.read().iter() { if sink.enabled(record.metadata()) { sink.log(record); } } } fn flush(&self) {} } static LOGGER: KernelLoggerSink = KernelLoggerSink; fn make_sysfs_sink_object(index: usize) -> Arc> { struct Level; impl StringAttributeOps for Level { type Data = usize; const NAME: &'static str = "level"; const LIMIT: usize = 16; const WRITEABLE: bool = true; fn read(state: &Self::Data) -> Result { let sinks = DEBUG_SINKS.read(); let sink = sinks.get(*state).ok_or(Error::InvalidFile)?; Ok(sink.level().to_string()) } fn write(state: &Self::Data, value: &str) -> Result<(), Error> { let level = LogLevel::from_str(value)?; let mut sinks = DEBUG_SINKS.write(); let sink = sinks.get_mut(*state).ok_or(Error::InvalidFile)?; sink.set_level(level); Ok(()) } } let object = KObject::new(index); object.add_attribute(StringAttribute::from(Level)).ok(); object } /// Print a trace message coming from a process pub fn program_trace(process: &Process, _thread: &Thread, message: &str) { log::debug!( target: ":program", "{} ({}) {message}\n", process.name, process.id, ); } /// Initializes kernel logging output. Prior to this call, no log entries are recorded. pub fn init_logger() { static LOGGER_SET_UP: OneTimeInit<()> = OneTimeInit::new(); LOGGER_SET_UP.or_init_with(|| { log::set_logger(&LOGGER) .map(|_| log::set_max_level(log::LevelFilter::Trace)) .ok(); }); } /// Resets the debugging terminal by clearing it pub fn init() { init_logger(); } /// Print a hex dump into the kernel log with given level pub fn hex_dump(level: log::Level, bytes: &[u8], start_address: u64, with_chars: bool) { if bytes.is_empty() { return; } let line_width = 16; let address_width = (start_address + bytes.len() as u64).ilog10() as usize + 1; for i in (0..bytes.len()).step_by(line_width) { log::log!(target: ":raw", level, "{:0width$X}: ", start_address + i as u64, width = address_width); for j in 0..line_width { if i + j < bytes.len() { log::log!(target: ":raw", level, "{:02X}", bytes[i + j]); } else { log::log!(target: ":raw", level, " "); } if j % 2 != 0 { log::log!(target: ":raw", level, " "); } } if with_chars { for j in 0..line_width { if i + j >= bytes.len() { break; } let ch = if bytes[i + j].is_ascii_graphic() { bytes[i + j] } else { b'.' }; log::log!(target: ":raw", level, "{}", ch as char); } } log::log!(target: ":raw", level, "\n"); } } impl fmt::Display for LogLevel { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let level = match self { Self::Trace => "trace", Self::Debug => "debug", Self::Info => "info", Self::Warning => "warn", Self::Error => "error", Self::Fatal => "fatal", }; f.write_str(level) } } impl FromStr for LogLevel { type Err = Error; fn from_str(s: &str) -> Result { match s { "trace" | "t" => Ok(Self::Trace), "debug" | "d" => Ok(Self::Debug), "info" | "i" => Ok(Self::Info), "warn" | "w" => Ok(Self::Warning), "error" | "e" => Ok(Self::Error), "fatal" | "f" => Ok(Self::Fatal), _ => Err(Error::InvalidArgument), } } }