hinoirisetr 1.4.2

A daemon to dim the screen at night
Documentation
use std::fmt::{Display, Formatter};
use std::str::FromStr;
use std::sync::atomic::{AtomicU8, Ordering};
use time::{OffsetDateTime, format_description::well_known::Rfc3339};

/// Levels of logging verbosity
#[derive(PartialEq, PartialOrd, Ord, Eq, Debug, Clone, Copy)]
pub enum LogLevel {
    Error = 1,
    Warn = 2,
    Info = 3,
    Debug = 4,
    Trace = 5,
}

static GLOBAL_LOG_LEVEL: AtomicU8 = AtomicU8::new(LogLevel::Info as u8);

/// Set the global log level at runtime
pub fn set_log_level(level: LogLevel) {
    GLOBAL_LOG_LEVEL.store(level as u8, Ordering::Relaxed);
}

/// Get the current global log level
pub fn get_log_level() -> LogLevel {
    match GLOBAL_LOG_LEVEL.load(Ordering::Relaxed) {
        1 => LogLevel::Error,
        2 => LogLevel::Warn,
        3 => LogLevel::Info,
        4 => LogLevel::Debug,
        5 => LogLevel::Trace,
        _ => LogLevel::Info,
    }
}

impl FromStr for LogLevel {
    type Err = String;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        match s.to_lowercase().as_str() {
            "error" => Ok(LogLevel::Error),
            "warn" => Ok(LogLevel::Warn),
            "info" => Ok(LogLevel::Info),
            "debug" => Ok(LogLevel::Debug),
            "trace" => Ok(LogLevel::Trace),
            _ => Err(format!("Invalid log level: {s}")),
        }
    }
}

impl Display for LogLevel {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        // ANSI color codes for terminal output
        const RED: &str = "\x1b[31m"; // Error
        const YELLOW: &str = "\x1b[33m"; // Warn
        const GREEN: &str = "\x1b[32m"; // Info
        const CYAN: &str = "\x1b[36m"; // Debug
        const MAGENTA: &str = "\x1b[35m"; // Trace
        const RESET: &str = "\x1b[0m"; // Reset color

        match self {
            LogLevel::Error => write!(f, "{}[{:>5}]{}", RED, "ERROR", RESET),
            LogLevel::Warn => write!(f, "{}[{:>5}]{}", YELLOW, "WARN", RESET),
            LogLevel::Info => write!(f, "{}[{:>5}]{}", GREEN, "INFO", RESET),
            LogLevel::Debug => write!(f, "{}[{:>5}]{}", CYAN, "DEBUG", RESET),
            LogLevel::Trace => write!(f, "{}[{:>5}]{}", MAGENTA, "TRACE", RESET),
        }
    }
}

/// Internal logger: prints if level is <= current global level
#[doc(hidden)]
pub fn log_internal(level: LogLevel, file: &str, line: u32, msg: &str) {
    if (level as u8) <= (get_log_level() as u8) {
        let odt = OffsetDateTime::now_local().unwrap_or_else(|_| {
            eprintln!("LOGGER ERROR: Failed to get local time; using UTC instead");
            OffsetDateTime::now_utc()
        });
        let timestamp = odt
            .format(&Rfc3339)
            .unwrap_or_else(|_| "????-??-??T??:??:??Z".to_string());

        if level == LogLevel::Trace {
            println!("{} |{timestamp}| {file}:{line} - {msg}", LogLevel::Trace);
        } else {
            println!("{level} |{timestamp}| - {msg}");
        }
    }
}

/// Log an error-level message
#[macro_export]
macro_rules! error {
    ($($arg:tt)+) => {
        $crate::log::log_internal(
            $crate::log::LogLevel::Error,
            file!(),
            line!(),
            &format!($($arg)+),
        );
    };
}

/// Log a warning-level message
#[macro_export]
macro_rules! warn {
    ($($arg:tt)+) => {
        $crate::log::log_internal(
            $crate::log::LogLevel::Warn,
            file!(),
            line!(),
            &format!($($arg)+),
        );
    };
}

/// Log an info-level message
#[macro_export]
macro_rules! info {
    ($($arg:tt)+) => {
        $crate::log::log_internal(
            $crate::log::LogLevel::Info,
            file!(),
            line!(),
            &format!($($arg)+),
        );
    };
}

/// Log a debug-level message (only in debug builds)
#[macro_export]
#[cfg(debug_assertions)]
macro_rules! debug {
    ($($arg:tt)+) => {
        $crate::log::log_internal(
            $crate::log::LogLevel::Debug,
            file!(),
            line!(),
            &format!($($arg)+),
        );
    };
}

/// No-op debug in release builds
#[macro_export]
#[cfg(not(debug_assertions))]
macro_rules! debug {
    ($($arg:tt)+) => {};
}

/// Log a trace-level message (only in debug builds)
#[macro_export]
#[cfg(debug_assertions)]
macro_rules! trace {
    ($($arg:tt)+) => {
        $crate::log::log_internal(
            $crate::log::LogLevel::Trace,
            file!(),
            line!(),
            &format!($($arg)+),
        );
    };
}

/// No-op trace in release builds
#[macro_export]
#[cfg(not(debug_assertions))]
macro_rules! trace {
    ($($arg:tt)+) => {};
}