use std::collections::HashMap;
use std::fmt;
use tracing::Level;
use tracing_subscriber::fmt::{FormatEvent, FormatFields};
pub struct InfrarustMessageFormatter {
show_target: bool,
show_level: bool,
time_format: String,
timestamp: bool,
all_fields: bool,
use_icons: bool,
print_template: String,
field_prefixes: HashMap<String, String>,
use_ansi: bool,
}
impl Default for InfrarustMessageFormatter {
fn default() -> Self {
Self {
show_target: true,
show_level: true,
time_format: "%Y-%m-%d %H:%M:%S%.3f".to_string(),
timestamp: true,
all_fields: true,
use_icons: true,
print_template: "{timestamp} {level} {target} {message} {fields}".to_string(),
field_prefixes: HashMap::new(),
use_ansi: true,
}
}
}
impl InfrarustMessageFormatter {
pub fn with_icons(mut self, use_icons: bool) -> Self {
self.use_icons = use_icons;
self
}
pub fn with_timestamp(mut self, enabled: bool) -> Self {
self.timestamp = enabled;
self
}
pub fn with_target(mut self, show_target: bool) -> Self {
self.show_target = show_target;
self
}
pub fn with_level(mut self, show_level: bool) -> Self {
self.show_level = show_level;
self
}
pub fn with_all_fields(mut self, all_fields: bool) -> Self {
self.all_fields = all_fields;
self
}
pub fn with_time_format(mut self, format: &str) -> Self {
self.time_format = format.to_string();
self
}
pub fn with_template(mut self, template: &str) -> Self {
self.print_template = template.to_string();
self
}
pub fn before_field(mut self, field_name: &str, prefix: &str) -> Self {
self.field_prefixes
.insert(field_name.to_string(), prefix.to_string());
self
}
pub fn with_ansi(mut self, enabled: bool) -> Self {
self.use_ansi = enabled;
self
}
fn colorize(&self, text: &str, color_code: &str) -> String {
if self.use_ansi {
format!("{}{}\x1B[0m", color_code, text)
} else {
text.to_string()
}
}
fn apply_template(&self, template: &str, values: &HashMap<&str, String>) -> String {
let mut result = template.to_string();
for (key, value) in values {
if !value.is_empty() {
let placeholder = format!("{{{}}}", key);
let replacement = if let Some(prefix) = self.field_prefixes.get(*key) {
format!("{}{}", prefix, value)
} else {
value.clone()
};
result = result.replace(&placeholder, &replacement);
} else {
result = result.replace(&format!("{{{}}}", key), "");
}
}
result
}
}
impl<S, N> FormatEvent<S, N> for InfrarustMessageFormatter
where
S: tracing::Subscriber + for<'a> tracing_subscriber::registry::LookupSpan<'a>,
N: for<'a> FormatFields<'a> + 'static,
{
fn format_event(
&self,
ctx: &tracing_subscriber::fmt::FmtContext<'_, S, N>,
mut writer: tracing_subscriber::fmt::format::Writer<'_>,
event: &tracing::Event<'_>,
) -> fmt::Result {
let mut components: HashMap<&str, String> = HashMap::new();
if self.timestamp {
let now = chrono::Local::now();
let timestamp = now.format(&self.time_format);
components.insert(
"timestamp",
self.colorize(×tamp.to_string(), "\x1B[38;5;240m"),
);
} else {
components.insert("timestamp", String::new());
}
let level = event.metadata().level();
if self.show_level {
if self.use_icons {
match *level {
Level::TRACE => {
components.insert("level", self.colorize("🔍 TRACE", "\x1B[37m"))
}
Level::DEBUG => {
components.insert("level", self.colorize("🔧 DEBUG", "\x1B[36m"))
}
Level::INFO => {
components.insert("level", self.colorize("✅ INFO ", "\x1B[32m"))
}
Level::WARN => {
components.insert("level", self.colorize("⚠️ WARN ", "\x1B[33m"))
}
Level::ERROR => {
components.insert("level", self.colorize("❌ ERROR", "\x1B[31m"))
}
};
} else {
match *level {
Level::TRACE => components.insert("level", self.colorize("TRACE", "\x1B[37m")),
Level::DEBUG => components.insert("level", self.colorize("DEBUG", "\x1B[36m")),
Level::INFO => components.insert("level", self.colorize("INFO ", "\x1B[32m")),
Level::WARN => components.insert("level", self.colorize("WARN", "\x1B[33m")),
Level::ERROR => components.insert("level", self.colorize("ERROR", "\x1B[31m")),
};
}
} else {
components.insert("level", String::new());
}
if self.show_target {
let target = event.metadata().target();
components.insert("target", self.colorize(target, "\x1B[35m"));
} else {
components.insert("target", String::new());
}
let mut spans = String::new();
if let Some(scope) = ctx.event_scope() {
let mut seen = false;
for span in scope.from_root() {
if seen {
spans.push('→');
} else if self.use_ansi {
spans.push_str("\x1B[34m");
}
seen = true;
spans.push_str(&format!("{}:", span.metadata().name()));
let ext = span.extensions();
if let Some(fields) = ext.get::<tracing_subscriber::fmt::FormattedFields<N>>() {
spans.push_str(&format!("{}", fields));
}
}
if seen && self.use_ansi {
spans.push_str("\x1B[0m ");
}
}
components.insert("spans", spans);
struct MessageVisitor {
message: Option<String>,
fields: Vec<(String, String)>,
}
impl tracing::field::Visit for MessageVisitor {
fn record_debug(&mut self, field: &tracing::field::Field, value: &dyn fmt::Debug) {
let val_str = format!("{:?}", value).trim_matches('"').to_string();
if field.name() == "message" {
self.message = Some(val_str);
} else {
self.fields.push((field.name().to_string(), val_str));
}
}
fn record_str(&mut self, field: &tracing::field::Field, value: &str) {
if field.name() == "message" {
self.message = Some(value.to_string());
} else {
self.fields
.push((field.name().to_string(), value.to_string()));
}
}
fn record_i64(&mut self, field: &tracing::field::Field, value: i64) {
self.fields
.push((field.name().to_string(), value.to_string()));
}
fn record_u64(&mut self, field: &tracing::field::Field, value: u64) {
self.fields
.push((field.name().to_string(), value.to_string()));
}
fn record_bool(&mut self, field: &tracing::field::Field, value: bool) {
self.fields
.push((field.name().to_string(), value.to_string()));
}
fn record_f64(&mut self, field: &tracing::field::Field, value: f64) {
self.fields
.push((field.name().to_string(), value.to_string()));
}
}
let mut visitor = MessageVisitor {
message: None,
fields: Vec::new(),
};
event.record(&mut visitor);
let message = if let Some(msg) = visitor.message {
match *level {
Level::ERROR => self.colorize(&msg, "\x1B[1;31m"),
Level::WARN => self.colorize(&msg, "\x1B[1;33m"),
_ => msg,
}
} else {
String::new()
};
components.insert("message", message);
let fields_str = if self.all_fields && !visitor.fields.is_empty() {
let mut fields = String::new();
if self.use_ansi {
fields.push_str("\x1B[90m{");
} else {
fields.push('{');
}
let mut first = true;
for (key, value) in visitor.fields {
if !first {
fields.push_str(", ");
}
first = false;
if self.use_ansi {
fields.push_str(&format!("{}=\x1B[36m{}\x1B[90m", key, value));
} else {
fields.push_str(&format!("{}={}", key, value));
}
}
if self.use_ansi {
fields.push_str("}\x1B[0m");
} else {
fields.push('}');
}
fields
} else {
String::new()
};
components.insert("fields", fields_str);
let formatted = self.apply_template(&self.print_template, &components);
write!(writer, "{}", formatted)?;
writeln!(writer)
}
}