#[cfg(all(not(target_arch = "wasm32"), feature = "logging"))]
mod standard_logger;
#[cfg(any(all(target_arch = "wasm32", feature = "logging"), test))]
mod wasm_logger;
#[cfg(not(feature = "logging"))]
mod null_logger;
#[cfg(feature = "progress_bar")]
mod progress_bar_encoder;
use std::collections::hash_map::Entry;
use std::sync::{LazyLock, Mutex, MutexGuard};
pub use log::{debug, error, info, trace, warn, LevelFilter};
#[cfg(all(not(target_arch = "wasm32"), feature = "logging"))]
use log4rs::Handle;
use crate::HashMap;
pub const DEFAULT_LOG_LEVEL: LevelFilter = LevelFilter::Error;
const DEFAULT_MODULE_FILTERS: [(&str, LevelFilter); 1] = [
("rustyline", LevelFilter::Off),
];
static LOG_CONFIGURATION: LazyLock<Mutex<LogConfiguration>> = LazyLock::new(Mutex::default);
#[derive(Debug, PartialEq)]
struct ModuleLogConfiguration {
module: String,
level: LevelFilter,
}
impl From<(&str, LevelFilter)> for ModuleLogConfiguration {
fn from((module, level): (&str, LevelFilter)) -> Self {
Self {
module: module.to_string(),
level,
}
}
}
#[derive(Debug)]
pub(in crate::log) struct LogConfiguration {
pub(in crate::log) global_log_level: LevelFilter,
pub(in crate::log) module_configurations: HashMap<String, ModuleLogConfiguration>,
#[cfg(all(not(target_arch = "wasm32"), feature = "logging"))]
root_handle: Option<Handle>,
#[cfg(all(target_arch = "wasm32", feature = "logging"))]
initialized: bool,
}
impl Default for LogConfiguration {
fn default() -> Self {
let module_configurations = DEFAULT_MODULE_FILTERS
.map(|(module, level)| (module.to_string(), (module, level).into()));
let module_configurations = HashMap::from_iter(module_configurations);
Self {
global_log_level: DEFAULT_LOG_LEVEL,
module_configurations,
#[cfg(all(not(target_arch = "wasm32"), feature = "logging"))]
root_handle: None,
#[cfg(all(target_arch = "wasm32", feature = "logging"))]
initialized: false,
}
}
}
impl LogConfiguration {
pub(in crate::log) fn set_log_level(&mut self, level: LevelFilter) {
self.global_log_level = level;
self.set_config();
}
fn insert_module_filter(&mut self, module: &String, level: LevelFilter) -> bool {
match self.module_configurations.entry(module.clone()) {
Entry::Occupied(mut entry) => {
let module_config = entry.get_mut();
if module_config.level == level {
return false;
}
module_config.level = level;
}
Entry::Vacant(entry) => {
let new_configuration = ModuleLogConfiguration {
module: module.to_string(),
level,
};
entry.insert(new_configuration);
}
}
true
}
pub(in crate::log) fn set_module_filter<S: ToString>(
&mut self,
module: &S,
level: LevelFilter,
) {
if self.insert_module_filter(&module.to_string(), level) {
self.set_config();
}
}
pub(in crate::log) fn set_module_filters<S: ToString>(
&mut self,
module_filters: &[(&S, LevelFilter)],
) {
let mut mutated: bool = false;
for (module, level) in module_filters {
mutated |= self.insert_module_filter(&module.to_string(), *level);
}
if mutated {
self.set_config();
}
}
pub(in crate::log) fn remove_module_filter(&mut self, module: &str) {
if self.module_configurations.remove(module).is_some() {
self.set_config();
}
}
}
pub fn enable_logging() {
set_log_level(LevelFilter::Trace);
}
pub fn disable_logging() {
set_log_level(LevelFilter::Off);
}
pub fn set_log_level(level: LevelFilter) {
let mut log_configuration = get_log_configuration();
log_configuration.set_log_level(level);
}
pub fn set_module_filter(module_path: &str, level_filter: LevelFilter) {
let mut log_configuration = get_log_configuration();
log_configuration.set_module_filter(&module_path, level_filter);
}
pub fn remove_module_filter(module_path: &str) {
let mut log_configuration = get_log_configuration();
log_configuration.remove_module_filter(module_path);
}
#[allow(clippy::implicit_hasher)]
pub fn set_module_filters<S: ToString>(module_filters: &[(&S, LevelFilter)]) {
let mut log_configuration = get_log_configuration();
log_configuration.set_module_filters(module_filters);
}
fn get_log_configuration() -> MutexGuard<'static, LogConfiguration> {
LOG_CONFIGURATION.lock().expect("Mutex poisoned")
}
pub(crate) fn level_to_string_list(level: LevelFilter) -> String {
let level_list = ["ERROR", "WARN", "INFO", "DEBUG", "TRACE"];
level_list[0..level as usize].join(", ")
}
#[cfg(test)]
mod tests {
use std::sync::{LazyLock, Mutex};
use log::{error, trace, LevelFilter};
use super::{
get_log_configuration, level_to_string_list, remove_module_filter, set_log_level,
set_module_filters,
};
static TEST_MUTEX: LazyLock<Mutex<()>> = LazyLock::new(Mutex::default);
#[test]
fn test_set_log_level() {
let _guard = TEST_MUTEX.lock().expect("Mutex poisoned");
set_log_level(LevelFilter::Trace);
set_log_level(LevelFilter::Error);
{
let config = get_log_configuration();
assert_eq!(config.global_log_level, LevelFilter::Error);
error!("test_set_log_level: global set to error");
trace!("test_set_log_level: NOT EMITTED");
}
set_log_level(LevelFilter::Trace);
{
let config = get_log_configuration();
assert_eq!(config.global_log_level, LevelFilter::Trace);
assert_eq!(log::max_level(), LevelFilter::Trace);
trace!("test_set_log_level: global set to trace");
}
}
#[test]
fn test_set_remove_module_filters() {
let _guard = TEST_MUTEX.lock().expect("Mutex poisoned");
set_log_level(LevelFilter::Trace);
{
let config = get_log_configuration();
assert_eq!(config.module_configurations.len(), 1);
let expected = ("rustyline", LevelFilter::Off).into();
assert_eq!(
config.module_configurations.get("rustyline"),
Some(&expected)
);
}
let filters: [(&&str, LevelFilter); 2] = [
(&"rustyline", LevelFilter::Error),
(&"ixa", LevelFilter::Debug),
];
set_module_filters(&filters);
{
let config = get_log_configuration();
assert_eq!(config.module_configurations.len(), 2);
for (module_path, level) in &filters {
assert_eq!(
config.module_configurations.get(**module_path),
Some(&((**module_path, *level).into()))
);
}
}
remove_module_filter("rustyline");
{
let config = get_log_configuration();
assert_eq!(config.module_configurations.len(), 1);
assert_eq!(
config.module_configurations.get("ixa"),
Some(&("ixa", LevelFilter::Debug).into())
);
}
}
#[test]
fn test_level_to_string_list() {
assert_eq!(level_to_string_list(LevelFilter::Off), "");
assert_eq!(level_to_string_list(LevelFilter::Error), "ERROR");
assert_eq!(level_to_string_list(LevelFilter::Warn), "ERROR, WARN");
assert_eq!(level_to_string_list(LevelFilter::Info), "ERROR, WARN, INFO");
assert_eq!(
level_to_string_list(LevelFilter::Debug),
"ERROR, WARN, INFO, DEBUG"
);
assert_eq!(
level_to_string_list(LevelFilter::Trace),
"ERROR, WARN, INFO, DEBUG, TRACE"
);
}
}