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 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    // Use `js_namespace` here to bind `console.log(..)` instead of just
42    // `log(..)`
43    #[wasm_bindgen(js_namespace = console)]
44    fn log(s: &str);
45}
46
47/// A message that could be sent by the logger to all listeners.
48pub struct LogMessage {
49    /// Kind of the message: information, warning or error.
50    pub kind: MessageKind,
51    /// The source message without logger prefixes.
52    pub content: String,
53    /// Time point at which the message was recorded. It is relative to the moment when the
54    /// logger was initialized.
55    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/// A kind of message.
70#[derive(Copy, Clone, PartialOrd, PartialEq, Eq, Ord, Hash)]
71#[repr(u32)]
72pub enum MessageKind {
73    /// Some useful information.
74    Information = 0,
75    /// A warning.
76    Warning = 1,
77    /// An error of some kind.
78    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
91/// See module docs.
92pub 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    /// Creates a new log file at the specified path.
103    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    /// Sets new file to write the log to.
112    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            // Notify listeners about the message and remove all disconnected listeners.
147            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    /// Writes a string to the console and optionally into the file (if set).
193    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    /// Writes a string to the console and optionally into the file (if set). Unlike [`Self::write`]
201    /// this method writes the message only once per given id if the message remains the same. If
202    /// the message changes, then the new version will be printed to the log. This method is useful
203    /// if you need to print error messages, but prevent them from flooding the log.
204    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    /// Writes a string to the console and optionally into the file (if set), adds a new line to the
212    /// end of the message.
213    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    /// Writes a string to the console and optionally into the file (if set), adds a new line to the
221    /// end of the message. Prints the message only once. See [`Self::write_once`] for more info.
222    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    /// Writes an information message.
230    pub fn info<S>(msg: S)
231    where
232        S: AsRef<str>,
233    {
234        Self::writeln(MessageKind::Information, msg)
235    }
236
237    /// Writes a warning message.
238    pub fn warn<S>(msg: S)
239    where
240        S: AsRef<str>,
241    {
242        Self::writeln(MessageKind::Warning, msg)
243    }
244
245    /// Writes error message.
246    pub fn err<S>(msg: S)
247    where
248        S: AsRef<str>,
249    {
250        Self::writeln(MessageKind::Error, msg)
251    }
252
253    /// Writes an information message once. See [`Self::write_once`] for more info.
254    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    /// Writes a warning message. See [`Self::write_once`] for more info.
262    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    /// Writes an error message once. See [`Self::write_once`] for more info.
270    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    /// Sets verbosity level.
278    pub fn set_verbosity(kind: MessageKind) {
279        LOG.lock().verbosity = kind;
280    }
281
282    /// Adds a listener that will receive a copy of every message passed into the log.
283    pub fn add_listener(listener: Sender<LogMessage>) {
284        LOG.lock().listeners.push(listener)
285    }
286
287    /// Allows you to verify that the result of the operation is Ok, or print the error in the log.
288    ///
289    /// # Use cases
290    ///
291    /// Typical use case for this method is that when you _can_ ignore errors, but want them to
292    /// be in the log.
293    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    /// Allows you to verify that the result of the operation is Ok, or print the error in the log.
306    ///
307    /// # Use cases
308    ///
309    /// Typical use case for this method is that when you _can_ ignore errors, but want them to
310    /// be in the log.
311    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}