fstdout_logger/formatter/mod.rs
1//! Log message formatting functionality.
2//!
3//! This module is responsible for formatting log messages for display in the terminal
4//! and for writing to log files. It handles colored output, timestamp formatting,
5//! and determining which information to include in log messages.
6
7use colored::{ColoredString, Colorize};
8use log::{Level, Record};
9
10use crate::config::LoggerConfig;
11
12/// Handles log formatting for both stdout and file outputs.
13///
14/// This struct is responsible for:
15/// - Formatting log messages differently for stdout and file outputs
16/// - Applying colors to terminal output when enabled
17/// - Including/excluding file information based on configuration
18/// - Managing date/time formatting in log messages
19pub struct LogFormatter {
20 /// The configuration that controls formatting behavior
21 config: LoggerConfig,
22}
23
24impl LogFormatter {
25 /// Create a new formatter with the given configuration.
26 ///
27 /// # Arguments
28 ///
29 /// * `config` - Configuration options that control formatting behavior
30 pub fn new(config: LoggerConfig) -> Self {
31 Self { config }
32 }
33
34 /// Get the appropriate color for a log level.
35 ///
36 /// Returns a `ColoredString` with the appropriate color for the given log level,
37 /// or a plain string if colors are disabled.
38 ///
39 /// # Colors Used
40 ///
41 /// - `Error`: Bold Red
42 /// - `Warn`: Bold Yellow
43 /// - `Info`: Bold Blue
44 /// - `Debug`: Green
45 /// - `Trace`: Normal terminal color
46 ///
47 /// # Arguments
48 ///
49 /// * `level` - The log level to get the color for
50 fn get_level_color(&self, level: Level) -> ColoredString {
51 if !self.config.use_colors {
52 return level.as_str().normal();
53 }
54
55 match level {
56 Level::Error => "ERROR".red().bold(),
57 Level::Warn => "WARN".yellow().bold(),
58 Level::Info => "INFO".blue().bold(),
59 Level::Debug => "DEBUG".green(),
60 Level::Trace => "TRACE".normal(),
61 }
62 }
63
64 /// Format a log record for stdout
65 pub fn format_stdout(&self, record: &Record) -> String {
66 let now = chrono::Local::now();
67
68 // Format timestamp (HH:MM:SS) without date for stdout
69 let timestamp = if self.config.show_date_in_stdout {
70 now.format("%Y-%m-%d %H:%M:%S").to_string()
71 } else {
72 now.format("%H:%M:%S").to_string()
73 };
74
75 // Get colored log level
76 let level_str = self.get_level_color(record.level());
77
78 // Format with or without file info
79 if self.config.show_file_info {
80 let file = record.file().unwrap_or("unknown");
81 let line = record.line().unwrap_or(0);
82
83 if self.config.use_colors {
84 let file_info = format!("{file}:{line}").bright_black();
85 format!(
86 "[{} {} {}] {}",
87 timestamp.bright_black(),
88 level_str,
89 file_info,
90 record.args()
91 )
92 } else {
93 format!(
94 "[{} {} {}:{}] {}",
95 timestamp,
96 level_str,
97 file,
98 line,
99 record.args()
100 )
101 }
102 } else {
103 // Simpler format without file info
104 if self.config.use_colors {
105 format!(
106 "[{} {}] {}",
107 timestamp.bright_black(),
108 level_str,
109 record.args()
110 )
111 } else {
112 format!("[{} {}] {}", timestamp, level_str, record.args())
113 }
114 }
115 }
116
117 /// Format a log record for file output.
118 ///
119 /// This creates a formatted log message for writing to a log file.
120 /// It always includes:
121 /// - Full date and time (YYYY-MM-DD HH:MM:SS)
122 /// - File and line information
123 /// - Plain text (no color codes)
124 ///
125 /// # Format
126 ///
127 /// `[YYYY-MM-DD HH:MM:SS LEVEL file:line] message\n`
128 ///
129 /// # Arguments
130 ///
131 /// * `record` - The log record to format
132 ///
133 /// # Returns
134 ///
135 /// A formatted string ready for writing to a file (includes trailing newline)
136 pub fn format_file(&self, record: &Record) -> String {
137 let timestamp = chrono::Local::now().format("%Y-%m-%d %H:%M:%S");
138 let file = record.file().unwrap_or("unknown");
139 let line = record.line().unwrap_or(0);
140
141 format!(
142 "[{} {} {}:{}] {}\n",
143 timestamp,
144 record.level(),
145 file,
146 line,
147 record.args()
148 )
149 }
150}