use std::sync::OnceLock;
static LOG_LEVEL: OnceLock<String> = OnceLock::new();
const VALID_LEVELS: &[&str] = &["trace", "debug", "info", "warn", "error", "off"];
pub fn init_logger(level: &str) -> Result<(), String> {
let normalized = level.to_lowercase();
if !VALID_LEVELS.contains(&normalized.as_str()) {
return Err(format!("Invalid log level '{level}': unknown variant `{level}`, expected one of `trace`, `debug`, `info`, `warn`, `error`, `off`"));
}
let _ = LOG_LEVEL.set(normalized);
Ok(())
}
pub fn log_info(message: &str) -> Result<(), String> {
tracing::info!("{message}");
Ok(())
}
pub fn log_warn(message: &str) -> Result<(), String> {
tracing::warn!("{message}");
Ok(())
}
pub fn log_error(message: &str) -> Result<(), String> {
tracing::error!("{message}");
Ok(())
}
pub fn log_debug(message: &str) -> Result<(), String> {
tracing::debug!("{message}");
Ok(())
}
pub fn log_trace(message: &str) -> Result<(), String> {
tracing::trace!("{message}");
Ok(())
}
pub fn get_level() -> Result<String, String> {
Ok(LOG_LEVEL
.get()
.cloned()
.unwrap_or_else(|| "info".to_string()))
}
pub fn is_level_enabled(level: &str) -> Result<bool, String> {
let normalized = level.to_lowercase();
if !VALID_LEVELS.contains(&normalized.as_str()) {
return Err(format!("Invalid log level '{level}': unknown variant `{level}`, expected one of `trace`, `debug`, `info`, `warn`, `error`"));
}
let current = LOG_LEVEL.get().map_or("info", String::as_str);
let level_order = |l: &str| -> i32 {
match l {
"off" => 0,
"error" => 1,
"warn" => 2,
"info" => 3,
"debug" => 4,
"trace" => 5,
_ => 0,
}
};
Ok(level_order(&normalized) <= level_order(current))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_init_logger_valid_levels() {
assert!(init_logger("trace").is_ok());
assert!(init_logger("debug").is_ok());
assert!(init_logger("info").is_ok());
assert!(init_logger("warn").is_ok());
assert!(init_logger("error").is_ok());
assert!(init_logger("off").is_ok());
}
#[test]
fn test_init_logger_invalid_level() {
assert!(init_logger("invalid").is_err());
assert!(init_logger("unknown").is_err());
}
#[test]
fn test_init_logger_case_insensitive() {
assert!(init_logger("INFO").is_ok());
assert!(init_logger("Debug").is_ok());
assert!(init_logger("WARN").is_ok());
}
#[test]
fn test_log_info_basic() {
let _ = init_logger("info");
assert!(log_info("Test info message").is_ok());
assert!(log_info("").is_ok()); }
#[test]
fn test_log_warn_basic() {
let _ = init_logger("warn");
assert!(log_warn("Test warning").is_ok());
assert!(log_warn("").is_ok());
}
#[test]
fn test_log_error_basic() {
let _ = init_logger("error");
assert!(log_error("Test error").is_ok());
assert!(log_error("").is_ok());
}
#[test]
fn test_log_debug_basic() {
let _ = init_logger("debug");
assert!(log_debug("Test debug").is_ok());
assert!(log_debug("").is_ok());
}
#[test]
fn test_log_trace_basic() {
let _ = init_logger("trace");
assert!(log_trace("Test trace").is_ok());
assert!(log_trace("").is_ok());
}
#[test]
fn test_get_level() {
let _ = init_logger("info");
let level = get_level().unwrap();
assert!(["trace", "debug", "info", "warn", "error", "off"].contains(&level.as_str()));
}
#[test]
fn test_is_level_enabled_valid() {
let _ = init_logger("info");
assert!(is_level_enabled("info").is_ok());
assert!(is_level_enabled("warn").is_ok());
assert!(is_level_enabled("error").is_ok());
assert!(is_level_enabled("debug").is_ok());
assert!(is_level_enabled("trace").is_ok());
}
#[test]
fn test_is_level_enabled_invalid() {
assert!(is_level_enabled("invalid").is_err());
assert!(is_level_enabled("unknown").is_err());
}
#[test]
fn test_multiple_init_calls() {
assert!(init_logger("info").is_ok());
assert!(init_logger("debug").is_ok());
assert!(init_logger("warn").is_ok());
}
#[test]
fn test_log_all_levels_workflow() {
let _ = init_logger("trace");
assert!(log_trace("Trace message").is_ok());
assert!(log_debug("Debug message").is_ok());
assert!(log_info("Info message").is_ok());
assert!(log_warn("Warn message").is_ok());
assert!(log_error("Error message").is_ok());
}
#[test]
fn test_log_special_characters() {
let _ = init_logger("info");
assert!(log_info("Message with \"quotes\"").is_ok());
assert!(log_info("Message with\nnewlines").is_ok());
assert!(log_info("Message with\ttabs").is_ok());
assert!(log_info("Message with emojis").is_ok());
}
#[test]
fn test_log_long_message() {
let _ = init_logger("info");
let long_msg = "x".repeat(10000);
assert!(log_info(&long_msg).is_ok());
}
#[test]
fn test_level_hierarchy() {
let _ = init_logger("warn");
let level = get_level().unwrap();
assert!(["warn", "error", "off", "info", "debug", "trace"].contains(&level.as_str()));
}
#[test]
fn test_init_logger_with_whitespace() {
assert!(init_logger(" info ").is_err()); assert!(init_logger("info").is_ok()); }
#[test]
fn test_is_level_enabled_case_variations() {
let _ = init_logger("info");
assert!(is_level_enabled("INFO").is_ok());
assert!(is_level_enabled("Info").is_ok());
}
#[test]
fn test_log_info_unicode() {
let _ = init_logger("info");
assert!(log_info("Test with unicode").is_ok());
assert!(log_info("Test text").is_ok());
assert!(log_info("Rocket launch").is_ok());
}
#[test]
fn test_log_warn_unicode() {
let _ = init_logger("warn");
assert!(log_warn("Warning message").is_ok());
}
#[test]
fn test_log_error_unicode() {
let _ = init_logger("error");
assert!(log_error("Error text").is_ok());
}
#[test]
fn test_log_debug_unicode() {
let _ = init_logger("debug");
assert!(log_debug("Debug message").is_ok());
}
#[test]
fn test_log_trace_unicode() {
let _ = init_logger("trace");
assert!(log_trace("Trace message").is_ok());
}
#[test]
fn test_log_multiline_messages() {
let _ = init_logger("info");
let multiline = "Line 1\nLine 2\nLine 3\n\nLine 5";
assert!(log_info(multiline).is_ok());
}
#[test]
fn test_log_with_format_specifiers() {
let _ = init_logger("info");
assert!(log_info("Test %s %d {name} {{escaped}}").is_ok());
}
#[test]
fn test_get_level_returns_lowercase() {
let _ = init_logger("INFO");
let level = get_level().unwrap();
assert_eq!(level, level.to_lowercase());
}
#[test]
fn test_is_level_enabled_all_levels() {
let _ = init_logger("trace");
assert!(is_level_enabled("trace").is_ok());
assert!(is_level_enabled("debug").is_ok());
assert!(is_level_enabled("info").is_ok());
assert!(is_level_enabled("warn").is_ok());
assert!(is_level_enabled("error").is_ok());
}
}