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::parking_lot::Mutex;
25use std::fmt::{Debug, Display};
26
27use crate::instant::Instant;
28#[cfg(not(target_arch = "wasm32"))]
29use std::io::{self, Write};
30use std::path::Path;
31use std::sync::mpsc::Sender;
32use std::sync::LazyLock;
33use std::time::Duration;
34
35#[cfg(target_arch = "wasm32")]
36use crate::wasm_bindgen::{self, prelude::*};
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    })
66});
67
68/// A kind of message.
69#[derive(Copy, Clone, PartialOrd, PartialEq, Eq, Ord, Hash)]
70#[repr(u32)]
71pub enum MessageKind {
72    /// Some useful information.
73    Information = 0,
74    /// A warning.
75    Warning = 1,
76    /// An error of some kind.
77    Error = 2,
78}
79
80impl MessageKind {
81    fn as_str(self) -> &'static str {
82        match self {
83            MessageKind::Information => "[INFO]: ",
84            MessageKind::Warning => "[WARNING]: ",
85            MessageKind::Error => "[ERROR]: ",
86        }
87    }
88}
89
90/// See module docs.
91pub struct Log {
92    #[cfg(all(not(target_arch = "wasm32"), not(target_os = "android")))]
93    file: Option<std::fs::File>,
94    verbosity: MessageKind,
95    listeners: Vec<Sender<LogMessage>>,
96    time_origin: Instant,
97}
98
99impl Log {
100    /// Creates a new log file at the specified path.
101    pub fn set_file_name<P: AsRef<Path>>(#[allow(unused_variables)] path: P) {
102        #[cfg(all(not(target_arch = "wasm32"), not(target_os = "android")))]
103        {
104            let mut guard = LOG.lock();
105            guard.file = std::fs::File::create(path).ok();
106        }
107    }
108
109    /// Sets new file to write the log to.
110    pub fn set_file(#[allow(unused_variables)] file: Option<std::fs::File>) {
111        #[cfg(all(not(target_arch = "wasm32"), not(target_os = "android")))]
112        {
113            let mut guard = LOG.lock();
114            guard.file = file;
115        }
116    }
117
118    fn write_internal<S>(&mut self, kind: MessageKind, message: S)
119    where
120        S: AsRef<str>,
121    {
122        let mut msg = message.as_ref().to_owned();
123        if kind as u32 >= self.verbosity as u32 {
124            // Notify listeners about the message and remove all disconnected listeners.
125            self.listeners.retain(|listener| {
126                listener
127                    .send(LogMessage {
128                        kind,
129                        content: msg.clone(),
130                        time: Instant::now() - self.time_origin,
131                    })
132                    .is_ok()
133            });
134
135            msg.insert_str(0, kind.as_str());
136
137            #[cfg(target_arch = "wasm32")]
138            {
139                log(&msg);
140            }
141
142            #[cfg(all(not(target_os = "android"), not(target_arch = "wasm32")))]
143            {
144                let _ = io::stdout().write_all(msg.as_bytes());
145
146                if let Some(log_file) = self.file.as_mut() {
147                    let _ = log_file.write_all(msg.as_bytes());
148                    let _ = log_file.flush();
149                }
150            }
151
152            #[cfg(target_os = "android")]
153            {
154                let _ = io::stdout().write_all(msg.as_bytes());
155            }
156        }
157    }
158
159    fn writeln_internal<S>(&mut self, kind: MessageKind, message: S)
160    where
161        S: AsRef<str>,
162    {
163        let mut msg = message.as_ref().to_owned();
164        msg.push('\n');
165        self.write_internal(kind, msg)
166    }
167
168    /// Writes string into console and into file.
169    pub fn write<S>(kind: MessageKind, msg: S)
170    where
171        S: AsRef<str>,
172    {
173        LOG.lock().write_internal(kind, msg);
174    }
175
176    /// Writes line into console and into file.
177    pub fn writeln<S>(kind: MessageKind, msg: S)
178    where
179        S: AsRef<str>,
180    {
181        LOG.lock().writeln_internal(kind, msg);
182    }
183
184    /// Writes information message.
185    pub fn info<S>(msg: S)
186    where
187        S: AsRef<str>,
188    {
189        Self::writeln(MessageKind::Information, msg)
190    }
191
192    /// Writes warning message.
193    pub fn warn<S>(msg: S)
194    where
195        S: AsRef<str>,
196    {
197        Self::writeln(MessageKind::Warning, msg)
198    }
199
200    /// Writes error message.
201    pub fn err<S>(msg: S)
202    where
203        S: AsRef<str>,
204    {
205        Self::writeln(MessageKind::Error, msg)
206    }
207
208    /// Sets verbosity level.
209    pub fn set_verbosity(kind: MessageKind) {
210        LOG.lock().verbosity = kind;
211    }
212
213    /// Adds a listener that will receive a copy of every message passed into the log.
214    pub fn add_listener(listener: Sender<LogMessage>) {
215        LOG.lock().listeners.push(listener)
216    }
217
218    /// Allows you to verify that the result of operation is Ok, or print the error in the log.
219    ///
220    /// # Use cases
221    ///
222    /// Typical use case for this method is that when you _can_ ignore errors, but want them to
223    /// be in the log.
224    pub fn verify<T, E>(result: Result<T, E>)
225    where
226        E: Debug,
227    {
228        if let Err(e) = result {
229            Self::writeln(
230                MessageKind::Error,
231                format!("Operation failed! Reason: {e:?}"),
232            );
233        }
234    }
235
236    /// Allows you to verify that the result of operation is Ok, or print the error in the log.
237    ///
238    /// # Use cases
239    ///
240    /// Typical use case for this method is that when you _can_ ignore errors, but want them to
241    /// be in the log.
242    pub fn verify_message<S, T, E>(result: Result<T, E>, msg: S)
243    where
244        E: Debug,
245        S: Display,
246    {
247        if let Err(e) = result {
248            Self::writeln(MessageKind::Error, format!("{msg}. Reason: {e:?}"));
249        }
250    }
251}
252
253#[macro_export]
254macro_rules! info {
255    ($($arg:tt)*) => {
256        $crate::log::Log::info(format!($($arg)*))
257    };
258}
259
260#[macro_export]
261macro_rules! warn {
262    ($($arg:tt)*) => {
263        $crate::log::Log::warn(format!($($arg)*))
264    };
265}
266
267#[macro_export]
268macro_rules! err {
269    ($($arg:tt)*) => {
270        $crate::log::Log::err(format!($($arg)*))
271    };
272}