zlog.rs

  1//! # logger
  2pub use log as log_impl;
  3
  4mod env_config;
  5
  6pub const SCOPE_DEPTH_MAX: usize = 4;
  7
  8pub fn init_from_env() {
  9    let Ok(env_config) = std::env::var("ZED_LOG").or_else(|_| std::env::var("RUST_LOG")) else {
 10        return;
 11    };
 12    match env_config::parse(&env_config) {
 13        Ok(filter) => {
 14            scope_map::init_env_filter(filter);
 15            scope_map::refresh();
 16            // TODO: set max level once removing `env_logger` and `simple_log` crates
 17        }
 18        Err(err) => {
 19            eprintln!("Failed to parse log filter: {}", err);
 20        }
 21    }
 22}
 23
 24/// because we are currently just wrapping the `log` crate in `zlog`,
 25/// we need to work around the fact that the `log` crate only provides a
 26/// single global level filter. In order to have more precise control until
 27/// we no longer wrap `log`, we bump up the priority of log level so that it
 28/// will be logged, even if the actual level is lower
 29/// This is fine for now, as we use a `info` level filter by default in releases,
 30/// which hopefully won't result in confusion like `warn` or `error` levels might.
 31pub fn min_printed_log_level(level: log_impl::Level) -> log_impl::Level {
 32    // this logic is defined based on the logic used in the `log` crate,
 33    // which checks that a logs level is <= both of these values,
 34    // so we take the minimum of the two values to ensure that check passes
 35    let level_min_static = log_impl::STATIC_MAX_LEVEL;
 36    let level_min_dynamic = log_impl::max_level();
 37    if level <= level_min_static && level <= level_min_dynamic {
 38        return level;
 39    }
 40    return log_impl::LevelFilter::min(level_min_static, level_min_dynamic)
 41        .to_level()
 42        .unwrap_or(level);
 43}
 44
 45#[macro_export]
 46macro_rules! log {
 47    ($logger:expr, $level:expr, $($arg:tt)+) => {
 48        let level = $level;
 49        let logger = $logger;
 50        let (enabled, level) = $crate::scope_map::is_scope_enabled(&logger.scope, level);
 51        if enabled {
 52            $crate::log_impl::log!(level, "[{}]: {}", &logger.fmt_scope(), format!($($arg)+));
 53        }
 54    }
 55}
 56
 57#[macro_export]
 58macro_rules! trace {
 59    ($logger:expr => $($arg:tt)+) => {
 60        $crate::log!($logger, $crate::log_impl::Level::Trace, $($arg)+);
 61    };
 62    ($($arg:tt)+) => {
 63        $crate::log!($crate::default_logger!(), $crate::log_impl::Level::Trace, $($arg)+);
 64    };
 65}
 66
 67#[macro_export]
 68macro_rules! debug {
 69    ($logger:expr => $($arg:tt)+) => {
 70        $crate::log!($logger, $crate::log_impl::Level::Debug, $($arg)+);
 71    };
 72    ($($arg:tt)+) => {
 73        $crate::log!($crate::default_logger!(), $crate::log_impl::Level::Debug, $($arg)+);
 74    };
 75}
 76
 77#[macro_export]
 78macro_rules! info {
 79    ($logger:expr => $($arg:tt)+) => {
 80        $crate::log!($logger, $crate::log_impl::Level::Info, $($arg)+);
 81    };
 82    ($($arg:tt)+) => {
 83        $crate::log!($crate::default_logger!(), $crate::log_impl::Level::Info, $($arg)+);
 84    };
 85}
 86
 87#[macro_export]
 88macro_rules! warn {
 89    ($logger:expr => $($arg:tt)+) => {
 90        $crate::log!($logger, $crate::log_impl::Level::Warn, $($arg)+);
 91    };
 92    ($($arg:tt)+) => {
 93        $crate::log!($crate::default_logger!(), $crate::log_impl::Level::Warn, $($arg)+);
 94    };
 95}
 96
 97#[macro_export]
 98macro_rules! error {
 99    ($logger:expr => $($arg:tt)+) => {
100        $crate::log!($logger, $crate::log_impl::Level::Error, $($arg)+);
101    };
102    ($($arg:tt)+) => {
103        $crate::log!($crate::default_logger!(), $crate::log_impl::Level::Error, $($arg)+);
104    };
105}
106
107/// Creates a timer that logs the duration it was active for either when
108/// it is dropped, or when explicitly stopped using the `end` method.
109/// Logs at the `trace` level.
110/// Note that it will include time spent across await points
111/// (i.e. should not be used to measure the performance of async code)
112/// However, this is a feature not a bug, as it allows for a more accurate
113/// understanding of how long the action actually took to complete, including
114/// interruptions, which can help explain why something may have timed out,
115/// why it took longer to complete than it would had the await points resolved
116/// immediately, etc.
117#[macro_export]
118macro_rules! time {
119    ($logger:expr => $name:expr) => {
120        $crate::Timer::new($logger, $name)
121    };
122    ($name:expr) => {
123        time!($crate::default_logger!() => $name)
124    };
125}
126
127#[macro_export]
128macro_rules! scoped {
129    ($parent:expr => $name:expr) => {{
130        let parent = $parent;
131        let name = $name;
132        let mut scope = parent.scope;
133        let mut index = 1; // always have crate/module name
134        while index < scope.len() && !scope[index].is_empty() {
135            index += 1;
136        }
137        if index >= scope.len() {
138            #[cfg(debug_assertions)]
139            {
140                panic!("Scope overflow trying to add scope {}", name);
141            }
142            #[cfg(not(debug_assertions))]
143            {
144                $crate::warn!(
145                    parent =>
146                    "Scope overflow trying to add scope {}... ignoring scope",
147                    name
148                );
149            }
150        }
151        scope[index] = name;
152        $crate::Logger { scope }
153    }};
154    ($name:expr) => {
155        $crate::scoped!($crate::default_logger!() => $name)
156    };
157}
158
159#[macro_export]
160macro_rules! default_logger {
161    () => {
162        $crate::Logger {
163            scope: $crate::private::scope_new(&[$crate::crate_name!()]),
164        }
165    };
166}
167
168#[macro_export]
169macro_rules! crate_name {
170    () => {
171        $crate::private::extract_crate_name_from_module_path(module_path!())
172    };
173}
174
175/// functions that are used in macros, and therefore must be public,
176/// but should not be used directly
177pub mod private {
178    use super::*;
179
180    pub fn extract_crate_name_from_module_path(module_path: &'static str) -> &'static str {
181        return module_path
182            .split_once("::")
183            .map(|(crate_name, _)| crate_name)
184            .unwrap_or(module_path);
185    }
186
187    pub fn scope_new(scopes: &[&'static str]) -> Scope {
188        assert!(scopes.len() <= SCOPE_DEPTH_MAX);
189        let mut scope = [""; SCOPE_DEPTH_MAX];
190        scope[0..scopes.len()].copy_from_slice(scopes);
191        scope
192    }
193
194    pub fn scope_alloc_new(scopes: &[&str]) -> ScopeAlloc {
195        assert!(scopes.len() <= SCOPE_DEPTH_MAX);
196        let mut scope = [""; SCOPE_DEPTH_MAX];
197        scope[0..scopes.len()].copy_from_slice(scopes);
198        scope.map(|s| s.to_string())
199    }
200
201    pub fn scope_to_alloc(scope: &Scope) -> ScopeAlloc {
202        return scope.map(|s| s.to_string());
203    }
204}
205
206pub type Scope = [&'static str; SCOPE_DEPTH_MAX];
207pub type ScopeAlloc = [String; SCOPE_DEPTH_MAX];
208const SCOPE_STRING_SEP: &'static str = ".";
209
210#[derive(Clone, Copy, Debug, PartialEq, Eq)]
211pub struct Logger {
212    pub scope: Scope,
213}
214
215impl Logger {
216    pub fn fmt_scope(&self) -> String {
217        let mut last = 0;
218        for s in self.scope {
219            if s.is_empty() {
220                break;
221            }
222            last += 1;
223        }
224
225        return self.scope[0..last].join(SCOPE_STRING_SEP);
226    }
227}
228
229pub struct Timer {
230    pub logger: Logger,
231    pub start_time: std::time::Instant,
232    pub name: &'static str,
233    pub warn_if_longer_than: Option<std::time::Duration>,
234    pub done: bool,
235}
236
237impl Drop for Timer {
238    fn drop(&mut self) {
239        self.finish();
240    }
241}
242
243impl Timer {
244    #[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"]
245    pub fn new(logger: Logger, name: &'static str) -> Self {
246        return Self {
247            logger,
248            name,
249            start_time: std::time::Instant::now(),
250            warn_if_longer_than: None,
251            done: false,
252        };
253    }
254    pub fn warn_if_gt(mut self, warn_limit: std::time::Duration) -> Self {
255        self.warn_if_longer_than = Some(warn_limit);
256        return self;
257    }
258
259    pub fn end(mut self) {
260        self.finish();
261    }
262
263    fn finish(&mut self) {
264        if self.done {
265            return;
266        }
267        let elapsed = self.start_time.elapsed();
268        if let Some(warn_limit) = self.warn_if_longer_than {
269            if elapsed > warn_limit {
270                crate::warn!(
271                    self.logger =>
272                    "Timer '{}' took {:?}. Which was longer than the expected limit of {:?}",
273                    self.name,
274                    elapsed,
275                    warn_limit
276                );
277                self.done = true;
278                return;
279            }
280        }
281        crate::trace!(
282            self.logger =>
283            "Timer '{}' finished in {:?}",
284            self.name,
285            elapsed
286        );
287        self.done = true;
288    }
289}
290
291pub mod scope_map {
292    use std::{
293        collections::{HashMap, VecDeque},
294        hash::{DefaultHasher, Hasher},
295        sync::{
296            OnceLock, RwLock,
297            atomic::{AtomicU64, Ordering},
298        },
299        usize,
300    };
301
302    use super::*;
303    static ENV_FILTER: OnceLock<env_config::EnvFilter> = OnceLock::new();
304    static SCOPE_MAP: RwLock<Option<ScopeMap>> = RwLock::new(None);
305    static SCOPE_MAP_HASH: AtomicU64 = AtomicU64::new(0);
306
307    pub fn init_env_filter(filter: env_config::EnvFilter) {
308        if ENV_FILTER.set(filter).is_err() {
309            panic!("Environment filter cannot be initialized twice");
310        }
311    }
312
313    pub fn is_scope_enabled(scope: &Scope, level: log_impl::Level) -> (bool, log_impl::Level) {
314        let level_min = min_printed_log_level(level);
315        if level <= level_min {
316            // [FAST PATH]
317            // if the message is at or below the minimum printed log level
318            // (where error < warn < info etc) then always enable
319            return (true, level);
320        }
321
322        let Ok(map) = SCOPE_MAP.read() else {
323            // on failure, default to enabled detection done by `log` crate
324            return (true, level);
325        };
326
327        let Some(map) = map.as_ref() else {
328            // on failure, default to enabled detection done by `log` crate
329            return (true, level);
330        };
331
332        if map.is_empty() {
333            // if no scopes are enabled, default to enabled detection done by `log` crate
334            return (true, level);
335        }
336        let enabled_status = map.is_enabled(&scope, level);
337        match enabled_status {
338            EnabledStatus::NotConfigured => {
339                // if this scope isn't configured, default to enabled detection done by `log` crate
340                return (true, level);
341            }
342            EnabledStatus::Enabled => {
343                // if this scope is enabled, enable logging
344                // note: bumping level to min level that will be printed
345                // to work around log crate limitations
346                return (true, level_min);
347            }
348            EnabledStatus::Disabled => {
349                // if the configured level is lower than the requested level, disable logging
350                // note: err = 0, warn = 1, etc.
351                return (false, level);
352            }
353        }
354    }
355
356    fn hash_scope_map_settings(map: &HashMap<String, String>) -> u64 {
357        let mut hasher = DefaultHasher::new();
358        let mut items = map.iter().collect::<Vec<_>>();
359        items.sort();
360        for (key, value) in items {
361            Hasher::write(&mut hasher, key.as_bytes());
362            Hasher::write(&mut hasher, value.as_bytes());
363        }
364        return hasher.finish();
365    }
366
367    pub(crate) fn refresh() {
368        refresh_from_settings(&HashMap::default());
369    }
370
371    pub fn refresh_from_settings(settings: &HashMap<String, String>) {
372        let hash_old = SCOPE_MAP_HASH.load(Ordering::Acquire);
373        let hash_new = hash_scope_map_settings(settings);
374        if hash_old == hash_new && hash_old != 0 {
375            return;
376        }
377        let env_config = ENV_FILTER.get();
378        let map_new = ScopeMap::new_from_settings_and_env(settings, env_config);
379
380        if let Ok(_) = SCOPE_MAP_HASH.compare_exchange(
381            hash_old,
382            hash_new,
383            Ordering::Release,
384            Ordering::Relaxed,
385        ) {
386            let mut map = SCOPE_MAP.write().unwrap_or_else(|err| {
387                SCOPE_MAP.clear_poison();
388                err.into_inner()
389            });
390            *map = Some(map_new);
391        }
392    }
393
394    fn level_from_level_str(level_str: &String) -> Option<log_impl::Level> {
395        let level = match level_str.to_ascii_lowercase().as_str() {
396            "" => log_impl::Level::Trace,
397            "trace" => log_impl::Level::Trace,
398            "debug" => log_impl::Level::Debug,
399            "info" => log_impl::Level::Info,
400            "warn" => log_impl::Level::Warn,
401            "error" => log_impl::Level::Error,
402            "off" | "disable" | "no" | "none" | "disabled" => {
403                crate::warn!(
404                    "Invalid log level \"{level_str}\", set to error to disable non-error logging. Defaulting to error"
405                );
406                log_impl::Level::Error
407            }
408            _ => {
409                crate::warn!("Invalid log level \"{level_str}\", ignoring");
410                return None;
411            }
412        };
413        return Some(level);
414    }
415
416    fn scope_alloc_from_scope_str(scope_str: &String) -> Option<ScopeAlloc> {
417        let mut scope_buf = [""; SCOPE_DEPTH_MAX];
418        let mut index = 0;
419        let mut scope_iter = scope_str.split(SCOPE_STRING_SEP);
420        while index < SCOPE_DEPTH_MAX {
421            let Some(scope) = scope_iter.next() else {
422                break;
423            };
424            if scope == "" {
425                continue;
426            }
427            scope_buf[index] = scope;
428            index += 1;
429        }
430        if index == 0 {
431            return None;
432        }
433        if let Some(_) = scope_iter.next() {
434            crate::warn!(
435                "Invalid scope key, too many nested scopes: '{scope_str}'. Max depth is {SCOPE_DEPTH_MAX}",
436            );
437            return None;
438        }
439        let scope = scope_buf.map(|s| s.to_string());
440        return Some(scope);
441    }
442
443    pub struct ScopeMap {
444        entries: Vec<ScopeMapEntry>,
445        root_count: usize,
446    }
447
448    pub struct ScopeMapEntry {
449        scope: String,
450        enabled: Option<log_impl::Level>,
451        descendants: std::ops::Range<usize>,
452    }
453
454    #[derive(Debug, Clone, Copy, PartialEq, Eq)]
455    pub enum EnabledStatus {
456        Enabled,
457        Disabled,
458        NotConfigured,
459    }
460
461    impl ScopeMap {
462        pub fn new_from_settings_and_env(
463            items_input_map: &HashMap<String, String>,
464            env_config: Option<&env_config::EnvFilter>,
465        ) -> Self {
466            let mut items = Vec::with_capacity(
467                items_input_map.len() + env_config.map_or(0, |c| c.directive_names.len()),
468            );
469            if let Some(env_filter) = env_config {
470                // TODO: parse on load instead of every reload
471                items.extend(
472                    env_filter
473                        .directive_names
474                        .iter()
475                        .zip(env_filter.directive_levels.iter())
476                        .filter_map(|(scope, level_filter)| {
477                            if items_input_map.get(scope).is_some() {
478                                return None;
479                            }
480                            let scope = scope_alloc_from_scope_str(scope)?;
481                            // TODO: use level filters instead of scopes in scope map
482                            let level = level_filter.to_level()?;
483
484                            Some((scope, level))
485                        }),
486                );
487            }
488            items.extend(
489                items_input_map
490                    .into_iter()
491                    .filter_map(|(scope_str, level_str)| {
492                        let scope = scope_alloc_from_scope_str(&scope_str)?;
493                        let level = level_from_level_str(&level_str)?;
494                        return Some((scope, level));
495                    }),
496            );
497
498            items.sort_by(|a, b| a.0.cmp(&b.0));
499
500            let mut this = Self {
501                entries: Vec::with_capacity(items.len() * SCOPE_DEPTH_MAX),
502                root_count: 0,
503            };
504
505            let items_count = items.len();
506
507            struct ProcessQueueEntry {
508                parent_index: usize,
509                depth: usize,
510                items_range: std::ops::Range<usize>,
511            }
512            let mut process_queue = VecDeque::new();
513            process_queue.push_back(ProcessQueueEntry {
514                parent_index: usize::MAX,
515                depth: 0,
516                items_range: 0..items_count,
517            });
518
519            let empty_range = 0..0;
520
521            while let Some(process_entry) = process_queue.pop_front() {
522                let ProcessQueueEntry {
523                    items_range,
524                    depth,
525                    parent_index,
526                } = process_entry;
527                let mut cursor = items_range.start;
528                let res_entries_start = this.entries.len();
529                while cursor < items_range.end {
530                    let sub_items_start = cursor;
531                    cursor += 1;
532                    let scope_name = &items[sub_items_start].0[depth];
533                    while cursor < items_range.end && &items[cursor].0[depth] == scope_name {
534                        cursor += 1;
535                    }
536                    let sub_items_end = cursor;
537                    if scope_name == "" {
538                        assert_eq!(sub_items_start + 1, sub_items_end);
539                        assert_ne!(depth, 0);
540                        assert_ne!(parent_index, usize::MAX);
541                        assert!(this.entries[parent_index].enabled.is_none());
542                        this.entries[parent_index].enabled = Some(items[sub_items_start].1);
543                        continue;
544                    }
545                    let is_valid_scope = scope_name != "";
546                    let is_last = depth + 1 == SCOPE_DEPTH_MAX || !is_valid_scope;
547                    let mut enabled = None;
548                    if is_last {
549                        assert_eq!(
550                            sub_items_start + 1,
551                            sub_items_end,
552                            "Expected one item: got: {:?}",
553                            &items[items_range.clone()]
554                        );
555                        enabled = Some(items[sub_items_start].1);
556                    } else {
557                        let entry_index = this.entries.len();
558                        process_queue.push_back(ProcessQueueEntry {
559                            items_range: sub_items_start..sub_items_end,
560                            parent_index: entry_index,
561                            depth: depth + 1,
562                        });
563                    }
564                    this.entries.push(ScopeMapEntry {
565                        scope: scope_name.to_owned(),
566                        enabled,
567                        descendants: empty_range.clone(),
568                    });
569                }
570                let res_entries_end = this.entries.len();
571                if parent_index != usize::MAX {
572                    this.entries[parent_index].descendants = res_entries_start..res_entries_end;
573                } else {
574                    this.root_count = res_entries_end;
575                }
576            }
577
578            return this;
579        }
580
581        pub fn is_empty(&self) -> bool {
582            self.entries.is_empty()
583        }
584
585        pub fn is_enabled<S>(
586            &self,
587            scope: &[S; SCOPE_DEPTH_MAX],
588            level: log_impl::Level,
589        ) -> EnabledStatus
590        where
591            S: AsRef<str>,
592        {
593            let mut enabled = None;
594            let mut cur_range = &self.entries[0..self.root_count];
595            let mut depth = 0;
596
597            'search: while !cur_range.is_empty()
598                && depth < SCOPE_DEPTH_MAX
599                && scope[depth].as_ref() != ""
600            {
601                for entry in cur_range {
602                    if entry.scope == scope[depth].as_ref() {
603                        // note:
604                        enabled = entry.enabled.or(enabled);
605                        cur_range = &self.entries[entry.descendants.clone()];
606                        depth += 1;
607                        continue 'search;
608                    }
609                }
610                break 'search;
611            }
612
613            return enabled.map_or(EnabledStatus::NotConfigured, |level_enabled| {
614                if level <= level_enabled {
615                    EnabledStatus::Enabled
616                } else {
617                    EnabledStatus::Disabled
618                }
619            });
620        }
621    }
622
623    #[cfg(test)]
624    mod tests {
625        use crate::private::scope_new;
626
627        use super::*;
628
629        fn scope_map_from_keys(kv: &[(&str, &str)]) -> ScopeMap {
630            let hash_map: HashMap<String, String> = kv
631                .iter()
632                .map(|(k, v)| (k.to_string(), v.to_string()))
633                .collect();
634            ScopeMap::new_from_settings_and_env(&hash_map, None)
635        }
636
637        #[test]
638        fn test_initialization() {
639            let map = scope_map_from_keys(&[("a.b.c.d", "trace")]);
640            assert_eq!(map.root_count, 1);
641            assert_eq!(map.entries.len(), 4);
642
643            let map = scope_map_from_keys(&[]);
644            assert_eq!(map.root_count, 0);
645            assert_eq!(map.entries.len(), 0);
646
647            let map = scope_map_from_keys(&[("", "trace")]);
648            assert_eq!(map.root_count, 0);
649            assert_eq!(map.entries.len(), 0);
650
651            let map = scope_map_from_keys(&[("foo..bar", "trace")]);
652            assert_eq!(map.root_count, 1);
653            assert_eq!(map.entries.len(), 2);
654
655            let map = scope_map_from_keys(&[
656                ("a.b.c.d", "trace"),
657                ("e.f.g.h", "debug"),
658                ("i.j.k.l", "info"),
659                ("m.n.o.p", "warn"),
660                ("q.r.s.t", "error"),
661            ]);
662            assert_eq!(map.root_count, 5);
663            assert_eq!(map.entries.len(), 20);
664            assert_eq!(map.entries[0].scope, "a");
665            assert_eq!(map.entries[1].scope, "e");
666            assert_eq!(map.entries[2].scope, "i");
667            assert_eq!(map.entries[3].scope, "m");
668            assert_eq!(map.entries[4].scope, "q");
669        }
670
671        fn scope_from_scope_str(scope_str: &'static str) -> Scope {
672            let mut scope_buf = [""; SCOPE_DEPTH_MAX];
673            let mut index = 0;
674            let mut scope_iter = scope_str.split(SCOPE_STRING_SEP);
675            while index < SCOPE_DEPTH_MAX {
676                let Some(scope) = scope_iter.next() else {
677                    break;
678                };
679                if scope == "" {
680                    continue;
681                }
682                scope_buf[index] = scope;
683                index += 1;
684            }
685            assert_ne!(index, 0);
686            assert!(scope_iter.next().is_none());
687            return scope_buf;
688        }
689
690        #[test]
691        fn test_is_enabled() {
692            let map = scope_map_from_keys(&[
693                ("a.b.c.d", "trace"),
694                ("e.f.g.h", "debug"),
695                ("i.j.k.l", "info"),
696                ("m.n.o.p", "warn"),
697                ("q.r.s.t", "error"),
698            ]);
699            use log_impl::Level;
700            assert_eq!(
701                map.is_enabled(&scope_from_scope_str("a.b.c.d"), Level::Trace),
702                EnabledStatus::Enabled
703            );
704            assert_eq!(
705                map.is_enabled(&scope_from_scope_str("a.b.c.d"), Level::Debug),
706                EnabledStatus::Enabled
707            );
708
709            assert_eq!(
710                map.is_enabled(&scope_from_scope_str("e.f.g.h"), Level::Debug),
711                EnabledStatus::Enabled
712            );
713            assert_eq!(
714                map.is_enabled(&scope_from_scope_str("e.f.g.h"), Level::Info),
715                EnabledStatus::Enabled
716            );
717            assert_eq!(
718                map.is_enabled(&scope_from_scope_str("e.f.g.h"), Level::Trace),
719                EnabledStatus::Disabled
720            );
721
722            assert_eq!(
723                map.is_enabled(&scope_from_scope_str("i.j.k.l"), Level::Info),
724                EnabledStatus::Enabled
725            );
726            assert_eq!(
727                map.is_enabled(&scope_from_scope_str("i.j.k.l"), Level::Warn),
728                EnabledStatus::Enabled
729            );
730            assert_eq!(
731                map.is_enabled(&scope_from_scope_str("i.j.k.l"), Level::Debug),
732                EnabledStatus::Disabled
733            );
734
735            assert_eq!(
736                map.is_enabled(&scope_from_scope_str("m.n.o.p"), Level::Warn),
737                EnabledStatus::Enabled
738            );
739            assert_eq!(
740                map.is_enabled(&scope_from_scope_str("m.n.o.p"), Level::Error),
741                EnabledStatus::Enabled
742            );
743            assert_eq!(
744                map.is_enabled(&scope_from_scope_str("m.n.o.p"), Level::Info),
745                EnabledStatus::Disabled
746            );
747
748            assert_eq!(
749                map.is_enabled(&scope_from_scope_str("q.r.s.t"), Level::Error),
750                EnabledStatus::Enabled
751            );
752            assert_eq!(
753                map.is_enabled(&scope_from_scope_str("q.r.s.t"), Level::Warn),
754                EnabledStatus::Disabled
755            );
756        }
757
758        fn scope_map_from_keys_and_env(
759            kv: &[(&str, &str)],
760            env: &env_config::EnvFilter,
761        ) -> ScopeMap {
762            let hash_map: HashMap<String, String> = kv
763                .iter()
764                .map(|(k, v)| (k.to_string(), v.to_string()))
765                .collect();
766            ScopeMap::new_from_settings_and_env(&hash_map, Some(env))
767        }
768
769        #[test]
770        fn test_initialization_with_env() {
771            let env_filter = env_config::parse("a.b=debug,u=error").unwrap();
772            let map = scope_map_from_keys_and_env(&[], &env_filter);
773            assert_eq!(map.root_count, 2);
774            assert_eq!(map.entries.len(), 3);
775            assert_eq!(
776                map.is_enabled(&scope_new(&["a"]), log_impl::Level::Debug),
777                EnabledStatus::NotConfigured
778            );
779            assert_eq!(
780                map.is_enabled(&scope_new(&["a", "b"]), log_impl::Level::Debug),
781                EnabledStatus::Enabled
782            );
783            assert_eq!(
784                map.is_enabled(&scope_new(&["a", "b", "c"]), log_impl::Level::Trace),
785                EnabledStatus::Disabled
786            );
787
788            let env_filter = env_config::parse("a.b=debug,e.f.g.h=trace,u=error").unwrap();
789            let map = scope_map_from_keys_and_env(
790                &[
791                    ("a.b.c.d", "trace"),
792                    ("e.f.g.h", "debug"),
793                    ("i.j.k.l", "info"),
794                    ("m.n.o.p", "warn"),
795                    ("q.r.s.t", "error"),
796                ],
797                &env_filter,
798            );
799            assert_eq!(map.root_count, 6);
800            assert_eq!(map.entries.len(), 21);
801            assert_eq!(map.entries[0].scope, "a");
802            assert_eq!(map.entries[1].scope, "e");
803            assert_eq!(map.entries[2].scope, "i");
804            assert_eq!(map.entries[3].scope, "m");
805            assert_eq!(map.entries[4].scope, "q");
806            assert_eq!(map.entries[5].scope, "u");
807            assert_eq!(
808                map.is_enabled(&scope_new(&["a", "b", "c", "d"]), log_impl::Level::Trace),
809                EnabledStatus::Enabled
810            );
811            assert_eq!(
812                map.is_enabled(&scope_new(&["a", "b", "c"]), log_impl::Level::Trace),
813                EnabledStatus::Disabled
814            );
815            assert_eq!(
816                map.is_enabled(&scope_new(&["u", "v"]), log_impl::Level::Warn),
817                EnabledStatus::Disabled
818            );
819            // settings override env
820            assert_eq!(
821                map.is_enabled(&scope_new(&["e", "f", "g", "h"]), log_impl::Level::Trace),
822                EnabledStatus::Disabled,
823            );
824        }
825    }
826}
827
828#[cfg(test)]
829mod tests {
830    use super::*;
831
832    #[test]
833    fn test_crate_name() {
834        assert_eq!(crate_name!(), "zlog");
835    }
836}