dyn_logger/
lib.rs

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}