mirror of
https://github.com/ClementTsang/bottom.git
synced 2025-11-29 10:04:18 +01:00
* refactor: use clippy::unwrap_used * fix log format use * fix a bunch of macos ones * oop * allow schema script * driveby schema bump
182 lines
4.6 KiB
Rust
182 lines
4.6 KiB
Rust
#[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!(
|
|
// The weird "[[[" is because we need to escape a bracket ("[[") to show
|
|
// one "[". See https://time-rs.github.io/book/api/format-description.html
|
|
"[[[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")]
|
|
/// We do this to ensure that the test logger is only initialized _once_ for
|
|
/// things like the default test runner that run tests in the same process.
|
|
///
|
|
/// This doesn't do anything if you use something like nextest, which runs
|
|
/// a test-per-process, but that's fine.
|
|
fn init_test_logger() {
|
|
use std::sync::atomic::{AtomicBool, Ordering};
|
|
|
|
static LOG_INIT: AtomicBool = AtomicBool::new(false);
|
|
|
|
if LOG_INIT.load(Ordering::SeqCst) {
|
|
return;
|
|
}
|
|
|
|
LOG_INIT.store(true, Ordering::SeqCst);
|
|
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.");
|
|
}
|
|
}
|