use std::sync::OnceLock;
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord)]
pub enum LogLevel {
Error = 0,
Warn = 1,
#[default]
Info = 2,
Debug = 3,
Trace = 4,
}
impl std::fmt::Display for LogLevel {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let s = match self {
LogLevel::Error => "error",
LogLevel::Warn => "warn",
LogLevel::Info => "info",
LogLevel::Debug => "debug",
LogLevel::Trace => "trace",
};
write!(f, "{s}")
}
}
impl From<LogLevel> for tracing::Level {
fn from(level: LogLevel) -> tracing::Level {
match level {
LogLevel::Error => tracing::Level::ERROR,
LogLevel::Warn => tracing::Level::WARN,
LogLevel::Info => tracing::Level::INFO,
LogLevel::Debug => tracing::Level::DEBUG,
LogLevel::Trace => tracing::Level::TRACE,
}
}
}
static INIT: OnceLock<()> = OnceLock::new();
#[derive(Clone, Debug)]
pub struct LoggingConfig {
pub level: LogLevel,
pub ansi_colors: bool,
pub with_file: bool,
pub with_thread_ids: bool,
}
impl Default for LoggingConfig {
fn default() -> Self {
LoggingConfig {
level: LogLevel::Info,
ansi_colors: true,
with_file: false,
with_thread_ids: false,
}
}
}
impl LoggingConfig {
pub fn new(level: LogLevel) -> Self {
LoggingConfig {
level,
..Default::default()
}
}
pub fn no_ansi(mut self) -> Self {
self.ansi_colors = false;
self
}
pub fn with_file(mut self) -> Self {
self.with_file = true;
self
}
pub fn with_thread_ids(mut self) -> Self {
self.with_thread_ids = true;
self
}
}
pub fn init_logging(level: LogLevel) {
init_with_config(LoggingConfig::new(level));
}
pub fn init_with_config(config: LoggingConfig) {
INIT.get_or_init(|| {
use tracing_subscriber::{fmt, EnvFilter};
let filter = EnvFilter::try_from_default_env()
.unwrap_or_else(|_| EnvFilter::new(config.level.to_string()));
let subscriber = fmt::Subscriber::builder()
.with_env_filter(filter)
.with_ansi(config.ansi_colors)
.with_file(config.with_file)
.with_thread_ids(config.with_thread_ids);
let _ = subscriber.try_init();
});
}
#[macro_export]
#[cfg(feature = "tracing")]
macro_rules! frame_span {
($label:expr) => {
tracing::trace_span!("oxiui::frame", label = $label)
};
}
#[macro_export]
#[cfg(feature = "tracing")]
macro_rules! layout_span {
($label:expr) => {
tracing::debug_span!("oxiui::layout", label = $label)
};
}
#[macro_export]
#[cfg(feature = "tracing")]
macro_rules! paint_span {
($label:expr) => {
tracing::debug_span!("oxiui::paint", label = $label)
};
}
#[macro_export]
#[cfg(feature = "tracing")]
macro_rules! event_span {
($label:expr) => {
tracing::debug_span!("oxiui::event", label = $label)
};
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn log_level_ordering() {
assert!(LogLevel::Error < LogLevel::Warn);
assert!(LogLevel::Warn < LogLevel::Info);
assert!(LogLevel::Info < LogLevel::Debug);
assert!(LogLevel::Debug < LogLevel::Trace);
}
#[test]
fn log_level_display() {
assert_eq!(LogLevel::Error.to_string(), "error");
assert_eq!(LogLevel::Info.to_string(), "info");
assert_eq!(LogLevel::Trace.to_string(), "trace");
}
#[test]
fn log_level_default_is_info() {
assert_eq!(LogLevel::default(), LogLevel::Info);
}
#[test]
fn logging_config_builder() {
let cfg = LoggingConfig::new(LogLevel::Debug)
.no_ansi()
.with_file()
.with_thread_ids();
assert_eq!(cfg.level, LogLevel::Debug);
assert!(!cfg.ansi_colors);
assert!(cfg.with_file);
assert!(cfg.with_thread_ids);
}
#[test]
fn init_logging_is_idempotent() {
init_logging(LogLevel::Info);
init_logging(LogLevel::Debug); }
#[test]
fn init_with_config_is_idempotent() {
let cfg = LoggingConfig::default();
init_with_config(cfg.clone());
init_with_config(cfg);
}
#[test]
fn log_level_into_tracing_level() {
let _: tracing::Level = LogLevel::Info.into();
let _: tracing::Level = LogLevel::Trace.into();
}
}