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