Skip to main content

witchcraft_log_util/
filter.rs

1// Copyright 2025 Palantir Technologies, Inc.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14//! A prefix-based target filter.
15
16use sequence_trie::SequenceTrie;
17use witchcraft_log::{LevelFilter, Metadata};
18
19/// A prefix-based target filter.
20///
21/// The filter is configured with a top-level [`LevelFilter`] and additional per-target filters. Targets are interpreted
22/// as a hierarchy by splitting on `::`. For example a target `foo::bar` will have a filter for the `foo` target
23/// applied to it if there is not also a filter for `foo::bar` itself.
24pub struct Filter {
25    trie: SequenceTrie<String, LevelFilter>,
26}
27
28impl Filter {
29    /// Returns a new builder.
30    #[inline]
31    pub fn builder() -> Builder {
32        Builder {
33            filter: Filter {
34                trie: SequenceTrie::new(),
35            },
36            root: LevelFilter::Error,
37        }
38    }
39
40    /// Determines if the provided log metadata matches the filter.
41    pub fn enabled(&self, metadata: &Metadata<'_>) -> bool {
42        metadata.level()
43            <= *self
44                .trie
45                .get_ancestor(metadata.target().split("::"))
46                .unwrap()
47    }
48
49    /// Returns the most verbose level in the filter.
50    pub fn max_level(&self) -> LevelFilter {
51        self.trie.values().max().copied().unwrap()
52    }
53}
54
55/// A builder for [`Filter`]s.
56pub struct Builder {
57    filter: Filter,
58    root: LevelFilter,
59}
60
61impl Builder {
62    /// Sets the level used for targets that don't match a more specific directive.
63    ///
64    /// Defaults to [`LevelFilter::Error`].
65    #[inline]
66    pub fn level(mut self, level: LevelFilter) -> Self {
67        self.root = level;
68        self
69    }
70
71    /// Sets the level used for a specific target.
72    #[inline]
73    pub fn target_level(mut self, target: &str, level: LevelFilter) -> Self {
74        self.filter.trie.insert(target.split("::"), level);
75        self
76    }
77
78    /// Consumes the builder, returning a filter.
79    #[inline]
80    pub fn build(mut self) -> Filter {
81        self.filter.trie.insert_owned([], self.root);
82        self.filter
83    }
84}
85
86#[cfg(test)]
87mod test {
88    use witchcraft_log::Level;
89
90    use super::*;
91
92    #[test]
93    fn empty() {
94        let filter = Filter::builder().build();
95
96        assert!(
97            filter.enabled(
98                &Metadata::builder()
99                    .level(Level::Error)
100                    .target("foo")
101                    .build()
102            )
103        );
104
105        assert!(!filter.enabled(&Metadata::builder().level(Level::Warn).target("foo").build()));
106    }
107
108    #[test]
109    fn nonempty() {
110        let filter = Filter::builder()
111            .level(LevelFilter::Warn)
112            .target_level("foo", LevelFilter::Debug)
113            .target_level("foo::bar", LevelFilter::Off)
114            .build();
115
116        assert!(
117            filter.enabled(
118                &Metadata::builder()
119                    .level(Level::Error)
120                    .target("bar")
121                    .build()
122            )
123        );
124        assert!(!filter.enabled(&Metadata::builder().level(Level::Info).target("bar").build()));
125
126        assert!(filter.enabled(&Metadata::builder().level(Level::Info).target("foo").build()));
127        assert!(
128            !filter.enabled(
129                &Metadata::builder()
130                    .level(Level::Trace)
131                    .target("foo")
132                    .build()
133            )
134        );
135
136        assert!(
137            !filter.enabled(
138                &Metadata::builder()
139                    .level(Level::Fatal)
140                    .target("foo::bar")
141                    .build()
142            )
143        );
144
145        assert!(
146            filter.enabled(
147                &Metadata::builder()
148                    .level(Level::Fatal)
149                    .target("foo::buz")
150                    .build()
151            )
152        );
153    }
154}