1use crate::events::Event;
7use crate::json::Json;
8use std::io::Write;
9use std::sync::atomic::{AtomicU8, Ordering};
10
11#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
12pub enum Level {
13 Debug = 0,
14 Info = 1,
15 Warn = 2,
16 Error = 3,
17}
18
19impl Level {
20 pub fn as_str(self) -> &'static str {
21 match self {
22 Level::Debug => "debug",
23 Level::Info => "info",
24 Level::Warn => "warn",
25 Level::Error => "error",
26 }
27 }
28
29 fn color(self) -> &'static str {
30 match self {
31 Level::Debug => "\x1b[38;5;244m",
32 Level::Info => "\x1b[38;5;39m",
33 Level::Warn => "\x1b[38;5;214m",
34 Level::Error => "\x1b[38;5;203m",
35 }
36 }
37
38 pub fn parse(value: &str) -> Option<Level> {
39 match value.to_ascii_lowercase().as_str() {
40 "debug" => Some(Level::Debug),
41 "info" => Some(Level::Info),
42 "warn" | "warning" => Some(Level::Warn),
43 "error" => Some(Level::Error),
44 _ => None,
45 }
46 }
47}
48
49static MIN_LEVEL: AtomicU8 = AtomicU8::new(Level::Info as u8);
50static USE_JSON: AtomicU8 = AtomicU8::new(0);
51
52pub fn set_level(level: Level) {
54 MIN_LEVEL.store(level as u8, Ordering::Relaxed);
55}
56
57pub fn set_json(enabled: bool) {
60 USE_JSON.store(u8::from(enabled), Ordering::Relaxed);
61}
62
63pub fn enabled(level: Level) -> bool {
64 level as u8 >= MIN_LEVEL.load(Ordering::Relaxed)
65}
66
67pub fn log(level: Level, message: impl AsRef<str>) {
68 let message = message.as_ref();
69
70 if enabled(level) {
71 let mut stderr = std::io::stderr().lock();
72 if USE_JSON.load(Ordering::Relaxed) == 1 {
73 let line = Json::object([
74 ("level", Json::from(level.as_str())),
75 ("message", Json::from(message)),
76 ("timestamp", Json::from(unix_seconds())),
77 ]);
78 let _ = writeln!(stderr, "{}", line);
79 } else {
80 let _ = writeln!(
81 stderr,
82 "{}{:>5}\x1b[0m {}",
83 level.color(),
84 level.as_str().to_uppercase(),
85 message
86 );
87 }
88 }
89
90 Event::new("log")
91 .with("level", level.as_str())
92 .with("message", message)
93 .dispatch();
94}
95
96fn unix_seconds() -> f64 {
97 std::time::SystemTime::now()
98 .duration_since(std::time::UNIX_EPOCH)
99 .map(|d| d.as_secs_f64())
100 .unwrap_or_default()
101}
102
103#[macro_export]
104macro_rules! debug {
105 ($($arg:tt)*) => { $crate::log::log($crate::log::Level::Debug, format!($($arg)*)) };
106}
107
108#[macro_export]
109macro_rules! info {
110 ($($arg:tt)*) => { $crate::log::log($crate::log::Level::Info, format!($($arg)*)) };
111}
112
113#[macro_export]
114macro_rules! warn {
115 ($($arg:tt)*) => { $crate::log::log($crate::log::Level::Warn, format!($($arg)*)) };
116}
117
118#[macro_export]
119macro_rules! error {
120 ($($arg:tt)*) => { $crate::log::log($crate::log::Level::Error, format!($($arg)*)) };
121}
122
123#[cfg(test)]
124mod tests {
125 use super::*;
126
127 #[test]
128 fn level_threshold_is_respected() {
129 set_level(Level::Warn);
130 assert!(!enabled(Level::Info));
131 assert!(enabled(Level::Error));
132 set_level(Level::Info);
133 }
134
135 #[test]
136 fn levels_parse_from_configuration_strings() {
137 assert_eq!(Level::parse("WARNING"), Some(Level::Warn));
138 assert_eq!(Level::parse("nonsense"), None);
139 }
140}