use crate::instant::Instant;
use crate::parking_lot::Mutex;
#[cfg(target_arch = "wasm32")]
use crate::wasm_bindgen::{self, prelude::*};
use crate::{reflect::prelude::*, visitor::prelude::*};
use fxhash::FxHashMap;
use std::collections::hash_map::Entry;
use std::fmt::{Debug, Display};
#[cfg(not(target_arch = "wasm32"))]
use std::io::{self, Write};
use std::path::Path;
use std::sync::mpsc::Sender;
use std::sync::LazyLock;
use std::time::Duration;
#[cfg(target_arch = "wasm32")]
#[wasm_bindgen]
extern "C" {
#[wasm_bindgen(js_namespace = console)]
fn log(s: &str);
}
pub struct LogMessage {
pub kind: MessageKind,
pub content: String,
pub time: Duration,
}
static LOG: LazyLock<Mutex<Log>> = LazyLock::new(|| {
Mutex::new(Log {
#[cfg(all(not(target_arch = "wasm32"), not(target_os = "android")))]
file: None,
log_info: true,
log_warning: true,
log_error: true,
listeners: Default::default(),
time_origin: Instant::now(),
one_shot_sources: Default::default(),
write_to_stdout: true,
})
});
#[derive(Debug, Default, Copy, Clone, PartialOrd, PartialEq, Eq, Ord, Hash, Visit, Reflect)]
#[repr(u32)]
pub enum MessageKind {
#[default]
Information = 0,
Warning = 1,
Error = 2,
}
impl MessageKind {
fn as_str(self) -> &'static str {
match self {
MessageKind::Information => "[INFO]: ",
MessageKind::Warning => "[WARNING]: ",
MessageKind::Error => "[ERROR]: ",
}
}
}
pub struct Log {
#[cfg(all(not(target_arch = "wasm32"), not(target_os = "android")))]
file: Option<std::fs::File>,
log_info: bool,
log_warning: bool,
log_error: bool,
listeners: Vec<Sender<LogMessage>>,
time_origin: Instant,
one_shot_sources: FxHashMap<usize, String>,
write_to_stdout: bool,
}
impl Log {
pub fn set_file_name<P: AsRef<Path>>(#[allow(unused_variables)] path: P) {
#[cfg(all(not(target_arch = "wasm32"), not(target_os = "android")))]
{
let mut guard = LOG.lock();
guard.file = std::fs::File::create(path).ok();
}
}
pub fn set_file(#[allow(unused_variables)] file: Option<std::fs::File>) {
#[cfg(all(not(target_arch = "wasm32"), not(target_os = "android")))]
{
let mut guard = LOG.lock();
guard.file = file;
}
}
fn write_internal<S>(&mut self, id: Option<usize>, kind: MessageKind, message: S) -> bool
where
S: AsRef<str>,
{
match kind {
MessageKind::Information if !self.log_info => {
return false;
}
MessageKind::Warning if !self.log_warning => {
return false;
}
MessageKind::Error if !self.log_error => {
return false;
}
_ => (),
}
let mut msg = message.as_ref().to_owned();
if let Some(id) = id {
let mut need_write = false;
match self.one_shot_sources.entry(id) {
Entry::Occupied(mut message) => {
if message.get() != &msg {
message.insert(msg.clone());
need_write = true;
}
}
Entry::Vacant(entry) => {
entry.insert(msg.clone());
need_write = true;
}
}
if !need_write {
return false;
}
}
self.listeners.retain(|listener| {
listener
.send(LogMessage {
kind,
content: msg.clone(),
time: Instant::now() - self.time_origin,
})
.is_ok()
});
msg.insert_str(0, kind.as_str());
#[cfg(target_arch = "wasm32")]
{
log(&msg);
}
#[cfg(all(not(target_os = "android"), not(target_arch = "wasm32")))]
{
if self.write_to_stdout {
let _ = io::stdout().write_all(msg.as_bytes());
}
if let Some(log_file) = self.file.as_mut() {
let _ = log_file.write_all(msg.as_bytes());
let _ = log_file.flush();
}
}
#[cfg(target_os = "android")]
{
if self.write_to_stdout {
let _ = io::stdout().write_all(msg.as_bytes());
}
}
true
}
fn writeln_internal<S>(&mut self, id: Option<usize>, kind: MessageKind, message: S) -> bool
where
S: AsRef<str>,
{
let mut msg = message.as_ref().to_owned();
msg.push('\n');
self.write_internal(id, kind, msg)
}
pub fn write<S>(kind: MessageKind, msg: S)
where
S: AsRef<str>,
{
LOG.lock().write_internal(None, kind, msg);
}
pub fn write_once<S>(id: usize, kind: MessageKind, msg: S) -> bool
where
S: AsRef<str>,
{
LOG.lock().write_internal(Some(id), kind, msg)
}
pub fn writeln<S>(kind: MessageKind, msg: S)
where
S: AsRef<str>,
{
LOG.lock().writeln_internal(None, kind, msg);
}
pub fn writeln_once<S>(id: usize, kind: MessageKind, msg: S) -> bool
where
S: AsRef<str>,
{
LOG.lock().writeln_internal(Some(id), kind, msg)
}
pub fn info<S>(msg: S)
where
S: AsRef<str>,
{
Self::writeln(MessageKind::Information, msg)
}
pub fn warn<S>(msg: S)
where
S: AsRef<str>,
{
Self::writeln(MessageKind::Warning, msg)
}
pub fn err<S>(msg: S)
where
S: AsRef<str>,
{
Self::writeln(MessageKind::Error, msg)
}
pub fn info_once<S>(id: usize, msg: S) -> bool
where
S: AsRef<str>,
{
Self::writeln_once(id, MessageKind::Information, msg)
}
pub fn warn_once<S>(id: usize, msg: S) -> bool
where
S: AsRef<str>,
{
Self::writeln_once(id, MessageKind::Warning, msg)
}
pub fn err_once<S>(id: usize, msg: S) -> bool
where
S: AsRef<str>,
{
Self::writeln_once(id, MessageKind::Error, msg)
}
pub fn enable_writing_to_stdout(enabled: bool) {
LOG.lock().write_to_stdout = enabled;
}
pub fn is_writing_to_stdout() -> bool {
LOG.lock().write_to_stdout
}
pub fn set_log_info(state: bool) {
LOG.lock().log_info = state;
}
pub fn is_logging_info() -> bool {
LOG.lock().log_info
}
pub fn set_log_warning(state: bool) {
LOG.lock().log_warning = state;
}
pub fn is_logging_warning() -> bool {
LOG.lock().log_warning
}
pub fn set_log_error(state: bool) {
LOG.lock().log_error = state;
}
pub fn is_logging_error() -> bool {
LOG.lock().log_error
}
pub fn add_listener(listener: Sender<LogMessage>) {
LOG.lock().listeners.push(listener)
}
pub fn verify<T, E>(result: Result<T, E>)
where
E: Display,
{
if let Err(e) = result {
Self::writeln(MessageKind::Error, format!("Operation failed! Reason: {e}"));
}
}
pub fn verify_message<S, T, E>(result: Result<T, E>, msg: S)
where
E: Debug,
S: Display,
{
if let Err(e) = result {
Self::writeln(MessageKind::Error, format!("{msg}. Reason: {e:?}"));
}
}
}
#[macro_export]
macro_rules! info {
($($arg:tt)*) => {
$crate::log::Log::info(format!($($arg)*))
};
}
#[macro_export]
macro_rules! warn {
($($arg:tt)*) => {
$crate::log::Log::warn(format!($($arg)*))
};
}
#[macro_export]
macro_rules! err {
($($arg:tt)*) => {
$crate::log::Log::err(format!($($arg)*))
};
}
#[macro_export]
macro_rules! info_once {
($id:expr, $($arg:tt)*) => {
$crate::log::Log::info_once($id, format!($($arg)*))
};
}
#[macro_export]
macro_rules! warn_once {
($id:expr, $($arg:tt)*) => {
$crate::log::Log::warn_once($id, format!($($arg)*))
};
}
#[macro_export]
macro_rules! err_once {
($id:expr, $($arg:tt)*) => {
$crate::log::Log::err_once($id, format!($($arg)*))
};
}