From a8ed131b3beb793c630e31a7153c5d5186f932d1 Mon Sep 17 00:00:00 2001 From: "Rodrigo Rodriguez (Pragmatismo)" Date: Tue, 27 Jan 2026 18:08:57 -0300 Subject: [PATCH] feat: Implement new log format specification --- src/logging.rs | 79 +++++++++++++++++++++++++++++++++++++------------- 1 file changed, 59 insertions(+), 20 deletions(-) diff --git a/src/logging.rs b/src/logging.rs index 703934b..4870ff0 100644 --- a/src/logging.rs +++ b/src/logging.rs @@ -2,20 +2,13 @@ use env_logger::fmt::Formatter; use log::Record; use std::io::Write; -// ANSI color codes -const RED: &str = "\x1b[31m"; -const YELLOW: &str = "\x1b[33m"; -const GREEN: &str = "\x1b[32m"; -const CYAN: &str = "\x1b[36m"; -const RESET: &str = "\x1b[0m"; - pub fn compact_format(buf: &mut Formatter, record: &Record) -> std::io::Result<()> { - let (level, color) = match record.level() { - log::Level::Error => ("E", RED), - log::Level::Warn => ("W", YELLOW), - log::Level::Info => ("I", GREEN), - log::Level::Debug => ("D", CYAN), - log::Level::Trace => ("T", ""), + let level_char = match record.level() { + log::Level::Error => 'E', + log::Level::Warn => 'I', // Mapping Warn to Info per I/T/E spec + log::Level::Info => 'I', + log::Level::Debug => 'T', // Mapping Debug to Trace per I/T/E spec + log::Level::Trace => 'T', }; let now = chrono::Local::now(); @@ -28,10 +21,58 @@ pub fn compact_format(buf: &mut Formatter, record: &Record) -> std::io::Result<( target }; - if color.is_empty() { - writeln!(buf, "{} {} {}:{}", timestamp, level, module, record.args()) + // Format: "YYYYMMDDHHMMSS.mmm L module:" + // Length: 18 + 1 + 1 + 1 + module.len() + 1 = 22 + module.len() + let prefix = format!("{} {} {}:", timestamp, level_char, module); + + // Max width 80 + // If prefix + message fits, print it. + // Else, wrap. + // Indent for wrapping is 21 spaces (18 timestamp + 1 space + 1 level + 1 space) + // Actually, based on spec: + // Position: 123456789012345678901234567890... + // Format: YYYYMMDDHHMMSS.mmm L module:Message + // 1 18 20 22 + // So indent is 21 spaces. + + let message = record.args().to_string(); + let indent = " "; // 21 spaces + + if prefix.len() + message.len() <= 80 { + writeln!(buf, "{}{}", prefix, message) } else { - writeln!(buf, "{} {}{}{} {}:{}", timestamp, color, level, RESET, module, record.args()) + let available_first_line = if prefix.len() < 80 { + 80 - prefix.len() + } else { + 0 + }; + let available_other_lines = 80 - 21; // 59 chars + + let mut current_pos = 0; + let chars: Vec = message.chars().collect(); + let total_chars = chars.len(); + + // First line + write!(buf, "{}", prefix)?; + + // If prefix is already >= 80, we force a newline immediately? + // Or we just print a bit and wrap? + // Let's assume typical usage where module name isn't huge. + + let take = std::cmp::min(available_first_line, total_chars); + let first_chunk: String = chars[0..take].iter().collect(); + writeln!(buf, "{}", first_chunk)?; + current_pos += take; + + while current_pos < total_chars { + write!(buf, "{}", indent)?; + let remaining = total_chars - current_pos; + let take = std::cmp::min(remaining, available_other_lines); + let chunk: String = chars[current_pos..current_pos + take].iter().collect(); + writeln!(buf, "{}", chunk)?; + current_pos += take; + } + Ok(()) } } @@ -42,8 +83,6 @@ pub fn init_compact_logger(default_filter: &str) { } pub fn init_compact_logger_with_style(default_filter: &str) { - env_logger::Builder::from_env(env_logger::Env::default().default_filter_or(default_filter)) - .format(compact_format) - .write_style(env_logger::WriteStyle::Always) - .init(); + // Style ignored to strictly follow text format spec + init_compact_logger(default_filter); }