zlog.rs

  1//! # logger
  2pub use log as log_impl;
  3
  4mod env_config;
  5pub mod filter;
  6pub mod sink;
  7
  8pub use sink::{flush, init_output_file, init_output_stderr, init_output_stdout};
  9
 10pub const SCOPE_DEPTH_MAX: usize = 4;
 11
 12pub fn init() {
 13    if let Err(err) = try_init() {
 14        log::error!("{err}");
 15        eprintln!("{err}");
 16    }
 17}
 18
 19pub fn try_init() -> anyhow::Result<()> {
 20    log::set_logger(&ZLOG)?;
 21    log::set_max_level(log::LevelFilter::max());
 22    process_env();
 23    filter::refresh_from_settings(&std::collections::HashMap::default());
 24    Ok(())
 25}
 26
 27pub fn init_test() {
 28    if get_env_config().is_some() && try_init().is_ok() {
 29        init_output_stdout();
 30    }
 31}
 32
 33fn get_env_config() -> Option<String> {
 34    std::env::var("ZED_LOG")
 35        .or_else(|_| std::env::var("RUST_LOG"))
 36        .ok()
 37}
 38
 39pub fn process_env() {
 40    let Some(env_config) = get_env_config() else {
 41        return;
 42    };
 43    match env_config::parse(&env_config) {
 44        Ok(filter) => {
 45            filter::init_env_filter(filter);
 46        }
 47        Err(err) => {
 48            eprintln!("Failed to parse log filter: {}", err);
 49        }
 50    }
 51}
 52
 53static ZLOG: Zlog = Zlog {};
 54
 55pub struct Zlog {}
 56
 57impl log::Log for Zlog {
 58    fn enabled(&self, metadata: &log::Metadata) -> bool {
 59        filter::is_possibly_enabled_level(metadata.level())
 60    }
 61
 62    fn log(&self, record: &log::Record) {
 63        if !self.enabled(record.metadata()) {
 64            return;
 65        }
 66        let (crate_name_scope, module_scope) = match record.module_path_static() {
 67            Some(module_path) => {
 68                let crate_name = private::extract_crate_name_from_module_path(module_path);
 69                let crate_name_scope = private::scope_new(&[crate_name]);
 70                let module_scope = private::scope_new(&[module_path]);
 71                (crate_name_scope, module_scope)
 72            }
 73            // TODO: when do we hit this
 74            None => (private::scope_new(&[]), private::scope_new(&["*unknown*"])),
 75        };
 76        let level = record.metadata().level();
 77        if !filter::is_scope_enabled(&crate_name_scope, record.module_path(), level) {
 78            return;
 79        }
 80        sink::submit(sink::Record {
 81            scope: module_scope,
 82            level,
 83            message: record.args(),
 84            // PERF(batching): store non-static paths in a cache + leak them and pass static str here
 85            module_path: record.module_path().or(record.file()),
 86        });
 87    }
 88
 89    fn flush(&self) {
 90        sink::flush();
 91    }
 92}
 93
 94#[macro_export]
 95macro_rules! log {
 96    ($logger:expr, $level:expr, $($arg:tt)+) => {
 97        let level = $level;
 98        let logger = $logger;
 99        let enabled = $crate::filter::is_scope_enabled(&logger.scope, Some(module_path!()), level);
100        if enabled {
101            $crate::sink::submit($crate::sink::Record {
102                scope: logger.scope,
103                level,
104                message: &format_args!($($arg)+),
105                module_path: Some(module_path!()),
106            });
107        }
108    }
109}
110
111#[macro_export]
112macro_rules! trace {
113    ($logger:expr => $($arg:tt)+) => {
114        $crate::log!($logger, $crate::log_impl::Level::Trace, $($arg)+);
115    };
116    ($($arg:tt)+) => {
117        $crate::log!($crate::default_logger!(), $crate::log_impl::Level::Trace, $($arg)+);
118    };
119}
120
121#[macro_export]
122macro_rules! debug {
123    ($logger:expr => $($arg:tt)+) => {
124        $crate::log!($logger, $crate::log_impl::Level::Debug, $($arg)+);
125    };
126    ($($arg:tt)+) => {
127        $crate::log!($crate::default_logger!(), $crate::log_impl::Level::Debug, $($arg)+);
128    };
129}
130
131#[macro_export]
132macro_rules! info {
133    ($logger:expr => $($arg:tt)+) => {
134        $crate::log!($logger, $crate::log_impl::Level::Info, $($arg)+);
135    };
136    ($($arg:tt)+) => {
137        $crate::log!($crate::default_logger!(), $crate::log_impl::Level::Info, $($arg)+);
138    };
139}
140
141#[macro_export]
142macro_rules! warn {
143    ($logger:expr => $($arg:tt)+) => {
144        $crate::log!($logger, $crate::log_impl::Level::Warn, $($arg)+);
145    };
146    ($($arg:tt)+) => {
147        $crate::log!($crate::default_logger!(), $crate::log_impl::Level::Warn, $($arg)+);
148    };
149}
150
151#[macro_export]
152macro_rules! error {
153    ($logger:expr => $($arg:tt)+) => {
154        $crate::log!($logger, $crate::log_impl::Level::Error, $($arg)+);
155    };
156    ($($arg:tt)+) => {
157        $crate::log!($crate::default_logger!(), $crate::log_impl::Level::Error, $($arg)+);
158    };
159}
160
161/// Creates a timer that logs the duration it was active for either when
162/// it is dropped, or when explicitly stopped using the `end` method.
163/// Logs at the `trace` level.
164/// Note that it will include time spent across await points
165/// (i.e. should not be used to measure the performance of async code)
166/// However, this is a feature not a bug, as it allows for a more accurate
167/// understanding of how long the action actually took to complete, including
168/// interruptions, which can help explain why something may have timed out,
169/// why it took longer to complete than it would have had the await points resolved
170/// immediately, etc.
171#[macro_export]
172macro_rules! time {
173    ($logger:expr => $name:expr) => {
174        $crate::Timer::new($logger, $name)
175    };
176    ($name:expr) => {
177        time!($crate::default_logger!() => $name)
178    };
179}
180
181#[macro_export]
182macro_rules! scoped {
183    ($parent:expr => $name:expr) => {{
184        let parent = $parent;
185        let name = $name;
186        let mut scope = parent.scope;
187        let mut index = 1; // always have crate/module name
188        while index < scope.len() && !scope[index].is_empty() {
189            index += 1;
190        }
191        if index >= scope.len() {
192            #[cfg(debug_assertions)]
193            {
194                unreachable!("Scope overflow trying to add scope... ignoring scope");
195            }
196        }
197        scope[index] = name;
198        $crate::Logger { scope }
199    }};
200    ($name:expr) => {
201        $crate::scoped!($crate::default_logger!() => $name)
202    };
203}
204
205#[macro_export]
206macro_rules! default_logger {
207    () => {
208        $crate::Logger {
209            scope: $crate::private::scope_new(&[$crate::crate_name!()]),
210        }
211    };
212}
213
214#[macro_export]
215macro_rules! crate_name {
216    () => {
217        $crate::private::extract_crate_name_from_module_path(module_path!())
218    };
219}
220
221/// functions that are used in macros, and therefore must be public,
222/// but should not be used directly
223pub mod private {
224    use super::*;
225
226    pub const fn extract_crate_name_from_module_path(module_path: &str) -> &str {
227        let mut i = 0;
228        let mod_path_bytes = module_path.as_bytes();
229        let mut index = mod_path_bytes.len();
230        while i + 1 < mod_path_bytes.len() {
231            if mod_path_bytes[i] == b':' && mod_path_bytes[i + 1] == b':' {
232                index = i;
233                break;
234            }
235            i += 1;
236        }
237        let Some((crate_name, _)) = module_path.split_at_checked(index) else {
238            return module_path;
239        };
240        crate_name
241    }
242
243    pub const fn scope_new(scopes: &[&'static str]) -> Scope {
244        assert!(scopes.len() <= SCOPE_DEPTH_MAX);
245        let mut scope = [""; SCOPE_DEPTH_MAX];
246        let mut i = 0;
247        while i < scopes.len() {
248            scope[i] = scopes[i];
249            i += 1;
250        }
251        scope
252    }
253
254    pub fn scope_alloc_new(scopes: &[&str]) -> ScopeAlloc {
255        assert!(scopes.len() <= SCOPE_DEPTH_MAX);
256        let mut scope = [""; SCOPE_DEPTH_MAX];
257        scope[0..scopes.len()].copy_from_slice(scopes);
258        scope.map(|s| s.to_string())
259    }
260
261    pub fn scope_to_alloc(scope: &Scope) -> ScopeAlloc {
262        scope.map(|s| s.to_string())
263    }
264}
265
266pub type Scope = [&'static str; SCOPE_DEPTH_MAX];
267pub type ScopeAlloc = [String; SCOPE_DEPTH_MAX];
268const SCOPE_STRING_SEP_STR: &str = ".";
269const SCOPE_STRING_SEP_CHAR: char = '.';
270
271#[derive(Clone, Copy, Debug, PartialEq, Eq)]
272pub struct Logger {
273    pub scope: Scope,
274}
275
276impl log::Log for Logger {
277    fn enabled(&self, metadata: &log::Metadata) -> bool {
278        filter::is_possibly_enabled_level(metadata.level())
279    }
280
281    fn log(&self, record: &log::Record) {
282        if !self.enabled(record.metadata()) {
283            return;
284        }
285        let level = record.metadata().level();
286        if !filter::is_scope_enabled(&self.scope, record.module_path(), level) {
287            return;
288        }
289        sink::submit(sink::Record {
290            scope: self.scope,
291            level,
292            message: record.args(),
293            module_path: record.module_path(),
294        });
295    }
296
297    fn flush(&self) {
298        sink::flush();
299    }
300}
301
302pub struct Timer {
303    pub logger: Logger,
304    pub start_time: std::time::Instant,
305    pub name: &'static str,
306    pub warn_if_longer_than: Option<std::time::Duration>,
307    pub done: bool,
308}
309
310impl Drop for Timer {
311    fn drop(&mut self) {
312        self.finish();
313    }
314}
315
316impl Timer {
317    #[must_use = "Timer will stop when dropped, the result of this function should be saved in a variable prefixed with `_` if it should stop when dropped"]
318    pub fn new(logger: Logger, name: &'static str) -> Self {
319        Self {
320            logger,
321            name,
322            start_time: std::time::Instant::now(),
323            warn_if_longer_than: None,
324            done: false,
325        }
326    }
327
328    pub fn warn_if_gt(mut self, warn_limit: std::time::Duration) -> Self {
329        self.warn_if_longer_than = Some(warn_limit);
330        self
331    }
332
333    pub fn end(mut self) {
334        self.finish();
335    }
336
337    fn finish(&mut self) {
338        if self.done {
339            return;
340        }
341        let elapsed = self.start_time.elapsed();
342        if let Some(warn_limit) = self.warn_if_longer_than
343            && elapsed > warn_limit
344        {
345            crate::warn!(
346                self.logger =>
347                "Timer '{}' took {:?}. Which was longer than the expected limit of {:?}",
348                self.name,
349                elapsed,
350                warn_limit
351            );
352            self.done = true;
353            return;
354        }
355        crate::trace!(
356            self.logger =>
357            "Timer '{}' finished in {:?}",
358            self.name,
359            elapsed
360        );
361        self.done = true;
362    }
363}
364
365#[cfg(test)]
366mod tests {
367    use super::*;
368
369    #[test]
370    fn test_crate_name() {
371        assert_eq!(crate_name!(), "zlog");
372        assert_eq!(
373            private::extract_crate_name_from_module_path("my_speedy_⚡️_crate::some_module"),
374            "my_speedy_⚡️_crate"
375        );
376        assert_eq!(
377            private::extract_crate_name_from_module_path("my_speedy_crate_⚡️::some_module"),
378            "my_speedy_crate_⚡️"
379        );
380        assert_eq!(
381            private::extract_crate_name_from_module_path("my_speedy_crate_:⚡️:some_module"),
382            "my_speedy_crate_:⚡️:some_module"
383        );
384        assert_eq!(
385            private::extract_crate_name_from_module_path("my_speedy_crate_::⚡️some_module"),
386            "my_speedy_crate_"
387        );
388    }
389}