Skip to main content

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    // 日志保留文件数
33    pub keep_day: Option<usize>,
34    // 保留文件数
35    pub keep_file_count: Option<usize>,
36
37    // 过滤日志模块
38    pub filters: Option<Vec<String>>,
39    // 日志文件名
40    pub log_name: Option<String>,
41    // 日志风格
42    pub style: LogStyle,
43    // 清理同步
44    pub cleanup_sync: Option<bool>,
45}
46
47fn default_usize() -> u64 {
48    3
49}
50fn default_console() -> bool {
51    true
52}
53
54fn default_level() -> u8 {
55    4
56}
57fn default_log_dir() -> String {
58    "./logs".to_string()
59}
60
61impl LogConfig {
62    pub fn to_level(&self) -> LevelFilter {
63        let level = match self.level {
64            1 => LevelFilter::Error,
65            2 => LevelFilter::Warn,
66            3 => LevelFilter::Info,
67            4 => LevelFilter::Debug,
68            5 => LevelFilter::Trace,
69            _ => LevelFilter::Debug,
70        };
71        level
72    }
73}
74
75impl Default for LogConfig {
76    fn default() -> Self {
77        Self {
78            level: default_level(),
79            size_mb: default_usize(),
80            console: default_console(),
81            dir: default_log_dir(),
82            keep_day: None,
83            keep_file_count: Some(3),
84            filters: None,
85            log_name: None,
86            style: LogStyle::Default,
87            cleanup_sync: Some(false),
88        }
89    }
90}
91
92pub fn string_to_level(level: &str) -> u8 {
93    let v = match level {
94        "trace" | "TRACE" => 5,
95        "debug" | "DEBUG" => 4,
96        "info" | "INFO" => 3,
97        "warn" | "WARN" => 2,
98        "error" | "ERROR" => 1,
99        _ => 4,
100    };
101    v
102}
103
104pub fn _default(
105    w: &mut dyn std::io::Write,
106    now: &mut DeferredNow,
107    record: &Record,
108) -> Result<(), std::io::Error> {
109    if record.level() == LevelFilter::Error || record.level() == LevelFilter::Warn {
110        write!(
111            w,
112            "{} [{}] {}:{}: ",
113            now.format(TIME_FORMAT),
114            record.level(),
115            record.file().unwrap_or("<unnamed>"),
116            record.line().unwrap_or(0),
117        )?;
118    } else {
119        write!(w, "{} [{}]: ", now.format(TIME_FORMAT), record.level(),)?;
120    }
121    write!(w, "{}", &record.args())
122}
123
124pub fn _line(
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.file().unwrap_or("<unnamed>"),
135        record.line().unwrap_or(0),
136    )?;
137    write!(w, "{}", &record.args())
138}
139
140pub fn _module(
141    w: &mut dyn std::io::Write,
142    now: &mut DeferredNow,
143    record: &Record,
144) -> Result<(), std::io::Error> {
145    write!(
146        w,
147        "{} [{}] [{}]: ",
148        now.format(TIME_FORMAT),
149        record.level(),
150        record.module_path().unwrap_or("<unnamed>"),
151    )?;
152    write!(w, "{}", &record.args())
153}
154
155pub fn _full(
156    w: &mut dyn std::io::Write,
157    now: &mut DeferredNow,
158    record: &Record,
159) -> Result<(), std::io::Error> {
160    write!(
161        w,
162        "{} [{}] [{}] {}:{}: ",
163        now.format(TIME_FORMAT),
164        record.level(),
165        record.module_path().unwrap_or("<unnamed>"),
166        record.file().unwrap_or("<unnamed>"),
167        record.line().unwrap_or(0),
168    )?;
169    write!(w, "{}", &record.args())
170}
171
172// 初始化日志
173pub fn setup(cfg: LogConfig) -> Result<()> {
174    let level = match cfg.level {
175        1 => LevelFilter::Error,
176        2 => LevelFilter::Warn,
177        3 => LevelFilter::Info,
178        4 => LevelFilter::Debug,
179        5 => LevelFilter::Trace,
180        _ => LevelFilter::Debug,
181    };
182
183    let mut log_spec = LogSpecification::builder();
184    log_spec.default(level);
185    if let Some(items) = &cfg.filters {
186        for m in items {
187            log_spec.module(m, LevelFilter::Warn);
188        }
189    }
190
191    let hand = Logger::with(log_spec.build());
192
193    //日志风格
194    let hand = match cfg.style {
195        LogStyle::Line => hand.format(_line),
196        LogStyle::Module => hand.format(_module),
197        LogStyle::Full => hand.format(_full),
198        _ => hand.format(_default),
199    };
200
201    let basename = match &cfg.log_name {
202        Some(name) => name,
203        None => "",
204    };
205    let filespec = FileSpec::default().directory(cfg.dir).basename(basename);
206
207    let mut crit = Criterion::Size(cfg.size_mb * 1024 * 1024);
208    let mut cleanup = Cleanup::KeepLogFiles(cfg.keep_file_count.unwrap_or(3));
209    let mut naming = Naming::Numbers;
210    if let Some(v) = cfg.keep_day {
211        crit = Criterion::AgeOrSize(Age::Day, cfg.size_mb * 1024 * 1024);
212        cleanup = Cleanup::KeepForDays(v);
213        naming = Naming::TimestampsCustomFormat {
214            current_infix: Some("rCURRENT"),
215            format: "r%Y-%m-%d_%H-%M-%S",
216        };
217    }
218
219    let mut hand = hand
220        .rotate(crit, naming, cleanup)
221        .cleanup_in_background_thread(cfg.cleanup_sync.unwrap_or(false))
222        .log_to_file(filespec);
223
224    if cfg.console {
225        let d = match cfg.level {
226            1 => Duplicate::Error,
227            2 => Duplicate::Warn,
228            3 => Duplicate::Info,
229            4 => Duplicate::Debug,
230            5 => Duplicate::Trace,
231            _ => Duplicate::Debug,
232        };
233        hand = hand.duplicate_to_stdout(d)
234    }
235    hand.start()?;
236
237    Ok(())
238}