Next Generation WASM Microkernel Operating System
1// Copyright 2025 Jonas Kruckenberg
2//
3// Licensed under the Apache License, Version 2.0, <LICENSE-APACHE or
4// http://apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or
5// http://opensource.org/licenses/MIT>, at your option. This file may not be
6// copied, modified, or distributed except according to those terms.
7
8use alloc::format;
9use alloc::string::String;
10use alloc::vec::Vec;
11use core::cmp::Ordering;
12use core::fmt;
13use core::fmt::Formatter;
14use core::str::FromStr;
15
16use fallible_iterator::{FallibleIterator, IteratorExt};
17use smallvec::SmallVec;
18use tracing::level_filters::STATIC_MAX_LEVEL;
19use tracing_core::metadata::ParseLevelFilterError;
20use tracing_core::{Level, LevelFilter, Metadata};
21
22#[derive(Debug)]
23pub enum Error {
24 UnexpectedEof,
25 TooManyEqualSigns,
26 InvalidLevelFilter,
27}
28
29impl From<ParseLevelFilterError> for Error {
30 fn from(_: ParseLevelFilterError) -> Self {
31 Self::InvalidLevelFilter
32 }
33}
34
35impl fmt::Display for Error {
36 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
37 match self {
38 Error::UnexpectedEof => writeln!(f, "input must not be empty"),
39 Error::TooManyEqualSigns => {
40 writeln!(f, "too many '=' in filter directive, expected 0 or 1")
41 }
42 // Error::TooManyFieldListBegins => {
43 // writeln!(f, "too many '[{{' in filter directive, expected 0 or 1")
44 // }
45 // Error::MissingFieldListEnd => writeln!(f, "expected fields list to end with '}}]'"),
46 Error::InvalidLevelFilter => writeln!(f, "encountered invalid level filter str"),
47 }
48 }
49}
50
51impl core::error::Error for Error {}
52
53pub struct Filter {
54 directives: SmallVec<[Directive; 8]>,
55 max_level: LevelFilter,
56}
57
58impl Default for Filter {
59 fn default() -> Self {
60 Self {
61 directives: SmallVec::new(),
62 max_level: LevelFilter::DEBUG,
63 }
64 }
65}
66
67impl FromStr for Filter {
68 type Err = Error;
69
70 fn from_str(s: &str) -> Result<Self, Self::Err> {
71 if s.is_empty() {
72 return Ok(Self::default());
73 }
74
75 let iter = s
76 .split(',')
77 .filter(|s| !s.is_empty())
78 .map(Directive::from_str)
79 .transpose_into_fallible();
80
81 Self::from_directives(iter)
82 }
83}
84
85impl Filter {
86 fn from_directives(
87 mut directives: impl FallibleIterator<Item = Directive, Error = Error>,
88 ) -> Result<Self, Error> {
89 let mut disabled = Vec::new();
90 let mut enabled = SmallVec::new();
91 let mut max_level = LevelFilter::OFF;
92
93 while let Some(directive) = directives.next()? {
94 if directive.level > STATIC_MAX_LEVEL {
95 disabled.push(directive);
96 } else {
97 if directive.level > max_level {
98 max_level = directive.level;
99 }
100
101 // insert the directive into the vec of directives, ordered by
102 // specificity (length of target + number of field filters). this
103 // ensures that, when finding a directive to match a span or event, we
104 // search the directive set in most specific first order.
105 match enabled.binary_search(&directive) {
106 Ok(i) => enabled[i] = directive,
107 Err(i) => enabled.insert(i, directive),
108 }
109 }
110 }
111
112 if !disabled.is_empty() {
113 tracing::warn!(
114 "some trace filter directives would enable traces that are disabled statically"
115 );
116 for directive in disabled {
117 let target = if let Some(target) = &directive.target {
118 format!("the `{target}` target")
119 } else {
120 "all targets".into()
121 };
122 let level = directive
123 .level
124 .into_level()
125 .expect("=off would not have enabled any filters");
126
127 tracing::warn!("`{directive:?}` would enable the {level} level for {target}");
128 }
129
130 tracing::warn!("the static max level is `{STATIC_MAX_LEVEL}`");
131
132 let help_msg = || {
133 let (feature, filter) = match STATIC_MAX_LEVEL.into_level() {
134 Some(Level::TRACE) => unreachable!(
135 "if the max level is trace, no static filtering features are enabled"
136 ),
137 Some(Level::DEBUG) => ("max_level_debug", Level::TRACE),
138 Some(Level::INFO) => ("max_level_info", Level::DEBUG),
139 Some(Level::WARN) => ("max_level_warn", Level::INFO),
140 Some(Level::ERROR) => ("max_level_error", Level::WARN),
141 None => return ("max_level_off", String::new()),
142 };
143 (feature, format!("{filter} "))
144 };
145 let (feature, earlier_level) = help_msg();
146 tracing::warn!(
147 "to enable {earlier_level}logging, remove the `{feature}` feature from the `tracing` crate"
148 );
149 }
150
151 tracing::debug!("{enabled:?} {max_level:?}");
152
153 Ok(Self {
154 directives: enabled,
155 max_level,
156 })
157 }
158
159 pub fn max_level(&self) -> LevelFilter {
160 self.max_level
161 }
162
163 pub(super) fn enabled(&self, meta: &Metadata<'_>) -> bool {
164 let level = *meta.level();
165 if self.max_level < level {
166 return false;
167 };
168
169 match self.directives_for(meta).next() {
170 Some(d) => d.level >= level,
171 None => true,
172 }
173 }
174
175 fn directives_for<'a>(
176 &self,
177 meta: &'a Metadata<'a>,
178 ) -> impl Iterator<Item = &Directive> + use<'_, 'a> {
179 self.directives.iter().filter(|d| d.cares_about(meta))
180 }
181}
182
183#[derive(Debug, PartialEq, Eq)]
184struct Directive {
185 level: LevelFilter,
186 target: Option<String>,
187}
188
189impl Directive {
190 fn cares_about(&self, meta: &Metadata<'_>) -> bool {
191 // Does this directive have a target filter, and does it match the
192 // metadata's target?
193 if let Some(ref target) = self.target
194 && !meta.target().starts_with(&target[..])
195 {
196 return false;
197 }
198
199 true
200 }
201}
202
203impl FromStr for Directive {
204 type Err = Error;
205
206 fn from_str(s: &str) -> Result<Self, Self::Err> {
207 // This method parses a filtering directive in one of the following
208 // forms:
209 //
210 // * `foo=trace` (TARGET=LEVEL)
211 // * `trace` (bare LEVEL)
212 // * `foo` (bare TARGET)
213 let mut split = s.split('=');
214 let part0 = split.next().ok_or(Error::UnexpectedEof)?;
215
216 // Directive includes an `=`:
217 // * `foo=trace`
218 if let Some(part1) = split.next() {
219 if split.next().is_some() {
220 return Err(Error::TooManyEqualSigns);
221 }
222
223 let mut split = part0.split("[{");
224 let target = split.next().map(String::from);
225
226 let level = part1.parse()?;
227 return Ok(Self { level, target });
228 }
229
230 // Okay, the part after the `=` was empty, the directive is either a
231 // bare level or a bare target.
232 // * `foo`
233 // * `info`
234 Ok(match part0.parse::<LevelFilter>() {
235 Ok(level) => Self {
236 level,
237 target: None,
238 },
239 Err(_) => Self {
240 target: Some(String::from(part0)),
241 level: LevelFilter::TRACE,
242 },
243 })
244 }
245}
246
247impl PartialOrd<Self> for Directive {
248 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
249 Some(self.cmp(other))
250 }
251}
252
253impl Ord for Directive {
254 fn cmp(&self, other: &Self) -> Ordering {
255 // We attempt to order directives by how "specific" they are. This
256 // ensures that we try the most specific directives first when
257 // attempting to match a piece of metadata.
258
259 // First, we compare based on whether a target is specified, and the
260 // lengths of those targets if both have targets.
261 let ordering = self
262 .target
263 .as_ref()
264 .map(String::len)
265 .cmp(&other.target.as_ref().map(String::len))
266 .reverse();
267
268 #[cfg(debug_assertions)]
269 {
270 if ordering == Ordering::Equal {
271 debug_assert_eq!(
272 self.target, other.target,
273 "invariant violated: Ordering::Equal must imply a.target == b.target"
274 );
275 }
276 }
277
278 ordering
279 }
280}