#![cfg_attr(docsrs, feature(doc_cfg))]
pub extern crate colored;
use colored::Color;
use colored::ColoredString;
use colored::Colorize;
use jiff::Timestamp;
use jiff::tz::TimeZone;
use logforth_core::Diagnostic;
use logforth_core::Error;
use logforth_core::kv::Key;
use logforth_core::kv::Value;
use logforth_core::kv::Visitor;
use logforth_core::layout::Layout;
use logforth_core::record::Level;
use logforth_core::record::Record;
#[derive(Debug, Clone, Default)]
pub struct TextLayout {
colors: LevelColor,
no_color: bool,
tz: Option<TimeZone>,
}
impl TextLayout {
pub fn fatal_color(mut self, color: Color) -> Self {
self.colors.fatal = color;
self
}
pub fn error_color(mut self, color: Color) -> Self {
self.colors.error = color;
self
}
pub fn warn_color(mut self, color: Color) -> Self {
self.colors.warn = color;
self
}
pub fn info_color(mut self, color: Color) -> Self {
self.colors.info = color;
self
}
pub fn debug_color(mut self, color: Color) -> Self {
self.colors.debug = color;
self
}
pub fn trace_color(mut self, color: Color) -> Self {
self.colors.trace = color;
self
}
pub fn no_color(mut self) -> Self {
self.no_color = true;
self
}
pub fn timezone(mut self, tz: TimeZone) -> Self {
self.tz = Some(tz);
self
}
fn format_record_level(&self, level: Level) -> ColoredString {
self.colors.colorize_record_level(self.no_color, level)
}
}
struct KvWriter {
text: String,
}
impl Visitor for KvWriter {
fn visit(&mut self, key: Key, value: Value) -> Result<(), Error> {
use std::fmt::Write;
write!(&mut self.text, " {key}={value}").unwrap();
Ok(())
}
}
impl Layout for TextLayout {
fn format(&self, record: &Record, diags: &[Box<dyn Diagnostic>]) -> Result<Vec<u8>, Error> {
let ts = Timestamp::try_from(record.time()).unwrap();
let tz = self.tz.clone().unwrap_or_else(TimeZone::system);
let offset = tz.to_offset(ts);
let time = ts.display_with_offset(offset);
let level = self.format_record_level(record.level());
let target = record.target();
let file = record.filename();
let line = record.line().unwrap_or_default();
let message = record.payload();
let mut visitor = KvWriter {
text: format!("{time:.6} {level:>6} {target}: {file}:{line} {message}"),
};
record.key_values().visit(&mut visitor)?;
for d in diags {
d.visit(&mut visitor)?;
}
Ok(visitor.text.into_bytes())
}
}
#[derive(Debug, Clone)]
struct LevelColor {
fatal: Color,
error: Color,
warn: Color,
info: Color,
debug: Color,
trace: Color,
}
impl Default for LevelColor {
fn default() -> Self {
Self {
fatal: Color::BrightRed,
error: Color::Red,
warn: Color::Yellow,
info: Color::Green,
debug: Color::Blue,
trace: Color::Magenta,
}
}
}
impl LevelColor {
fn colorize_record_level(&self, no_color: bool, level: Level) -> ColoredString {
if no_color {
ColoredString::from(level.to_string())
} else {
let color = match level {
Level::Fatal | Level::Fatal2 | Level::Fatal3 | Level::Fatal4 => self.fatal,
Level::Error | Level::Error2 | Level::Error3 | Level::Error4 => self.error,
Level::Warn | Level::Warn2 | Level::Warn3 | Level::Warn4 => self.warn,
Level::Info | Level::Info2 | Level::Info3 | Level::Info4 => self.info,
Level::Debug | Level::Debug2 | Level::Debug3 | Level::Debug4 => self.debug,
Level::Trace | Level::Trace2 | Level::Trace3 | Level::Trace4 => self.trace,
};
ColoredString::from(level.to_string()).color(color)
}
}
}