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}