use tracing_subscriber::FmtSubscriber;
use {std::env, tracing::Level};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum LoggerInit {
Installed,
AlreadyInstalled,
Unsupported,
}
fn level_from(log_level: &str) -> Level {
match log_level.trim().to_uppercase().as_str() {
"DEBUG" => Level::DEBUG,
"ERROR" => Level::ERROR,
"WARN" => Level::WARN,
"TRACE" => Level::TRACE,
_ => Level::INFO,
}
}
pub fn try_setup_logger_with_level(log_level: &str) -> LoggerInit {
#[cfg(target_arch = "wasm32")]
{
let _ = log_level;
LoggerInit::Unsupported
}
#[cfg(not(target_arch = "wasm32"))]
{
let level = level_from(log_level);
let subscriber = FmtSubscriber::builder().with_max_level(level).finish();
match tracing::subscriber::set_global_default(subscriber) {
Ok(()) => {
tracing::debug!("Log level set to: {}", level);
LoggerInit::Installed
}
Err(_) => {
tracing::debug!("A tracing subscriber is already installed; leaving it in place");
LoggerInit::AlreadyInstalled
}
}
}
}
pub fn try_setup_logger() -> LoggerInit {
#[cfg(target_arch = "wasm32")]
{
LoggerInit::Unsupported
}
#[cfg(not(target_arch = "wasm32"))]
{
let log_level = env::var("LOGLEVEL").unwrap_or_else(|_| "INFO".to_string());
try_setup_logger_with_level(&log_level)
}
}
pub fn setup_logger() {
let _ = try_setup_logger();
}
pub fn setup_logger_with_level(log_level: &str) {
let _ = try_setup_logger_with_level(log_level);
}
#[cfg(test)]
mod tests_setup_logger {
use super::*;
use std::env;
use tracing::subscriber::set_global_default;
use tracing_subscriber::FmtSubscriber;
#[test]
fn test_logger_initialization_info() {
unsafe {
env::set_var("LOGLEVEL", "INFO");
}
setup_logger();
assert!(
set_global_default(FmtSubscriber::builder().finish()).is_err(),
"Logger should already be set"
);
}
#[test]
fn test_logger_initialization_debug() {
unsafe {
env::set_var("LOGLEVEL", "DEBUG");
}
setup_logger();
assert!(
set_global_default(FmtSubscriber::builder().finish()).is_err(),
"Logger should already be set"
);
}
#[test]
fn test_logger_initialization_default() {
unsafe {
env::remove_var("LOGLEVEL");
}
setup_logger();
assert!(
set_global_default(FmtSubscriber::builder().finish()).is_err(),
"Logger should already be set"
);
}
#[test]
fn test_logger_called_once() {
unsafe {
env::set_var("LOGLEVEL", "INFO");
}
setup_logger(); setup_logger();
assert!(
set_global_default(FmtSubscriber::builder().finish()).is_err(),
"Logger should already be set and should not be reset"
);
}
}
#[cfg(test)]
mod tests_setup_logger_bis {
use super::*;
use std::sync::Mutex;
use tracing::subscriber::with_default;
use tracing_subscriber::Layer;
use tracing_subscriber::layer::SubscriberExt;
static TEST_MUTEX: Mutex<()> = Mutex::new(());
#[derive(Clone)]
struct TestLayer {
level: std::sync::Arc<Mutex<Option<Level>>>,
}
impl<S> Layer<S> for TestLayer
where
S: tracing::Subscriber,
{
fn on_event(
&self,
event: &tracing::Event<'_>,
_ctx: tracing_subscriber::layer::Context<'_, S>,
) {
let mut level = self.level.lock().unwrap();
*level = Some(*event.metadata().level());
}
}
fn create_test_layer() -> (TestLayer, std::sync::Arc<Mutex<Option<Level>>>) {
let level = std::sync::Arc::new(Mutex::new(None));
(
TestLayer {
level: level.clone(),
},
level,
)
}
#[test]
fn test_default_log_level() {
let _lock = TEST_MUTEX.lock().unwrap();
unsafe {
env::remove_var("LOGLEVEL");
}
let (layer, level) = create_test_layer();
let subscriber = tracing_subscriber::registry().with(layer);
with_default(subscriber, || {
setup_logger();
tracing::info!("Test log");
});
assert_eq!(*level.lock().unwrap(), Some(Level::INFO));
}
#[test]
fn test_debug_log_level() {
let _lock = TEST_MUTEX.lock().unwrap();
unsafe {
env::set_var("LOGLEVEL", "DEBUG");
}
let (layer, level) = create_test_layer();
let subscriber = tracing_subscriber::registry().with(layer);
with_default(subscriber, || {
setup_logger();
tracing::debug!("Test log");
});
assert_eq!(*level.lock().unwrap(), Some(Level::DEBUG));
unsafe {
env::remove_var("LOGLEVEL");
}
}
#[test]
fn test_error_log_level() {
let _lock = TEST_MUTEX.lock().unwrap();
unsafe {
env::set_var("LOGLEVEL", "ERROR");
}
let (layer, level) = create_test_layer();
let subscriber = tracing_subscriber::registry().with(layer);
with_default(subscriber, || {
setup_logger();
tracing::error!("Test log");
});
assert_eq!(*level.lock().unwrap(), Some(Level::ERROR));
unsafe {
env::remove_var("LOGLEVEL");
}
}
#[test]
fn test_warn_log_level() {
let _lock = TEST_MUTEX.lock().unwrap();
unsafe {
env::set_var("LOGLEVEL", "WARN");
}
let (layer, level) = create_test_layer();
let subscriber = tracing_subscriber::registry().with(layer);
with_default(subscriber, || {
setup_logger();
tracing::warn!("Test log");
});
assert_eq!(*level.lock().unwrap(), Some(Level::WARN));
unsafe {
env::remove_var("LOGLEVEL");
}
}
#[test]
fn test_trace_log_level() {
let _lock = TEST_MUTEX.lock().unwrap();
unsafe {
env::set_var("LOGLEVEL", "TRACE");
}
let (layer, level) = create_test_layer();
let subscriber = tracing_subscriber::registry().with(layer);
with_default(subscriber, || {
setup_logger();
tracing::trace!("Test log");
});
assert_eq!(*level.lock().unwrap(), Some(Level::TRACE));
unsafe {
env::remove_var("LOGLEVEL");
}
}
#[test]
fn test_invalid_log_level() {
let _lock = TEST_MUTEX.lock().unwrap();
unsafe {
env::set_var("LOGLEVEL", "INVALID");
}
let (layer, level) = create_test_layer();
let subscriber = tracing_subscriber::registry().with(layer);
with_default(subscriber, || {
setup_logger();
tracing::info!("Test log");
});
assert_eq!(*level.lock().unwrap(), Some(Level::INFO));
unsafe {
env::remove_var("LOGLEVEL");
}
}
}
#[cfg(all(test, not(target_arch = "wasm32")))]
mod tests_no_global_seizure {
use super::*;
#[test]
fn a_second_installation_reports_instead_of_panicking() {
let first = try_setup_logger_with_level("WARN");
assert_ne!(first, LoggerInit::Unsupported);
for _ in 0..3 {
assert_eq!(
try_setup_logger_with_level("DEBUG"),
LoggerInit::AlreadyInstalled,
"a subscriber is installed, so no later call may claim otherwise"
);
}
}
#[test]
fn level_parsing_is_case_and_whitespace_insensitive() {
assert_eq!(level_from(" debug "), Level::DEBUG);
assert_eq!(level_from("Warn"), Level::WARN);
assert_eq!(level_from("TRACE"), Level::TRACE);
assert_eq!(level_from("ERROR"), Level::ERROR);
assert_eq!(level_from("verbose"), Level::INFO);
assert_eq!(level_from(""), Level::INFO);
}
}