#[cfg(feature = "logging")]
use std::sync::OnceLock;
#[cfg(feature = "logging")]
pub static OFFSET: OnceLock<time::UtcOffset> = OnceLock::new();
#[cfg(feature = "logging")]
pub fn init_logger(
min_level: log::LevelFilter, debug_file_name: Option<&std::ffi::OsStr>,
) -> anyhow::Result<()> {
let dispatch = fern::Dispatch::new()
.format(|out, message, record| {
let offset = OFFSET.get_or_init(|| {
time::UtcOffset::current_local_offset().unwrap_or(time::UtcOffset::UTC)
});
let offset_time = {
let utc = time::OffsetDateTime::now_utc();
utc.checked_to_offset(*offset).unwrap_or(utc)
};
out.finish(format_args!(
"{}[{}][{}] {}",
offset_time
.format(&time::macros::format_description!(
"[[[year]-[month]-[day]][[[hour]:[minute]:[second][subsecond digits:9]]"
))
.expect("log formatting shouldn't fail"),
record.target(),
record.level(),
message
))
})
.level(min_level);
if let Some(debug_file_name) = debug_file_name {
dispatch.chain(fern::log_file(debug_file_name)?).apply()?;
} else {
dispatch.chain(std::io::stdout()).apply()?;
}
Ok(())
}
#[macro_export]
macro_rules! error {
($($x:tt)*) => {
#[cfg(feature = "logging")]
{
log::error!($($x)*);
}
};
}
#[macro_export]
macro_rules! warn {
($($x:tt)*) => {
#[cfg(feature = "logging")]
{
log::warn!($($x)*);
}
};
}
#[macro_export]
macro_rules! info {
($($x:tt)*) => {
#[cfg(feature = "logging")]
{
log::info!($($x)*);
}
};
}
#[macro_export]
macro_rules! debug {
($($x:tt)*) => {
#[cfg(feature = "logging")]
{
log::debug!($($x)*);
}
};
}
#[macro_export]
macro_rules! trace {
($($x:tt)*) => {
#[cfg(feature = "logging")]
{
log::trace!($($x)*);
}
};
}
#[macro_export]
macro_rules! log {
($($x:tt)*) => {
#[cfg(feature = "logging")]
{
log::log!(log::Level::Trace, $($x)*);
}
};
($level:expr, $($x:tt)*) => {
#[cfg(feature = "logging")]
{
log::log!($level, $($x)*);
}
};
}
#[macro_export]
macro_rules! info_every_n_secs {
($n:expr, $($x:tt)*) => {
#[cfg(feature = "logging")]
{
$crate::log_every_n_secs!(log::Level::Info, $n, $($x)*);
}
};
}
#[macro_export]
macro_rules! log_every_n_secs {
($level:expr, $n:expr, $($x:tt)*) => {
#[cfg(feature = "logging")]
{
static LAST_LOG: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
let since_last_log = LAST_LOG.load(std::sync::atomic::Ordering::Relaxed);
let now = std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).expect("should be valid").as_secs();
if now - since_last_log > $n {
LAST_LOG.store(now, std::sync::atomic::Ordering::Relaxed);
log::log!($level, $($x)*);
}
}
};
}
#[cfg(test)]
mod test {
#[cfg(feature = "logging")]
fn init_test_logger() {
use std::sync::Once;
static INIT: Once = Once::new();
INIT.call_once(|| {
super::init_logger(log::LevelFilter::Trace, None)
.expect("initializing the logger should succeed");
});
}
#[cfg(feature = "logging")]
#[test]
fn test_logging_macros() {
init_test_logger();
error!("This is an error.");
warn!("This is a warning.");
info!("This is an info.");
debug!("This is a debug.");
info!("This is a trace.");
}
#[cfg(feature = "logging")]
#[test]
fn test_log_every_macros() {
init_test_logger();
info_every_n_secs!(10, "This is an info every 10 seconds.");
}
}