use std::collections::{HashMap, HashSet};
use std::sync::Arc;
use arc_swap::ArcSwap;
use tracing::{Event, Level, Subscriber};
use tracing_subscriber::fmt::{FmtContext, FormatEvent, FormatFields};
use tracing_subscriber::registry::LookupSpan;
use crate::color;
use crate::config::{Alignment, Color, Config};
pub struct ItraceFormatter {
config: Arc<ArcSwap<Config>>,
}
impl ItraceFormatter {
pub fn new(config: Arc<ArcSwap<Config>>) -> Self {
Self { config }
}
}
impl<S, N> FormatEvent<S, N> for ItraceFormatter
where
S: Subscriber + for<'a> LookupSpan<'a>,
N: for<'a> FormatFields<'a> + 'static,
{
fn format_event(
&self,
_ctx: &FmtContext<'_, S, N>,
mut writer: tracing_subscriber::fmt::format::Writer<'_>,
event: &Event<'_>,
) -> std::fmt::Result {
let config = self.config.load();
let mut visitor = FieldVisitor::default();
event.record(&mut visitor);
let message = visitor.fields.remove("message").unwrap_or_default();
write_level_badge(&mut writer, event.metadata().level(), &config)?;
write!(writer, " ")?;
let dt = crate::datetime::format_now(&config.datetime.format);
write_column(
&mut writer,
&dt,
dt.len() + 2,
&Alignment::Left,
None,
None,
config.general.colors,
)?;
let mut consumed: HashSet<&str> = HashSet::new();
for col_name in &config.columns.order {
let def = config
.columns
.definitions
.get(col_name)
.unwrap_or(&config.columns.default);
let value = visitor
.fields
.get(col_name)
.map(String::as_str)
.unwrap_or("");
if !value.is_empty() {
consumed.insert(col_name.as_str());
}
write!(writer, " ")?;
write_column(
&mut writer,
value,
def.width,
&def.align,
def.bg.as_ref(),
def.fg.as_ref(),
config.general.colors,
)?;
}
let extras: Vec<String> = visitor
.fields
.iter()
.filter(|(k, _)| !consumed.contains(k.as_str()))
.map(|(k, v)| format!("{k}={v}"))
.collect();
let full_message = if extras.is_empty() {
message
} else {
format!("{message} [{}]", extras.join(", "))
};
write!(writer, " {full_message}")?;
writeln!(writer)
}
}
#[derive(Default)]
struct FieldVisitor {
fields: HashMap<String, String>,
}
impl tracing::field::Visit for FieldVisitor {
fn record_str(&mut self, field: &tracing::field::Field, value: &str) {
self.fields
.insert(field.name().to_string(), value.to_string());
}
fn record_i64(&mut self, field: &tracing::field::Field, value: i64) {
self.fields
.insert(field.name().to_string(), value.to_string());
}
fn record_u64(&mut self, field: &tracing::field::Field, value: u64) {
self.fields
.insert(field.name().to_string(), value.to_string());
}
fn record_bool(&mut self, field: &tracing::field::Field, value: bool) {
self.fields
.insert(field.name().to_string(), value.to_string());
}
fn record_debug(&mut self, field: &tracing::field::Field, value: &dyn std::fmt::Debug) {
self.fields
.insert(field.name().to_string(), format!("{value:?}"));
}
}
fn write_level_badge(
writer: &mut tracing_subscriber::fmt::format::Writer<'_>,
level: &Level,
config: &Config,
) -> std::fmt::Result {
let badge = match *level {
Level::TRACE => "T",
Level::DEBUG => "D",
Level::INFO => "I",
Level::WARN => "W",
Level::ERROR => "E",
};
let level_key = match *level {
Level::TRACE => "trace",
Level::DEBUG => "debug",
Level::INFO => "info",
Level::WARN => "warn",
Level::ERROR => "error",
};
let style = config.level_styles.get(level_key);
if config.general.colors {
if let Some(s) = style {
if let Some(bg) = &s.bg {
write!(writer, "{}", color::color_bg(bg))?;
}
if let Some(fg) = &s.fg {
write!(writer, "{}", color::color_fg(fg))?;
}
}
}
write!(writer, " {badge} ")?;
if config.general.colors {
write!(writer, "{}", color::RESET)?;
}
Ok(())
}
fn write_column(
writer: &mut tracing_subscriber::fmt::format::Writer<'_>,
value: &str,
width: usize,
align: &Alignment,
bg: Option<&Color>,
fg: Option<&Color>,
colors: bool,
) -> std::fmt::Result {
if colors {
if let Some(bg) = bg {
write!(writer, "{}", color::color_bg(bg))?;
}
if let Some(fg) = fg {
write!(writer, "{}", color::color_fg(fg))?;
}
}
let padded = match align {
Alignment::Left => format!(" {value:<width$} "),
Alignment::Right => format!(" {value:>width$} "),
Alignment::Center => format!(" {value:^width$} "),
};
write!(writer, "{padded}")?;
if colors {
write!(writer, "{}", color::RESET)?;
}
Ok(())
}