filter.rs

  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}