zlsrs 0.1.6

Rust 标准库扩展工具集,提供更便捷的使用方式
Documentation
use tracing_subscriber::{
    filter, fmt,
    prelude::*,
    reload::{self},
};

pub use tracing::Level;

fn init_log(
    global: bool,
    file: String,
    time_format: String,
    show_line_number: bool,
) -> (
    Box<dyn Fn(Level) + Send + Sync + 'static>,
    tracing_appender::non_blocking::WorkerGuard,
) {
    let (layer, guard) = init_layer(file, time_format, show_line_number);

    let (layer, _reload_layer_handle) = reload::Layer::new(layer);

    let filter = filter::LevelFilter::WARN;
    let (filter, reload_filter) = reload::Layer::new(filter);

    let reg = tracing_subscriber::registry().with(filter).with(layer);
    if global {
        reg.init()
    }
    return (
        Box::new(move |level: Level| {
            let _ = reload_filter.modify(|filter| *filter = level.into());
        }),
        guard,
    );
}

fn init_layer_writer(
    file: &str,
) -> (
    bool,
    tracing_appender::non_blocking::NonBlocking,
    tracing_appender::non_blocking::WorkerGuard,
) {
    let stdout = file == "";
    let (writer, guard) = if stdout {
        let (writer, guard) = tracing_appender::non_blocking(std::io::stdout());
        (writer, guard)
    } else {
        let (dir, file_name) = match file.rfind('/') {
            Some(idx) => {
                let (dir, file) = file.split_at(idx);
                let file_name = &file[1..];
                (dir, file_name)
            }
            None => ("", file),
        };

        let file_appender = tracing_appender::rolling::never(dir, file_name);
        let (writer, guard) = tracing_appender::non_blocking(file_appender);
        (writer, guard)
    };

    (stdout, writer, guard)
}

fn init_layer(
    file: String,
    time_format: String,
    show_line_number: bool,
) -> (
    fmt::Layer<
        tracing_subscriber::layer::Layered<
            reload::Layer<filter::LevelFilter, tracing_subscriber::Registry>,
            tracing_subscriber::Registry,
        >,
        fmt::format::DefaultFields,
        fmt::format::Format<
            fmt::format::Full,
            fmt::time::UtcTime<Vec<time::format_description::BorrowedFormatItem<'static>>>,
        >,
        tracing_appender::non_blocking::NonBlocking,
    >,
    tracing_appender::non_blocking::WorkerGuard,
) {
    let format_str: &'static str = Box::leak(time_format.into_boxed_str());
    let format = time::format_description::parse(format_str)
        .expect("Invalid time format")
        .to_owned();
    let timer = tracing_subscriber::fmt::time::UtcTime::new(format);

    let (stdout, writer, guard) = init_layer_writer(file.as_str());
    let layer = fmt::Layer::default()
        .with_timer(timer)
        .with_file(show_line_number)
        .with_line_number(show_line_number)
        .with_writer(writer)
        .with_ansi(stdout);

    return (layer, guard);
}

extern crate time;
extern crate tracing;
extern crate tracing_subscriber;
use std::sync::OnceLock;

static LOG: OnceLock<(
    Box<dyn Fn(Level) + Send + Sync + 'static>,
    tracing_appender::non_blocking::WorkerGuard,
)> = OnceLock::new();

pub struct Options {
    /// 日志级别
    pub level: Level,
    /// 日志文件
    pub set_file: String,
    /// 日志时间格式
    pub time_format: String,
    /// 显示日志行号
    pub show_line_number: bool,
}

impl Default for Options {
    fn default() -> Self {
        Self {
            level: Level::INFO,
            set_file: "".to_string(),
            time_format: "[year]-[month]-[day] [hour repr:24]:[minute]:[second]".to_string(),
            show_line_number: false,
        }
    }
}

/// 初始化日志
///
/// # Examples
///
/// ```rust
/// use zlsrs::zlog;
///
/// fn main() {
///     zlog::init(zlog::Options {
///         level: zlog::Level::INFO,
///         time_format: "[year]-[month]-[day] [hour repr:24]:[minute]:[second]".to_string(),
///         ..Default::default()
///     });
/// }
/// ```
pub fn init<T: Into<Option<Options>>>(options: T) {
    let mut options = options.into();
    if options.is_none() {
        options = Some(Options::default());
    }
    let options = options.unwrap();

    let set_file = options.set_file;
    let (reload_handle, _) = LOG.get_or_init(|| {
        init_log(
            true,
            set_file,
            options.time_format,
            options.show_line_number,
        )
    });

    let level = options.level;
    reload_handle(level.into());
}