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}