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