log_plus/
lib.rs

1use std::{
2    collections::{HashMap, VecDeque},
3    io::Write, mem::MaybeUninit, str::FromStr, sync::Mutex
4};
5
6use std::sync::RwLock;
7
8#[cfg(feature = "time")]
9use time::format_description::OwnedFormatItem;
10
11#[cfg(not(feature = "tokio"))]
12use std::{fs::File, io::{LineWriter, Stdout}, sync::mpsc::Sender};
13
14#[cfg(feature = "tokio")]
15use tokio::{
16    fs::File,
17    io::{AsyncWriteExt, BufWriter, Stdout},
18    sync::mpsc::{UnboundedReceiver, UnboundedSender, unbounded_channel}
19};
20
21const CACHE_STR_ARRAY_SIZE: usize = 16;
22const CACHE_STR_INIT_SIZE: usize = 256;
23
24static mut LOGGER_PLUS: MaybeUninit<LogPlus> = MaybeUninit::uninit();
25#[cfg(debug_assertions)]
26static mut INITED: bool = false;
27
28
29type Result<T> = std::result::Result<T, Box<dyn std::error::Error + Send + Sync>>;
30type BoxPlugin = Box<dyn std::io::Write + Send + Sync + 'static>;
31type BoxCustomFilter = Box<dyn CustomFilter>;
32type LevelFilter = RwLock<HashMap<String, log::LevelFilter>>;
33
34
35/// `Builder` is a struct that holds the configuration for the logger.
36///
37/// The `level` field is the minimum log level that will be logged. The `log_file` field is the name of
38/// the log file. The `log_file_max` field is the maximum size of the log file in megabytes. The
39/// `use_console` field is a boolean that indicates whether or not to log to the console. The
40/// `use_async` field is a boolean that indicates whether or not to use an asynchronous logger.
41///
42/// The `new()` method
43///
44/// Properties:
45///
46/// * `level`: The log level to use.
47/// * `log_file`: The name of the log file.
48/// * `log_file_max`: The maximum number of log files to keep.
49/// * `use_console`: If true, the logger will log to the console.
50/// * `use_async`: Whether to use the async logger or not.
51/// * `filter`: Use user-defined log filtering functions.
52/// * `plugin`: Use a user-defined log output plug-in.
53///
54/// # Examples
55///
56/// ```
57/// asnyclog::Builder::new()
58///     .level(log::LevelFilter::Debug)
59///     .log_file(String::from("./app.log"))
60///     .log_file_max(1024 * 1024)
61///     .use_console(true)
62///     .use_async(true)
63///     .filter(|r: &log::Record| r.target() != "gensql::sql_builder" || r.line().unwrap_or(0) != 72)
64///     .builder()?;
65/// ```
66pub struct Builder {
67    level:          log::LevelFilter,
68    log_file:       String,
69    log_file_max:   u32,
70    use_console:    bool,
71    use_async:      bool,
72    plugin:         Option<BoxPlugin>,
73    filter:         Option<BoxCustomFilter>,
74}
75
76/// 自定义过滤函数
77pub trait CustomFilter: Send + Sync + 'static {
78    fn enabled(&self, record: &log::Record) -> bool;
79}
80
81struct LogPlus {
82    level:          log::LevelFilter,               // 日志的有效级别,小于该级别的日志允许输出
83    #[cfg(feature = "time")]
84    dt_fmt:         OwnedFormatItem,                // 日志条目时间格式化样式
85    #[cfg(feature = "chrono")]
86    dt_fmt:         String,                         // 日志条目时间格式化样式
87    log_file:       String,                         // 日志文件名
88    max_size:       u32,                            // 日志文件允许的最大长度
89    level_filter:   LevelFilter,                    // 用户自定义的过滤目标->过滤级别映射
90    fmt_cache:      Mutex<VecDeque<Vec<u8>>>,       // 格式化日志条目时,从该处获取缓存变量存放最后格式化结果
91    #[cfg(feature = "tokio")]
92    msg_tx:         UnboundedSender<AsyncLogType>,  // 异步模式下,用于暂存等待写入日志文件的日志条目
93    filter:         Option<BoxCustomFilter>,        // 自定义日志过滤器,如果启用了自定义日志过滤器,则对象有值
94    #[cfg(not(feature = "tokio"))]
95    logger_data:    Mutex<LogData>,                 // 日志关联的动态变化的数据
96}
97
98struct LogData {
99    log_size:       u32,                            // 当前日志文件的大小,跟随写入新的日志内容而变化
100    #[cfg(feature = "tokio")]
101    use_file:       bool,                           // 是否输出到文件
102    #[cfg(feature = "tokio")]
103    console:        Option<BufWriter<Stdout>>,      // 控制台对象,如果启用了控制台输出,则对象有值
104    #[cfg(not(feature = "tokio"))]
105    console:        Option<LineWriter<Stdout>>,     // 控制台对象,如果启用了控制台输出,则对象有值
106    #[cfg(feature = "tokio")]
107    fileout:        Option<BufWriter<File>>,        // 文件对象,如果启用了文件输出,则对象有值
108    #[cfg(not(feature = "tokio"))]
109    fileout:        Option<LineWriter<File>>,       // 文件对象,如果启用了文件输出,则对象有值
110    #[cfg(not(feature = "tokio"))]
111    sender:         Option<Sender<AsyncLogType>>,   // 异步发送频道,如果启用了异步日志模式,则对象有值
112    plugin:         Option<BoxPlugin>,              // 插件对象,如果启用了插件输出,则对象有值
113}
114
115struct SkipAnsiColorIter<'a> {
116    data: &'a [u8],
117    pos: usize,
118    find_len: usize,
119}
120
121enum AsyncLogType {
122    Message(Vec<u8>),
123    Flush,
124}
125
126
127#[inline]
128pub fn init_log_simple(level: &str, log_file: String, log_file_max: &str,
129        use_console: bool, use_async: bool) -> Result<()> {
130    init_log_inner(parse_level(level)?, log_file, parse_size(log_file_max)?,
131    use_console, use_async, None, None)
132}
133
134/// It creates a new logger, initializes it, and then sets it as the global logger
135///
136/// Arguments:
137///
138/// * `level`: log level
139/// * `log_file`: The log file path. ignore if the value is empty
140/// * `log_file_max`: The maximum size of the log file, The units that can be used are k/m/g.
141/// * `use_console`: Whether to output to the console
142/// * `use_async`: Whether to use asynchronous logging, if true, the log will be written to the file in
143/// a separate thread, and the log will not be blocked.
144///
145/// Returns:
146///
147/// A Result<(), anyhow::error>
148///
149/// # Examples
150///
151/// ```
152/// asnyclog::init_log(log::LevelFilter::Debug, String::from("./app.log", 1024 * 1024, true, true)?;
153/// ````
154#[inline]
155pub fn init_log(level: log::LevelFilter, log_file: String, log_file_max: u32,
156        use_console: bool, use_async: bool) -> Result<()> {
157    init_log_inner(level, log_file, log_file_max, use_console, use_async, None, None)
158}
159
160#[inline]
161pub fn init_log_with_plugin<P>(level: log::LevelFilter, log_file: String,
162        log_file_max: u32, use_console: bool, use_async: bool,
163        plugin: P) -> Result<()>
164where
165    P: std::io::Write + Send + Sync + 'static,
166{
167    init_log_inner(level, log_file, log_file_max, use_console, use_async,
168        Some(Box::new(plugin)), None)
169}
170
171#[inline]
172pub fn init_log_with_filter(level: log::LevelFilter, log_file: String,
173        log_file_max: u32, use_console: bool, use_async: bool,
174        filter: impl CustomFilter) -> Result<()> {
175    init_log_inner(level, log_file, log_file_max, use_console, use_async,
176        None, Some(Box::new(filter)))
177}
178
179pub fn init_log_with_all<P>(level: log::LevelFilter, log_file: String,
180        log_file_max: u32, use_console: bool, use_async: bool,
181        plugin: P, filter: impl CustomFilter) -> Result<()>
182where
183    P: std::io::Write + Send + Sync + 'static,
184{
185    init_log_inner(level, log_file, log_file_max, use_console, use_async,
186        Some(Box::new(plugin)), Some(Box::new(filter)))
187}
188
189/// Set log level for target
190///
191/// Arguments:
192///
193/// * `target`: log target
194/// * `level`: The log level(off/error/warn/info/debug/trace)
195pub fn set_level(target: String, level: log::LevelFilter) {
196    if let Ok(mut f) = get_logger_plus().level_filter.write() {
197        f.insert(target, level);
198    }
199}
200
201/// It takes a string and returns a `Result` of a `log::LevelFilter`
202///
203/// Arguments:
204///
205/// * `level`: The log level(off/error/warn/info/debug/trace) to parse.
206///
207/// Returns:
208///
209/// A Result<log::LevelFilter>
210pub fn parse_level(level: &str) -> Result<log::LevelFilter> {
211    match log::LevelFilter::from_str(level) {
212        Ok(num) => Ok(num),
213        Err(_) => Err(format!("can't parse log level: {level}").into()),
214    }
215}
216
217/// It parses a string into a number, The units that can be used are k/m/g
218///
219/// Arguments:
220///
221/// * `size`: The size of the file to be generated(uints: k/m/g).
222///
223/// Returns:
224///
225/// A Result<u32, anyhow::Error>
226pub fn parse_size(size: &str) -> Result<u32> {
227    match size.parse() {
228        Ok(n) => Ok(n),
229        Err(_) => match size[..size.len() - 1].parse() {
230            Ok(n) => {
231                let s = size.as_bytes();
232                match s[s.len() - 1] {
233                    b'b' | b'B' => Ok(n),
234                    b'k' | b'K' => Ok(n * 1024),
235                    b'm' | b'M' => Ok(n * 1024 * 1024),
236                    b'g' | b'G' => Ok(n * 1024 * 1024 * 1024),
237                    _ => Err(format!("parse size error, unit is unknown: {size}").into()),
238                }
239            },
240            Err(e) => Err(e.into()),
241        }
242    }
243}
244
245
246#[cfg(not(feature = "tokio"))]
247impl LogPlus {
248    // 输出日志到控制台和文件
249    fn write(&self, msg: &[u8]) {
250        let mut logger_data = match self.logger_data.lock() {
251            Ok(v) => v,
252            Err(e) => {
253                eprint!("log mutex lock failed: {e:?}");
254                return;
255            }
256        };
257
258        // 如果启用了控制台输出,则写入控制台
259        if let Some(ref mut console) = logger_data.console {
260            console.write_all(msg).expect("write log to console fail");
261        }
262
263        // 判断日志长度是否到达最大限制,如果到了,需要备份当前日志文件并重新创建新的日志文件
264        if logger_data.log_size > self.max_size {
265            let mut log_file_closed = false;
266
267            // 如果启用了日志文件,刷新缓存并关闭日志文件
268            if let Some(ref mut fileout) = logger_data.fileout {
269                fileout.flush().expect("flush log file fail");
270                logger_data.fileout.take();
271                log_file_closed = true;
272            }
273
274            // 之所以把关闭文件和重新创建文件分开写,是因为rust限制了可变借用(fileout)只允许1次
275            if log_file_closed {
276                // 删除已有备份,并重命名现有文件为备份文件
277                let bak = format!("{}.bak", self.log_file);
278                std::fs::remove_file(&bak).unwrap_or_default();
279                std::fs::rename(&self.log_file, &bak).expect("backup log file fail");
280
281                let f = std::fs::OpenOptions::new()
282                        .write(true)
283                        .create(true)
284                        .open(&self.log_file)
285                        .expect("reopen log file fail");
286
287                logger_data.fileout = Some(LineWriter::new(f));
288                logger_data.log_size = 0;
289            }
290        }
291
292        if let Some(ref mut fileout) = logger_data.fileout {
293            let ws = write_text(fileout, msg).unwrap();
294            logger_data.log_size += ws as u32;
295        }
296
297        if let Some(plugin) = &mut logger_data.plugin {
298            plugin.write_all(msg).expect("write log to plugin fail");
299        }
300    }
301
302    // 刷新日志的控制台和文件缓存
303    fn flush_inner(&self) {
304        let mut logger_data = match self.logger_data.lock() {
305            Ok(v) => v,
306            Err(e) => {
307                eprint!("log mutex lock failed: {e:?}");
308                return;
309            }
310        };
311
312        if let Some(ref mut console) = logger_data.console {
313            console.flush().expect("flush log console error");
314        }
315
316        if let Some(ref mut fileout) = logger_data.fileout {
317            fileout.flush().expect("flush log file error");
318        }
319    }
320
321}
322
323
324impl log::Log for LogPlus {
325    fn enabled(&self, metadata: &log::Metadata) -> bool {
326        if metadata.level() <= self.level {
327            if let Ok(level_filters) = self.level_filter.read() {
328                let mut target = metadata.target();
329                while !target.is_empty() {
330                    if let Some(level) = level_filters.get(target) {
331                        return metadata.level() <= *level;
332                    }
333
334                    target = match target.rfind("::") {
335                        Some(rpos) => &target[..rpos],
336                        None => ""
337                    };
338                }
339                return true;
340            }
341        }
342        false
343    }
344
345    #[cfg(feature = "tokio")]
346    fn log(&self, record: &log::Record) {
347        if !self.enabled(record.metadata()) { return; }
348        if let Some(filter) = &self.filter {
349            if !filter.enabled(record) { return; }
350        }
351
352        #[cfg(feature = "time")]
353        unsafe { time::util::local_offset::set_soundness(time::util::local_offset::Soundness::Unsound); }
354        #[cfg(feature = "time")]
355        let now = time::OffsetDateTime::now_local().unwrap().format(&self.dt_fmt).unwrap();
356        #[cfg(feature = "chrono")]
357        let now = chrono::Local::now().format(&self.dt_fmt);
358
359        let mut msg = get_msg_from_cache();
360        let is_detail = self.level >= log::LevelFilter::Debug;
361        let log_level = record.level();
362
363        // 日志条目格式化
364        if is_detail {
365            write!(&mut msg, "[\x1b[36m{now}\x1b[0m] [{}{log_level:5}\x1b[0m]",
366                level_color(log_level),
367            ).unwrap();
368        } else {
369            write!(&mut msg, "[{now}] [{log_level:5}]").unwrap();
370        }
371        if let Some(task_id) = tokio::task::try_id() {
372            write!(&mut msg, " [TASK-{task_id}]").unwrap();
373        }
374        if is_detail {
375            write!(&mut msg, " [{}::{}]", record.target(), record.line().unwrap_or(0)).unwrap();
376        };
377        write!(&mut msg, " - {}\n", record.args()).unwrap();
378
379        // 将需要输出的日志条目加入队列尾部,并返回是否已有任务正在处理日志
380        if let Err(e) = get_logger_plus().msg_tx.send(AsyncLogType::Message(msg)) {
381            eprintln!("failed in log::Log.log, {e:?}");
382        }
383
384    }
385
386    #[cfg(not(feature = "tokio"))]
387    fn log(&self, record: &log::Record) {
388        if !self.enabled(record.metadata()) { return; }
389        if let Some(filter) = &self.filter {
390            if !filter.enabled(record) { return; }
391        }
392
393        #[cfg(feature = "time")]
394        unsafe { time::util::local_offset::set_soundness(time::util::local_offset::Soundness::Unsound); }
395        #[cfg(feature = "time")]
396        let now = time::OffsetDateTime::now_local().unwrap().format(&self.dt_fmt).unwrap();
397        #[cfg(feature = "chrono")]
398        let now = chrono::Local::now().format(&self.dt_fmt);
399
400        let mut msg = get_msg_from_cache();
401
402        // 日志条目格式化
403        if self.level >= log::LevelFilter::Debug {
404            write!(&mut msg, "[\x1b[36m{now}\x1b[0m] [{}{:5}\x1b[0m] [{}::{}] - {}\n",
405                    level_color(record.level()),
406                    record.level(),
407                    record.target(),
408                    record.line().unwrap_or(0),
409                    record.args()).unwrap();
410        } else {
411            write!(&mut msg, "[{now}] [{:5}] - {}\n", record.level(), record.args()).unwrap();
412        };
413
414        // 异步写入模式
415        match self.logger_data.lock() {
416            Ok(logger_data) => {
417                if let Some(ref sender) = logger_data.sender {
418                    // 采用独立的单线程写入日志的方式,向channel发送要写入的日志消息即可
419                    sender.send(AsyncLogType::Message(msg)).unwrap();
420                    return;
421                }
422            },
423            Err(e) => {
424                eprint!("log mutex lock failed: {e:?}");
425                return;
426            }
427        }
428
429        // 同步写入模式
430        self.write(&msg);
431        put_msg_to_cache(msg);
432    }
433
434    #[cfg(feature = "tokio")]
435    fn flush(&self) {
436        tokio::spawn(async move {
437            if let Err(e) = get_logger_plus().msg_tx.send(AsyncLogType::Flush) {
438                eprintln!("failed in log send to channel: {e:?}");
439            }
440        });
441    }
442
443    #[cfg(not(feature = "tokio"))]
444    fn flush(&self) {
445        if let Ok(logger_data) = self.logger_data.lock() {
446            if let Some(ref sender) = logger_data.sender {
447                if let Err(e) = sender.send(AsyncLogType::Flush) {
448                    eprint!("failed in log::flush: {e:?}");
449                }
450            } else {
451                drop(logger_data);
452                self.flush_inner();
453            }
454        }
455    }
456
457}
458
459
460impl Builder {
461    #[inline]
462    pub fn new() -> Self {
463        Self {
464            level:          log::LevelFilter::Info,
465            log_file:       String::new(),
466            log_file_max:   10 * 1024 * 1024,
467            use_console:    true,
468            use_async:      true,
469            plugin:         None,
470            filter:         None,
471        }
472    }
473
474    #[inline]
475    pub fn builder(self) -> Result<()> {
476        init_log_inner(self.level, self.log_file, self.log_file_max,
477                self.use_console, self.use_async, self.plugin, self.filter)
478    }
479
480    #[inline]
481    pub fn level(mut self, level: log::LevelFilter) -> Self {
482        self.level = level;
483        self
484    }
485
486    #[inline]
487    pub fn log_file(mut self, log_file: String) -> Self {
488        self.log_file = log_file;
489        self
490    }
491
492    #[inline]
493    pub fn log_file_max(mut self, log_file_max: u32) -> Self {
494        self.log_file_max = log_file_max;
495        self
496    }
497
498    #[inline]
499    pub fn use_console(mut self, use_console: bool) -> Self {
500        self.use_console = use_console;
501        self
502    }
503
504    #[inline]
505    pub fn use_async(mut self, use_async: bool) -> Self {
506        self.use_async = use_async;
507        self
508    }
509
510    #[inline]
511    pub fn level_str(mut self, level: &str) -> Result<Self> {
512        self.level = parse_level(level)?;
513        Ok(self)
514    }
515
516    #[inline]
517    pub fn log_file_max_str(mut self, log_file_max: &str) -> Result<Self> {
518        self.log_file_max = parse_size(log_file_max)?;
519        Ok(self)
520    }
521
522    pub fn plugin<T: std::io::Write + Send + Sync + 'static>(mut self, plugin: T) -> Self {
523        self.plugin = Some(Box::new(plugin));
524        self
525    }
526
527    pub fn filter(mut self, filter: impl CustomFilter) -> Self {
528        self.filter = Some(Box::new(filter));
529        self
530    }
531}
532
533impl<F: Fn(&log::Record) -> bool + Send + Sync + 'static> CustomFilter for F {
534    fn enabled(&self, record: &log::Record) -> bool {
535        self(record)
536    }
537}
538
539impl<'a> SkipAnsiColorIter<'a> {
540    pub fn new(data: &'a [u8]) -> Self {
541        let find_len = if data.len() > 3 {
542            data.len() - 3
543        } else {
544            0
545        };
546
547        SkipAnsiColorIter {
548            data,
549            pos: 0,
550            find_len,
551        }
552    }
553}
554
555
556impl<'a> Iterator for SkipAnsiColorIter<'a> {
557    type Item = &'a [u8];
558
559    #[inline]
560    fn next(&mut self) -> Option<Self::Item> {
561        // 过滤ansi颜色
562        let (mut pos, find_len, data) = (self.pos, self.find_len, self.data);
563        while pos < find_len {
564            unsafe {
565                if *data.get_unchecked(pos) != 0x1b || *data.get_unchecked(pos + 1) != b'[' {
566                    pos += 1;
567                    continue;
568                }
569
570                // 找到ansi颜色前缀,返回前缀前的字符串并更新当前位置和已写入字节
571                let n = if *data.get_unchecked(pos + 3) == b'm' { 4 } else { 5 };
572                let p = self.pos;
573                self.pos = pos + n;
574                return Some(&data[p..pos]);
575            }
576        }
577
578        // 写入剩余的数据
579        let dl = data.len();
580        if pos < dl {
581            let p = self.pos;
582            self.pos = dl;
583            return Some(&data[p..dl]);
584        }
585
586        None
587    }
588}
589
590#[cfg(feature = "tokio")]
591pub fn init_log_inner(
592    level: log::LevelFilter,
593    log_file: String,
594    log_file_max: u32,
595    use_console: bool,
596    _use_async: bool,
597    plugin: Option<BoxPlugin>,
598    filter: Option<BoxCustomFilter>
599) -> Result<()>
600{
601
602    #[cfg(debug_assertions)]
603    debug_check_init();
604
605    log::set_max_level(level);
606
607    #[cfg(feature = "time")]
608    // let dt_fmt = if level >= log::LevelFilter::Debug {
609    //     "[month]-[day] [hour]:[minute]:[second]"
610    // } else {
611    //     "[year]-[month]-[day] [hour]:[minute]:[second]"
612    // };
613    let dt_fmt = "[year]-[month]-[day] [hour]:[minute]:[second]";
614    #[cfg(feature = "time")]
615    let dt_fmt = time::format_description::parse_owned::<2>(dt_fmt).unwrap();
616
617    #[cfg(feature = "chrono")]
618    let dt_fmt = if level >= log::LevelFilter::Debug {
619        "%m-%d %H:%M:%S"
620    } else {
621        "%Y-%m-%d %H:%M:%S"
622    }.to_owned();
623
624    let use_file = !log_file.is_empty();
625    let console = if use_console {
626        Some(BufWriter::new(tokio::io::stdout()))
627    } else {
628        None
629    };
630
631    let (tx, rx) = unbounded_channel();
632
633    let logger = LogPlus {
634        level,
635        dt_fmt,
636        log_file,
637        max_size: log_file_max,
638        level_filter: RwLock::new(HashMap::new()),
639        fmt_cache: Mutex::new(VecDeque::with_capacity(CACHE_STR_ARRAY_SIZE)),
640        msg_tx: tx,
641        filter,
642    };
643
644    unsafe {
645        #[cfg(debug_assertions)]
646        {
647            debug_assert!(!INITED);
648            INITED = true;
649        }
650        LOGGER_PLUS.write(logger);
651    }
652
653    // 设置全局日志对象
654    log::set_logger(get_logger_plus()).expect("init_log call set_logger error");
655
656    tokio::spawn(write_async(LogData {
657        log_size: 0,
658        use_file,
659        console,
660        fileout: None,
661        plugin,
662    }, rx));
663
664    Ok(())
665}
666
667#[cfg(not(feature = "tokio"))]
668fn init_log_inner(
669    level: log::LevelFilter,
670    log_file: String,
671    log_file_max: u32,
672    use_console: bool,
673    use_async: bool,
674    plugin: Option<BoxPlugin>,
675    filter: Option<BoxCustomFilter>
676) -> Result<()>
677{
678
679    #[cfg(debug_assertions)]
680    debug_check_init();
681
682    log::set_max_level(level);
683
684    #[cfg(feature = "time")]
685    let dt_fmt = if level >= log::LevelFilter::Debug {
686        "[month]-[day] [hour]:[minute]:[second]"
687    } else {
688        "[year]-[month]-[day] [hour]:[minute]:[second]"
689    };
690    #[cfg(feature = "time")]
691    let dt_fmt = time::format_description::parse_owned::<2>(dt_fmt).unwrap();
692
693    #[cfg(feature = "chrono")]
694    let dt_fmt = if level >= log::LevelFilter::Debug {
695        "%m-%d %H:%M:%S"
696    } else {
697        "%Y-%m-%d %H:%M:%S"
698    }.to_owned();
699
700    // 如果启用控制台输出,创建一个控制台共享句柄
701    let console = if use_console {
702        Some(LineWriter::new(std::io::stdout()))
703    } else {
704        None
705    };
706
707    // 如果启用文件输出,打开日志文件
708    let (fileout, log_size) = if !log_file.is_empty() {
709        let f = std::fs::OpenOptions::new()
710            .append(true)
711            .create(true)
712            .open(&log_file)?;
713        let log_size = std::fs::metadata(&log_file)?.len() as u32;
714        let fileout = Some(LineWriter::new(f));
715        (fileout, log_size)
716    } else {
717        (None, 0)
718    };
719
720    // 如果启用异步日志,开启一个线程不停读取channel中的数据进行日志写入,属于多生产者单消费者模式
721    let sender = if use_async {
722        let (sender, receiver) = std::sync::mpsc::channel::<AsyncLogType>();
723        std::thread::spawn(move || loop {
724            match receiver.recv() {
725                Ok(data) => match data {
726                    AsyncLogType::Message(msg) => {
727                        get_logger_plus().write(&msg);
728                        put_msg_to_cache(msg);
729                    },
730                    AsyncLogType::Flush => get_logger_plus().flush_inner(),
731                },
732                Err(e) => eprintln!("logger channel recv error: {}", e),
733            }
734        });
735        Some(sender)
736    } else {
737        None
738    };
739
740    let logger = LogPlus {
741        level,
742        dt_fmt,
743        log_file,
744        max_size: log_file_max,
745        level_filter: RwLock::new(HashMap::new()),
746        fmt_cache: Mutex::new(VecDeque::with_capacity(CACHE_STR_ARRAY_SIZE)),
747        filter,
748        logger_data: Mutex::new(LogData {
749            log_size,
750            console,
751            fileout,
752            sender,
753            plugin,
754        }),
755    };
756
757    unsafe {
758        #[cfg(debug_assertions)]
759        {
760            debug_assert!(!INITED);
761            INITED = true;
762        }
763        LOGGER_PLUS.write(logger);
764    }
765
766    // 设置全局日志对象
767    log::set_logger(get_logger_plus()).expect("init_log call set_logger error");
768
769    Ok(())
770}
771
772
773#[cfg(not(feature = "tokio"))]
774fn write_text(w: &mut LineWriter<File>, msg: &[u8]) -> std::io::Result<usize> {
775    let mut write_len = 0;
776    for item in SkipAnsiColorIter::new(msg) {
777        write_len += item.len();
778        w.write_all(item)?;
779    }
780
781    // 如果已换行符结尾, 则刷新缓冲区
782    let len = msg.len();
783    if len > 0 && (msg[len - 1] == b'\n' || msg[len - 1] == b'\r') {
784        w.flush()?;
785    }
786
787    Ok(write_len)
788}
789
790#[cfg(feature = "tokio")]
791async fn write_async(mut log_data: LogData, mut rx: UnboundedReceiver<AsyncLogType>) {
792    let logger_plus = get_logger_plus();
793
794    // 首次使用,初始化日志文件
795    if log_data.use_file && log_data.fileout.is_none() {
796        let (f, size) = open_log_file(&logger_plus.log_file, true).await;
797        log_data.fileout = Some(f);
798        log_data.log_size = size;
799    }
800
801    // 循环读取消息队列并输出,当消息队列为空时,设置任务终止标志并终止任务
802    while let Some(data) = rx.recv().await {
803        match data {
804            AsyncLogType::Message(msg) => {
805                // 写入日志消息
806                write_to_log(&mut log_data, &msg).await;
807                // 回收msg到缓存队列,以便下次使用
808                put_msg_to_cache(msg);
809            }
810            AsyncLogType::Flush => {
811                if let Some(ref mut console) =  log_data.console {
812                    if let Err(e) = console.flush().await {
813                        println!("flush log to console failed: {e:?}");
814                    }
815                }
816                if let Some(ref mut fileout) = log_data.fileout {
817                    if let Err(e) = fileout.flush().await {
818                        println!("flush log to file failed: {e:?}");
819                    }
820                }
821            }
822        }
823    }
824}
825
826#[cfg(feature = "tokio")]
827async fn write_text(w: &mut BufWriter<File>, msg: &[u8]) -> std::io::Result<usize> {
828    let mut write_len = 0;
829    for item in SkipAnsiColorIter::new(msg) {
830        write_len += item.len();
831        w.write_all(item).await?;
832    }
833
834    // 如果已换行符结尾, 则刷新缓冲区
835    let len = msg.len();
836    if len > 0 && (msg[len - 1] == b'\n' || msg[len - 1] == b'\r') {
837        w.flush().await?;
838    }
839
840    Ok(write_len)
841}
842
843#[cfg(feature = "tokio")]
844async fn open_log_file(log_file: &str, append: bool) -> (BufWriter<File>, u32) {
845    let f = tokio::fs::OpenOptions::new()
846        .append(append)
847        .write(true)
848        .create(true)
849        .open(log_file)
850        .await
851        .expect("reopen log file fail");
852
853    let size = if append {
854        tokio::fs::metadata(log_file).await.map_or_else(|_| 0, |m| m.len())
855    } else {
856        0
857    };
858    return (BufWriter::new(f), size as u32);
859}
860
861#[cfg(feature = "tokio")]
862async fn write_to_log(log_data: &mut LogData, msg: &[u8]) {
863    if msg.is_empty() { return; }
864
865    // 如果启用了控制台输出,则写入控制台
866    if let Some(ref mut console) = log_data.console {
867        if console.write_all(&msg).await.is_ok() {
868            let c = msg[msg.len() - 1];
869            if c == b'\n' || c == b'\r' {
870                let _ = console.flush().await;
871            }
872        }
873    }
874
875    // 判断日志长度是否到达最大限制,如果到了,需要备份当前日志文件并重新创建新的日志文件
876    let logger_plus = get_logger_plus();
877    if log_data.log_size > logger_plus.max_size {
878        let has_file =  match log_data.fileout {
879            Some(ref mut fileout) => {
880                // 刷新缓存并关闭日志文件
881                if let Err(e) = fileout.flush().await {
882                    eprintln!("failed in log flush log file: {e:?}");
883                }
884                true
885            }
886            None => false
887        };
888
889        if has_file {
890            log_data.fileout.take();
891
892            // 之所以把关闭文件和重新创建文件分开写,是因为rust限制了可变借用(fileout)只允许1次
893            // 删除已有备份,并重命名现有文件为备份文件
894            let bak = format!("{}.bak", logger_plus.log_file);
895            let _ = tokio::fs::remove_file(&bak).await;
896            match tokio::fs::rename(&logger_plus.log_file, &bak).await {
897                Ok(_) => {
898                    log_data.fileout = Some(open_log_file(&logger_plus.log_file, false).await.0);
899                    log_data.log_size = 0;
900                }
901                Err(e) => eprintln!("failed in log rename file: {e:?}"),
902            }
903        }
904    }
905
906    if let Some(ref mut fileout) = log_data.fileout {
907        match write_text(fileout, &msg).await {
908            Ok(size) => log_data.log_size += size as u32,
909            Err(e) => eprintln!("failed in write to file: {e:?}"),
910        }
911    }
912
913    if let Some(ref mut plugin) = log_data.plugin {
914        if let Err(e) = plugin.write_all(&msg) {
915            eprintln!("failed in log plugin: {e:?}");
916        }
917    }
918}
919
920fn get_logger_plus() -> &'static LogPlus {
921    unsafe {
922        #[cfg(debug_assertions)]
923        debug_assert!(INITED);
924
925        LOGGER_PLUS.assume_init_ref()
926    }
927}
928
929// 返回日志级别对应的ansi颜色
930fn level_color(level: log::Level) -> &'static str {
931    // const RESET:    &str = "\x1b[0m";
932    // const BLACK:    &str = "\x1b[30m";
933    const RED:      &str = "\x1b[31m";
934    const GREEN:    &str = "\x1b[32m";
935    const YELLOW:   &str = "\x1b[33m";
936    const BLUE:     &str = "\x1b[34m";
937    const MAGENTA:  &str = "\x1b[35m";
938    // const CYAN:     &str = "\x1b[36m";
939    // const WHITE:    &str = "\x1b[37m";
940
941    match level {
942        log::Level::Trace => GREEN,
943        log::Level::Debug => YELLOW,
944        log::Level::Info => BLUE,
945        log::Level::Warn => MAGENTA,
946        log::Level::Error => RED,
947    }
948}
949
950/// 获取一个用于保存格式化日志条目的对象, 优先从缓存获取,缓存没有则新建一个
951fn get_msg_from_cache() -> Vec<u8> {
952    if let Ok(mut fmt_cache) = get_logger_plus().fmt_cache.lock() {
953            if let Some(vec) = fmt_cache.pop_back() {
954                return vec;
955            }
956    }
957
958    Vec::with_capacity(CACHE_STR_INIT_SIZE)
959}
960
961/// 回收Vec<u8>对象,用于下次使用,避免频繁分配内存造成内存碎片
962fn put_msg_to_cache(mut value: Vec<u8>) {
963    if value.capacity() <= CACHE_STR_INIT_SIZE {
964        value.clear();
965
966        if let Ok(mut fmt_cache) = get_logger_plus().fmt_cache.lock() {
967            if fmt_cache.len() < fmt_cache.capacity() {
968                fmt_cache.push_back(value);
969            }
970        }
971    }
972}
973
974#[cfg(debug_assertions)]
975fn debug_check_init() {
976    use std::sync::atomic::{AtomicBool, Ordering};
977
978    static INITED: AtomicBool = AtomicBool::new(false);
979
980    if let Err(true) = INITED.compare_exchange(false, true, Ordering::Release, Ordering::Relaxed) {
981        panic!("init_log must run once!");
982    }
983}