1use crate::formatters::FormatterTrait;
2use crate::level::LogLevel;
3use crate::record::Record;
4use chrono::Local;
5use colored::*;
6use std::fmt;
7use std::sync::Arc;
8
9pub type FormatFn = Arc<dyn Fn(&Record) -> String + Send + Sync>;
11
12#[derive(Clone)]
14pub struct TextFormatter {
15 use_colors: bool,
17 include_timestamp: bool,
19 include_level: bool,
21 include_module: bool,
23 include_location: bool,
25 pattern: String,
27 format_fn: Option<FormatFn>,
29}
30
31impl fmt::Debug for TextFormatter {
32 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
33 f.debug_struct("TextFormatter")
34 .field("use_colors", &self.use_colors)
35 .field("include_timestamp", &self.include_timestamp)
36 .field("include_level", &self.include_level)
37 .field("include_module", &self.include_module)
38 .field("include_location", &self.include_location)
39 .field("pattern", &self.pattern)
40 .field("format_fn", &"<format_fn>")
41 .finish()
42 }
43}
44
45impl Default for TextFormatter {
46 fn default() -> Self {
47 Self {
48 use_colors: true,
49 include_timestamp: true,
50 include_level: true,
51 include_module: true,
52 include_location: true,
53 pattern: "{timestamp} {level} {module} {location} {message}".to_string(),
54 format_fn: None,
55 }
56 }
57}
58
59impl FormatterTrait for TextFormatter {
60 fn fmt(&self, record: &Record) -> String {
61 if let Some(format_fn) = &self.format_fn {
62 return format_fn(record);
63 }
64
65 let mut output = self.pattern.clone();
66
67 if self.include_timestamp {
68 let timestamp = Local::now().format("%Y-%m-%d %H:%M:%S%.3f");
69 output = output.replace("{timestamp}", ×tamp.to_string());
70 } else {
71 output = output.replace("{timestamp}", "");
72 }
73
74 if self.include_level {
75 let level = record.level().to_string();
76 if self.use_colors {
77 let colored_level = match record.level() {
78 LogLevel::Error => level.red().to_string(),
79 LogLevel::Warning => level.yellow().to_string(),
80 LogLevel::Info => level.green().to_string(),
81 LogLevel::Debug => level.blue().to_string(),
82 LogLevel::Trace => level.cyan().to_string(),
83 LogLevel::Success => level.green().to_string(),
84 LogLevel::Critical => level.red().to_string(),
85 };
86 output = output.replace("{level}", &colored_level);
87 } else {
88 output = output.replace("{level}", &level);
89 }
90 } else {
91 output = output.replace("{level}", "");
92 }
93
94 if self.include_module {
95 output = output.replace("{module}", record.module());
96 } else {
97 output = output.replace("{module}", "");
98 }
99
100 if self.include_location {
101 let location = format!("{}:{}", record.file(), record.line());
102 output = output.replace("{location}", &location);
103 } else {
104 output = output.replace("{location}", "");
105 }
106
107 output = output.replace("{message}", record.message());
108
109 output.trim().to_string()
110 }
111
112 fn with_colors(&mut self, use_colors: bool) {
113 self.use_colors = use_colors;
114 }
115
116 fn with_timestamp(&mut self, include_timestamp: bool) {
117 self.include_timestamp = include_timestamp;
118 }
119
120 fn with_level(&mut self, include_level: bool) {
121 self.include_level = include_level;
122 }
123
124 fn with_module(&mut self, include_module: bool) {
125 self.include_module = include_module;
126 }
127
128 fn with_location(&mut self, include_location: bool) {
129 self.include_location = include_location;
130 }
131
132 fn with_pattern(&mut self, pattern: String) {
133 self.pattern = pattern;
134 }
135
136 fn with_format(&mut self, format_fn: FormatFn) {
137 self.format_fn = Some(format_fn);
138 }
139
140 fn box_clone(&self) -> Box<dyn FormatterTrait + Send + Sync> {
141 Box::new(Self {
142 use_colors: self.use_colors,
143 include_timestamp: self.include_timestamp,
144 include_level: self.include_level,
145 include_module: self.include_module,
146 include_location: self.include_location,
147 pattern: self.pattern.clone(),
148 format_fn: self.format_fn.clone(),
149 })
150 }
151}
152
153#[cfg(test)]
154mod tests {
155 use super::*;
156 use crate::level::LogLevel;
157
158 #[test]
159 fn test_text_formatter_default() {
160 let formatter = TextFormatter::default();
161 let record = Record::new(
162 LogLevel::Info,
163 "Test message",
164 Some("test".to_string()),
165 Some("test.rs".to_string()),
166 Some(42),
167 );
168
169 let formatted = FormatterTrait::fmt(&formatter, &record);
170 assert!(formatted.contains("Test message"));
171 assert!(formatted.contains("INFO"));
172 assert!(formatted.contains("test"));
173 assert!(formatted.contains("test.rs:42"));
174 }
175
176 #[test]
177 fn test_text_formatter_no_colors() {
178 let mut formatter = TextFormatter::default();
179 formatter.with_colors(false);
180 let record = Record::new(
181 LogLevel::Info,
182 "Test message",
183 Some("test".to_string()),
184 Some("test.rs".to_string()),
185 Some(42),
186 );
187
188 let formatted = FormatterTrait::fmt(&formatter, &record);
189 assert!(formatted.contains("Test message"));
190 assert!(formatted.contains("INFO"));
191 assert!(formatted.contains("test"));
192 assert!(formatted.contains("test.rs:42"));
193 assert!(!formatted.contains("\x1b["));
194 }
195
196 #[test]
197 fn test_text_formatter_no_timestamp() {
198 let mut formatter = TextFormatter::default();
199 formatter.with_timestamp(false);
200 let record = Record::new(
201 LogLevel::Info,
202 "Test message",
203 Some("test".to_string()),
204 Some("test.rs".to_string()),
205 Some(42),
206 );
207
208 let formatted = FormatterTrait::fmt(&formatter, &record);
209 assert!(formatted.contains("Test message"));
210 assert!(formatted.contains("INFO"));
211 assert!(formatted.contains("test"));
212 assert!(formatted.contains("test.rs:42"));
213 assert!(!formatted.contains("2023")); }
215
216 #[test]
217 fn test_text_formatter_no_level() {
218 let mut formatter = TextFormatter::default();
219 formatter.with_level(false);
220 let record = Record::new(
221 LogLevel::Info,
222 "Test message",
223 Some("test".to_string()),
224 Some("test.rs".to_string()),
225 Some(42),
226 );
227
228 let formatted = FormatterTrait::fmt(&formatter, &record);
229 assert!(formatted.contains("Test message"));
230 assert!(!formatted.contains("INFO"));
231 assert!(formatted.contains("test"));
232 assert!(formatted.contains("test.rs:42"));
233 }
234
235 #[test]
236 fn test_text_formatter_no_module() {
237 let mut formatter = TextFormatter::default();
238 formatter.with_module(false);
239 let record = Record::new(
240 LogLevel::Info,
241 "Test message",
242 Some("test_module".to_string()),
243 Some("test.rs".to_string()),
244 Some(42),
245 );
246
247 let formatted = FormatterTrait::fmt(&formatter, &record);
248 assert!(formatted.contains("Test message"));
249 assert!(formatted.contains("INFO"));
250 assert!(!formatted.contains("test_module"));
251 assert!(formatted.contains("test.rs:42"));
252 }
253
254 #[test]
255 fn test_text_formatter_custom_format() {
256 let mut formatter = TextFormatter::default();
257 formatter.with_format(Arc::new(|record: &Record| {
258 format!("CUSTOM: {}", record.message())
259 }));
260 let record = Record::new(
261 LogLevel::Info,
262 "Test message",
263 Some("test".to_string()),
264 Some("test.rs".to_string()),
265 Some(42),
266 );
267
268 let formatted = FormatterTrait::fmt(&formatter, &record);
269 assert_eq!(formatted, "CUSTOM: Test message");
270 }
271}