use std::{
cell::{Cell, RefCell},
io::{IsTerminal, stdin},
};
use colored::{ColoredString, Colorize};
use logforth::{
Error as LogforthError, Layout,
diagnostic::Diagnostic,
record::{Level, Record},
};
use crate::Kv;
thread_local! {
static LAST_TS: Cell<i64> = const { Cell::new(0) };
static LAST_STR: RefCell<String> = const { RefCell::new(String::new()) };
}
#[derive(Debug)]
pub struct Text {
pub color: bool,
}
pub fn level_color(level: Level) -> ColoredString {
match level {
Level::Error => level.to_string().red(),
Level::Warn => level.to_string().yellow(),
Level::Info => level.to_string().green(),
Level::Debug => level.to_string().blue(),
Level::Trace => level.to_string().magenta(),
_ => level.to_string().into(),
}
}
impl Default for Text {
fn default() -> Self {
Self {
color: stdin().is_terminal(),
}
}
}
impl Layout for Text {
fn format(
&self,
record: &Record<'_>,
diagnostics: &[Box<dyn Diagnostic>],
) -> Result<Vec<u8>, LogforthError> {
let level = record.level();
let file = record.file().unwrap_or(record.target());
let file_line = if let Some(line) = record.line() {
format!("{file}:{line}")
} else {
file.into()
};
let msg = record.payload();
let mut visitor = Kv {
text: String::new(),
};
record.key_values().visit(&mut visitor)?;
for d in diagnostics {
d.visit(&mut visitor)?;
}
let msg = if visitor.text.is_empty() {
msg.to_string()
} else {
format!("{} {}", msg, visitor.text)
};
let msg = if self.color {
let level = level_color(level);
let file_line = file_line.bright_black();
format!("{level} {file_line} {msg}")
} else {
let ts = coarsetime::Clock::now_since_epoch().as_secs() as i64;
let ts_str = LAST_TS.with(|last_ts_cell| {
LAST_STR.with(|last_str_cell| {
let last_ts = last_ts_cell.get();
if last_ts == ts {
last_str_cell.borrow().clone()
} else {
let new_str = if let Ok(timestamp) = jiff::Timestamp::from_second(ts) {
jiff::Zoned::new(timestamp, crate::TZ.clone())
.strftime("%Y-%m-%d %H:%M:%S")
.to_string()
} else {
ts.to_string()
};
last_ts_cell.set(ts);
*last_str_cell.borrow_mut() = new_str.clone();
new_str
}
})
});
format!("{level} {ts_str} {file_line} {msg}")
};
Ok(msg.into_bytes())
}
}