fyrox_core/
log.rs

1// Copyright (c) 2019-present Dmitry Stepanov and Fyrox Engine contributors.
2//
3// Permission is hereby granted, free of charge, to any person obtaining a copy
4// of this software and associated documentation files (the "Software"), to deal
5// in the Software without restriction, including without limitation the rights
6// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7// copies of the Software, and to permit persons to whom the Software is
8// furnished to do so, subject to the following conditions:
9//
10// The above copyright notice and this permission notice shall be included in all
11// copies or substantial portions of the Software.
12//
13// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19// SOFTWARE.
20
21//! Simple logger. By default, it writes in the console only. To enable logging into a file, call
22//! [`Log::set_file_name`] somewhere in your `main` function.
23
24use 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    // Use `js_namespace` here to bind `console.log(..)` instead of just
43    // `log(..)`
44    #[wasm_bindgen(js_namespace = console)]
45    fn log(s: &str);
46}
47
48/// A message that could be sent by the logger to all listeners.
49pub struct LogMessage {
50    /// Kind of the message: information, warning or error.
51    pub kind: MessageKind,
52    /// The source message without logger prefixes.
53    pub content: String,
54    /// Time point at which the message was recorded. It is relative to the moment when the
55    /// logger was initialized.
56    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    })
68});
69
70/// A kind of message.
71#[derive(Debug, Default, Copy, Clone, PartialOrd, PartialEq, Eq, Ord, Hash, Visit, Reflect)]
72#[repr(u32)]
73pub enum MessageKind {
74    /// Some useful information.
75    #[default]
76    Information = 0,
77    /// A warning.
78    Warning = 1,
79    /// An error of some kind.
80    Error = 2,
81}
82
83impl MessageKind {
84    fn as_str(self) -> &'static str {
85        match self {
86            MessageKind::Information => "[INFO]: ",
87            MessageKind::Warning => "[WARNING]: ",
88            MessageKind::Error => "[ERROR]: ",
89        }
90    }
91}
92
93/// See module docs.
94pub struct Log {
95    #[cfg(all(not(target_arch = "wasm32"), not(target_os = "android")))]
96    file: Option<std::fs::File>,
97    verbosity: MessageKind,
98    listeners: Vec<Sender<LogMessage>>,
99    time_origin: Instant,
100    one_shot_sources: FxHashMap<usize, String>,
101}
102
103impl Log {
104    /// Creates a new log file at the specified path.
105    pub fn set_file_name<P: AsRef<Path>>(#[allow(unused_variables)] path: P) {
106        #[cfg(all(not(target_arch = "wasm32"), not(target_os = "android")))]
107        {
108            let mut guard = LOG.lock();
109            guard.file = std::fs::File::create(path).ok();
110        }
111    }
112
113    /// Sets new file to write the log to.
114    pub fn set_file(#[allow(unused_variables)] file: Option<std::fs::File>) {
115        #[cfg(all(not(target_arch = "wasm32"), not(target_os = "android")))]
116        {
117            let mut guard = LOG.lock();
118            guard.file = file;
119        }
120    }
121
122    fn write_internal<S>(&mut self, id: Option<usize>, kind: MessageKind, message: S) -> bool
123    where
124        S: AsRef<str>,
125    {
126        let mut msg = message.as_ref().to_owned();
127        if kind as u32 >= self.verbosity as u32 {
128            if let Some(id) = id {
129                let mut need_write = false;
130                match self.one_shot_sources.entry(id) {
131                    Entry::Occupied(mut message) => {
132                        if message.get() != &msg {
133                            message.insert(msg.clone());
134                            need_write = true;
135                        }
136                    }
137                    Entry::Vacant(entry) => {
138                        entry.insert(msg.clone());
139                        need_write = true;
140                    }
141                }
142
143                if !need_write {
144                    return false;
145                }
146            }
147
148            // Notify listeners about the message and remove all disconnected listeners.
149            self.listeners.retain(|listener| {
150                listener
151                    .send(LogMessage {
152                        kind,
153                        content: msg.clone(),
154                        time: Instant::now() - self.time_origin,
155                    })
156                    .is_ok()
157            });
158
159            msg.insert_str(0, kind.as_str());
160
161            #[cfg(target_arch = "wasm32")]
162            {
163                log(&msg);
164            }
165
166            #[cfg(all(not(target_os = "android"), not(target_arch = "wasm32")))]
167            {
168                let _ = io::stdout().write_all(msg.as_bytes());
169
170                if let Some(log_file) = self.file.as_mut() {
171                    let _ = log_file.write_all(msg.as_bytes());
172                    let _ = log_file.flush();
173                }
174            }
175
176            #[cfg(target_os = "android")]
177            {
178                let _ = io::stdout().write_all(msg.as_bytes());
179            }
180        }
181
182        true
183    }
184
185    fn writeln_internal<S>(&mut self, id: Option<usize>, kind: MessageKind, message: S) -> bool
186    where
187        S: AsRef<str>,
188    {
189        let mut msg = message.as_ref().to_owned();
190        msg.push('\n');
191        self.write_internal(id, kind, msg)
192    }
193
194    /// Writes a string to the console and optionally into the file (if set).
195    pub fn write<S>(kind: MessageKind, msg: S)
196    where
197        S: AsRef<str>,
198    {
199        LOG.lock().write_internal(None, kind, msg);
200    }
201
202    /// Writes a string to the console and optionally into the file (if set). Unlike [`Self::write`]
203    /// this method writes the message only once per given id if the message remains the same. If
204    /// the message changes, then the new version will be printed to the log. This method is useful
205    /// if you need to print error messages, but prevent them from flooding the log.
206    pub fn write_once<S>(id: usize, kind: MessageKind, msg: S) -> bool
207    where
208        S: AsRef<str>,
209    {
210        LOG.lock().write_internal(Some(id), kind, msg)
211    }
212
213    /// Writes a string to the console and optionally into the file (if set), adds a new line to the
214    /// end of the message.
215    pub fn writeln<S>(kind: MessageKind, msg: S)
216    where
217        S: AsRef<str>,
218    {
219        LOG.lock().writeln_internal(None, kind, msg);
220    }
221
222    /// Writes a string to the console and optionally into the file (if set), adds a new line to the
223    /// end of the message. Prints the message only once. See [`Self::write_once`] for more info.
224    pub fn writeln_once<S>(id: usize, kind: MessageKind, msg: S) -> bool
225    where
226        S: AsRef<str>,
227    {
228        LOG.lock().writeln_internal(Some(id), kind, msg)
229    }
230
231    /// Writes an information message.
232    pub fn info<S>(msg: S)
233    where
234        S: AsRef<str>,
235    {
236        Self::writeln(MessageKind::Information, msg)
237    }
238
239    /// Writes a warning message.
240    pub fn warn<S>(msg: S)
241    where
242        S: AsRef<str>,
243    {
244        Self::writeln(MessageKind::Warning, msg)
245    }
246
247    /// Writes error message.
248    pub fn err<S>(msg: S)
249    where
250        S: AsRef<str>,
251    {
252        Self::writeln(MessageKind::Error, msg)
253    }
254
255    /// Writes an information message once. See [`Self::write_once`] for more info.
256    pub fn info_once<S>(id: usize, msg: S) -> bool
257    where
258        S: AsRef<str>,
259    {
260        Self::writeln_once(id, MessageKind::Information, msg)
261    }
262
263    /// Writes a warning message. See [`Self::write_once`] for more info.
264    pub fn warn_once<S>(id: usize, msg: S) -> bool
265    where
266        S: AsRef<str>,
267    {
268        Self::writeln_once(id, MessageKind::Warning, msg)
269    }
270
271    /// Writes an error message once. See [`Self::write_once`] for more info.
272    pub fn err_once<S>(id: usize, msg: S) -> bool
273    where
274        S: AsRef<str>,
275    {
276        Self::writeln_once(id, MessageKind::Error, msg)
277    }
278
279    /// Sets verbosity level.
280    pub fn set_verbosity(kind: MessageKind) {
281        LOG.lock().verbosity = kind;
282    }
283
284    /// Adds a listener that will receive a copy of every message passed into the log.
285    pub fn add_listener(listener: Sender<LogMessage>) {
286        LOG.lock().listeners.push(listener)
287    }
288
289    /// Allows you to verify that the result of the operation is Ok, or print the error in the log.
290    ///
291    /// # Use cases
292    ///
293    /// Typical use case for this method is that when you _can_ ignore errors, but want them to
294    /// be in the log.
295    pub fn verify<T, E>(result: Result<T, E>)
296    where
297        E: Debug,
298    {
299        if let Err(e) = result {
300            Self::writeln(
301                MessageKind::Error,
302                format!("Operation failed! Reason: {e:?}"),
303            );
304        }
305    }
306
307    /// Allows you to verify that the result of the operation is Ok, or print the error in the log.
308    ///
309    /// # Use cases
310    ///
311    /// Typical use case for this method is that when you _can_ ignore errors, but want them to
312    /// be in the log.
313    pub fn verify_message<S, T, E>(result: Result<T, E>, msg: S)
314    where
315        E: Debug,
316        S: Display,
317    {
318        if let Err(e) = result {
319            Self::writeln(MessageKind::Error, format!("{msg}. Reason: {e:?}"));
320        }
321    }
322}
323
324#[macro_export]
325macro_rules! info {
326    ($($arg:tt)*) => {
327        $crate::log::Log::info(format!($($arg)*))
328    };
329}
330
331#[macro_export]
332macro_rules! warn {
333    ($($arg:tt)*) => {
334        $crate::log::Log::warn(format!($($arg)*))
335    };
336}
337
338#[macro_export]
339macro_rules! err {
340    ($($arg:tt)*) => {
341        $crate::log::Log::err(format!($($arg)*))
342    };
343}
344
345#[macro_export]
346macro_rules! info_once {
347    ($id:expr, $($arg:tt)*) => {
348        $crate::log::Log::info_once($id, format!($($arg)*))
349    };
350}
351
352#[macro_export]
353macro_rules! warn_once {
354    ($id:expr, $($arg:tt)*) => {
355        $crate::log::Log::warn_once($id, format!($($arg)*))
356    };
357}
358
359#[macro_export]
360macro_rules! err_once {
361    ($id:expr, $($arg:tt)*) => {
362        $crate::log::Log::err_once($id, format!($($arg)*))
363    };
364}