captains_log/
formatter.rs

1use std::*;
2
3use log::{kv::Key, *};
4
5use crate::time::Timer;
6
7pub type FormatFunc = fn(FormatRecord) -> String;
8
9#[derive(Clone)]
10/// Custom formatter which adds into a log sink
11pub struct LogFormat
12{
13    time_fmt: String,
14    format_fn: FormatFunc,
15}
16
17impl LogFormat {
18
19    /// # Arguments
20    ///
21    /// time_fmt: refer to chrono::format::strftime.
22    ///
23    /// format_fn:
24    /// Since std::fmt only support compile time format,
25    /// you have to write a static function to format the log line
26    ///
27    /// # Example
28    /// ```
29    /// use captains_log::{LogFile, LogFormat, FormatRecord};
30    /// fn format_f(r: FormatRecord) -> String {
31    ///     let time = r.time();
32    ///     let level = r.level();
33    ///     let msg = r.msg();
34    ///     let req_id = r.key("req_id");
35    ///     format!("[{time}][{level}] {msg}{req_id}\n").to_string()
36    /// }
37    /// let log_format = LogFormat::new("%Y-%m-%d %H:%M:%S%.6f", format_f);
38    /// let log_sink = LogFile::new("/tmp", "test.log", log::Level::Info, log_format);
39    /// ```
40
41    pub fn new(time_fmt: &str, format_fn: FormatFunc) -> Self {
42        Self{
43            time_fmt: time_fmt.to_string(),
44            format_fn,
45        }
46    }
47
48    #[inline(always)]
49    pub fn process(&self, now: &Timer, record: &Record) -> String {
50        let time = TimeFormatter{
51            now, fmt_str: &self.time_fmt,
52        };
53        let r = FormatRecord{
54            record, time,
55        };
56        return (self.format_fn)(r);
57    }
58}
59
60pub struct TimeFormatter<'a> {
61    pub now: &'a Timer,
62    pub fmt_str: &'a String,
63}
64
65impl<'a> TimeFormatter<'a> {
66
67    #[inline(always)]
68    fn time_str(&self) -> String {
69        self.now.format(&self.fmt_str).to_string()
70    }
71}
72
73pub struct FormatRecord<'a> {
74    pub record: &'a Record<'a>,
75    pub time: TimeFormatter<'a>
76}
77
78impl<'a> FormatRecord<'a> {
79
80    #[inline(always)]
81    pub fn file(&self) -> &str {
82        basename(self.record.file().unwrap_or("<none>"))
83    }
84
85    #[inline(always)]
86    pub fn line(&self) -> u32 {
87        self.record.line().unwrap_or(0)
88    }
89
90    #[inline(always)]
91    pub fn time(&self) -> String {
92        self.time.time_str()
93    }
94
95    #[inline(always)]
96    pub fn key(&self, key: &str) -> String {
97        let source = self.record.key_values();
98        if let Some(v) = source.get(Key::from_str(key)) {
99            return format!(" ({})", v).to_string()
100        } else {
101            return "".to_string()
102        }
103    }
104
105    #[inline(always)]
106    pub fn level(&self) -> Level {
107        self.record.level()
108    }
109
110    #[inline(always)]
111    pub fn msg(&self) -> &'a fmt::Arguments<'a> {
112        self.record.args()
113    }
114}
115
116fn basename(path: &str) -> &str {
117    let res = path.rfind('/');
118    match res {
119        Some(idx) => path.get(idx + 1..).unwrap_or(path),
120        None => path,
121    }
122}