Skip to content
Snippets Groups Projects
syslog.rs 5.63 KiB
Newer Older
pigeonmoelleux's avatar
pigeonmoelleux committed
//! Simple logger for `SkavOS`

use alloc::format;
use alloc::string::String;
pigeonmoelleux's avatar
pigeonmoelleux committed
use core::fmt::Display;
use core::sync::atomic::{AtomicU64, Ordering};

use circular_buffer::CircularBuffer;
pigeonmoelleux's avatar
pigeonmoelleux committed
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 {
pigeonmoelleux's avatar
pigeonmoelleux committed
    /// Unique identifier
    id: MessageId,

    /// Log level of the message
    level: Level,

    /// Message content
    content: String,
impl Message {
pigeonmoelleux's avatar
pigeonmoelleux committed
    /// Creates a new message from its log level and its content
    fn new(level: Level, content: String) -> Self {
pigeonmoelleux's avatar
pigeonmoelleux committed
        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 {
pigeonmoelleux's avatar
pigeonmoelleux committed
    /// Messages that have not been flushed yet
    messages: CircularBuffer<100, Message>,
impl SysLog {
pigeonmoelleux's avatar
pigeonmoelleux committed
    /// 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();
        };
    }

pigeonmoelleux's avatar
pigeonmoelleux committed
    /// Prints all messages contained in the logger
    pub fn display_messages(&self) {
pigeonmoelleux's avatar
pigeonmoelleux committed
        if self.messages.is_empty() {
            println!("Empty logger");
            return;
        }

        println!("Logs:");

        for message in &self.messages {
pigeonmoelleux's avatar
pigeonmoelleux committed
            message.print();
        }
    }

    /// Removes the oldest message saved in the logger
    pub fn remove_oldest(&mut self) {
        self.messages.pop_front();
    }

pigeonmoelleux's avatar
pigeonmoelleux committed
    /// Removes all the messages saved in the logger
    pub fn clear(&mut self) {
        self.messages.clear();
    }
}

impl Log for Locked<SysLog> {
pigeonmoelleux's avatar
pigeonmoelleux committed
    fn enabled(&self, _metadata: &Metadata) -> bool {
        true
    }

    fn log(&self, record: &Record) {
        let content = format!("{}", record.args());
pigeonmoelleux's avatar
pigeonmoelleux committed
        let message = Message::new(record.level(), content);
        self.lock().messages.push_back(message);
pigeonmoelleux's avatar
pigeonmoelleux committed
    }

    fn flush(&self) {}
}

/// Global logger
pub static SYS_LOGGER: Locked<SysLog> = Locked::new(SysLog::new());

#[cfg(test)]
mod test {
    use alloc::borrow::ToOwned;
pigeonmoelleux's avatar
pigeonmoelleux committed
    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(),
        );
pigeonmoelleux's avatar
pigeonmoelleux committed
        let previous_message_id = NEXT_MESSAGE_ID.load(Ordering::Relaxed) - 1;
        assert_eq!(syslog.lock().messages.back().unwrap(), &Message {
pigeonmoelleux's avatar
pigeonmoelleux committed
            id: MessageId(previous_message_id),
            level: Level::Info,
            content: "Hello world! 42".to_owned()
pigeonmoelleux's avatar
pigeonmoelleux committed
        });
        syslog.log(&Record::builder().level(Level::Warn).args(format_args!("1 + {} = 2", 1_usize)).build());
        assert_eq!(syslog.lock().messages.back().unwrap(), &Message {
pigeonmoelleux's avatar
pigeonmoelleux committed
            id: MessageId(previous_message_id + 1),
            level: Level::Warn,
            content: "1 + 1 = 2".to_owned()
pigeonmoelleux's avatar
pigeonmoelleux committed
        });
        syslog.flush();
    }
}