1use log::{LevelFilter, Log};
73use mtlog_core::{
74 spawn_log_thread_file, spawn_log_thread_stdout, FileLogger, LogFile, LogFileSizeRotation,
75 LogFileTimeRotation, LogMessage, LogSender, LogStdout,
76};
77
78pub use mtlog_core::{SizeRotationConfig, TimeRotationConfig};
79use std::{
80 future::Future,
81 path::Path,
82 sync::{Arc, LazyLock, RwLock},
83};
84
85#[derive(Clone)]
87struct LogConfig {
88 sender_file: Option<Arc<LogSender>>,
90 sender_stdout: Option<Arc<LogSender>>,
92 name: Option<String>,
94 level: LevelFilter,
96}
97
98static GLOBAL_LOG_CONFIG: LazyLock<Arc<RwLock<LogConfig>>> = LazyLock::new(|| {
100 log::set_boxed_logger(Box::new(MTLogger)).unwrap();
101 log::set_max_level(LevelFilter::Info);
102 let sender = spawn_log_thread_stdout(LogStdout::default());
103 Arc::new(RwLock::new(LogConfig {
104 sender_stdout: Some(Arc::new(sender)),
105 sender_file: None,
106 name: None,
107 level: LevelFilter::Info,
108 }))
109});
110
111tokio::task_local! {
112 pub static LOG_CONFIG: LogConfig;
114}
115
116struct MTLogger;
118
119impl Log for MTLogger {
120 fn enabled(&self, _: &log::Metadata) -> bool {
121 true
122 }
123
124 fn log(&self, record: &log::Record) {
125 LOG_CONFIG.with(|config| {
126 let level = record.level();
127 if level > config.level {
128 return;
129 }
130 let log_message = Arc::new(LogMessage {
131 level,
132 name: config.name.clone(),
133 message: record.args().to_string(),
134 });
135 if let Some(sender) = &config.sender_stdout {
136 sender
137 .send(log_message.clone())
138 .expect("Unable to send log message to stdout logging thread");
139 }
140 if let Some(sender) = &config.sender_file {
141 sender
142 .send(log_message)
143 .expect("Unable to send log message to file logging thread");
144 }
145 });
146 }
147
148 fn flush(&self) {
149 if let Some(s) = GLOBAL_LOG_CONFIG.write().unwrap().sender_stdout.as_deref() {
150 s.shutdown();
151 }
152 if let Some(s) = GLOBAL_LOG_CONFIG.write().unwrap().sender_file.as_deref() {
153 s.shutdown();
154 }
155 }
156}
157
158pub struct ConfigBuilder {
160 log_file: Option<FileLogger>,
161 no_stdout: bool,
162 no_file: bool,
163 log_level: LevelFilter,
164 name: Option<String>,
165}
166
167impl Default for ConfigBuilder {
168 fn default() -> Self {
169 Self {
170 log_file: None,
171 no_stdout: false,
172 no_file: false,
173 log_level: LevelFilter::Info,
174 name: None,
175 }
176 }
177}
178
179impl ConfigBuilder {
180 fn build(self) -> LogConfig {
181 let Self {
182 log_file,
183 no_stdout,
184 no_file,
185 log_level,
186 name,
187 } = self;
188 let sender_file = if no_file {
189 None
190 } else if let Some(log_file) = log_file {
191 let sender = spawn_log_thread_file(log_file);
192 Some(Arc::new(sender))
193 } else {
194 GLOBAL_LOG_CONFIG.read().unwrap().sender_file.clone()
195 };
196 let sender_stdout = if no_stdout {
197 None
198 } else {
199 GLOBAL_LOG_CONFIG.read().unwrap().sender_stdout.clone()
200 };
201 LogConfig {
202 sender_file,
203 sender_stdout,
204 name,
205 level: log_level,
206 }
207 }
208
209 pub fn with_log_file<P: AsRef<Path>>(self, path: P) -> Result<Self, std::io::Error> {
211 Ok(Self {
212 log_file: Some(FileLogger::Single(LogFile::new(path)?)),
213 ..self
214 })
215 }
216 pub fn maybe_with_log_file<P: AsRef<Path>>(
218 self,
219 path: Option<P>,
220 ) -> Result<Self, std::io::Error> {
221 Ok(Self {
222 log_file: path
223 .map(|p| LogFile::new(p).map(FileLogger::Single))
224 .transpose()?,
225 ..self
226 })
227 }
228 pub fn with_time_rotation(self, config: TimeRotationConfig) -> Result<Self, std::io::Error> {
230 Ok(Self {
231 log_file: Some(FileLogger::TimeRotation(LogFileTimeRotation::new(config)?)),
232 ..self
233 })
234 }
235 pub fn with_size_rotation(self, config: SizeRotationConfig) -> Result<Self, std::io::Error> {
237 Ok(Self {
238 log_file: Some(FileLogger::SizeRotation(LogFileSizeRotation::new(config)?)),
239 ..self
240 })
241 }
242 pub fn no_stdout(self) -> Self {
244 Self {
245 no_stdout: true,
246 ..self
247 }
248 }
249 pub fn with_stdout(self, yes: bool) -> Self {
251 Self {
252 no_stdout: !yes,
253 ..self
254 }
255 }
256 pub fn no_file(self) -> Self {
258 Self {
259 no_file: true,
260 ..self
261 }
262 }
263 pub fn with_name(self, name: &str) -> Self {
265 Self {
266 name: Some(name.into()),
267 ..self
268 }
269 }
270 pub fn maybe_with_name(self, name: Option<&str>) -> Self {
272 Self {
273 name: name.map(String::from),
274 ..self
275 }
276 }
277 pub async fn scope_global<F: Future>(self, f: F) -> F::Output {
280 let config = self.build();
281 let mut senders = Vec::new();
282 if let Some(ref sender) = config.sender_stdout {
283 senders.push(Arc::clone(sender));
284 }
285 if let Some(ref sender) = config.sender_file {
286 senders.push(Arc::clone(sender));
287 }
288 *GLOBAL_LOG_CONFIG.write().unwrap() = config.clone();
289 let result = LOG_CONFIG.scope(config, f).await;
290 for sender in senders {
292 sender.shutdown();
293 }
294 result
295 }
296 pub async fn scope_local<F: Future>(self, f: F) -> F::Output {
298 LOG_CONFIG.scope(self.build(), f).await
299 }
300}
301
302pub fn logger_config() -> ConfigBuilder {
304 ConfigBuilder::default()
305}