custom_utils/util_logger/
builder.rs

1use ansi_term::{Color, Style};
2use anyhow::Result;
3use flexi_logger::writers::LogWriter;
4use flexi_logger::{Age, Duplicate, FlexiLoggerError, LogSpecification};
5use flexi_logger::{Cleanup, Criterion, FileSpec, Naming};
6use flexi_logger::{
7    DeferredNow, FormatFunction, LevelFilter, LogSpecBuilder, Logger, LoggerHandle, Record,
8    WriteMode,
9};
10use std::path::Path;
11use std::path::PathBuf;
12use std::str::FromStr;
13use std::thread;
14// #[cfg(feature = "prod")]
15const TS_DASHES_BLANK_COLONS_DOT_BLANK: &str = "%m-%d %H:%M:%S%.3f";
16
17const HOUR_MINUTER_SECONDE: &str = "%H:%M:%S%.3f";
18
19#[allow(dead_code)]
20fn with_thread(
21    w: &mut dyn std::io::Write,
22    now: &mut DeferredNow,
23    record: &Record,
24) -> Result<(), std::io::Error> {
25    let level = record.level();
26    write!(
27        w,
28        "[{}][{}][{:5}][{}:{}] {}",
29        now.format(TS_DASHES_BLANK_COLONS_DOT_BLANK),
30        thread::current().name().unwrap_or("<unnamed>"),
31        level.to_string(),
32        record.target(),
33        record.line().unwrap_or(0),
34        &record.args()
35    )
36}
37#[allow(dead_code)]
38pub fn colored_with_thread(
39    w: &mut dyn std::io::Write,
40    now: &mut DeferredNow,
41    record: &Record,
42) -> Result<(), std::io::Error> {
43    let level = record.level();
44    write!(
45        w,
46        "{}",
47        format_args!(
48            "[{}][{}][{:5}][{}:{}] {}",
49            now.format(TS_DASHES_BLANK_COLONS_DOT_BLANK),
50            thread::current().name().unwrap_or("<unnamed>"),
51            style(level).paint(format_args!("{:6}", level.to_string()).to_string()),
52            record.target(),
53            record.line().unwrap_or(0),
54            &record.args()
55        )
56    )
57}
58
59#[allow(dead_code)]
60pub fn colored_with_thread_target(
61    w: &mut dyn std::io::Write,
62    now: &mut DeferredNow,
63    record: &Record,
64) -> Result<(), std::io::Error> {
65    let level = record.level();
66    write!(
67        w,
68        "{}",
69        format_args!(
70            "[{}][{}][{:5}][{}:{}] {}",
71            now.format(TS_DASHES_BLANK_COLONS_DOT_BLANK),
72            thread::current().name().unwrap_or("<unnamed>"),
73            style(level).paint(format_args!("{:6}", level.to_string()).to_string()),
74            record.target(),
75            record.line().unwrap_or(0),
76            &record.args()
77        )
78    )
79}
80
81#[allow(dead_code)]
82pub fn simple_colored_with_thread_target(
83    w: &mut dyn std::io::Write,
84    now: &mut DeferredNow,
85    record: &Record,
86) -> Result<(), std::io::Error> {
87    let level = record.level();
88    write!(
89        w,
90        "{}",
91        format_args!(
92            "[{}][{}][{:5}][{}:{}] {}",
93            now.format(HOUR_MINUTER_SECONDE),
94            thread::current().name().unwrap_or("<unnamed>"),
95            style(level).paint(format_args!("{:6}", level.to_string()).to_string()),
96            record.target(),
97            record.line().unwrap_or(0),
98            &record.args()
99        )
100    )
101}
102
103pub struct LoggerBuilder {
104    display_target: bool,
105    log_spec_builder: LogSpecBuilder,
106}
107impl LoggerBuilder {
108    pub fn default(level: LevelFilter) -> Self {
109        let mut log_spec_builder = LogSpecBuilder::new();
110        log_spec_builder.default(level);
111        Self {
112            log_spec_builder,
113            display_target: false,
114        }
115    }
116    pub fn module<M: AsRef<str>>(mut self, module_name: M, lf: LevelFilter) -> Self {
117        self.log_spec_builder.module(module_name, lf);
118        self
119    }
120    pub fn build_default(self) -> LoggerBuilder2 {
121        LoggerBuilder2 {
122            logger: Logger::with(self.log_spec_builder.build())
123                .format(if self.display_target {
124                    colored_with_thread_target
125                } else {
126                    colored_with_thread
127                })
128                .write_mode(WriteMode::Direct),
129        }
130    }
131
132    pub fn log_to_stdout(self) {
133        Logger::with(self.log_spec_builder.build())
134            .format(if self.display_target {
135                colored_with_thread_target
136            } else {
137                colored_with_thread
138            })
139            .write_mode(WriteMode::Direct)
140            .log_to_stdout()
141            .start()
142            .unwrap();
143    }
144
145    pub fn build_with(self, format: FormatFunction, write_mode: WriteMode) -> LoggerBuilder2 {
146        LoggerBuilder2 {
147            logger: Logger::with(self.log_spec_builder.build())
148                .format(format)
149                .write_mode(write_mode),
150        }
151    }
152}
153pub struct LoggerBuilder2 {
154    logger: Logger,
155}
156pub struct LoggerBuilder3 {
157    logger: Logger,
158}
159impl LoggerBuilder3 {
160    #[must_use]
161    pub fn start(self) -> LoggerHandle {
162        self.logger.start().unwrap()
163    }
164    pub fn _start(self) -> Result<LoggerHandle> {
165        Ok(self.logger.start()?)
166    }
167
168    #[must_use]
169    pub fn start_with_specfile(self, p: impl AsRef<Path>) -> LoggerHandle {
170        self.logger.start_with_specfile(p).unwrap()
171    }
172    #[must_use]
173    pub fn start_with_specfile_default(self, app: &str) -> LoggerHandle {
174        let path = PathBuf::from_str("/var/local/etc/")
175            .unwrap()
176            .join(app)
177            .join("logspecification.toml");
178        self.logger.start_with_specfile(path).unwrap()
179    }
180}
181impl LoggerBuilder2 {
182    pub fn log_to_stdout(self) -> LoggerBuilder3 {
183        LoggerBuilder3 {
184            logger: self.logger.log_to_stdout(),
185        }
186    }
187    pub fn log_to_file_default(self, app: &str) -> LoggerBuilder3 {
188        let fs_path = PathBuf::from_str("/var/local/log").unwrap().join(app);
189        let fs = FileSpec::default()
190            .directory(fs_path)
191            .basename(app)
192            .suffix("log");
193        // 若为true,则会覆盖rotate中的数字、keep^
194        self.log_to_file(
195            fs,
196            Criterion::AgeOrSize(Age::Day, 10_000_000),
197            Naming::Numbers,
198            Cleanup::KeepLogFiles(10),
199            true,
200        )
201    }
202    pub fn log_to_writer(self, w: Box<dyn LogWriter>) -> LoggerBuilder3 {
203        LoggerBuilder3 {
204            logger: self.logger.log_to_writer(w),
205        }
206    }
207    pub fn log_to_file(
208        self,
209        fs: FileSpec,
210        criterion: Criterion,
211        naming: Naming,
212        cleanup: Cleanup,
213        append: bool,
214    ) -> LoggerBuilder3 {
215        LoggerBuilder3 {
216            logger: self
217                .logger
218                .log_to_file(fs)
219                .o_append(append)
220                .rotate(criterion, naming, cleanup),
221        }
222    }
223}
224#[allow(dead_code)]
225pub struct LoggerFeatureBuilder {
226    _app: String,
227    _debug_level: DebugLevel,
228    _prod_level: LevelFilter,
229    fs: FileSpec,
230    criterion: Criterion,
231    naming: Naming,
232    cleanup: Cleanup,
233    append: bool,
234    modules: Vec<(String, LevelFilter)>,
235    writer: Option<Box<dyn LogWriter>>,
236    log_etc_path: PathBuf,
237}
238impl LoggerFeatureBuilder {
239    pub fn default(
240        app: &str,
241        _debug_level: DebugLevel,
242        prod_level: LevelFilter,
243        log_etc_path: PathBuf,
244        log_path: PathBuf,
245    ) -> Self {
246        // let fs_path = PathBuf::from_str("/var/local/log").unwrap().join(app);
247        let fs = FileSpec::default()
248            .directory(log_path)
249            .basename(app)
250            .suffix("log");
251        // 若为true,则会覆盖rotate中的数字、keep^
252        let criterion = Criterion::AgeOrSize(Age::Day, 10_000_000);
253        let naming = Naming::Numbers;
254        let cleanup = Cleanup::KeepLogFiles(10);
255        let append = true;
256        Self {
257            _app: app.to_string(),
258            _debug_level,
259            _prod_level: prod_level,
260            fs,
261            criterion,
262            naming,
263            cleanup,
264            append,
265            modules: Vec::new(),
266            writer: None,
267            log_etc_path,
268        }
269    }
270    pub fn module<M: AsRef<str>>(mut self, module_name: M, lf: LevelFilter) -> Self {
271        self.modules.push((module_name.as_ref().to_owned(), lf));
272        self
273    }
274    pub fn log_to_write(mut self, w: Box<dyn LogWriter>) -> Self {
275        self.writer = Some(w);
276        self
277    }
278    pub fn config(
279        mut self,
280        fs: FileSpec,
281        criterion: Criterion,
282        naming: Naming,
283        cleanup: Cleanup,
284        append: bool,
285    ) -> Self {
286        self.fs = fs;
287        self.criterion = criterion;
288        self.naming = naming;
289        self.cleanup = cleanup;
290        self.append = append;
291        self
292    }
293    #[cfg(feature = "prod")]
294    #[must_use]
295    pub fn build(self) -> LoggerHandle {
296        let mut log_spec_builder = LogSpecBuilder::new();
297        log_spec_builder.default(self._prod_level);
298        for (module, level) in self.modules {
299            log_spec_builder.module(module, level);
300        }
301        let path = self.log_etc_path.join(format!("{}.toml", self._app));
302        if let Some(w) = self.writer {
303            Logger::with(log_spec_builder.build())
304                .format(with_thread)
305                .write_mode(WriteMode::Direct)
306                .log_to_file_and_writer(self.fs, w)
307                .o_append(self.append)
308                .rotate(self.criterion, self.naming, self.cleanup)
309                .start_with_specfile(path)
310                .unwrap()
311        } else {
312            Logger::with(log_spec_builder.build())
313                .format(with_thread)
314                .write_mode(WriteMode::Direct)
315                .log_to_file(self.fs)
316                .o_append(self.append)
317                .rotate(self.criterion, self.naming, self.cleanup)
318                .start_with_specfile(path)
319                .unwrap()
320        }
321    }
322    #[cfg(not(feature = "prod"))]
323    #[must_use]
324    pub fn build(self) -> LoggerHandle {
325        let specification = match self._debug_level {
326            DebugLevel::Filter(debug_level) => {
327                let mut log_spec_builder = LogSpecBuilder::new();
328                log_spec_builder.default(debug_level);
329                for (module, level) in self.modules {
330                    log_spec_builder.module(module, level);
331                }
332                log_spec_builder.build()
333            }
334            DebugLevel::Env(default) => {
335                if let Some(env_val) =
336                    std::env::vars().find_map(|x| if x.0 == self._app { Some(x.1) } else { None })
337                {
338                    match LogSpecification::env_or_parse(env_val) {
339                        Ok(rs) => rs,
340                        Err(_) => LogSpecification::env_or_parse(default).unwrap(),
341                    }
342                } else {
343                    LogSpecification::env_or_parse(default).unwrap()
344                }
345            }
346        };
347        if let Some(w) = self.writer {
348            LoggerBuilder2 {
349                logger: Logger::with(specification)
350                    .format(colored_with_thread)
351                    .write_mode(WriteMode::Direct)
352                    .duplicate_to_stdout(Duplicate::All),
353            }
354            .log_to_writer(w)
355            .start()
356        } else {
357            LoggerBuilder2 {
358                logger: Logger::with(specification)
359                    .format(colored_with_thread)
360                    .write_mode(WriteMode::Direct),
361            }
362            .log_to_stdout()
363            .start()
364        }
365    }
366}
367
368lazy_static::lazy_static! {
369    static ref MY_PALETTE: std::sync::RwLock<Palette> = std::sync::RwLock::new(Palette::default());
370}
371pub fn style(level: log::Level) -> Style {
372    let palette = &*(MY_PALETTE.read().unwrap());
373    match level {
374        log::Level::Error => palette.error,
375        log::Level::Warn => palette.warn,
376        log::Level::Info => palette.info,
377        log::Level::Debug => palette.debug,
378        log::Level::Trace => palette.trace,
379    }
380}
381
382#[derive(Debug)]
383struct Palette {
384    pub error: Style,
385    pub warn: Style,
386    pub info: Style,
387    pub debug: Style,
388    pub trace: Style,
389}
390impl Palette {
391    fn default() -> Palette {
392        Palette {
393            error: Style::default().fg(Color::Red).bold(),
394            warn: Style::default().fg(Color::Yellow).bold(),
395            info: Style::default(),
396            debug: Style::default().fg(Color::Fixed(28)),
397            trace: Style::default().fg(Color::Fixed(8)),
398        }
399    }
400}
401
402pub enum DebugLevel {
403    Filter(LevelFilter),
404    Env(String),
405}
406
407impl From<LevelFilter> for DebugLevel {
408    fn from(value: LevelFilter) -> Self {
409        Self::Filter(value)
410    }
411}
412impl From<&str> for DebugLevel {
413    fn from(value: &str) -> Self {
414        Self::Env(value.to_string())
415    }
416}