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 a reference to the configuration.
35 pub fn config(&self) -> &LoggerConfig {
36 &self.config
37 }
38
39 /// Get the appropriate color for a log level.
40 ///
41 /// Returns a `ColoredString` with the appropriate color for the given log level,
42 /// or a plain string if colors are disabled.
43 ///
44 /// # Colors Used
45 ///
46 /// - `Error`: Bold Red
47 /// - `Warn`: Bold Yellow
48 /// - `Info`: Bold Blue
49 /// - `Debug`: Green
50 /// - `Trace`: Normal terminal color
51 ///
52 /// # Arguments
53 ///
54 /// * `level` - The log level to get the color for
55 fn get_level_color(&self, level: Level) -> ColoredString {
56 if !self.config.use_colors {
57 return level.as_str().normal();
58 }
59
60 match level {
61 Level::Error => "ERROR".red().bold(),
62 Level::Warn => "WARN".yellow().bold(),
63 Level::Info => "INFO".blue().bold(),
64 Level::Debug => "DEBUG".green(),
65 Level::Trace => "TRACE".normal(),
66 }
67 }
68
69 /// Format a log record for stdout
70 pub fn format_stdout(&self, record: &Record) -> String {
71 let now = chrono::Local::now();
72
73 // Format timestamp (HH:MM:SS) without date for stdout
74 let timestamp = if self.config.show_date_in_stdout {
75 now.format("%Y-%m-%d %H:%M:%S").to_string()
76 } else {
77 now.format("%H:%M:%S").to_string()
78 };
79
80 // Get colored log level
81 let level_str = self.get_level_color(record.level());
82
83 // Format with or without file info
84 if self.config.show_file_info {
85 let file = record.file().unwrap_or("unknown");
86 let line = record.line().unwrap_or(0);
87
88 if self.config.use_colors {
89 let file_info = format!("{file}:{line}").bright_black();
90 format!(
91 "[{} {} {}] {}",
92 timestamp.bright_black(),
93 level_str,
94 file_info,
95 record.args()
96 )
97 } else {
98 format!(
99 "[{} {} {}:{}] {}",
100 timestamp,
101 level_str,
102 file,
103 line,
104 record.args()
105 )
106 }
107 } else {
108 // Simpler format without file info
109 if self.config.use_colors {
110 format!(
111 "[{} {}] {}",
112 timestamp.bright_black(),
113 level_str,
114 record.args()
115 )
116 } else {
117 format!("[{} {}] {}", timestamp, level_str, record.args())
118 }
119 }
120 }
121
122 /// Format a log record for file output.
123 ///
124 /// This creates a formatted log message for writing to a log file.
125 /// It always includes:
126 /// - Full date and time (YYYY-MM-DD HH:MM:SS)
127 /// - File and line information
128 /// - Plain text (no color codes)
129 ///
130 /// # Format
131 ///
132 /// `[YYYY-MM-DD HH:MM:SS LEVEL file:line] message\n`
133 ///
134 /// # Arguments
135 ///
136 /// * `record` - The log record to format
137 ///
138 /// # Returns
139 ///
140 /// A formatted string ready for writing to a file (includes trailing newline)
141 pub fn format_file(&self, record: &Record) -> String {
142 let timestamp = chrono::Local::now().format("%Y-%m-%d %H:%M:%S");
143 let file = record.file().unwrap_or("unknown");
144 let line = record.line().unwrap_or(0);
145
146 format!(
147 "[{} {} {}:{}] {}\n",
148 timestamp,
149 record.level(),
150 file,
151 line,
152 record.args()
153 )
154 }
155}