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