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