Skip to main content

poly_logger/
log_formatter.rs

1use strfmt::strfmt;
2use std::collections::HashMap;
3
4pub struct LogFormatter {
5    // strftime format string
6    timestamp_format: &'static str,
7
8    // e.g. [{timestamp}] {level} [{path}] - {msg}
9    msg_format: &'static str,
10
11    // Flag to indicate we need to do more expensive
12    // formatting with strfmt
13    use_strfmt: bool,
14}
15
16// NOTE: Using default error type
17type MsgResult = Result<String, strfmt::FmtError>;
18
19impl Clone for LogFormatter {
20    fn clone(&self) -> LogFormatter {
21        LogFormatter {
22            timestamp_format: self.timestamp_format.clone(),
23            msg_format: self.msg_format.clone(),
24            use_strfmt: self.use_strfmt,
25        }
26    }
27}
28
29impl LogFormatter {
30    pub fn new() -> Self {
31        LogFormatter {
32            timestamp_format: "%+",
33            msg_format: "",
34            use_strfmt: false,
35        }
36    }
37
38    // Set format options
39    pub fn timestamp_format(&mut self, format: &'static str) -> &mut Self {
40        self.timestamp_format = format;
41        self
42    }
43
44    pub fn msg_format(&mut self, format: &'static str) -> &mut Self {
45        // Using custom format
46        self.use_strfmt = true;
47        self.msg_format = format;
48        self
49    }
50
51    // Format value accessors
52    pub fn msg(&self, record: &log::Record) -> MsgResult {
53        // NOTE - Use strfmt only if custom message
54        // as it's more expensive. 
55        // Future option: We could add various
56        // canned defaults for performance reasons
57        match self.use_strfmt {
58            false => {
59                Ok(self.default_msg(record))
60            },
61            true => {
62                self.custom_msg(record)
63            },
64        }
65    }
66
67    pub fn default_msg(&self, record: &log::Record) -> String {
68        format!(
69            "[{timestamp}] {level} [{file}:{line}] {args}", 
70            timestamp=self.timestamp(),
71            level=record.metadata().level(), 
72            file=self.file(record),
73            line=self.line(record),
74            args=record.args())
75    }
76
77    fn custom_msg(&self, record: &log::Record) -> MsgResult {
78        let mut vars = HashMap::new();
79        vars.insert("timestamp".to_string(), self.timestamp());
80        vars.insert("level".to_string(), 
81                    record.metadata().level().to_string());
82        vars.insert("file".to_string(), self.file(record));
83        vars.insert("line".to_string(), self.line(record).to_string());
84        vars.insert("args".to_string(), record.args().to_string());
85        strfmt(self.msg_format, &vars)
86    }
87
88    fn timestamp(&self) -> String {
89        match &self.timestamp_format {
90            &"" => {
91                "".to_string()
92            },
93            f => {
94                // Note that we might want to separate the 
95                // timestamping of a message with the formatting of the
96                // timestamp, especially if we move to a producer/consumer
97                // queue
98                let now = chrono::Local::now();
99                now.format(&f).to_string()
100            }
101        }
102    }
103
104    fn line(&self, record: &log::Record) -> u32 {
105        match record.line() {
106            Some(l) => l,
107            None => 0,
108        }
109    }
110
111    fn file(&self, record: &log::Record) -> String {
112        match record.file() {
113            Some(f) => f.to_string(),
114            None => "<no_file>".to_string(),
115        }
116    }
117}