1use crate::instant::Instant;
25use crate::parking_lot::Mutex;
26#[cfg(target_arch = "wasm32")]
27use crate::wasm_bindgen::{self, prelude::*};
28use fxhash::FxHashMap;
29use std::collections::hash_map::Entry;
30use std::fmt::{Debug, Display};
31#[cfg(not(target_arch = "wasm32"))]
32use std::io::{self, Write};
33use std::path::Path;
34use std::sync::mpsc::Sender;
35use std::sync::LazyLock;
36use std::time::Duration;
37
38#[cfg(target_arch = "wasm32")]
39#[wasm_bindgen]
40extern "C" {
41 #[wasm_bindgen(js_namespace = console)]
44 fn log(s: &str);
45}
46
47pub struct LogMessage {
49 pub kind: MessageKind,
51 pub content: String,
53 pub time: Duration,
56}
57
58static LOG: LazyLock<Mutex<Log>> = LazyLock::new(|| {
59 Mutex::new(Log {
60 #[cfg(all(not(target_arch = "wasm32"), not(target_os = "android")))]
61 file: None,
62 verbosity: MessageKind::Information,
63 listeners: Default::default(),
64 time_origin: Instant::now(),
65 one_shot_sources: Default::default(),
66 })
67});
68
69#[derive(Copy, Clone, PartialOrd, PartialEq, Eq, Ord, Hash)]
71#[repr(u32)]
72pub enum MessageKind {
73 Information = 0,
75 Warning = 1,
77 Error = 2,
79}
80
81impl MessageKind {
82 fn as_str(self) -> &'static str {
83 match self {
84 MessageKind::Information => "[INFO]: ",
85 MessageKind::Warning => "[WARNING]: ",
86 MessageKind::Error => "[ERROR]: ",
87 }
88 }
89}
90
91pub struct Log {
93 #[cfg(all(not(target_arch = "wasm32"), not(target_os = "android")))]
94 file: Option<std::fs::File>,
95 verbosity: MessageKind,
96 listeners: Vec<Sender<LogMessage>>,
97 time_origin: Instant,
98 one_shot_sources: FxHashMap<usize, String>,
99}
100
101impl Log {
102 pub fn set_file_name<P: AsRef<Path>>(#[allow(unused_variables)] path: P) {
104 #[cfg(all(not(target_arch = "wasm32"), not(target_os = "android")))]
105 {
106 let mut guard = LOG.lock();
107 guard.file = std::fs::File::create(path).ok();
108 }
109 }
110
111 pub fn set_file(#[allow(unused_variables)] file: Option<std::fs::File>) {
113 #[cfg(all(not(target_arch = "wasm32"), not(target_os = "android")))]
114 {
115 let mut guard = LOG.lock();
116 guard.file = file;
117 }
118 }
119
120 fn write_internal<S>(&mut self, id: Option<usize>, kind: MessageKind, message: S) -> bool
121 where
122 S: AsRef<str>,
123 {
124 let mut msg = message.as_ref().to_owned();
125 if kind as u32 >= self.verbosity as u32 {
126 if let Some(id) = id {
127 let mut need_write = false;
128 match self.one_shot_sources.entry(id) {
129 Entry::Occupied(mut message) => {
130 if message.get() != &msg {
131 message.insert(msg.clone());
132 need_write = true;
133 }
134 }
135 Entry::Vacant(entry) => {
136 entry.insert(msg.clone());
137 need_write = true;
138 }
139 }
140
141 if !need_write {
142 return false;
143 }
144 }
145
146 self.listeners.retain(|listener| {
148 listener
149 .send(LogMessage {
150 kind,
151 content: msg.clone(),
152 time: Instant::now() - self.time_origin,
153 })
154 .is_ok()
155 });
156
157 msg.insert_str(0, kind.as_str());
158
159 #[cfg(target_arch = "wasm32")]
160 {
161 log(&msg);
162 }
163
164 #[cfg(all(not(target_os = "android"), not(target_arch = "wasm32")))]
165 {
166 let _ = io::stdout().write_all(msg.as_bytes());
167
168 if let Some(log_file) = self.file.as_mut() {
169 let _ = log_file.write_all(msg.as_bytes());
170 let _ = log_file.flush();
171 }
172 }
173
174 #[cfg(target_os = "android")]
175 {
176 let _ = io::stdout().write_all(msg.as_bytes());
177 }
178 }
179
180 true
181 }
182
183 fn writeln_internal<S>(&mut self, id: Option<usize>, kind: MessageKind, message: S) -> bool
184 where
185 S: AsRef<str>,
186 {
187 let mut msg = message.as_ref().to_owned();
188 msg.push('\n');
189 self.write_internal(id, kind, msg)
190 }
191
192 pub fn write<S>(kind: MessageKind, msg: S)
194 where
195 S: AsRef<str>,
196 {
197 LOG.lock().write_internal(None, kind, msg);
198 }
199
200 pub fn write_once<S>(id: usize, kind: MessageKind, msg: S) -> bool
205 where
206 S: AsRef<str>,
207 {
208 LOG.lock().write_internal(Some(id), kind, msg)
209 }
210
211 pub fn writeln<S>(kind: MessageKind, msg: S)
214 where
215 S: AsRef<str>,
216 {
217 LOG.lock().writeln_internal(None, kind, msg);
218 }
219
220 pub fn writeln_once<S>(id: usize, kind: MessageKind, msg: S) -> bool
223 where
224 S: AsRef<str>,
225 {
226 LOG.lock().writeln_internal(Some(id), kind, msg)
227 }
228
229 pub fn info<S>(msg: S)
231 where
232 S: AsRef<str>,
233 {
234 Self::writeln(MessageKind::Information, msg)
235 }
236
237 pub fn warn<S>(msg: S)
239 where
240 S: AsRef<str>,
241 {
242 Self::writeln(MessageKind::Warning, msg)
243 }
244
245 pub fn err<S>(msg: S)
247 where
248 S: AsRef<str>,
249 {
250 Self::writeln(MessageKind::Error, msg)
251 }
252
253 pub fn info_once<S>(id: usize, msg: S) -> bool
255 where
256 S: AsRef<str>,
257 {
258 Self::writeln_once(id, MessageKind::Information, msg)
259 }
260
261 pub fn warn_once<S>(id: usize, msg: S) -> bool
263 where
264 S: AsRef<str>,
265 {
266 Self::writeln_once(id, MessageKind::Warning, msg)
267 }
268
269 pub fn err_once<S>(id: usize, msg: S) -> bool
271 where
272 S: AsRef<str>,
273 {
274 Self::writeln_once(id, MessageKind::Error, msg)
275 }
276
277 pub fn set_verbosity(kind: MessageKind) {
279 LOG.lock().verbosity = kind;
280 }
281
282 pub fn add_listener(listener: Sender<LogMessage>) {
284 LOG.lock().listeners.push(listener)
285 }
286
287 pub fn verify<T, E>(result: Result<T, E>)
294 where
295 E: Debug,
296 {
297 if let Err(e) = result {
298 Self::writeln(
299 MessageKind::Error,
300 format!("Operation failed! Reason: {e:?}"),
301 );
302 }
303 }
304
305 pub fn verify_message<S, T, E>(result: Result<T, E>, msg: S)
312 where
313 E: Debug,
314 S: Display,
315 {
316 if let Err(e) = result {
317 Self::writeln(MessageKind::Error, format!("{msg}. Reason: {e:?}"));
318 }
319 }
320}
321
322#[macro_export]
323macro_rules! info {
324 ($($arg:tt)*) => {
325 $crate::log::Log::info(format!($($arg)*))
326 };
327}
328
329#[macro_export]
330macro_rules! warn {
331 ($($arg:tt)*) => {
332 $crate::log::Log::warn(format!($($arg)*))
333 };
334}
335
336#[macro_export]
337macro_rules! err {
338 ($($arg:tt)*) => {
339 $crate::log::Log::err(format!($($arg)*))
340 };
341}
342
343#[macro_export]
344macro_rules! info_once {
345 ($id:expr, $($arg:tt)*) => {
346 $crate::log::Log::info_once($id, format!($($arg)*))
347 };
348}
349
350#[macro_export]
351macro_rules! warn_once {
352 ($id:expr, $($arg:tt)*) => {
353 $crate::log::Log::warn_once($id, format!($($arg)*))
354 };
355}
356
357#[macro_export]
358macro_rules! err_once {
359 ($id:expr, $($arg:tt)*) => {
360 $crate::log::Log::err_once($id, format!($($arg)*))
361 };
362}