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}