1use std::{
2 io::{Write, stderr},
3 sync::{LazyLock, Mutex},
4};
5
6use crate::HttpError;
7
8#[derive(Clone, Copy, PartialEq, PartialOrd)]
9pub enum LogLevel {
10 None = 0,
11 Error = 1,
12 Warn = 2,
13 Info = 3,
14}
15
16impl TryFrom<u8> for LogLevel {
17 type Error = HttpError;
18
19 fn try_from(value: u8) -> crate::Result<Self> {
20 Ok(match value {
21 0 => LogLevel::None,
22 1 => LogLevel::Error,
23 2 => LogLevel::Warn,
24 3 => LogLevel::Info,
25 _ => return Err(format!("Invalid log level: {value}").into()),
26 })
27 }
28}
29
30#[allow(clippy::missing_panics_doc)]
31pub fn get_level() -> LogLevel {
32 #[allow(clippy::unwrap_used)]
33 LOGGER.lock().unwrap().get_level()
34}
35
36#[allow(clippy::missing_panics_doc)]
37pub fn set_level(level: LogLevel) {
38 #[allow(clippy::unwrap_used)]
39 LOGGER.lock().unwrap().set_level(level);
40}
41
42pub trait Logger: Send + Sync {
43 fn log(&mut self, level: LogLevel, args: core::fmt::Arguments);
44 fn set_level(&mut self, level: LogLevel);
45 fn get_level(&self) -> LogLevel;
46}
47
48struct StdErrLogger(LogLevel);
49
50impl Logger for StdErrLogger {
51 fn log(&mut self, level: LogLevel, args: core::fmt::Arguments) {
52 if level <= self.0 {
53 #[allow(clippy::unwrap_used)]
54 stderr().write_fmt(args).unwrap();
55 }
56 }
57 fn set_level(&mut self, level: LogLevel) {
58 self.0 = level;
59 }
60 fn get_level(&self) -> LogLevel {
61 self.0
62 }
63}
64
65#[cfg(not(debug_assertions))]
66const DEFAULT_LOG_LEVEL: LogLevel = LogLevel::Warn;
67
68#[cfg(debug_assertions)]
69const DEFAULT_LOG_LEVEL: LogLevel = LogLevel::Info;
70
71static LOGGER: LazyLock<Mutex<Box<dyn Logger>>> =
72 LazyLock::new(|| Mutex::new(Box::new(StdErrLogger(DEFAULT_LOG_LEVEL))));
73
74#[macro_export]
75#[doc(hidden)]
76macro_rules! __log {
77 ($lev:expr , $($arg:tt)*) => {
78 {
79 #[allow(clippy::used_underscore_items)]
80 $crate::log::_log($lev, format_args!($($arg)*))
81 }
82 };
83}
84
85#[macro_export]
86#[doc(hidden)]
87macro_rules! __log_info {
88 () => ($crate::log::__log!("\n"));
89 ($($arg:tt)*) => ($crate::__log!($crate::log::LogLevel::Info, "INFO: {}\n", format_args!($($arg)*)));
90}
91
92#[macro_export]
93#[doc(hidden)]
94macro_rules! __log_warn {
95 () => ($crate::log!("\n"));
96 ($($arg:tt)*) => ($crate::__log!($crate::log::LogLevel::Warn, "WARNING: {}\n", format_args!($($arg)*)));
97}
98
99#[macro_export]
100#[doc(hidden)]
101macro_rules! __log_error {
102 () => ($crate::log!("\n"));
103 ($($arg:tt)*) => ($crate::__log!($crate::log::LogLevel::Error, "ERROR: {}\n", format_args!($($arg)*)));
104}
105
106pub mod prelude {
107 pub use __log_error as log_error;
108 pub use __log_info as log_info;
109 pub use __log_warn as log_warn;
110}
111
112#[doc(hidden)]
113pub fn _log(level: LogLevel, args: core::fmt::Arguments) {
114 #[allow(clippy::unwrap_used)]
115 LOGGER.lock().unwrap().log(level, args);
116}