1use std::{
2 collections::{HashMap, VecDeque},
3 hash::{DefaultHasher, Hasher},
4 sync::{
5 OnceLock, RwLock,
6 atomic::{AtomicU8, Ordering},
7 },
8 usize,
9};
10
11use crate::{SCOPE_DEPTH_MAX, SCOPE_STRING_SEP_STR, Scope, ScopeAlloc, env_config};
12
13use log;
14
15static ENV_FILTER: OnceLock<env_config::EnvFilter> = OnceLock::new();
16static SCOPE_MAP: RwLock<Option<GlobalScopeMap>> = RwLock::new(None);
17struct GlobalScopeMap {
18 map: ScopeMap,
19 hash: u64,
20}
21
22const LEVEL_ENABLED_MAX_DEFAULT: log::LevelFilter = log::LevelFilter::Info;
23/// The maximum log level of verbosity that is enabled by default.
24/// All messages more verbose than this level will be discarded
25/// by default unless specially configured.
26///
27/// This is used instead of the `log::max_level` as we need to tell the `log`
28/// crate that the max level is everything, so that we can dynamically enable
29/// logs that are more verbose than this level without the `log` crate throwing
30/// them away before we see them
31static mut LEVEL_ENABLED_MAX_STATIC: log::LevelFilter = LEVEL_ENABLED_MAX_DEFAULT;
32
33/// A cache of the true maximum log level that _could_ be printed. This is based
34/// on the maximally verbose level that is configured by the user, and is used
35/// to filter out logs more verbose than any configured level.
36///
37/// E.g. if `LEVEL_ENABLED_MAX_STATIC `is 'info' but a user has configured some
38/// scope to print at a `debug` level, then this will be `debug`, and all
39/// `trace` logs will be discarded.
40/// Therefore, it should always be `>= LEVEL_ENABLED_MAX_STATIC`
41// PERF: this doesn't need to be an atomic, we don't actually care about race conditions here
42static LEVEL_ENABLED_MAX_CONFIG: AtomicU8 = AtomicU8::new(LEVEL_ENABLED_MAX_DEFAULT as u8);
43
44pub fn init_env_filter(filter: env_config::EnvFilter) {
45 if let Some(level_max) = filter.level_global {
46 unsafe { LEVEL_ENABLED_MAX_STATIC = level_max }
47 }
48 if ENV_FILTER.set(filter).is_err() {
49 panic!("Environment filter cannot be initialized twice");
50 }
51}
52
53pub fn is_possibly_enabled_level(level: log::Level) -> bool {
54 return level as u8 <= LEVEL_ENABLED_MAX_CONFIG.load(Ordering::Relaxed);
55}
56
57pub fn is_scope_enabled(scope: &Scope, level: log::Level) -> bool {
58 if level <= unsafe { LEVEL_ENABLED_MAX_STATIC } {
59 // [FAST PATH]
60 // if the message is at or below the minimum printed log level
61 // (where error < warn < info etc) then always enable
62 return true;
63 }
64 if !is_possibly_enabled_level(level) {
65 // [FAST PATH PT. 2]
66 // if the message is above the maximum enabled log level
67 // (where error < warn < info etc) then disable without checking
68 // scope map
69 return false;
70 }
71 let global_scope_map = SCOPE_MAP.read().unwrap_or_else(|err| {
72 SCOPE_MAP.clear_poison();
73 return err.into_inner();
74 });
75
76 let Some(GlobalScopeMap { map, .. }) = global_scope_map.as_ref() else {
77 // on failure, return false because it's not <= LEVEL_ENABLED_MAX_STATIC
78 return false;
79 };
80
81 if map.is_empty() {
82 // if no scopes are enabled, return false because it's not <= LEVEL_ENABLED_MAX_STATIC
83 return false;
84 }
85 let enabled_status = map.is_enabled(&scope, level);
86 return match enabled_status {
87 // if it isn't configured, then it it's disabled because it's not <= LEVEL_ENABLED_MAX_STATIC
88 EnabledStatus::NotConfigured => false,
89 EnabledStatus::Enabled => true,
90 EnabledStatus::Disabled => false,
91 };
92}
93
94fn hash_scope_map_settings(map: &HashMap<String, String>) -> u64 {
95 let mut hasher = DefaultHasher::new();
96 let mut items = map.iter().collect::<Vec<_>>();
97 items.sort();
98 for (key, value) in items {
99 Hasher::write(&mut hasher, key.as_bytes());
100 Hasher::write(&mut hasher, value.as_bytes());
101 }
102 return hasher.finish();
103}
104
105pub(crate) fn refresh() {
106 refresh_from_settings(&HashMap::default());
107}
108
109pub fn refresh_from_settings(settings: &HashMap<String, String>) {
110 let hash_old = {
111 SCOPE_MAP
112 .read()
113 .unwrap_or_else(|err| {
114 SCOPE_MAP.clear_poison();
115 err.into_inner()
116 })
117 .as_ref()
118 .map(|scope_map| scope_map.hash)
119 };
120 let hash_new = hash_scope_map_settings(settings);
121 if hash_old == Some(hash_new) {
122 return;
123 }
124 let env_config = ENV_FILTER.get();
125 let map_new = ScopeMap::new_from_settings_and_env(settings, env_config);
126 let mut level_enabled_max = unsafe { LEVEL_ENABLED_MAX_STATIC };
127 for entry in &map_new.entries {
128 if let Some(level) = entry.enabled {
129 level_enabled_max = level_enabled_max.max(level.to_level_filter());
130 }
131 }
132 LEVEL_ENABLED_MAX_CONFIG.store(level_enabled_max as u8, Ordering::Release);
133
134 let mut global_map = SCOPE_MAP.write().unwrap_or_else(|err| {
135 SCOPE_MAP.clear_poison();
136 err.into_inner()
137 });
138 global_map.replace(GlobalScopeMap {
139 map: map_new,
140 hash: hash_new,
141 });
142}
143
144fn level_from_level_str(level_str: &String) -> Option<log::Level> {
145 let level = match level_str.to_ascii_lowercase().as_str() {
146 "" => log::Level::Trace,
147 "trace" => log::Level::Trace,
148 "debug" => log::Level::Debug,
149 "info" => log::Level::Info,
150 "warn" => log::Level::Warn,
151 "error" => log::Level::Error,
152 "off" | "disable" | "no" | "none" | "disabled" => {
153 crate::warn!(
154 "Invalid log level \"{level_str}\", set to error to disable non-error logging. Defaulting to error"
155 );
156 log::Level::Error
157 }
158 _ => {
159 crate::warn!("Invalid log level \"{level_str}\", ignoring");
160 return None;
161 }
162 };
163 return Some(level);
164}
165
166fn scope_alloc_from_scope_str(scope_str: &String) -> Option<ScopeAlloc> {
167 let mut scope_buf = [""; SCOPE_DEPTH_MAX];
168 let mut index = 0;
169 let mut scope_iter = scope_str.split(SCOPE_STRING_SEP_STR);
170 while index < SCOPE_DEPTH_MAX {
171 let Some(scope) = scope_iter.next() else {
172 break;
173 };
174 if scope == "" {
175 continue;
176 }
177 scope_buf[index] = scope;
178 index += 1;
179 }
180 if index == 0 {
181 return None;
182 }
183 if let Some(_) = scope_iter.next() {
184 crate::warn!(
185 "Invalid scope key, too many nested scopes: '{scope_str}'. Max depth is {SCOPE_DEPTH_MAX}",
186 );
187 return None;
188 }
189 let scope = scope_buf.map(|s| s.to_string());
190 return Some(scope);
191}
192
193pub struct ScopeMap {
194 entries: Vec<ScopeMapEntry>,
195 root_count: usize,
196}
197
198pub struct ScopeMapEntry {
199 scope: String,
200 enabled: Option<log::Level>,
201 descendants: std::ops::Range<usize>,
202}
203
204#[derive(Debug, Clone, Copy, PartialEq, Eq)]
205pub enum EnabledStatus {
206 Enabled,
207 Disabled,
208 NotConfigured,
209}
210
211impl ScopeMap {
212 pub fn new_from_settings_and_env(
213 items_input_map: &HashMap<String, String>,
214 env_config: Option<&env_config::EnvFilter>,
215 ) -> Self {
216 let mut items = Vec::with_capacity(
217 items_input_map.len() + env_config.map_or(0, |c| c.directive_names.len()),
218 );
219 if let Some(env_filter) = env_config {
220 // TODO: parse on load instead of every reload
221 items.extend(
222 env_filter
223 .directive_names
224 .iter()
225 .zip(env_filter.directive_levels.iter())
226 .filter_map(|(scope, level_filter)| {
227 if items_input_map.get(scope).is_some() {
228 return None;
229 }
230 let scope = scope_alloc_from_scope_str(scope)?;
231 // TODO: use level filters instead of scopes in scope map
232 let level = level_filter.to_level()?;
233
234 Some((scope, level))
235 }),
236 );
237 }
238 items.extend(
239 items_input_map
240 .into_iter()
241 .filter_map(|(scope_str, level_str)| {
242 let scope = scope_alloc_from_scope_str(&scope_str)?;
243 let level = level_from_level_str(&level_str)?;
244 return Some((scope, level));
245 }),
246 );
247
248 items.sort_by(|a, b| a.0.cmp(&b.0));
249
250 let mut this = Self {
251 entries: Vec::with_capacity(items.len() * SCOPE_DEPTH_MAX),
252 root_count: 0,
253 };
254
255 let items_count = items.len();
256
257 struct ProcessQueueEntry {
258 parent_index: usize,
259 depth: usize,
260 items_range: std::ops::Range<usize>,
261 }
262 let mut process_queue = VecDeque::new();
263 process_queue.push_back(ProcessQueueEntry {
264 parent_index: usize::MAX,
265 depth: 0,
266 items_range: 0..items_count,
267 });
268
269 let empty_range = 0..0;
270
271 while let Some(process_entry) = process_queue.pop_front() {
272 let ProcessQueueEntry {
273 items_range,
274 depth,
275 parent_index,
276 } = process_entry;
277 let mut cursor = items_range.start;
278 let res_entries_start = this.entries.len();
279 while cursor < items_range.end {
280 let sub_items_start = cursor;
281 cursor += 1;
282 let scope_name = &items[sub_items_start].0[depth];
283 while cursor < items_range.end && &items[cursor].0[depth] == scope_name {
284 cursor += 1;
285 }
286 let sub_items_end = cursor;
287 if scope_name == "" {
288 assert_eq!(sub_items_start + 1, sub_items_end);
289 assert_ne!(depth, 0);
290 assert_ne!(parent_index, usize::MAX);
291 assert!(this.entries[parent_index].enabled.is_none());
292 this.entries[parent_index].enabled = Some(items[sub_items_start].1);
293 continue;
294 }
295 let is_valid_scope = scope_name != "";
296 let is_last = depth + 1 == SCOPE_DEPTH_MAX || !is_valid_scope;
297 let mut enabled = None;
298 if is_last {
299 assert_eq!(
300 sub_items_start + 1,
301 sub_items_end,
302 "Expected one item: got: {:?}",
303 &items[items_range.clone()]
304 );
305 enabled = Some(items[sub_items_start].1);
306 } else {
307 let entry_index = this.entries.len();
308 process_queue.push_back(ProcessQueueEntry {
309 items_range: sub_items_start..sub_items_end,
310 parent_index: entry_index,
311 depth: depth + 1,
312 });
313 }
314 this.entries.push(ScopeMapEntry {
315 scope: scope_name.to_owned(),
316 enabled,
317 descendants: empty_range.clone(),
318 });
319 }
320 let res_entries_end = this.entries.len();
321 if parent_index != usize::MAX {
322 this.entries[parent_index].descendants = res_entries_start..res_entries_end;
323 } else {
324 this.root_count = res_entries_end;
325 }
326 }
327
328 return this;
329 }
330
331 pub fn is_empty(&self) -> bool {
332 self.entries.is_empty()
333 }
334
335 pub fn is_enabled<S>(&self, scope: &[S; SCOPE_DEPTH_MAX], level: log::Level) -> EnabledStatus
336 where
337 S: AsRef<str>,
338 {
339 let mut enabled = None;
340 let mut cur_range = &self.entries[0..self.root_count];
341 let mut depth = 0;
342
343 'search: while !cur_range.is_empty()
344 && depth < SCOPE_DEPTH_MAX
345 && scope[depth].as_ref() != ""
346 {
347 for entry in cur_range {
348 if entry.scope == scope[depth].as_ref() {
349 // note:
350 enabled = entry.enabled.or(enabled);
351 cur_range = &self.entries[entry.descendants.clone()];
352 depth += 1;
353 continue 'search;
354 }
355 }
356 break 'search;
357 }
358
359 return enabled.map_or(EnabledStatus::NotConfigured, |level_enabled| {
360 if level <= level_enabled {
361 EnabledStatus::Enabled
362 } else {
363 EnabledStatus::Disabled
364 }
365 });
366 }
367}
368
369#[cfg(test)]
370mod tests {
371 use crate::private::scope_new;
372
373 use super::*;
374
375 fn scope_map_from_keys(kv: &[(&str, &str)]) -> ScopeMap {
376 let hash_map: HashMap<String, String> = kv
377 .iter()
378 .map(|(k, v)| (k.to_string(), v.to_string()))
379 .collect();
380 ScopeMap::new_from_settings_and_env(&hash_map, None)
381 }
382
383 #[test]
384 fn test_initialization() {
385 let map = scope_map_from_keys(&[("a.b.c.d", "trace")]);
386 assert_eq!(map.root_count, 1);
387 assert_eq!(map.entries.len(), 4);
388
389 let map = scope_map_from_keys(&[]);
390 assert_eq!(map.root_count, 0);
391 assert_eq!(map.entries.len(), 0);
392
393 let map = scope_map_from_keys(&[("", "trace")]);
394 assert_eq!(map.root_count, 0);
395 assert_eq!(map.entries.len(), 0);
396
397 let map = scope_map_from_keys(&[("foo..bar", "trace")]);
398 assert_eq!(map.root_count, 1);
399 assert_eq!(map.entries.len(), 2);
400
401 let map = scope_map_from_keys(&[
402 ("a.b.c.d", "trace"),
403 ("e.f.g.h", "debug"),
404 ("i.j.k.l", "info"),
405 ("m.n.o.p", "warn"),
406 ("q.r.s.t", "error"),
407 ]);
408 assert_eq!(map.root_count, 5);
409 assert_eq!(map.entries.len(), 20);
410 assert_eq!(map.entries[0].scope, "a");
411 assert_eq!(map.entries[1].scope, "e");
412 assert_eq!(map.entries[2].scope, "i");
413 assert_eq!(map.entries[3].scope, "m");
414 assert_eq!(map.entries[4].scope, "q");
415 }
416
417 fn scope_from_scope_str(scope_str: &'static str) -> Scope {
418 let mut scope_buf = [""; SCOPE_DEPTH_MAX];
419 let mut index = 0;
420 let mut scope_iter = scope_str.split(SCOPE_STRING_SEP_STR);
421 while index < SCOPE_DEPTH_MAX {
422 let Some(scope) = scope_iter.next() else {
423 break;
424 };
425 if scope == "" {
426 continue;
427 }
428 scope_buf[index] = scope;
429 index += 1;
430 }
431 assert_ne!(index, 0);
432 assert!(scope_iter.next().is_none());
433 return scope_buf;
434 }
435
436 #[test]
437 fn test_is_enabled() {
438 let map = scope_map_from_keys(&[
439 ("a.b.c.d", "trace"),
440 ("e.f.g.h", "debug"),
441 ("i.j.k.l", "info"),
442 ("m.n.o.p", "warn"),
443 ("q.r.s.t", "error"),
444 ]);
445 use log::Level;
446 assert_eq!(
447 map.is_enabled(&scope_from_scope_str("a.b.c.d"), Level::Trace),
448 EnabledStatus::Enabled
449 );
450 assert_eq!(
451 map.is_enabled(&scope_from_scope_str("a.b.c.d"), Level::Debug),
452 EnabledStatus::Enabled
453 );
454
455 assert_eq!(
456 map.is_enabled(&scope_from_scope_str("e.f.g.h"), Level::Debug),
457 EnabledStatus::Enabled
458 );
459 assert_eq!(
460 map.is_enabled(&scope_from_scope_str("e.f.g.h"), Level::Info),
461 EnabledStatus::Enabled
462 );
463 assert_eq!(
464 map.is_enabled(&scope_from_scope_str("e.f.g.h"), Level::Trace),
465 EnabledStatus::Disabled
466 );
467
468 assert_eq!(
469 map.is_enabled(&scope_from_scope_str("i.j.k.l"), Level::Info),
470 EnabledStatus::Enabled
471 );
472 assert_eq!(
473 map.is_enabled(&scope_from_scope_str("i.j.k.l"), Level::Warn),
474 EnabledStatus::Enabled
475 );
476 assert_eq!(
477 map.is_enabled(&scope_from_scope_str("i.j.k.l"), Level::Debug),
478 EnabledStatus::Disabled
479 );
480
481 assert_eq!(
482 map.is_enabled(&scope_from_scope_str("m.n.o.p"), Level::Warn),
483 EnabledStatus::Enabled
484 );
485 assert_eq!(
486 map.is_enabled(&scope_from_scope_str("m.n.o.p"), Level::Error),
487 EnabledStatus::Enabled
488 );
489 assert_eq!(
490 map.is_enabled(&scope_from_scope_str("m.n.o.p"), Level::Info),
491 EnabledStatus::Disabled
492 );
493
494 assert_eq!(
495 map.is_enabled(&scope_from_scope_str("q.r.s.t"), Level::Error),
496 EnabledStatus::Enabled
497 );
498 assert_eq!(
499 map.is_enabled(&scope_from_scope_str("q.r.s.t"), Level::Warn),
500 EnabledStatus::Disabled
501 );
502 }
503
504 fn scope_map_from_keys_and_env(kv: &[(&str, &str)], env: &env_config::EnvFilter) -> ScopeMap {
505 let hash_map: HashMap<String, String> = kv
506 .iter()
507 .map(|(k, v)| (k.to_string(), v.to_string()))
508 .collect();
509 ScopeMap::new_from_settings_and_env(&hash_map, Some(env))
510 }
511
512 #[test]
513 fn test_initialization_with_env() {
514 let env_filter = env_config::parse("a.b=debug,u=error").unwrap();
515 let map = scope_map_from_keys_and_env(&[], &env_filter);
516 assert_eq!(map.root_count, 2);
517 assert_eq!(map.entries.len(), 3);
518 assert_eq!(
519 map.is_enabled(&scope_new(&["a"]), log::Level::Debug),
520 EnabledStatus::NotConfigured
521 );
522 assert_eq!(
523 map.is_enabled(&scope_new(&["a", "b"]), log::Level::Debug),
524 EnabledStatus::Enabled
525 );
526 assert_eq!(
527 map.is_enabled(&scope_new(&["a", "b", "c"]), log::Level::Trace),
528 EnabledStatus::Disabled
529 );
530
531 let env_filter = env_config::parse("a.b=debug,e.f.g.h=trace,u=error").unwrap();
532 let map = scope_map_from_keys_and_env(
533 &[
534 ("a.b.c.d", "trace"),
535 ("e.f.g.h", "debug"),
536 ("i.j.k.l", "info"),
537 ("m.n.o.p", "warn"),
538 ("q.r.s.t", "error"),
539 ],
540 &env_filter,
541 );
542 assert_eq!(map.root_count, 6);
543 assert_eq!(map.entries.len(), 21);
544 assert_eq!(map.entries[0].scope, "a");
545 assert_eq!(map.entries[1].scope, "e");
546 assert_eq!(map.entries[2].scope, "i");
547 assert_eq!(map.entries[3].scope, "m");
548 assert_eq!(map.entries[4].scope, "q");
549 assert_eq!(map.entries[5].scope, "u");
550 assert_eq!(
551 map.is_enabled(&scope_new(&["a", "b", "c", "d"]), log::Level::Trace),
552 EnabledStatus::Enabled
553 );
554 assert_eq!(
555 map.is_enabled(&scope_new(&["a", "b", "c"]), log::Level::Trace),
556 EnabledStatus::Disabled
557 );
558 assert_eq!(
559 map.is_enabled(&scope_new(&["u", "v"]), log::Level::Warn),
560 EnabledStatus::Disabled
561 );
562 // settings override env
563 assert_eq!(
564 map.is_enabled(&scope_new(&["e", "f", "g", "h"]), log::Level::Trace),
565 EnabledStatus::Disabled,
566 );
567 }
568}