1use std::{io::Write, panic, sync::mpsc::Sender, thread};
28
29use colored::Colorize;
30use log::{Level, LevelFilter};
31use time::{OffsetDateTime, macros::format_description};
32
33#[derive(Clone)]
34struct Logger(Vec<String>, Sender<(OutputChannel, String)>);
35
36enum OutputChannel {
37 Standard,
38 Error,
39}
40
41impl From<Level> for OutputChannel {
42 fn from(level: Level) -> Self {
43 match level {
44 Level::Error => OutputChannel::Error,
45 _ => OutputChannel::Standard,
46 }
47 }
48}
49
50impl Logger {
51 fn new(modules: Vec<String>) -> Self {
52 let (sender, receiver) = std::sync::mpsc::channel();
53
54 thread::spawn(move || {
55 let mut std_lock = std::io::stdout().lock();
56 let mut err_lock = std::io::stderr().lock();
57
58 for (output, line) in receiver {
59 match output {
60 OutputChannel::Standard => {
61 writeln!(std_lock, "{line}").ok();
62 std_lock.flush().ok();
63 }
64 OutputChannel::Error => {
65 writeln!(err_lock, "{line}").ok();
66 err_lock.flush().ok();
67 }
68 }
69 }
70 });
71
72 Self(modules, sender)
73 }
74}
75
76impl log::Log for Logger {
77 fn enabled(&self, metadata: &log::Metadata) -> bool {
78 if self.0.is_empty() {
79 return true;
80 }
81
82 for module in &self.0 {
83 if metadata.target() == *module || metadata.target().starts_with(&format!("{module}::"))
84 {
85 return true;
86 }
87 }
88
89 false
90 }
91
92 fn log(&self, record: &log::Record) {
93 if self.enabled(record.metadata()) {
94 self.1
95 .send((
96 record.level().into(),
97 format!(
98 "{} {} {}",
99 get_formatted_timestamp(),
100 get_formatted_level(record.level().as_str()),
101 record.args(),
102 ),
103 ))
104 .ok();
105 }
106 }
107
108 fn flush(&self) {}
109}
110
111use std::sync::OnceLock;
112
113static LOGGER: OnceLock<Logger> = OnceLock::new();
114
115pub fn init(filter: LevelFilter, modules: impl IntoIterator<Item = impl ToString>) {
137 LOGGER
138 .set(Logger::new(
139 modules.into_iter().map(|m| m.to_string()).collect(),
140 ))
141 .ok();
142
143 log::set_logger(LOGGER.get().unwrap())
144 .map(|()| log::set_max_level(filter))
145 .unwrap();
146
147 panic::set_hook(Box::new(move |panic_info| {
148 if filter == LevelFilter::Off {
149 return;
150 }
151
152 let line = if let Some(s) = panic_info.payload().downcast_ref::<&str>() {
153 format!(
154 "{} {} {}",
155 get_formatted_timestamp(),
156 get_formatted_level("PANIC"),
157 s,
158 )
159 } else if let Some(s) = panic_info.payload().downcast_ref::<String>() {
160 format!(
161 "{} {} {}",
162 get_formatted_timestamp(),
163 get_formatted_level("PANIC"),
164 s,
165 )
166 } else {
167 format!(
168 "{} {} A panic occurred! Exitting...",
169 get_formatted_timestamp(),
170 get_formatted_level("PANIC"),
171 )
172 };
173
174 LOGGER
175 .get()
176 .unwrap()
177 .1
178 .send((OutputChannel::Error, line))
179 .ok();
180 }));
181}
182
183fn get_formatted_timestamp() -> String {
184 let now = OffsetDateTime::now_local().unwrap_or_else(|_| OffsetDateTime::now_utc());
185
186 let format = format_description!(
187 "[day]/[month]/[year] at [hour]:[minute]:[second].[subsecond digits:2]"
188 );
189
190 now.format(&format).unwrap().dimmed().to_string()
191}
192
193fn get_formatted_level(level: &str) -> String {
194 let string = format!("[{level}]");
195 let string = format!("{string:<7}");
196
197 match level {
198 "TRACE" => string.dimmed().to_string(),
199 "DEBUG" => string.white().to_string(),
200 "INFO" => string.blue().to_string(),
201 "WARN" => string.yellow().to_string(),
202 "ERROR" | "PANIC" => string.red().bold().to_string(),
203 _ => string.red().bold().to_string(),
204 }
205}