1use error::DynLogAPIErr;
2use serde::Deserialize;
3use std::{
4 cell::RefCell,
5 fmt::Debug,
6 fs::{create_dir_all, read_to_string},
7 path::Path,
8 str::FromStr,
9};
10use tracing::metadata::LevelFilter;
11use tracing_appender::non_blocking::WorkerGuard;
12use tracing_subscriber::{EnvFilter, Layer, Registry, filter::Targets, fmt, prelude::*};
13
14pub mod error;
15mod logger;
16mod utils;
17
18use logger::{FileLogger, GlobalLogger, LogFormat, StreamLogger};
19
20#[derive(Debug, Deserialize)]
21struct LogConfig {
22 global: GlobalLogger,
23 stream_logger: StreamLogger,
24 file_logger: Option<Vec<FileLogger>>,
25}
26
27pub struct DynamicLogger {
28 config: LogConfig,
29 layers: RefCell<Vec<Box<dyn Layer<Registry> + Send + Sync>>>,
30 guards: RefCell<Vec<WorkerGuard>>,
31}
32
33impl DynamicLogger {
34 pub fn new(path: impl AsRef<Path>) -> Result<Self, DynLogAPIErr> {
35 let config = toml::from_str(&read_to_string(path.as_ref()).map_err(|source| {
36 DynLogAPIErr::FileReadError {
37 filename: path.as_ref().to_string_lossy().to_string(),
38 source,
39 }
40 })?)?;
41 Ok(Self {
42 config,
43 layers: RefCell::new(Vec::new()),
44 guards: RefCell::new(Vec::new()),
45 })
46 }
47
48 pub fn with_file_logger(self) -> Result<Self, DynLogAPIErr> {
49 if self.config.global.options.enabled {
50 self.init_filelogger()?;
51 }
52
53 Ok(self)
54 }
55
56 pub fn with_stdout(self) -> Result<Self, DynLogAPIErr> {
57 if self.config.global.options.enabled {
58 self.init_stdout()?;
59 }
60 Ok(self)
61 }
62
63 pub fn init(&self) -> Result<(), DynLogAPIErr> {
64 let global = &self.config.global;
65 if global.options.enabled {
66 let stream_targets = Targets::from_str(&self.config.stream_logger.modules.join(","))
67 .map(|targets| {
68 targets
69 .into_iter()
70 .map(|(filter, _)| (filter, LevelFilter::OFF))
71 .collect::<Targets>()
72 })?;
73
74 let layer = fmt::layer()
75 .with_file(global.options.file)
76 .with_line_number(global.options.line_number)
77 .with_thread_names(global.options.thread_name)
78 .with_thread_ids(global.options.thread_id);
79
80 let env_layer = match global.options.format {
81 LogFormat::Full => layer.boxed(),
82 LogFormat::Compact => layer.compact().boxed(),
83 LogFormat::Pretty => layer.pretty().boxed(),
84 LogFormat::Json => layer.json().boxed(),
85 };
86
87 let mut ref_layers = self.layers.borrow_mut();
88 if stream_targets.iter().count() > 0 {
89 ref_layers.push(
90 env_layer
91 .with_filter(
92 stream_targets.with_default(LevelFilter::from_level(global.log_level)),
93 )
94 .boxed(),
95 );
96 } else {
97 let envfilter =
98 EnvFilter::from_default_env().add_directive(global.log_level.into());
99 ref_layers.push(env_layer.with_filter(envfilter).boxed());
100 }
101 }
102
103 tracing_subscriber::registry()
104 .with(self.layers.take())
105 .init();
106
107 Ok(())
108 }
109
110 fn register_filelogger_target(&self, entry: &FileLogger) -> Result<(), DynLogAPIErr> {
111 let log_dir = &entry.path;
112 create_dir_all(log_dir).map_err(|source| DynLogAPIErr::CreateLogDirError {
113 path: log_dir.to_string_lossy().to_string(),
114 source,
115 })?;
116 let appender = tracing_appender::rolling::never(log_dir, &entry.filename);
117 let (file_writer, guard) = tracing_appender::non_blocking(appender);
118 self.guards.borrow_mut().push(guard);
119
120 let options = &entry.options;
121 let file_targets = Targets::from_str(&entry.modules.join(","))?;
122 let file_layer = fmt::Layer::new()
123 .with_writer(file_writer)
124 .with_ansi(false)
125 .with_file(options.file)
126 .with_line_number(options.line_number)
127 .with_thread_names(options.thread_name)
128 .with_thread_ids(options.thread_id);
129 let layer = match options.format {
130 LogFormat::Full => file_layer.with_filter(file_targets).boxed(),
131 LogFormat::Compact => file_layer.compact().with_filter(file_targets).boxed(),
132 LogFormat::Pretty => file_layer.pretty().with_filter(file_targets).boxed(),
133 LogFormat::Json => file_layer.json().with_filter(file_targets).boxed(),
134 };
135
136 self.layers.borrow_mut().push(layer);
137 Ok(())
138 }
139
140 #[must_use]
141 pub fn add_layer(self, layer: Box<dyn Layer<Registry> + Send + Sync>) -> Self {
142 self.layers.borrow_mut().push(layer);
143 self
144 }
145
146 #[must_use]
147 pub fn add_layer_with_stream_logger_targets(
148 self,
149 layer: Box<dyn Layer<Registry> + Send + Sync>,
150 ) -> Result<Self, DynLogAPIErr> {
151 let target_layer = {
152 let file_targets = Targets::from_str(&self.config.stream_logger.modules.join(","))?;
153 if file_targets.iter().count() > 0 {
154 layer.with_filter(file_targets).boxed()
155 } else {
156 layer
157 }
158 };
159
160 self.layers.borrow_mut().push(target_layer);
161
162 Ok(self)
163 }
164
165 #[must_use]
166 pub fn add_layers<T>(self, layers: T) -> Self
167 where
168 T: IntoIterator<Item = Box<dyn Layer<Registry> + Send + Sync>>,
169 {
170 {
171 let mut ref_layers = self.layers.borrow_mut();
172 for layer in layers {
173 ref_layers.push(layer);
174 }
175 }
176 self
177 }
178}
179
180pub trait DynamicLogging {
181 type Error;
182 fn init_stdout(&self) -> Result<(), Self::Error>;
183 fn init_filelogger(&self) -> Result<(), Self::Error>;
184}
185
186impl DynamicLogging for DynamicLogger {
187 type Error = DynLogAPIErr;
188 fn init_stdout(&self) -> Result<(), Self::Error> {
189 let stream_logger = &self.config.stream_logger;
190 if !stream_logger.options.enabled {
191 return Ok(());
192 }
193
194 let targets = Targets::from_str(&stream_logger.modules.join(",")).expect("");
195 let stream_layer = fmt::Layer::new()
196 .with_writer(std::io::stdout)
197 .with_file(stream_logger.options.file)
198 .with_line_number(stream_logger.options.line_number)
199 .with_thread_names(stream_logger.options.thread_name)
200 .with_thread_ids(stream_logger.options.thread_id);
201 let layer = match stream_logger.options.format {
202 LogFormat::Full => stream_layer
203 .with_ansi(stream_logger.color)
204 .with_filter(targets)
205 .boxed(),
206 LogFormat::Compact => stream_layer
207 .with_ansi(stream_logger.color)
208 .compact()
209 .with_filter(targets)
210 .boxed(),
211 LogFormat::Pretty => stream_layer.pretty().with_filter(targets).boxed(),
212 LogFormat::Json => stream_layer.json().with_filter(targets).boxed(),
213 };
214
215 self.layers.borrow_mut().push(layer);
216 Ok(())
217 }
218
219 fn init_filelogger(&self) -> Result<(), Self::Error> {
220 let file_logger_table = &self
221 .config
222 .file_logger
223 .as_ref()
224 .ok_or(DynLogAPIErr::InitializeFileloggerError)?;
225 for entry in file_logger_table.iter().filter(|file| file.options.enabled) {
226 self.register_filelogger_target(entry)?;
227 }
228 Ok(())
229 }
230}