use std::{
fmt::{Arguments, Write},
sync::{atomic::AtomicUsize, Arc},
time::Instant,
};
pub struct Detailer {
level: log::LevelFilter,
accumulated: String,
current_indentation: Arc<AtomicUsize>,
start: Option<Instant>,
soft_limit: usize,
}
pub enum TimingSetting {
WithTiming,
WithoutTiming,
}
#[macro_export(local_inner_macros)]
macro_rules! new_detailer {
() => {
new_detailer!(Info)
};
($level:tt) => {
new_detailer!($level, WithTiming)
};
($level:tt, $timing_setting:tt) => {
new_detailer!($level, $timing_setting, 4 * 1024)
};
($level:tt, $timing_setting:tt, $soft_limit:expr) => {
detailer::Detailer::new(
log::LevelFilter::$level,
detailer::TimingSetting::$timing_setting,
$soft_limit,
)
};
}
#[macro_export(local_inner_macros)]
macro_rules! detail {
($detail_tracker:expr, $($arg:tt)+) => {
($detail_tracker.log(
log::Level::Info,
core::format_args!($($arg)+))
);
};
}
#[macro_export(local_inner_macros)]
macro_rules! detail_at {
($detail_tracker:expr, $log_level:tt, $($arg:tt)+) => {
($detail_tracker.log(
log::Level::$log_level,
core::format_args!($($arg)+))
);
};
}
#[macro_export(local_inner_macros)]
macro_rules! scope {
($detail_tracker:expr, $($arg:tt)+) => {
($detail_tracker.scope(
core::format_args!($($arg)+))
);
};
}
impl Detailer {
pub fn new(level: log::LevelFilter, timing_setting: TimingSetting, limit: usize) -> Detailer {
Detailer {
level,
accumulated: Default::default(),
current_indentation: Default::default(),
start: match timing_setting {
TimingSetting::WithTiming => Some(Instant::now()),
TimingSetting::WithoutTiming => None,
},
soft_limit: limit,
}
}
pub fn peek(&self) -> &str {
&self.accumulated
}
pub fn reset(&mut self) {
self.accumulated.clear();
if self.start.is_some() {
self.start = Some(Instant::now());
}
}
pub fn flush(&mut self) {
let to_flush = self.accumulated.trim_end();
if !to_flush.is_empty() {
log::log!(
self.level.to_level().unwrap_or(log::Level::Info),
"{}",
to_flush
);
}
self.reset();
}
pub fn scope(&mut self, scope_name: Arguments) -> DetailScopeGuard {
if let Some(level) = self.level.to_level() {
self.log(level, scope_name);
}
DetailScopeGuard::new(self.current_indentation.clone())
}
pub fn log(&mut self, level: log::Level, message: Arguments) {
if level <= self.level {
if self.soft_limit <= self.accumulated.len() {
log::warn!("truncated");
return;
}
let current_indentation = self
.current_indentation
.load(std::sync::atomic::Ordering::Relaxed);
if 0 < current_indentation {
let message = message.to_string();
let mut lines = message.split('\n');
if let Some(first_line) = lines.next() {
if let Some(start) = &self.start {
let elapsed = start.elapsed().as_micros() as u64;
let _ = self.accumulated.write_fmt(format_args!("{elapsed:<6} "));
}
for _ in 0..current_indentation {
let _ = self.accumulated.write_str(" ");
}
let _ = self.accumulated.write_fmt(format_args!("{first_line}\n"));
}
for line in lines {
for _ in 0..current_indentation {
let _ = self.accumulated.write_str(" ");
}
let _ = self.accumulated.write_fmt(format_args!("{line}\n"));
}
} else {
if let Some(start) = &self.start {
let elapsed = start.elapsed().as_micros() as u64;
let _ = self.accumulated.write_fmt(format_args!("{elapsed:<6} "));
}
let _ = self.accumulated.write_fmt(message);
let _ = self.accumulated.write_char('\n');
}
}
}
pub fn info(&mut self, message: Arguments) {
self.log(log::Level::Info, message)
}
pub fn trace(&mut self, message: Arguments) {
self.log(log::Level::Trace, message)
}
pub fn debug(&mut self, message: Arguments) {
self.log(log::Level::Debug, message)
}
pub fn warn(&mut self, message: Arguments) {
self.log(log::Level::Warn, message)
}
pub fn error(&mut self, message: Arguments) {
self.log(log::Level::Error, message)
}
pub fn level(&mut self, level: log::LevelFilter) {
self.level = level;
}
}
impl Drop for Detailer {
fn drop(&mut self) {
if !self.accumulated.is_empty() {
self.log(
self.level.to_level().unwrap_or(log::Level::Info),
format_args!("dropped"),
);
}
self.flush()
}
}
impl Default for Detailer {
fn default() -> Self {
Self {
level: log::LevelFilter::Info,
accumulated: Default::default(),
current_indentation: Default::default(),
start: Some(Instant::now()),
soft_limit: 4 * 1024,
}
}
}
pub struct DetailScopeGuard {
level: Arc<AtomicUsize>,
}
impl DetailScopeGuard {
fn new(level: Arc<AtomicUsize>) -> Self {
level.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
Self { level }
}
}
impl Drop for DetailScopeGuard {
fn drop(&mut self) {
self.level
.fetch_sub(1, std::sync::atomic::Ordering::Relaxed);
}
}