1use colored::*;
2use std::fmt;
3use std::sync::Arc;
4
5use crate::formatters::{FormatFn, FormatterTrait};
6use crate::level::LogLevel;
7use crate::record::Record;
8
9#[derive(Clone)]
11pub struct TemplateFormatter {
12 use_colors: bool,
14 include_timestamp: bool,
16 include_level: bool,
18 include_module: bool,
20 include_location: bool,
22 include_metadata: bool,
24 include_data: bool,
26 pattern: String,
28 format_fn: Option<FormatFn>,
30 placeholders: Vec<(usize, usize, String)>,
32}
33
34impl fmt::Debug for TemplateFormatter {
35 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
36 f.debug_struct("TemplateFormatter")
37 .field("use_colors", &self.use_colors)
38 .field("include_timestamp", &self.include_timestamp)
39 .field("include_level", &self.include_level)
40 .field("include_module", &self.include_module)
41 .field("include_location", &self.include_location)
42 .field("include_metadata", &self.include_metadata)
43 .field("include_data", &self.include_data)
44 .field("pattern", &self.pattern)
45 .field("format_fn", &"<format_fn>")
46 .finish()
47 }
48}
49
50impl Default for TemplateFormatter {
51 fn default() -> Self {
52 let pattern =
53 "{timestamp} {level} {module} {location} {message} {metadata} {data}".to_string();
54 let mut formatter = Self {
55 use_colors: true,
56 include_timestamp: true,
57 include_level: true,
58 include_module: true,
59 include_location: true,
60 include_metadata: true,
61 include_data: true,
62 pattern: pattern.clone(),
63 format_fn: None,
64 placeholders: Vec::new(),
65 };
66 formatter.update_placeholders();
67 formatter
68 }
69}
70
71impl TemplateFormatter {
72 pub fn new(pattern: impl Into<String>) -> Self {
73 let pattern = pattern.into();
74 let mut formatter = Self {
75 use_colors: true,
76 include_timestamp: pattern.contains("{timestamp}"),
77 include_level: pattern.contains("{level}"),
78 include_module: pattern.contains("{module}"),
79 include_location: pattern.contains("{location}"),
80 include_metadata: pattern.contains("{metadata}"),
81 include_data: pattern.contains("{data}"),
82 pattern,
83 format_fn: None,
84 placeholders: Vec::new(),
85 };
86 formatter.update_placeholders();
87 formatter
88 }
89
90 fn update_flags_from_pattern(&mut self) {
91 self.include_timestamp = self.pattern.contains("{timestamp}");
92 self.include_level = self.pattern.contains("{level}");
93 self.include_module = self.pattern.contains("{module}");
94 self.include_location = self.pattern.contains("{location}");
95 self.include_metadata = self.pattern.contains("{metadata}");
96 self.include_data = self.pattern.contains("{data}");
97 }
98
99 fn update_placeholders(&mut self) {
100 self.placeholders.clear();
101 let mut pos = 0;
102 while let Some(start) = self.pattern[pos..].find('{') {
103 if let Some(end) = self.pattern[pos + start..].find('}') {
104 let placeholder = self.pattern[pos + start + 1..pos + start + end].to_string();
105 self.placeholders
106 .push((pos + start, pos + start + end + 1, placeholder));
107 pos += start + end + 1;
108 } else {
109 break;
110 }
111 }
112 }
113
114 pub fn with_colors(mut self, use_colors: bool) -> Self {
115 self.use_colors = use_colors;
116 self
117 }
118
119 pub fn with_timestamp(mut self, include_timestamp: bool) -> Self {
120 self.include_timestamp = include_timestamp;
121 self
122 }
123
124 pub fn with_level(mut self, include_level: bool) -> Self {
125 self.include_level = include_level;
126 self
127 }
128
129 pub fn with_module(mut self, include_module: bool) -> Self {
130 self.include_module = include_module;
131 self
132 }
133
134 pub fn with_location(mut self, include_location: bool) -> Self {
135 self.include_location = include_location;
136 self
137 }
138
139 pub fn with_pattern(mut self, pattern: impl Into<String>) -> Self {
140 self.pattern = pattern.into();
141 self.update_flags_from_pattern();
142 self.update_placeholders();
143 self
144 }
145
146 pub fn with_format<F>(mut self, format_fn: F) -> Self
147 where
148 F: Fn(&Record) -> String + Send + Sync + 'static,
149 {
150 self.format_fn = Some(Arc::new(format_fn));
151 self
152 }
153}
154
155impl FormatterTrait for TemplateFormatter {
156 fn fmt(&self, record: &Record) -> String {
157 if let Some(format_fn) = &self.format_fn {
158 let result = format_fn(record);
160 if result.ends_with('\n') {
161 result[..result.len() - 1].to_string()
162 } else {
163 result
164 }
165 } else {
166 let mut result = String::with_capacity(self.pattern.len() * 2);
168 let mut last_pos = 0;
169
170 for &(start, end, ref placeholder) in &self.placeholders {
172 result.push_str(&self.pattern[last_pos..start]);
173
174 match placeholder.as_str() {
175 "timestamp" => {
176 if self.include_timestamp {
177 result.push_str(&record.timestamp().to_rfc3339())
178 }
179 }
180 "level" => {
181 if self.include_level {
182 let level_str = record.level().to_string();
183 if self.use_colors {
184 result.push_str(&match record.level() {
185 LogLevel::Trace => level_str.white().to_string(),
186 LogLevel::Debug => level_str.blue().to_string(),
187 LogLevel::Info => level_str.green().to_string(),
188 LogLevel::Warning => level_str.yellow().to_string(),
189 LogLevel::Error => level_str.red().to_string(),
190 LogLevel::Critical => level_str.red().bold().to_string(),
191 LogLevel::Success => level_str.green().bold().to_string(),
192 });
193 } else {
194 result.push_str(&level_str);
195 }
196 }
197 }
198 "module" => {
199 if self.include_module {
200 let module = record.module();
201 if module != "unknown" {
202 result.push_str(module);
203 }
204 }
205 }
206 "location" => {
207 if self.include_location {
208 let file = record.file();
210 let line = record.line();
211 if file != "unknown" {
212 result.push_str(&format!("{}:{}", file, line));
213 }
214 }
215 }
216 "message" => {
217 result.push_str(record.message());
218 }
219 "metadata" => {
220 if self.include_metadata {
221 let metadata = record.metadata();
222 if !metadata.is_empty() {
223 let metadata_str = metadata
224 .iter()
225 .map(|(k, v)| format!("{}={}", k, v))
226 .collect::<Vec<_>>()
227 .join(" ");
228 result.push_str(&metadata_str);
229 }
230 }
231 }
232 "data" => {
233 if self.include_data {
234 let metadata = record.metadata();
235 if !metadata.is_empty() {
236 let data_str = metadata
237 .iter()
238 .filter(|(k, _)| k.starts_with("data."))
239 .map(|(k, v)| format!("{}={}", k, v))
240 .collect::<Vec<_>>()
241 .join(" ");
242 if !data_str.is_empty() {
243 result.push_str(&data_str);
244 }
245 }
246 }
247 }
248 _ => {
249 result.push_str(&self.pattern[start..end]);
250 }
251 }
252 last_pos = end;
253 }
254
255 result.push_str(&self.pattern[last_pos..]);
257
258 if !result.ends_with('\n') {
260 result.push('\n');
261 }
262
263 result
264 }
265 }
266
267 fn with_colors(&mut self, use_colors: bool) {
268 self.use_colors = use_colors;
269 }
270
271 fn with_timestamp(&mut self, include_timestamp: bool) {
272 self.include_timestamp = include_timestamp;
273 }
274
275 fn with_level(&mut self, include_level: bool) {
276 self.include_level = include_level;
277 }
278
279 fn with_module(&mut self, include_module: bool) {
280 self.include_module = include_module;
281 }
282
283 fn with_location(&mut self, include_location: bool) {
284 self.include_location = include_location;
285 }
286
287 fn with_pattern(&mut self, pattern: String) {
288 self.pattern = pattern;
289 self.update_flags_from_pattern();
290 self.update_placeholders();
291 }
292
293 fn with_format(&mut self, format_fn: FormatFn) {
294 self.format_fn = Some(format_fn);
295 }
296
297 fn box_clone(&self) -> Box<dyn FormatterTrait + Send + Sync> {
298 Box::new(self.clone())
299 }
300}
301
302#[cfg(test)]
303mod tests {
304 use super::*;
305
306 #[test]
307 fn test_template_formatter_default() {
308 let formatter = TemplateFormatter::default();
309 let record = Record::new(
310 LogLevel::Info,
311 "Test message",
312 Some("test".to_string()),
313 Some("test.rs".to_string()),
314 Some(42),
315 );
316
317 let formatted = formatter.fmt(&record);
318 assert!(formatted.contains("Test message"));
319 assert!(formatted.contains("INFO"));
320 assert!(formatted.contains("test"));
321 assert!(formatted.contains("test.rs:42"));
322 }
323
324 #[test]
325 fn test_template_formatter_no_colors() {
326 let formatter = TemplateFormatter::default().with_colors(false);
327 let record = Record::new(
328 LogLevel::Info,
329 "Test message",
330 Some("test".to_string()),
331 Some("test.rs".to_string()),
332 Some(42),
333 );
334
335 let formatted = formatter.fmt(&record);
336 assert!(formatted.contains("Test message"));
337 assert!(formatted.contains("INFO"));
338 assert!(formatted.contains("test"));
339 assert!(formatted.contains("test.rs:42"));
340 assert!(!formatted.contains("\x1b["));
341 }
342
343 #[test]
344 fn test_template_formatter_no_timestamp() {
345 let formatter = TemplateFormatter::default().with_timestamp(false);
346 let record = Record::new(
347 LogLevel::Info,
348 "Test message",
349 Some("test".to_string()),
350 Some("test.rs".to_string()),
351 Some(42),
352 );
353
354 let formatted = formatter.fmt(&record);
355 assert!(formatted.contains("Test message"));
356 assert!(formatted.contains("INFO"));
357 assert!(formatted.contains("test"));
358 assert!(formatted.contains("test.rs:42"));
359 assert!(!formatted.contains("2023")); }
361
362 #[test]
363 fn test_template_formatter_no_level() {
364 let formatter = TemplateFormatter::default().with_level(false);
365 let record = Record::new(
366 LogLevel::Info,
367 "Test message",
368 Some("test".to_string()),
369 Some("test.rs".to_string()),
370 Some(42),
371 );
372
373 let formatted = formatter.fmt(&record);
374 assert!(formatted.contains("Test message"));
375 assert!(!formatted.contains("INFO"));
376 assert!(formatted.contains("test"));
377 assert!(formatted.contains("test.rs:42"));
378 }
379
380 #[test]
381 fn test_template_formatter_no_module() {
382 let formatter = TemplateFormatter::default().with_module(false);
383 let record = Record::new(
384 LogLevel::Info,
385 "Test message",
386 Some("test_module".to_string()),
387 Some("test.rs".to_string()),
388 Some(42),
389 );
390
391 let formatted = formatter.fmt(&record);
392 assert!(formatted.contains("Test message"));
393 assert!(formatted.contains("INFO"));
394 assert!(!formatted.contains("test_module"));
395 assert!(formatted.contains("test.rs:42"));
396 }
397
398 #[test]
399 fn test_template_formatter_no_location() {
400 let formatter = TemplateFormatter::default().with_location(false);
401 let record = Record::new(
402 LogLevel::Info,
403 "Test message",
404 Some("test".to_string()),
405 Some("test.rs".to_string()),
406 Some(42),
407 );
408
409 let formatted = formatter.fmt(&record);
410 assert!(formatted.contains("Test message"));
411 assert!(formatted.contains("INFO"));
412 assert!(formatted.contains("test"));
413 assert!(!formatted.contains("test.rs:42"));
414 }
415
416 #[test]
417 fn test_template_formatter_custom_format() {
418 let formatter = TemplateFormatter::default()
419 .with_format(|record: &Record| format!("CUSTOM: {}", record.message()));
420 let record = Record::new(
421 LogLevel::Info,
422 "Test message",
423 Some("test_module".to_string()),
424 Some("test.rs".to_string()),
425 Some(42),
426 );
427
428 let formatted = formatter.fmt(&record);
429 assert_eq!(formatted, "CUSTOM: Test message");
430 }
431}