1use std::cmp;
2use std::str::FromStr;
3
4use tracing::{Level, Metadata};
5use tracing_subscriber::layer::Context;
6
7#[derive(Debug, Clone, derive_builder::Builder)]
8#[non_exhaustive]
9#[builder(default, derive(Debug), setter(into))]
10pub struct LoggingOptions {
12 pub verbose: bool,
14
15 pub otlp_endpoint: Option<String>,
17
18 pub app_name: String,
20
21 pub levels: LogFilters,
23}
24
25impl LoggingOptions {
26 #[must_use]
28 pub fn with_level(level: LogLevel) -> Self {
29 Self {
30 levels: LogFilters::with_level(level),
31 ..Default::default()
32 }
33 }
34}
35
36impl Default for LoggingOptions {
37 fn default() -> Self {
38 Self {
39 verbose: Default::default(),
40 otlp_endpoint: Default::default(),
41 app_name: "app".to_owned(),
42 levels: Default::default(),
43 }
44 }
45}
46
47#[derive(Debug, Default, Clone, derive_builder::Builder)]
48#[builder(default)]
49#[non_exhaustive]
50pub struct LogFilters {
52 pub telemetry: FilterOptions,
54 pub stderr: FilterOptions,
56}
57
58impl LogFilters {
59 #[must_use]
61 pub fn with_level(level: LogLevel) -> Self {
62 Self {
63 telemetry: FilterOptions {
64 level,
65 ..Default::default()
66 },
67 stderr: FilterOptions {
68 level,
69 ..Default::default()
70 },
71 }
72 }
73}
74
75#[derive(Debug, Clone)]
77#[non_exhaustive]
78pub struct FilterOptions {
79 pub level: LogLevel,
81 pub filter: Vec<TargetLevel>,
83}
84
85impl Default for FilterOptions {
86 fn default() -> Self {
87 Self {
88 level: LogLevel::Info,
89 filter: vec![],
90 }
91 }
92}
93
94impl FilterOptions {
95 #[must_use]
97 pub fn new(level: LogLevel, filter: Vec<TargetLevel>) -> Self {
98 Self { level, filter }
99 }
100
101 fn test_enabled(&self, module: &str, level: Level) -> bool {
102 let matches = self.filter.iter().filter(|config| module.starts_with(&config.target));
103 let match_hit = matches.fold(None, |acc, next| {
104 let enabled = next.modifier.compare(filter_as_usize(level), next.level as usize);
105 let next_len = next.target.len();
106 acc.map_or(Some((next_len, enabled)), |(last_len, last_enabled)| {
107 match next_len.cmp(&last_len) {
108 cmp::Ordering::Greater => {
109 Some((next_len, enabled))
111 }
112 cmp::Ordering::Equal => {
113 Some((last_len, enabled && last_enabled))
115 }
116 cmp::Ordering::Less => {
117 Some((last_len, last_enabled))
119 }
120 }
121 })
122 });
123 match_hit.map_or(self.level >= level, |(_, enabled)| enabled)
124 }
125}
126
127impl<S> tracing_subscriber::layer::Filter<S> for FilterOptions
128where
129 S: tracing::Subscriber + for<'lookup> tracing_subscriber::registry::LookupSpan<'lookup>,
130{
131 fn enabled(&self, metadata: &Metadata<'_>, _cx: &Context<'_, S>) -> bool {
132 let enabled = metadata.target().starts_with("wick")
133 || metadata.target().starts_with("flow")
134 || metadata.target().starts_with("wasmrs");
135
136 metadata.is_span() || enabled
137 }
138
139 fn event_enabled(&self, event: &tracing::Event<'_>, _cx: &Context<'_, S>) -> bool {
140 let module = event.metadata().target().split("::").next().unwrap_or_default();
141 let level = event.metadata().level();
142 self.test_enabled(module, *level)
143 }
144}
145
146#[derive(Debug, Default, PartialEq, Clone)]
148#[non_exhaustive]
149pub struct TargetLevel {
150 pub target: String,
152 pub level: LogLevel,
154 pub modifier: LogModifier,
156}
157
158impl TargetLevel {
159 pub fn new<T: Into<String>>(target: T, level: LogLevel, modifier: LogModifier) -> Self {
161 Self {
162 target: target.into(),
163 level,
164 modifier,
165 }
166 }
167
168 #[must_use]
170 pub fn not<T: Into<String>>(target: T, level: LogLevel) -> Self {
171 Self::new(target, level, LogModifier::Not)
172 }
173
174 #[must_use]
176 pub fn gt<T: Into<String>>(target: T, level: LogLevel) -> Self {
177 Self::new(target, level, LogModifier::GreaterThan)
178 }
179
180 #[must_use]
182 pub fn gte<T: Into<String>>(target: T, level: LogLevel) -> Self {
183 Self::new(target, level, LogModifier::GreaterThanOrEqualTo)
184 }
185
186 #[must_use]
188 pub fn lt<T: Into<String>>(target: T, level: LogLevel) -> Self {
189 Self::new(target, level, LogModifier::LessThan)
190 }
191
192 #[must_use]
194 pub fn lte<T: Into<String>>(target: T, level: LogLevel) -> Self {
195 Self::new(target, level, LogModifier::LessThanOrEqualTo)
196 }
197
198 #[must_use]
200 pub fn is<T: Into<String>>(target: T, level: LogLevel) -> Self {
201 Self::new(target, level, LogModifier::Equal)
202 }
203}
204
205impl LoggingOptions {
206 pub fn name<T: Into<String>>(&self, name: T) -> Self {
208 Self {
209 app_name: name.into(),
210 ..self.clone()
211 }
212 }
213}
214
215#[derive(Debug, Clone, PartialEq, Copy)]
216#[non_exhaustive]
217pub enum LogModifier {
219 Not,
221 GreaterThan,
223 GreaterThanOrEqualTo,
225 LessThan,
227 LessThanOrEqualTo,
229 Equal,
231}
232
233impl Default for LogModifier {
234 fn default() -> Self {
235 Self::LessThanOrEqualTo
236 }
237}
238
239impl std::fmt::Display for LogModifier {
240 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
241 match self {
242 LogModifier::Not => write!(f, "!="),
243 LogModifier::GreaterThan => write!(f, ">"),
244 LogModifier::GreaterThanOrEqualTo => write!(f, ">="),
245 LogModifier::LessThan => write!(f, "<"),
246 LogModifier::LessThanOrEqualTo => write!(f, "<="),
247 LogModifier::Equal => write!(f, "="),
248 }
249 }
250}
251
252impl LogModifier {
253 const fn compare(self, a: usize, b: usize) -> bool {
254 match self {
255 LogModifier::Not => a != b,
256 LogModifier::GreaterThan => a > b,
257 LogModifier::GreaterThanOrEqualTo => a >= b,
258 LogModifier::LessThan => a < b,
259 LogModifier::LessThanOrEqualTo => a <= b,
260 LogModifier::Equal => a == b,
261 }
262 }
263}
264
265impl FromStr for LogModifier {
266 type Err = ();
267
268 fn from_str(s: &str) -> Result<Self, Self::Err> {
269 match s {
270 "!=" => Ok(LogModifier::Not),
271 ">" => Ok(LogModifier::GreaterThan),
272 ">=" => Ok(LogModifier::GreaterThanOrEqualTo),
273 "<" => Ok(LogModifier::LessThan),
274 "<=" => Ok(LogModifier::LessThanOrEqualTo),
275 "=" | "==" => Ok(LogModifier::Equal),
276
277 _ => Err(()),
278 }
279 }
280}
281
282#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
284#[non_exhaustive]
285#[repr(usize)]
286pub enum LogLevel {
287 Quiet = 0,
289 Error = 1,
291 Warn = 2,
293 Info = 3,
295 Debug = 4,
297 Trace = 5,
299}
300
301impl Default for LogLevel {
302 fn default() -> Self {
303 Self::Info
304 }
305}
306
307impl FromStr for LogLevel {
308 type Err = ();
309
310 fn from_str(s: &str) -> Result<Self, Self::Err> {
311 match s.to_lowercase().as_str() {
312 "quiet" => Ok(LogLevel::Quiet),
313 "error" => Ok(LogLevel::Error),
314 "warn" => Ok(LogLevel::Warn),
315 "info" => Ok(LogLevel::Info),
316 "debug" => Ok(LogLevel::Debug),
317 "trace" => Ok(LogLevel::Trace),
318 _ => Err(()),
319 }
320 }
321}
322
323impl std::fmt::Display for LogLevel {
324 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
325 match self {
326 LogLevel::Quiet => write!(f, "QUIET"),
327 LogLevel::Error => write!(f, "ERROR"),
328 LogLevel::Warn => write!(f, "WARN"),
329 LogLevel::Info => write!(f, "INFO"),
330 LogLevel::Debug => write!(f, "DEBUG"),
331 LogLevel::Trace => write!(f, "TRACE"),
332 }
333 }
334}
335
336impl PartialEq<Level> for LogLevel {
337 #[inline(always)]
338 fn eq(&self, other: &Level) -> bool {
339 match self {
340 LogLevel::Quiet => false,
341 LogLevel::Error => other.eq(&Level::ERROR),
342 LogLevel::Warn => other.eq(&Level::WARN),
343 LogLevel::Info => other.eq(&Level::INFO),
344 LogLevel::Debug => other.eq(&Level::DEBUG),
345 LogLevel::Trace => other.eq(&Level::TRACE),
346 }
347 }
348}
349
350impl PartialOrd<Level> for LogLevel {
351 #[inline(always)]
352 fn partial_cmp(&self, other: &Level) -> Option<cmp::Ordering> {
353 Some((*self as usize).cmp(&filter_as_usize(*other)))
354 }
355
356 #[inline(always)]
357 fn lt(&self, other: &Level) -> bool {
358 (*self as usize) < filter_as_usize(*other)
359 }
360
361 #[inline(always)]
362 fn le(&self, other: &Level) -> bool {
363 (*self as usize) <= filter_as_usize(*other)
364 }
365
366 #[inline(always)]
367 fn gt(&self, other: &Level) -> bool {
368 (*self as usize) > filter_as_usize(*other)
369 }
370
371 #[inline(always)]
372 fn ge(&self, other: &Level) -> bool {
373 (*self as usize) >= filter_as_usize(*other)
374 }
375}
376
377#[inline(always)]
378const fn filter_as_usize(x: Level) -> usize {
379 (match x {
380 Level::ERROR => 0,
381 Level::WARN => 1,
382 Level::INFO => 2,
383 Level::DEBUG => 3,
384 Level::TRACE => 4,
385 } + 1)
386}
387
388#[cfg(test)]
389mod test {
390
391 use super::*;
392
393 #[test]
394 fn test_modifier_compare() {
395 assert!(LogModifier::Equal.compare(2, 2));
396 assert!(LogModifier::GreaterThan.compare(4, 2));
397 assert!(LogModifier::GreaterThanOrEqualTo.compare(4, 2));
398 assert!(LogModifier::GreaterThanOrEqualTo.compare(2, 2));
399 assert!(LogModifier::Not.compare(4, 3));
400 assert!(LogModifier::LessThan.compare(1, 2));
401 assert!(LogModifier::LessThanOrEqualTo.compare(1, 2));
402 assert!(LogModifier::LessThanOrEqualTo.compare(2, 2));
403 }
404
405 #[test]
406 fn test_modifier_compare_level() {
407 assert!(LogModifier::Equal.compare(filter_as_usize(Level::TRACE), LogLevel::Trace as usize));
408 assert!(LogModifier::GreaterThan.compare(filter_as_usize(Level::TRACE), LogLevel::Warn as usize));
409 assert!(LogModifier::GreaterThanOrEqualTo.compare(filter_as_usize(Level::INFO), LogLevel::Info as usize));
410 assert!(LogModifier::GreaterThanOrEqualTo.compare(filter_as_usize(Level::TRACE), LogLevel::Debug as usize));
411 assert!(LogModifier::Not.compare(filter_as_usize(Level::ERROR), LogLevel::Trace as usize));
412 assert!(LogModifier::LessThan.compare(filter_as_usize(Level::INFO), LogLevel::Debug as usize));
413 assert!(LogModifier::LessThanOrEqualTo.compare(filter_as_usize(Level::TRACE), LogLevel::Trace as usize));
414 assert!(LogModifier::LessThanOrEqualTo.compare(filter_as_usize(Level::INFO), LogLevel::Trace as usize));
415 }
416
417 #[allow(clippy::needless_pass_by_value)]
418 fn opts<const K: usize>(default: LogLevel, targets: [TargetLevel; K]) -> FilterOptions {
419 FilterOptions {
420 level: default,
421 filter: targets.to_vec(),
422 }
423 }
424
425 #[test]
426 fn test_default_level() {
427 assert!(opts(LogLevel::Info, []).test_enabled("wick", Level::INFO));
428 assert!(!opts(LogLevel::Info, []).test_enabled("wick", Level::TRACE));
429 }
430
431 #[rstest::rstest]
432 #[case(LogLevel::Info, [TargetLevel::lte("wick",LogLevel::Trace)], "wick", Level::TRACE, true)]
433 #[case(LogLevel::Info, [TargetLevel::lte("wick",LogLevel::Info),TargetLevel::lte("wick_packet",LogLevel::Trace)], "wick_packet", Level::TRACE, true)]
434 #[case(LogLevel::Info, [
435 TargetLevel::lte("a",LogLevel::Info),
436 TargetLevel::not("ab",LogLevel::Trace),
437 TargetLevel::lte("abc",LogLevel::Trace)
438 ], "abcdef", Level::TRACE, true)]
439 #[case(LogLevel::Info, [
440 TargetLevel::lte("a",LogLevel::Info),
441 TargetLevel::lte("abc",LogLevel::Trace),
442 TargetLevel::not("ab",LogLevel::Trace),
443 ], "abcdef", Level::TRACE, true)]
444 fn test_specificity<const K: usize>(
445 #[case] default: LogLevel,
446 #[case] filter: [TargetLevel; K],
447 #[case] span_target: &str,
448 #[case] span_level: Level,
449 #[case] expect_enabled: bool,
450 ) {
451 assert_eq!(
452 opts(default, filter).test_enabled(span_target, span_level),
453 expect_enabled
454 );
455 }
456
457 #[rstest::rstest]
458 #[case(LogLevel::Info, LogLevel::Trace, Level::TRACE, false)]
459 #[case(LogLevel::Info, LogLevel::Trace, Level::DEBUG, true)]
460 #[case(LogLevel::Info, LogLevel::Trace, Level::INFO, true)]
461 #[case(LogLevel::Info, LogLevel::Trace, Level::WARN, true)]
462 #[case(LogLevel::Info, LogLevel::Trace, Level::ERROR, true)]
463 fn test_not(
464 #[case] default: LogLevel,
465 #[case] target_level: LogLevel,
466 #[case] span_level: Level,
467 #[case] expect_enabled: bool,
468 ) {
469 assert_eq!(
470 opts(default, [TargetLevel::not("wick", target_level)]).test_enabled("wick", span_level),
471 expect_enabled
472 );
473 }
474
475 #[rstest::rstest]
476 #[case(LogLevel::Info, LogLevel::Trace, Level::TRACE, false)]
477 #[case(LogLevel::Info, LogLevel::Trace, Level::DEBUG, true)]
478 #[case(LogLevel::Info, LogLevel::Trace, Level::INFO, true)]
479 #[case(LogLevel::Info, LogLevel::Trace, Level::WARN, true)]
480 #[case(LogLevel::Info, LogLevel::Trace, Level::ERROR, true)]
481 fn test_lt(
482 #[case] default: LogLevel,
483 #[case] target_level: LogLevel,
484 #[case] span_level: Level,
485 #[case] expect_enabled: bool,
486 ) {
487 assert_eq!(
488 opts(default, [TargetLevel::lt("wick", target_level)]).test_enabled("wick", span_level),
489 expect_enabled
490 );
491 }
492
493 #[rstest::rstest]
494 #[case(LogLevel::Info, LogLevel::Trace, Level::TRACE, true)]
495 #[case(LogLevel::Info, LogLevel::Trace, Level::DEBUG, true)]
496 #[case(LogLevel::Info, LogLevel::Trace, Level::INFO, true)]
497 #[case(LogLevel::Info, LogLevel::Trace, Level::WARN, true)]
498 #[case(LogLevel::Info, LogLevel::Trace, Level::ERROR, true)]
499 fn test_lte(
500 #[case] default: LogLevel,
501 #[case] target_level: LogLevel,
502 #[case] span_level: Level,
503 #[case] expect_enabled: bool,
504 ) {
505 assert_eq!(
506 opts(default, [TargetLevel::lte("wick", target_level)]).test_enabled("wick", span_level),
507 expect_enabled
508 );
509 }
510
511 #[rstest::rstest]
512 #[case(LogLevel::Info, LogLevel::Info, Level::TRACE, true)]
513 #[case(LogLevel::Info, LogLevel::Info, Level::DEBUG, true)]
514 #[case(LogLevel::Info, LogLevel::Info, Level::INFO, true)]
515 #[case(LogLevel::Info, LogLevel::Info, Level::WARN, false)]
516 #[case(LogLevel::Info, LogLevel::Info, Level::ERROR, false)]
517 fn test_gte(
518 #[case] default: LogLevel,
519 #[case] target_level: LogLevel,
520 #[case] span_level: Level,
521 #[case] expect_enabled: bool,
522 ) {
523 assert_eq!(
524 opts(default, [TargetLevel::gte("wick", target_level)]).test_enabled("wick", span_level),
525 expect_enabled
526 );
527 }
528
529 #[rstest::rstest]
530 #[case(LogLevel::Info, LogLevel::Info, Level::TRACE, true)]
531 #[case(LogLevel::Info, LogLevel::Info, Level::DEBUG, true)]
532 #[case(LogLevel::Info, LogLevel::Info, Level::INFO, false)]
533 #[case(LogLevel::Info, LogLevel::Info, Level::WARN, false)]
534 #[case(LogLevel::Info, LogLevel::Info, Level::ERROR, false)]
535 fn test_gt(
536 #[case] default: LogLevel,
537 #[case] target_level: LogLevel,
538 #[case] span_level: Level,
539 #[case] expect_enabled: bool,
540 ) {
541 assert_eq!(
542 opts(default, [TargetLevel::gt("wick", target_level)]).test_enabled("wick", span_level),
543 expect_enabled
544 );
545 }
546
547 #[rstest::rstest]
548 #[case(LogLevel::Info, LogLevel::Info, Level::TRACE, false)]
549 #[case(LogLevel::Info, LogLevel::Info, Level::DEBUG, false)]
550 #[case(LogLevel::Info, LogLevel::Info, Level::INFO, true)]
551 #[case(LogLevel::Info, LogLevel::Info, Level::WARN, false)]
552 #[case(LogLevel::Info, LogLevel::Info, Level::ERROR, false)]
553 fn test_eq(
554 #[case] default: LogLevel,
555 #[case] target_level: LogLevel,
556 #[case] span_level: Level,
557 #[case] expect_enabled: bool,
558 ) {
559 assert_eq!(
560 opts(default, [TargetLevel::is("wick", target_level)]).test_enabled("wick", span_level),
561 expect_enabled
562 );
563 }
564}