Skip to main content

lunar_lib/
log.rs

1use std::{
2    fmt::{self, Arguments, Debug},
3    str::FromStr,
4    sync::{
5        OnceLock,
6        atomic::{AtomicU8, Ordering},
7    },
8};
9
10mod cli_logger;
11pub use cli_logger::*;
12
13static LOGGER: OnceLock<&'static dyn Log> = OnceLock::new();
14
15/// Sets the logger for the running program. This can only be done once and cannot be reversed
16///
17/// # Errors
18///
19/// Errors if a logger is already set, returns the set logger
20pub fn set_logger(logger: &'static dyn Log) -> Result<(), &'static dyn Log> {
21    LOGGER.set(logger)
22}
23
24/// Gets the logger for the running program, if any
25pub fn get_logger() -> Option<&'static dyn Log> {
26    LOGGER.get().copied()
27}
28
29#[derive(Debug, thiserror::Error)]
30pub enum LogLevelParseError {
31    #[error("Invalid log level: {0}")]
32    InvalidInput(String),
33}
34
35/// A level filter for any incoming logs to filter logs that should be disabled
36#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
37pub enum LevelFilter {
38    Off,
39    Error,
40    Warn,
41    Info,
42    Debug,
43    Trace,
44}
45
46/// The log level of something.
47///
48/// This is used in logging to prevent logs which should be filtered out by the [`LevelFilter`]
49#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
50pub enum LogLevel {
51    Error = 1,
52    Warn,
53    Info,
54    Debug,
55    Trace,
56}
57
58impl fmt::Display for LogLevel {
59    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
60        match self {
61            LogLevel::Error => f.write_str("Error"),
62            LogLevel::Warn => f.write_str("Warn"),
63            LogLevel::Info => f.write_str("Info"),
64            LogLevel::Debug => f.write_str("Debug"),
65            LogLevel::Trace => f.write_str("Trace"),
66        }
67    }
68}
69
70impl FromStr for LevelFilter {
71    type Err = LogLevelParseError;
72
73    fn from_str(s: &str) -> Result<Self, Self::Err> {
74        match s.to_ascii_lowercase().as_str() {
75            "off" | "quiet" => Ok(LevelFilter::Off),
76            "error" => Ok(LevelFilter::Error),
77            "warn" => Ok(LevelFilter::Warn),
78            "info" => Ok(LevelFilter::Info),
79            "debug" => Ok(LevelFilter::Debug),
80            "trace" => Ok(LevelFilter::Trace),
81            _ => Err(LogLevelParseError::InvalidInput(s.to_owned())),
82        }
83    }
84}
85
86static MAX_LOG_LEVEL: AtomicU8 = AtomicU8::new(0);
87/// The max log level of a program. Defaults to [`LevelFilter::Off`]
88///
89/// This function does not have to be used if you want custom log filtering logic. See [`Log::enabled()`] for more info
90pub fn set_max_log_level(level: LevelFilter) {
91    MAX_LOG_LEVEL.store(level as u8, Ordering::Relaxed);
92}
93
94/// A basic function to check if a [`LogLevel`] is filtered out by the max log level's [`LevelFilter`]
95///
96/// This function does not have to be used if you want custom log filtering logic. See [`Log::enabled()`] for more info
97pub fn can_log(level: LogLevel) -> bool {
98    MAX_LOG_LEVEL.load(Ordering::Relaxed) >= level as u8
99}
100
101pub trait Log: Sync + Debug {
102    /// This function should return true for the given log level if its allowed to be logged
103    fn enabled(&self, level: LogLevel) -> bool {
104        can_log(level)
105    }
106
107    /// This function should provide an unchecked log function, like calling `println!()`
108    ///
109    /// See [`Self::log()`] for a version that uses [`Self::enabled()`] to only log a string if logging is enabled
110    fn log_unchecked(&self, level: LogLevel, msg: Arguments);
111
112    /// Logs a string if allowed
113    fn log(&self, level: LogLevel, msg: Arguments) {
114        if self.enabled(level) {
115            self.log_unchecked(level, msg);
116        }
117    }
118}
119
120/// Attempts to log format args to the current logger lazily
121///
122/// If the logger rejects the log or none is set, the args will not be parsed
123#[macro_export]
124macro_rules! log {
125    ($level:expr, $($arg:tt)*) => {{
126        if let Some(logger) = $crate::log::get_logger()
127            && logger.enabled($level) {
128                logger.log_unchecked($level, format_args!($($arg)*))
129        }
130    }};
131}
132
133/// Attempts to log format args as an error to the current logger lazily
134///
135/// If the logger rejects the log or none is set, the args will not be parsed
136#[macro_export]
137macro_rules! error {
138    ($($arg:tt)*) => { $crate::log!($crate::log::LogLevel::Error, $($arg)*) };
139}
140
141/// Attempts to log format args as a warning to the current logger lazily
142///
143/// If the logger rejects the log or none is set, the args will not be parsed
144#[macro_export]
145macro_rules! warn {
146    ($($arg:tt)*) => { $crate::log!($crate::log::LogLevel::Warn, $($arg)*) };
147}
148
149/// Attempts to log format args as info to the current logger lazily
150///
151/// If the logger rejects the log or none is set, the args will not be parsed
152#[macro_export]
153macro_rules! info {
154    ($($arg:tt)*) => { $crate::log!($crate::log::LogLevel::Info, $($arg)*) };
155}
156
157/// Attempts to log format args as debug to the current logger lazily
158///
159/// If the logger rejects the log or none is set, the args will not be parsed
160#[macro_export]
161macro_rules! debug {
162    ($($arg:tt)*) => { $crate::log!($crate::log::LogLevel::Debug, $($arg)*) };
163}
164
165/// Attempts to log format args as a trace to the current logger lazily
166///
167/// If the logger rejects the log or none is set, the args will not be parsed
168#[macro_export]
169macro_rules! trace {
170    ($($arg:tt)*) => { $crate::log!($crate::log::LogLevel::Trace, $($arg)*) };
171}