nihility_log/
lib.rs

1mod error;
2
3use crate::error::NihilityLogError;
4use serde::{Deserialize, Serialize};
5use std::sync::OnceLock;
6use time::format_description::well_known::Iso8601;
7use tracing::debug;
8use tracing::level_filters::LevelFilter;
9use tracing_appender::non_blocking::WorkerGuard;
10use tracing_appender::{non_blocking, rolling};
11use tracing_subscriber::fmt::time::LocalTime;
12use tracing_subscriber::layer::SubscriberExt;
13use tracing_subscriber::util::SubscriberInitExt;
14use tracing_subscriber::{Layer, fmt};
15
16#[derive(Deserialize, Serialize, Default, Debug, Clone)]
17pub enum LogOutType {
18    #[default]
19    Console,
20    File(String),
21}
22
23#[derive(Deserialize, Serialize, Default, Debug, Clone)]
24#[serde(rename_all = "camelCase")]
25pub enum LogLevel {
26    Trace,
27    Debug,
28    #[default]
29    Info,
30    Warn,
31    Error,
32}
33
34#[derive(Deserialize, Serialize, Debug, Clone)]
35pub struct LogConfig {
36    pub log: Vec<LogConfigChunk>,
37}
38
39#[derive(Deserialize, Serialize, Debug, Clone)]
40pub struct LogConfigChunk {
41    pub enable: bool,
42    pub out_type: LogOutType,
43    pub level: LogLevel,
44    pub with_file: bool,
45    pub with_line_number: bool,
46    pub with_thread_ids: bool,
47    pub with_target: bool,
48}
49
50impl Default for LogConfig {
51    fn default() -> Self {
52        Self {
53            log: vec![
54                LogConfigChunk::default(),
55                LogConfigChunk {
56                    enable: false,
57                    out_type: LogOutType::File("log".to_string()),
58                    level: LogLevel::Debug,
59                    with_file: false,
60                    with_line_number: true,
61                    with_thread_ids: true,
62                    with_target: true,
63                },
64            ],
65        }
66    }
67}
68
69static CONSOLE_WORK_GUARD: OnceLock<WorkerGuard> = OnceLock::new();
70static FILE_WORK_GUARD: OnceLock<WorkerGuard> = OnceLock::new();
71
72const LOG_FILE_NAME: &str = ".log";
73
74impl Default for LogConfigChunk {
75    fn default() -> Self {
76        LogConfigChunk {
77            enable: true,
78            out_type: LogOutType::default(),
79            level: LogLevel::default(),
80            with_file: false,
81            with_line_number: false,
82            with_thread_ids: true,
83            with_target: true,
84        }
85    }
86}
87
88pub fn init() -> Result<(), NihilityLogError> {
89    let configs =
90        nihility_config::get_config::<LogConfig>(env!("CARGO_PKG_NAME").to_string())?.log;
91    let mut layers = Vec::new();
92
93    for config in configs {
94        if !config.enable {
95            continue;
96        }
97        let non_blocking = match &config.out_type {
98            LogOutType::Console => {
99                let (non_blocking, guard) = non_blocking(std::io::stdout());
100                CONSOLE_WORK_GUARD.get_or_init(|| guard);
101                non_blocking
102            }
103            LogOutType::File(out_path) => {
104                let file_appender = rolling::daily(out_path, LOG_FILE_NAME);
105                let (non_blocking, guard) = non_blocking(file_appender);
106                FILE_WORK_GUARD.get_or_init(|| guard);
107                non_blocking
108            }
109        };
110        let layer = fmt::layer()
111            .with_ansi(false)
112            .with_file(config.with_file)
113            .with_line_number(config.with_line_number)
114            .with_thread_ids(config.with_thread_ids)
115            .with_target(config.with_target)
116            .with_timer(LocalTime::new(Iso8601::DATE_TIME_OFFSET))
117            .with_writer(non_blocking);
118        let layer = match config.level {
119            LogLevel::Trace => layer.with_filter(LevelFilter::TRACE),
120            LogLevel::Debug => layer.with_filter(LevelFilter::DEBUG),
121            LogLevel::Info => layer.with_filter(LevelFilter::INFO),
122            LogLevel::Warn => layer.with_filter(LevelFilter::WARN),
123            LogLevel::Error => layer.with_filter(LevelFilter::ERROR),
124        }
125            .boxed();
126        layers.push(layer);
127    }
128    tracing_subscriber::registry().with(layers).init();
129    debug!("Log Subscriber Init Success");
130    Ok(())
131}