toolkit_rs/
logger.rs

1use anyhow::Result;
2use flexi_logger::{
3    Age, Cleanup, Criterion, DeferredNow, Duplicate, FileSpec, LogSpecification, Logger, Naming,
4    Record,
5};
6use log::LevelFilter;
7use serde::{Deserialize, Serialize};
8const TIME_FORMAT: &str = "%Y.%m.%d %H:%M:%S%.3f";
9#[derive(Clone, Debug, Serialize, Deserialize)]
10pub enum LogStyle {
11    Default,
12    Line,
13    Module,
14    Full,
15}
16
17#[derive(Clone, Debug, Serialize, Deserialize)]
18pub struct LogConfig {
19    //等级
20    #[serde(default = "default_level")]
21    pub level: u8,
22    //大小 MB
23    #[serde(default = "default_usize")]
24    pub size_mb: u64,
25    //是否打印到控制台
26    #[serde(default = "default_console")]
27    pub console: bool,
28    // 日志输出目录
29    #[serde(default = "default_log_dir")]
30    pub dir: String,
31    // 日志保留文件数
32    #[serde(default = "default_keep_day")]
33    pub keep_day: usize,
34    // 过滤日志模块
35    pub filters: Option<Vec<String>>,
36    // 日志文件名
37    pub log_name: Option<String>,
38    // 日志风格
39    pub style: LogStyle,
40    // 清理同步
41    pub cleanup_sync: Option<bool>,
42}
43
44fn default_usize() -> u64 {
45    3
46}
47fn default_console() -> bool {
48    true
49}
50fn default_keep_day() -> usize {
51    3
52}
53fn default_level() -> u8 {
54    4
55}
56fn default_log_dir() -> String {
57    "./logs".to_string()
58}
59
60impl Default for LogConfig {
61    fn default() -> Self {
62        Self {
63            level: default_level(),
64            size_mb: default_usize(),
65            console: default_console(),
66            dir: default_log_dir(),
67            keep_day: default_keep_day(),
68            filters: None,
69            log_name: None,
70            style: LogStyle::Default,
71            cleanup_sync: Some(false),
72        }
73    }
74}
75
76pub fn string_to_level(level: &str) -> u8 {
77    let v = match level {
78        "trace" | "TRACE" => 5,
79        "debug" | "DEBUG" => 4,
80        "info" | "INFO" => 3,
81        "warn" | "WARN" => 2,
82        "error" | "ERROR" => 1,
83        _ => 4,
84    };
85    v
86}
87
88pub fn _default(
89    w: &mut dyn std::io::Write,
90    now: &mut DeferredNow,
91    record: &Record,
92) -> Result<(), std::io::Error> {
93    if record.level() == LevelFilter::Error || record.level() == LevelFilter::Warn {
94        write!(
95            w,
96            "{} [{}] {}:{}: ",
97            now.format(TIME_FORMAT),
98            record.level(),
99            record.file().unwrap_or("<unnamed>"),
100            record.line().unwrap_or(0),
101        )?;
102    } else {
103        write!(w, "{} [{}]: ", now.format(TIME_FORMAT), record.level(),)?;
104    }
105    write!(w, "{}", &record.args())
106}
107
108pub fn _line(
109    w: &mut dyn std::io::Write,
110    now: &mut DeferredNow,
111    record: &Record,
112) -> Result<(), std::io::Error> {
113    write!(
114        w,
115        "{} [{}] {}:{}: ",
116        now.format(TIME_FORMAT),
117        record.level(),
118        record.file().unwrap_or("<unnamed>"),
119        record.line().unwrap_or(0),
120    )?;
121    write!(w, "{}", &record.args())
122}
123
124pub fn _module(
125    w: &mut dyn std::io::Write,
126    now: &mut DeferredNow,
127    record: &Record,
128) -> Result<(), std::io::Error> {
129    write!(
130        w,
131        "{} [{}] [{}]: ",
132        now.format(TIME_FORMAT),
133        record.level(),
134        record.module_path().unwrap_or("<unnamed>"),
135    )?;
136    write!(w, "{}", &record.args())
137}
138
139pub fn _full(
140    w: &mut dyn std::io::Write,
141    now: &mut DeferredNow,
142    record: &Record,
143) -> Result<(), std::io::Error> {
144    write!(
145        w,
146        "{} [{}] [{}] {}:{}: ",
147        now.format(TIME_FORMAT),
148        record.level(),
149        record.module_path().unwrap_or("<unnamed>"),
150        record.file().unwrap_or("<unnamed>"),
151        record.line().unwrap_or(0),
152    )?;
153    write!(w, "{}", &record.args())
154}
155pub fn setup(cfg: LogConfig) -> Result<()> {
156    let level = match cfg.level {
157        1 => LevelFilter::Error,
158        2 => LevelFilter::Warn,
159        3 => LevelFilter::Info,
160        4 => LevelFilter::Debug,
161        5 => LevelFilter::Trace,
162        _ => LevelFilter::Debug,
163    };
164
165    let mut log_spec = LogSpecification::builder();
166    log_spec.default(level);
167    if let Some(items) = &cfg.filters {
168        for m in items {
169            log_spec.module(m, LevelFilter::Warn);
170        }
171    }
172
173    let hand = Logger::with(log_spec.build());
174
175    //日志风格
176    let hand = match cfg.style {
177        LogStyle::Line => hand.format(_line),
178        LogStyle::Module => hand.format(_module),
179        LogStyle::Full => hand.format(_full),
180        _ => hand.format(_default),
181    };
182
183    let basename = match &cfg.log_name {
184        Some(name) => name,
185        None => "",
186    };
187    let filespec = FileSpec::default().directory(cfg.dir).basename(basename);
188
189    let mut hand = hand
190        .rotate(
191            Criterion::AgeOrSize(Age::Day, cfg.size_mb * 1024 * 1024),
192            Naming::TimestampsCustomFormat {
193                current_infix: None,
194                format: "r%Y-%m-%d_%H-%M-%S",
195            },
196            Cleanup::KeepForDays(cfg.keep_day),
197        )
198        .cleanup_in_background_thread(cfg.cleanup_sync.unwrap_or(false))
199        .log_to_file(filespec);
200
201    if cfg.console {
202        let d = match cfg.level {
203            1 => Duplicate::Error,
204            2 => Duplicate::Warn,
205            3 => Duplicate::Info,
206            4 => Duplicate::Debug,
207            5 => Duplicate::Trace,
208            _ => Duplicate::Debug,
209        };
210        hand = hand.duplicate_to_stdout(d)
211    }
212    hand.start()?;
213
214    Ok(())
215}