Skip to main content

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        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/// A kind of message.
74#[derive(Debug, Default, Copy, Clone, PartialOrd, PartialEq, Eq, Ord, Hash, Visit, Reflect)]
75#[repr(u32)]
76pub enum MessageKind {
77    /// Some useful information.
78    #[default]
79    Information = 0,
80    /// A warning.
81    Warning = 1,
82    /// An error of some kind.
83    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
96/// See module docs.
97pub 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    /// Creates a new log file at the specified path.
111    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    /// Sets new file to write the log to.
120    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        // Notify listeners about the message and remove all disconnected listeners.
168        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    /// Writes a string to the console and optionally into the file (if set).
217    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    /// Writes a string to the console and optionally into the file (if set). Unlike [`Self::write`]
225    /// this method writes the message only once per given id if the message remains the same. If
226    /// the message changes, then the new version will be printed to the log. This method is useful
227    /// if you need to print error messages, but prevent them from flooding the log.
228    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    /// Writes a string to the console and optionally into the file (if set), adds a new line to the
236    /// end of the message.
237    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    /// Writes a string to the console and optionally into the file (if set), adds a new line to the
245    /// end of the message. Prints the message only once. See [`Self::write_once`] for more info.
246    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    /// Writes an information message.
254    pub fn info<S>(msg: S)
255    where
256        S: AsRef<str>,
257    {
258        Self::writeln(MessageKind::Information, msg)
259    }
260
261    /// Writes a warning message.
262    pub fn warn<S>(msg: S)
263    where
264        S: AsRef<str>,
265    {
266        Self::writeln(MessageKind::Warning, msg)
267    }
268
269    /// Writes error message.
270    pub fn err<S>(msg: S)
271    where
272        S: AsRef<str>,
273    {
274        Self::writeln(MessageKind::Error, msg)
275    }
276
277    /// Writes an information message once. See [`Self::write_once`] for more info.
278    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    /// Writes a warning message. See [`Self::write_once`] for more info.
286    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    /// Writes an error message once. See [`Self::write_once`] for more info.
294    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    /// Enables or disables writing the messages to stdout stream.
302    pub fn enable_writing_to_stdout(enabled: bool) {
303        LOG.lock().write_to_stdout = enabled;
304    }
305
306    /// Returns `true` if the logger allowed writing to stdout stream, `false` - otherwise.
307    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    /// Adds a listener that will receive a copy of every message passed into the log.
336    pub fn add_listener(listener: Sender<LogMessage>) {
337        LOG.lock().listeners.push(listener)
338    }
339
340    /// Allows you to verify that the result of the operation is Ok, or print the error in the log.
341    ///
342    /// # Use cases
343    ///
344    /// Typical use case for this method is that when you _can_ ignore errors, but want them to
345    /// be in the log.
346    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    /// Allows you to verify that the result of the operation is Ok, or print the error in the log.
356    ///
357    /// # Use cases
358    ///
359    /// Typical use case for this method is that when you _can_ ignore errors, but want them to
360    /// be in the log.
361    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}