//! Simple logger for `SkavOS` use alloc::format; use alloc::string::String; use core::fmt::Display; use core::sync::atomic::{AtomicU64, Ordering}; use circular_buffer::CircularBuffer; use log::{Level, Log, Metadata, Record}; use crate::kernel::screen::color::{Color, CYAN, ORANGE, RED, WHITE, YELLOW}; use crate::mutex::Locked; use crate::{print, println}; /// Next message identifier static NEXT_MESSAGE_ID: AtomicU64 = AtomicU64::new(0); /// Structure for messages' unique identifiers #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] struct MessageId(u64); impl From<MessageId> for u64 { fn from(value: MessageId) -> Self { value.0 } } impl Display for MessageId { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { write!(f, "{}", self.0) } } impl MessageId { /// Returns a new identifier that has not been given beforehand fn new() -> Self { Self(NEXT_MESSAGE_ID.fetch_add(1, Ordering::Relaxed)) } } /// Structure of messages logged #[derive(Debug, Clone, PartialEq, Eq)] struct Message { /// Unique identifier id: MessageId, /// Log level of the message level: Level, /// Message content content: String, } impl Message { /// Creates a new message from its log level and its content fn new(level: Level, content: String) -> Self { Self { id: MessageId::new(), level, content, } } /// Prints the message on the screen /// /// The color of the message depends on the message level fn print(&self) { /// Gives a color to each level const fn level_color(level: Level) -> Color { match level { Level::Error => RED, Level::Warn => ORANGE, Level::Info => YELLOW, Level::Debug => CYAN, Level::Trace => WHITE, } } print!(" * "); println!(color: level_color(self.level), "{:04} {:?}: \"{}\"", Into::<u64>::into(self.id), self.level, self.content); } } /// Simple logger for `SkavOS` #[derive(Debug)] pub struct SysLog { /// Messages that have not been flushed yet messages: CircularBuffer<100, Message>, } impl SysLog { /// Creates a new logger const fn new() -> Self { Self { messages: CircularBuffer::new(), } } /// Prints the next message that have not been displayed yet pub fn display_next_message(&mut self) { if self.messages.is_empty() { println!("Empty logger"); return; } // SAFETY: `self.messages` is not empty unsafe { self.messages.front().unwrap_unchecked().print(); }; } /// Prints all messages contained in the logger pub fn display_messages(&self) { if self.messages.is_empty() { println!("Empty logger"); return; } println!("Logs:"); for message in &self.messages { message.print(); } } /// Removes the oldest message saved in the logger pub fn remove_oldest(&mut self) { self.messages.pop_front(); } /// Removes all the messages saved in the logger pub fn clear(&mut self) { self.messages.clear(); } } impl Log for Locked<SysLog> { fn enabled(&self, _metadata: &Metadata) -> bool { true } fn log(&self, record: &Record) { let content = format!("{}", record.args()); let message = Message::new(record.level(), content); self.lock().messages.push_back(message); } fn flush(&self) {} } /// Global logger pub static SYS_LOGGER: Locked<SysLog> = Locked::new(SysLog::new()); #[cfg(test)] mod test { use alloc::borrow::ToOwned; use core::sync::atomic::Ordering; use log::{Level, Log, Metadata, Record}; use super::{Message, MessageId, SysLog}; use crate::mutex::Locked; use crate::syslog::NEXT_MESSAGE_ID; #[test_case] fn unique_id() { let mut ids = [MessageId::new(); 5]; for id in &mut ids { id.clone_from(&MessageId::new()); } for i in 0..5 { for j in 0..i { assert_ne!(ids.get(i).unwrap(), ids.get(j).unwrap()); } } } #[test_case] fn is_enabled() { let syslog = Locked::new(SysLog::new()); assert!(syslog.enabled(&Metadata::builder().level(Level::Debug).build())); assert!(syslog.enabled(&Metadata::builder().level(Level::Error).build())); assert!(syslog.enabled(&Metadata::builder().level(Level::Info).build())); assert!(syslog.enabled(&Metadata::builder().level(Level::Trace).build())); assert!(syslog.enabled(&Metadata::builder().level(Level::Warn).build())); } #[test_case] fn log() { let syslog = Locked::new(SysLog::new()); syslog.log( &Record::builder() .level(Level::Info) .args(format_args!("Hello world! {}", 42_usize)) .build(), ); let previous_message_id = NEXT_MESSAGE_ID.load(Ordering::Relaxed) - 1; assert_eq!(syslog.lock().messages.back().unwrap(), &Message { id: MessageId(previous_message_id), level: Level::Info, content: "Hello world! 42".to_owned() }); syslog.log(&Record::builder().level(Level::Warn).args(format_args!("1 + {} = 2", 1_usize)).build()); assert_eq!(syslog.lock().messages.back().unwrap(), &Message { id: MessageId(previous_message_id + 1), level: Level::Warn, content: "1 + 1 = 2".to_owned() }); syslog.flush(); } }