batbox_logger/
lib.rs

1//! Logging
2//!
3//! Initialize the logger using one of the methods here, then just use the [log] crate stuff
4//!
5//! ```
6//! batbox_logger::init();
7//! log::info!("This is a doctest");
8//! ```
9#![warn(missing_docs)]
10
11use std::sync::Mutex;
12#[cfg(target_arch = "wasm32")]
13use wasm_bindgen::prelude::*;
14
15struct Logger {
16    inner: env_logger::Logger,
17}
18
19static LOGGERS: once_cell::sync::Lazy<Mutex<Vec<Box<dyn log::Log>>>> =
20    once_cell::sync::Lazy::new(|| Mutex::new(Vec::new()));
21
22impl log::Log for Logger {
23    fn enabled(&self, metadata: &log::Metadata) -> bool {
24        if !self.inner.enabled(metadata) {
25            return false;
26        }
27        match metadata.target().split_terminator(':').next().unwrap() {
28            "ws" => metadata.level() <= log::Level::Error,
29            "mio" => false,
30            _ => true,
31        }
32    }
33
34    fn log(&self, record: &log::Record) {
35        if !self.enabled(record.metadata()) {
36            return;
37        }
38        self.inner.log(record);
39        if self.inner.matches(record) {
40            for logger in LOGGERS.lock().unwrap().iter_mut() {
41                if logger.enabled(record.metadata()) {
42                    logger.log(record);
43                }
44            }
45            #[cfg(target_arch = "wasm32")]
46            {
47                #[wasm_bindgen]
48                extern "C" {
49                    #[wasm_bindgen(js_namespace = console, js_name = log)]
50                    fn console_log(s: &str);
51                }
52                console_log(&format!("{} - {}", record.level(), record.args()));
53            }
54        }
55    }
56
57    fn flush(&self) {
58        self.inner.flush();
59        for logger in LOGGERS.lock().unwrap().iter_mut() {
60            logger.flush();
61        }
62    }
63}
64
65/// Initialize with a custom config
66pub fn init_with(mut builder: env_logger::Builder) -> Result<(), log::SetLoggerError> {
67    let builder_info = format!("{builder:?}");
68    let logger = Logger {
69        inner: builder.build(),
70    };
71    log::set_max_level(logger.inner.filter());
72    log::set_boxed_logger(Box::new(logger))?;
73    log::trace!("Logger initialized with {}", builder_info);
74    std::panic::set_hook(Box::new(|info| {
75        log::error!("{}", info);
76        log::error!("{:?}", backtrace::Backtrace::new());
77    }));
78    Ok(())
79}
80
81/// Get the default logger builder configuration
82pub fn builder() -> env_logger::Builder {
83    let mut builder = env_logger::Builder::new();
84    builder
85        .filter_level(if cfg!(debug_assertions) {
86            log::LevelFilter::Debug
87        } else {
88            log::LevelFilter::Info
89        })
90        .format_timestamp(None)
91        .format_module_path(true)
92        .format_target(false)
93        .parse_env("LOG");
94    builder
95}
96
97/// Initialize using default config
98pub fn init() {
99    try_init().expect("Failed to initialize logger");
100}
101
102/// Initialize using default config, or return error
103pub fn try_init() -> Result<(), log::SetLoggerError> {
104    init_with(builder())
105}
106
107/// Initialize for tests ([crates::env_logger::Builder::is_test])
108pub fn init_for_tests() {
109    let mut builder = builder();
110    builder.is_test(true);
111    let _ = init_with(builder);
112}
113
114/// Add another custom logger to use in addition to the main one
115pub fn add_logger(logger: Box<dyn log::Log>) {
116    LOGGERS.lock().unwrap().push(logger);
117}