1use once_cell::sync::OnceCell;
5use std::{
6 path::{Path, PathBuf},
7 sync::RwLock,
8 sync::atomic::{AtomicUsize, Ordering},
9};
10use tracing::{self, event};
11use tracing_appender::rolling::{RollingFileAppender, RollingWriter, Rotation};
12use tracing_subscriber::{
13 Registry,
14 filter::Filtered,
15 fmt::{
16 Layer,
17 format::{DefaultFields, Format},
18 },
19 layer::Layered,
20};
21
22use tracing_subscriber::{
23 self,
24 filter::{self, LevelFilter},
25 prelude::*,
26 reload::{self, Handle},
27};
28
29use std::str::FromStr;
30
31type InnerFiltered = Filtered<Layer<Registry>, LevelFilter, Registry>;
33type InnerLayered = Layered<reload::Layer<InnerFiltered, Registry>, Registry>;
35type FileReload = Handle<
37 Filtered<
38 Layer<InnerLayered, DefaultFields, Format, LazyRollingFileAppender>,
39 LevelFilter,
40 InnerLayered,
41 >,
42 InnerLayered,
43>;
44
45pub struct Reloads {
46 console_reload: RwLock<reload::Handle<InnerFiltered, Registry>>,
47 file_reload: RwLock<FileReload>,
48}
49
50pub struct InitiateOnce {
51 init_once: OnceCell<Reloads>,
52}
53
54pub static INITIATE_ONCE: InitiateOnce = InitiateOnce {
55 init_once: OnceCell::new(),
56};
57
58pub static CURRENT_MAX_VERBOSITY: AtomicUsize = AtomicUsize::new(0);
65
66#[inline]
68pub fn level_may_be_enabled(level: tracing::Level) -> bool {
69 let needed = match level {
70 tracing::Level::ERROR => 1,
71 tracing::Level::WARN => 2,
72 tracing::Level::INFO => 3,
73 tracing::Level::DEBUG => 4,
74 tracing::Level::TRACE => 5,
75 };
76 CURRENT_MAX_VERBOSITY.load(Ordering::Relaxed) >= needed
77}
78
79const FILE_DIRECTORY: &str = "glide-logs";
80const ENV_GLIDE_LOG_DIR: &str = "GLIDE_LOG_DIR";
81
82struct LazyRollingFileAppender {
86 file_appender: OnceCell<RollingFileAppender>,
87 rotation: Rotation,
88 directory: PathBuf,
89 filename_prefix: PathBuf,
90}
91
92impl LazyRollingFileAppender {
93 fn new(
94 rotation: Rotation,
95 directory: impl AsRef<Path>,
96 filename_prefix: impl AsRef<Path>,
97 ) -> LazyRollingFileAppender {
98 LazyRollingFileAppender {
99 file_appender: OnceCell::new(),
100 rotation,
101 directory: directory.as_ref().to_path_buf(),
102 filename_prefix: filename_prefix.as_ref().to_path_buf(),
103 }
104 }
105}
106
107impl<'a> tracing_subscriber::fmt::writer::MakeWriter<'a> for LazyRollingFileAppender {
108 type Writer = RollingWriter<'a>;
109 fn make_writer(&'a self) -> Self::Writer {
110 let file_appender = self.file_appender.get_or_init(|| {
111 RollingFileAppender::new(
112 self.rotation.clone(),
113 self.directory.clone(),
114 self.filename_prefix.clone(),
115 )
116 });
117 file_appender.make_writer()
118 }
119}
120
121#[derive(Debug)]
122pub enum Level {
123 Error = 0,
124 Warn = 1,
125 Info = 2,
126 Debug = 3,
127 Trace = 4,
128 Off = 5,
129}
130impl Level {
131 fn to_filter(&self) -> filter::LevelFilter {
132 match self {
133 Level::Trace => LevelFilter::TRACE,
134 Level::Debug => LevelFilter::DEBUG,
135 Level::Info => LevelFilter::INFO,
136 Level::Warn => LevelFilter::WARN,
137 Level::Error => LevelFilter::ERROR,
138 Level::Off => LevelFilter::OFF,
139 }
140 }
141}
142
143pub fn create_directory_from_env(envname: &str) -> Option<String> {
147 let Ok(dirpath) = std::env::var(envname) else {
148 return None;
149 };
150
151 if dirpath.trim().is_empty() || std::fs::create_dir_all(&dirpath).is_err() {
152 return None;
153 }
154
155 Some(dirpath)
156}
157
158pub fn init(minimal_level: Option<Level>, file_name: Option<&str>) -> Level {
163 let level = minimal_level.unwrap_or(Level::Warn);
164 let level_filter = level.to_filter();
165 let reloads = INITIATE_ONCE.init_once.get_or_init(|| {
166 let stdout_fmt = tracing_subscriber::fmt::layer()
167 .with_ansi(true)
168 .with_filter(LevelFilter::OFF);
169
170 let (stdout_layer, stdout_reload) = reload::Layer::new(stdout_fmt);
171
172 let logs_dir =
174 create_directory_from_env(ENV_GLIDE_LOG_DIR).unwrap_or(FILE_DIRECTORY.to_string());
175 let file_appender = LazyRollingFileAppender::new(
176 Rotation::HOURLY,
177 logs_dir,
178 file_name.unwrap_or("output.log"),
179 );
180
181 let file_fmt = tracing_subscriber::fmt::layer()
182 .with_writer(file_appender)
183 .with_filter(LevelFilter::OFF);
184 let (file_layer, file_reload) = reload::Layer::new(file_fmt);
185
186 let log_level = if let Ok(level) = std::env::var("RUST_LOG") {
188 let trace_level = tracing::Level::from_str(&level).unwrap_or(tracing::Level::TRACE);
189 LevelFilter::from(trace_level)
190 } else {
191 LevelFilter::TRACE
192 };
193
194 let targets_filter = filter::Targets::new()
196 .with_target("glide", log_level)
197 .with_target("redis", log_level)
198 .with_target("glide_logger", log_level)
199 .with_target(std::env!("CARGO_PKG_NAME"), log_level);
200
201 tracing_subscriber::registry()
205 .with(stdout_layer)
206 .with(file_layer)
207 .with(targets_filter)
208 .try_init()
209 .ok(); let reloads: Reloads = Reloads {
212 console_reload: RwLock::new(stdout_reload),
213 file_reload: RwLock::new(file_reload),
214 };
215 reloads
216 });
217
218 match file_name {
219 None => {
220 let _ = reloads
221 .console_reload
222 .write()
223 .expect("error reloading stdout")
224 .modify(|layer| *layer.filter_mut() = level_filter);
225 let _ = reloads
226 .file_reload
227 .write()
228 .expect("error reloading file appender")
229 .modify(|layer| {
230 *layer.filter_mut() = LevelFilter::OFF;
231 });
232 }
233 Some(file) => {
234 let logs_dir =
236 create_directory_from_env(ENV_GLIDE_LOG_DIR).unwrap_or(FILE_DIRECTORY.to_string());
237 let file_appender = LazyRollingFileAppender::new(Rotation::HOURLY, logs_dir, file);
238 let _ = reloads
239 .file_reload
240 .write()
241 .expect("error reloading file appender")
242 .modify(|layer| {
243 *layer.filter_mut() = level_filter;
244 *layer.inner_mut().writer_mut() = file_appender;
245 });
246 let _ = reloads
247 .console_reload
248 .write()
249 .expect("error reloading stdout")
250 .modify(|layer| *layer.filter_mut() = LevelFilter::OFF);
251 }
252 };
253 CURRENT_MAX_VERBOSITY.store(
255 match level {
256 Level::Off => 0,
257 Level::Error => 1,
258 Level::Warn => 2,
259 Level::Info => 3,
260 Level::Debug => 4,
261 Level::Trace => 5,
262 },
263 Ordering::Relaxed,
264 );
265 level
266}
267
268macro_rules! create_log {
269 ($name:ident, $uppercase_level:tt) => {
270 pub fn $name<Message: AsRef<str>, Identifier: AsRef<str>>(
271 log_identifier: Identifier,
272 message: Message,
273 ) {
274 if INITIATE_ONCE.init_once.get().is_none() {
275 init(Some(Level::Warn), None);
276 };
277 let message_ref = message.as_ref();
278 let identifier_ref = log_identifier.as_ref();
279 event!(
280 tracing::Level::$uppercase_level,
281 "{identifier_ref} - {message_ref}"
282 )
283 }
284 };
285}
286
287create_log!(log_trace, TRACE);
288create_log!(log_debug, DEBUG);
289create_log!(log_info, INFO);
290create_log!(log_warn, WARN);
291create_log!(log_error, ERROR);
292
293#[macro_export]
302macro_rules! log_trace_lazy {
303 ($identifier:expr, $message:expr) => {
304 if $crate::level_may_be_enabled(tracing::Level::TRACE)
305 && tracing::event_enabled!(tracing::Level::TRACE)
306 {
307 $crate::log_trace($identifier, $message);
308 }
309 };
310}
311
312#[macro_export]
313macro_rules! log_debug_lazy {
314 ($identifier:expr, $message:expr) => {
315 if $crate::level_may_be_enabled(tracing::Level::DEBUG)
316 && tracing::event_enabled!(tracing::Level::DEBUG)
317 {
318 $crate::log_debug($identifier, $message);
319 }
320 };
321}
322
323#[macro_export]
324macro_rules! log_info_lazy {
325 ($identifier:expr, $message:expr) => {
326 if $crate::level_may_be_enabled(tracing::Level::INFO)
327 && tracing::event_enabled!(tracing::Level::INFO)
328 {
329 $crate::log_info($identifier, $message);
330 }
331 };
332}
333
334#[macro_export]
335macro_rules! log_warn_lazy {
336 ($identifier:expr, $message:expr) => {
337 if tracing::event_enabled!(tracing::Level::WARN) {
338 $crate::log_warn($identifier, $message);
339 }
340 };
341}
342
343#[macro_export]
344macro_rules! log_error_lazy {
345 ($identifier:expr, $message:expr) => {
346 if tracing::event_enabled!(tracing::Level::ERROR) {
347 $crate::log_error($identifier, $message);
348 }
349 };
350}
351
352#[macro_export]
360macro_rules! log_warn_rate_limited {
361 ($identifier:expr, $interval_secs:expr, $message:expr) => {{
362 static LAST_LOG: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
363 let now = std::time::SystemTime::now()
364 .duration_since(std::time::UNIX_EPOCH)
365 .unwrap_or_default()
366 .as_secs();
367 let last = LAST_LOG.load(std::sync::atomic::Ordering::Relaxed);
368 if now >= last + $interval_secs {
369 LAST_LOG.store(now, std::sync::atomic::Ordering::Relaxed);
370 $crate::log_warn($identifier, $message);
371 }
372 }};
373}
374
375#[macro_export]
376macro_rules! log_info_rate_limited {
377 ($identifier:expr, $interval_secs:expr, $message:expr) => {{
378 static LAST_LOG: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
379 let now = std::time::SystemTime::now()
380 .duration_since(std::time::UNIX_EPOCH)
381 .unwrap_or_default()
382 .as_secs();
383 let last = LAST_LOG.load(std::sync::atomic::Ordering::Relaxed);
384 if now >= last + $interval_secs {
385 LAST_LOG.store(now, std::sync::atomic::Ordering::Relaxed);
386 $crate::log_info($identifier, $message);
387 }
388 }};
389}
390
391#[macro_export]
392macro_rules! log_debug_rate_limited {
393 ($identifier:expr, $interval_secs:expr, $message:expr) => {{
394 static LAST_LOG: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
395 let now = std::time::SystemTime::now()
396 .duration_since(std::time::UNIX_EPOCH)
397 .unwrap_or_default()
398 .as_secs();
399 let last = LAST_LOG.load(std::sync::atomic::Ordering::Relaxed);
400 if now >= last + $interval_secs {
401 LAST_LOG.store(now, std::sync::atomic::Ordering::Relaxed);
402 $crate::log_debug($identifier, $message);
403 }
404 }};
405}
406
407pub fn log<Message: AsRef<str>, Identifier: AsRef<str>>(
412 log_level: Level,
413 log_identifier: Identifier,
414 message: Message,
415) {
416 match log_level {
417 Level::Debug => log_debug(log_identifier, message),
418 Level::Trace => log_trace(log_identifier, message),
419 Level::Info => log_info(log_identifier, message),
420 Level::Warn => log_warn(log_identifier, message),
421 Level::Error => log_error(log_identifier, message),
422 Level::Off => (),
423 }
424}
425
426#[cfg(test)]
427mod tests {
428 use super::*;
429
430 #[test]
431 fn test_directory_from_env() {
432 let dir_path = format!("{}/glide-logs", std::env::temp_dir().display());
433 let _ = std::fs::remove_dir_all(&dir_path);
436 assert!(std::fs::create_dir_all(&dir_path).is_ok());
438
439 unsafe { std::env::set_var(ENV_GLIDE_LOG_DIR, &dir_path) };
440 assert!(create_directory_from_env(ENV_GLIDE_LOG_DIR).is_some());
441 assert!(std::fs::metadata(&dir_path).is_ok());
442
443 let _ = std::fs::remove_dir_all(&dir_path);
445
446 assert!(std::fs::create_dir_all(&dir_path).is_ok());
448 assert!(std::fs::metadata(&dir_path).is_ok());
449
450 unsafe { std::env::set_var(ENV_GLIDE_LOG_DIR, &dir_path) };
451 assert!(create_directory_from_env(ENV_GLIDE_LOG_DIR).is_some());
452
453 let _ = std::fs::remove_dir_all(&dir_path);
455
456 unsafe { std::env::set_var(ENV_GLIDE_LOG_DIR, "") };
458 assert!(create_directory_from_env(ENV_GLIDE_LOG_DIR).is_none());
459 }
460}