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 log_info: true,
64 log_warning: true,
65 log_error: true,
66 listeners: Default::default(),
67 time_origin: Instant::now(),
68 one_shot_sources: Default::default(),
69 write_to_stdout: true,
70 })
71});
72
73#[derive(Debug, Default, Copy, Clone, PartialOrd, PartialEq, Eq, Ord, Hash, Visit, Reflect)]
75#[repr(u32)]
76pub enum MessageKind {
77 #[default]
79 Information = 0,
80 Warning = 1,
82 Error = 2,
84}
85
86impl MessageKind {
87 fn as_str(self) -> &'static str {
88 match self {
89 MessageKind::Information => "[INFO]: ",
90 MessageKind::Warning => "[WARNING]: ",
91 MessageKind::Error => "[ERROR]: ",
92 }
93 }
94}
95
96pub struct Log {
98 #[cfg(all(not(target_arch = "wasm32"), not(target_os = "android")))]
99 file: Option<std::fs::File>,
100 log_info: bool,
101 log_warning: bool,
102 log_error: bool,
103 listeners: Vec<Sender<LogMessage>>,
104 time_origin: Instant,
105 one_shot_sources: FxHashMap<usize, String>,
106 write_to_stdout: bool,
107}
108
109impl Log {
110 pub fn set_file_name<P: AsRef<Path>>(#[allow(unused_variables)] path: P) {
112 #[cfg(all(not(target_arch = "wasm32"), not(target_os = "android")))]
113 {
114 let mut guard = LOG.lock();
115 guard.file = std::fs::File::create(path).ok();
116 }
117 }
118
119 pub fn set_file(#[allow(unused_variables)] file: Option<std::fs::File>) {
121 #[cfg(all(not(target_arch = "wasm32"), not(target_os = "android")))]
122 {
123 let mut guard = LOG.lock();
124 guard.file = file;
125 }
126 }
127
128 fn write_internal<S>(&mut self, id: Option<usize>, kind: MessageKind, message: S) -> bool
129 where
130 S: AsRef<str>,
131 {
132 match kind {
133 MessageKind::Information if !self.log_info => {
134 return false;
135 }
136 MessageKind::Warning if !self.log_warning => {
137 return false;
138 }
139 MessageKind::Error if !self.log_error => {
140 return false;
141 }
142 _ => (),
143 }
144
145 let mut msg = message.as_ref().to_owned();
146
147 if let Some(id) = id {
148 let mut need_write = false;
149 match self.one_shot_sources.entry(id) {
150 Entry::Occupied(mut message) => {
151 if message.get() != &msg {
152 message.insert(msg.clone());
153 need_write = true;
154 }
155 }
156 Entry::Vacant(entry) => {
157 entry.insert(msg.clone());
158 need_write = true;
159 }
160 }
161
162 if !need_write {
163 return false;
164 }
165 }
166
167 self.listeners.retain(|listener| {
169 listener
170 .send(LogMessage {
171 kind,
172 content: msg.clone(),
173 time: Instant::now() - self.time_origin,
174 })
175 .is_ok()
176 });
177
178 msg.insert_str(0, kind.as_str());
179
180 #[cfg(target_arch = "wasm32")]
181 {
182 log(&msg);
183 }
184
185 #[cfg(all(not(target_os = "android"), not(target_arch = "wasm32")))]
186 {
187 if self.write_to_stdout {
188 let _ = io::stdout().write_all(msg.as_bytes());
189 }
190
191 if let Some(log_file) = self.file.as_mut() {
192 let _ = log_file.write_all(msg.as_bytes());
193 let _ = log_file.flush();
194 }
195 }
196
197 #[cfg(target_os = "android")]
198 {
199 if self.write_to_stdout {
200 let _ = io::stdout().write_all(msg.as_bytes());
201 }
202 }
203
204 true
205 }
206
207 fn writeln_internal<S>(&mut self, id: Option<usize>, kind: MessageKind, message: S) -> bool
208 where
209 S: AsRef<str>,
210 {
211 let mut msg = message.as_ref().to_owned();
212 msg.push('\n');
213 self.write_internal(id, kind, msg)
214 }
215
216 pub fn write<S>(kind: MessageKind, msg: S)
218 where
219 S: AsRef<str>,
220 {
221 LOG.lock().write_internal(None, kind, msg);
222 }
223
224 pub fn write_once<S>(id: usize, kind: MessageKind, msg: S) -> bool
229 where
230 S: AsRef<str>,
231 {
232 LOG.lock().write_internal(Some(id), kind, msg)
233 }
234
235 pub fn writeln<S>(kind: MessageKind, msg: S)
238 where
239 S: AsRef<str>,
240 {
241 LOG.lock().writeln_internal(None, kind, msg);
242 }
243
244 pub fn writeln_once<S>(id: usize, kind: MessageKind, msg: S) -> bool
247 where
248 S: AsRef<str>,
249 {
250 LOG.lock().writeln_internal(Some(id), kind, msg)
251 }
252
253 pub fn info<S>(msg: S)
255 where
256 S: AsRef<str>,
257 {
258 Self::writeln(MessageKind::Information, msg)
259 }
260
261 pub fn warn<S>(msg: S)
263 where
264 S: AsRef<str>,
265 {
266 Self::writeln(MessageKind::Warning, msg)
267 }
268
269 pub fn err<S>(msg: S)
271 where
272 S: AsRef<str>,
273 {
274 Self::writeln(MessageKind::Error, msg)
275 }
276
277 pub fn info_once<S>(id: usize, msg: S) -> bool
279 where
280 S: AsRef<str>,
281 {
282 Self::writeln_once(id, MessageKind::Information, msg)
283 }
284
285 pub fn warn_once<S>(id: usize, msg: S) -> bool
287 where
288 S: AsRef<str>,
289 {
290 Self::writeln_once(id, MessageKind::Warning, msg)
291 }
292
293 pub fn err_once<S>(id: usize, msg: S) -> bool
295 where
296 S: AsRef<str>,
297 {
298 Self::writeln_once(id, MessageKind::Error, msg)
299 }
300
301 pub fn enable_writing_to_stdout(enabled: bool) {
303 LOG.lock().write_to_stdout = enabled;
304 }
305
306 pub fn is_writing_to_stdout() -> bool {
308 LOG.lock().write_to_stdout
309 }
310
311 pub fn set_log_info(state: bool) {
312 LOG.lock().log_info = state;
313 }
314
315 pub fn is_logging_info() -> bool {
316 LOG.lock().log_info
317 }
318
319 pub fn set_log_warning(state: bool) {
320 LOG.lock().log_warning = state;
321 }
322
323 pub fn is_logging_warning() -> bool {
324 LOG.lock().log_warning
325 }
326
327 pub fn set_log_error(state: bool) {
328 LOG.lock().log_error = state;
329 }
330
331 pub fn is_logging_error() -> bool {
332 LOG.lock().log_error
333 }
334
335 pub fn add_listener(listener: Sender<LogMessage>) {
337 LOG.lock().listeners.push(listener)
338 }
339
340 pub fn verify<T, E>(result: Result<T, E>)
347 where
348 E: Display,
349 {
350 if let Err(e) = result {
351 Self::writeln(MessageKind::Error, format!("Operation failed! Reason: {e}"));
352 }
353 }
354
355 pub fn verify_message<S, T, E>(result: Result<T, E>, msg: S)
362 where
363 E: Debug,
364 S: Display,
365 {
366 if let Err(e) = result {
367 Self::writeln(MessageKind::Error, format!("{msg}. Reason: {e:?}"));
368 }
369 }
370}
371
372#[macro_export]
373macro_rules! info {
374 ($($arg:tt)*) => {
375 $crate::log::Log::info(format!($($arg)*))
376 };
377}
378
379#[macro_export]
380macro_rules! warn {
381 ($($arg:tt)*) => {
382 $crate::log::Log::warn(format!($($arg)*))
383 };
384}
385
386#[macro_export]
387macro_rules! err {
388 ($($arg:tt)*) => {
389 $crate::log::Log::err(format!($($arg)*))
390 };
391}
392
393#[macro_export]
394macro_rules! info_once {
395 ($id:expr, $($arg:tt)*) => {
396 $crate::log::Log::info_once($id, format!($($arg)*))
397 };
398}
399
400#[macro_export]
401macro_rules! warn_once {
402 ($id:expr, $($arg:tt)*) => {
403 $crate::log::Log::warn_once($id, format!($($arg)*))
404 };
405}
406
407#[macro_export]
408macro_rules! err_once {
409 ($id:expr, $($arg:tt)*) => {
410 $crate::log::Log::err_once($id, format!($($arg)*))
411 };
412}