1use std::sync::{Arc, RwLock};
2
3use evident::event::origin::Origin;
4
5use crate::log_id::{LogId, LogLevel};
6
7use super::{event_entry::AddonKind, msg::LogMsg};
8
9mod filter_builders;
10
11pub use filter_builders::*;
12
13#[derive(Default, Debug)]
14pub struct LogFilter {
15 filter: Arc<RwLock<FilterConfig>>,
16}
17
18#[allow(unreachable_code)]
23#[allow(unused_variables)]
24fn allow_level(level: LogLevel) -> bool {
25 #[cfg(feature = "log_traces")]
26 return true; #[cfg(feature = "log_debugs")]
29 return level >= LogLevel::Debug;
30
31 level > LogLevel::Debug
32}
33
34impl LogFilter {
35 pub fn new() -> Self {
36 let filter_config = FilterConfig::new(&filter_config());
37
38 LogFilter {
39 filter: Arc::new(RwLock::new(filter_config)),
40 }
41 }
42
43 pub fn set_filter(&self, filter_config: FilterConfig) -> Result<(), FilterError> {
44 match self.filter.write() {
45 Ok(mut locked_filter) => {
46 locked_filter.replace(filter_config);
47 }
48 Err(mut err) => {
49 **err.get_mut() = filter_config;
51 }
52 }
53
54 Ok(())
55 }
56
57 pub fn allow_addon(&self, id: LogId, origin: &Origin, addon: &AddonKind) -> bool {
58 if !allow_level(id.log_level) {
59 return false;
60 } else if let Ok(addon_filter) = AddonFilter::try_from(addon) {
61 if addon_filter == AddonFilter::Debugs && !allow_level(LogLevel::Debug)
62 || addon_filter == AddonFilter::Traces && !allow_level(LogLevel::Trace)
63 {
64 return false;
65 }
66 }
67
68 match self.filter.read() {
69 Ok(locked_filter) => locked_filter.allow_addon(id, origin, addon),
70 Err(_) => false,
71 }
72 }
73
74 pub fn show_origin_info(&self, id: LogId, origin: &Origin) -> bool {
75 match self.filter.read() {
76 Ok(locked_filter) => locked_filter.show_origin_info(id, origin),
77 Err(_) => false,
78 }
79 }
80
81 pub fn show_id(&self, id: LogId, origin: &Origin) -> bool {
82 match self.filter.read() {
83 Ok(locked_filter) => locked_filter.show_id(id, origin),
84 Err(_) => false,
85 }
86 }
87}
88
89fn filter_config() -> String {
90 if cfg!(feature = "test_filter") {
91 return "trace(all)".to_string();
92 }
93
94 match std::env::var("LOGID_FILTER") {
95 Ok(config) => config,
96 Err(_) => "error".to_string(),
97 }
98}
99
100pub fn set_filter<T>(into_filter: T) -> Result<(), crate::logging::filter::FilterError>
101where
102 T: Into<FilterConfig>,
103{
104 if let Some(filter) = crate::logging::LOGGER.get_filter() {
105 filter.set_filter(into_filter.into())
106 } else {
107 Err(crate::logging::filter::FilterError::SettingFilter)
108 }
109}
110
111impl evident::event::filter::Filter<LogId, LogMsg> for LogFilter {
112 fn allow_entry(&self, entry: &impl evident::event::entry::EventEntry<LogId, LogMsg>) -> bool {
113 if !allow_level(entry.get_event_id().log_level) {
114 return false;
115 }
116
117 match self.filter.read() {
118 Ok(locked_filter) => locked_filter.allow_entry(entry),
119 Err(_) => false,
120 }
121 }
122}
123
124#[derive(Default, Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
125pub enum AddonFilter {
126 #[default]
127 Id,
128 Origin,
129 Infos,
130 Debugs,
131 Traces,
132 Related,
133 AllAllowed,
134
135 #[cfg(feature = "hint_note")]
136 Hint,
137 #[cfg(feature = "hint_note")]
138 Note,
139
140 #[cfg(feature = "diagnostics")]
141 Diagnostics,
142
143 #[cfg(feature = "payloads")]
144 Payloads,
145}
146
147impl From<&AddonKind> for AddonFilter {
148 fn from(value: &AddonKind) -> Self {
149 match value {
150 AddonKind::Info(_) => AddonFilter::Infos,
151 AddonKind::Debug(_) => AddonFilter::Debugs,
152 AddonKind::Trace(_) => AddonFilter::Traces,
153 AddonKind::Related(_) => AddonFilter::Related,
154
155 #[cfg(feature = "fmt")]
156 AddonKind::FmtInfo(_) => AddonFilter::Infos,
157 #[cfg(feature = "fmt")]
158 AddonKind::FmtDebug(_) => AddonFilter::Debugs,
159 #[cfg(feature = "fmt")]
160 AddonKind::FmtTrace(_) => AddonFilter::Traces,
161
162 #[cfg(feature = "hint_note")]
163 AddonKind::Hint(_) => AddonFilter::Hint,
164 #[cfg(all(feature = "hint_note", feature = "fmt"))]
165 AddonKind::FmtHint(_) => AddonFilter::Hint,
166 #[cfg(feature = "hint_note")]
167 AddonKind::Note(_) => AddonFilter::Note,
168 #[cfg(all(feature = "hint_note", feature = "fmt"))]
169 AddonKind::FmtNote(_) => AddonFilter::Note,
170
171 #[cfg(feature = "diagnostics")]
172 AddonKind::Diagnostic(_) => AddonFilter::Diagnostics,
173 #[cfg(all(feature = "diagnostics", feature = "fmt"))]
174 AddonKind::FmtDiagnostic(_) => AddonFilter::Diagnostics,
175
176 #[cfg(feature = "payloads")]
177 AddonKind::Payload(_) => AddonFilter::Payloads,
178 #[cfg(all(feature = "payloads", feature = "fmt"))]
179 AddonKind::FmtPayload(_) => AddonFilter::Payloads,
180 }
181 }
182}
183
184impl TryFrom<&str> for AddonFilter {
185 type Error = FilterError;
186
187 fn try_from(value: &str) -> Result<Self, Self::Error> {
188 let addon = match value {
189 "id" => AddonFilter::Id,
190 "origin" => AddonFilter::Origin,
191 "infos" => AddonFilter::Infos,
192 "debugs" => AddonFilter::Debugs,
193 "traces" => AddonFilter::Traces,
194 "related" => AddonFilter::Related,
195 "all" => AddonFilter::AllAllowed,
196
197 #[cfg(feature = "hint_note")]
198 "hints" => AddonFilter::Hint,
199 #[cfg(feature = "hint_note")]
200 "notes" => AddonFilter::Note,
201
202 #[cfg(feature = "diagnostics")]
203 "diagnostics" => AddonFilter::Diagnostics,
204
205 #[cfg(feature = "payloads")]
206 "payloads" => AddonFilter::Payloads,
207
208 _ => {
209 return Err(FilterError::ParsingAddons(value.to_string()));
210 }
211 };
212
213 Ok(addon)
214 }
215}
216
217impl IntoIterator for AddonFilter {
218 type Item = Self;
219
220 type IntoIter = std::iter::Once<Self::Item>;
221
222 fn into_iter(self) -> Self::IntoIter {
223 std::iter::once(self)
224 }
225}
226
227#[derive(Default, Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
228pub struct LogIdFilter {
229 module_path: String,
230 identifier: String,
231}
232
233impl PartialEq<LogId> for LogIdFilter {
234 fn eq(&self, other: &LogId) -> bool {
235 self.module_path == other.module_path && self.identifier == other.identifier
236 }
237}
238
239impl TryFrom<&str> for LogIdFilter {
240 type Error = FilterError;
241
242 fn try_from(value: &str) -> Result<Self, Self::Error> {
243 let parts = value.split("::");
244 let len = parts.clone().count();
245
246 if len < 2 {
247 return Err(FilterError::ParsingLogId(value.to_string()));
248 }
249
250 let mut module_parts = String::default();
253 let mut identifier = String::default();
254
255 for (i, part) in parts.enumerate() {
256 if part.trim().is_empty() {
257 return Err(FilterError::ParsingLogId(value.to_string()));
258 }
259
260 if i < len - 1 {
261 module_parts.push_str(part);
262 module_parts.push_str("::");
263 } else {
264 identifier.push_str(part);
265 }
266 }
267 let module_path = module_parts
268 .strip_suffix("::")
269 .ok_or(FilterError::ParsingLogId(value.to_string()))?;
270
271 if identifier.is_empty() {
272 return Err(FilterError::ParsingLogId(value.to_string()));
273 }
274
275 Ok(LogIdFilter {
276 module_path: module_path.to_string(),
277 identifier: identifier.to_string(),
278 })
279 }
280}
281
282#[derive(Default, Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
283pub struct LogIdAddonFilter {
284 log_id: LogIdFilter,
285 allowed_addons: Vec<AddonFilter>,
286}
287
288impl IntoIterator for LogIdAddonFilter {
289 type Item = Self;
290
291 type IntoIter = std::iter::Once<Self>;
292
293 fn into_iter(self) -> Self::IntoIter {
294 std::iter::once(self)
295 }
296}
297
298impl TryFrom<&str> for LogIdAddonFilter {
299 type Error = FilterError;
300
301 fn try_from(value: &str) -> Result<Self, Self::Error> {
302 let mut stripped_id = value.to_string();
303 let addons = get_addons(&mut stripped_id);
304
305 Ok(LogIdAddonFilter {
306 log_id: LogIdFilter::try_from(stripped_id.as_str())?,
307 allowed_addons: addons,
308 })
309 }
310}
311
312#[derive(Default, Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
313pub struct LogIdModuleFilter {
314 no_general_logging: bool,
315 origin_module_path: String,
316 level: LogLevel,
317 allowed_ids: Vec<LogIdAddonFilter>,
318 allowed_addons: Vec<AddonFilter>,
319}
320
321impl IntoIterator for LogIdModuleFilter {
322 type Item = Self;
323
324 type IntoIter = std::iter::Once<Self>;
325
326 fn into_iter(self) -> Self::IntoIter {
327 std::iter::once(self)
328 }
329}
330
331impl LogIdModuleFilter {
332 pub fn origin_in_module(&self, origin: &Origin) -> bool {
333 origin.module_path.starts_with(&self.origin_module_path)
334 }
335
336 pub fn event_allowed(&self, id: LogId, origin: &Origin) -> bool {
337 if !self.origin_in_module(origin) {
338 return false;
339 }
340
341 (!self.no_general_logging && self.level <= id.log_level)
342 || id_allowed(&self.allowed_ids, id)
343 }
344
345 pub fn addon_allowed(&self, id: LogId, origin: &Origin, addon: &AddonFilter) -> bool {
346 if !self.origin_in_module(origin) {
347 return false;
348 }
349
350 (!self.no_general_logging
351 && self.level <= id.log_level
352 && self.allowed_addons.contains(addon))
353 || addon_allowed(&self.allowed_ids, id, addon)
354 }
355
356 fn try_from(
357 s: &str,
358 ids: Vec<LogIdAddonFilter>,
359 addons: Vec<AddonFilter>,
360 ) -> Result<Self, FilterError> {
361 let mut module_filter = s.split('=');
362 let len = module_filter.clone().count();
363 if len != 1 && len != 2 {
364 return Err(FilterError::ParsingModule(s.to_string()));
365 }
366
367 let module = module_filter
368 .next()
369 .ok_or(FilterError::ParsingModule(s.to_string()))?
370 .trim();
371
372 if module.is_empty() {
373 return Err(FilterError::ParsingModule(s.to_string()));
374 }
375
376 if len == 1 {
377 return Ok(LogIdModuleFilter {
378 no_general_logging: true,
379 origin_module_path: module.to_string(),
380 allowed_ids: ids,
381 allowed_addons: addons,
382 ..Default::default()
383 });
384 }
385
386 let level_part = module_filter
387 .next()
388 .ok_or(FilterError::ParsingModule(s.to_string()))?
389 .trim();
390
391 let level =
392 try_into_log_level(level_part).ok_or(FilterError::ParsingModule(s.to_string()))?;
393
394 Ok(LogIdModuleFilter {
395 no_general_logging: false,
396 origin_module_path: module.to_string(),
397 level,
398 allowed_ids: ids,
399 allowed_addons: addons,
400 })
401 }
402}
403
404#[derive(Default, Debug)]
405pub struct FilterConfig {
406 general_logging_enabled: bool,
407 general_level: LogLevel,
408 general_addons: Vec<AddonFilter>,
409 allowed_global_ids: Vec<LogIdAddonFilter>,
411 allowed_modules: Vec<LogIdModuleFilter>,
412}
413
414impl FilterConfig {
415 pub fn new(filter: &str) -> Self {
416 if filter.trim().is_empty() || filter.to_lowercase() == "off" {
417 return FilterConfig {
418 general_logging_enabled: false,
419 ..Default::default()
420 };
421 }
422
423 let mut log_filter = FilterConfig {
424 general_logging_enabled: false,
425 general_level: LogLevel::Error,
426 general_addons: Vec::new(),
427 allowed_global_ids: Vec::new(),
428 allowed_modules: Vec::new(),
429 };
430
431 for filter_part in filter.split(',') {
432 let mut stripped_filter_part = filter_part.to_string();
433
434 let mut ids = get_ids(&mut stripped_filter_part);
435
436 if stripped_filter_part.starts_with("on") && !ids.is_empty() {
437 log_filter.allowed_global_ids.append(&mut ids);
438 } else {
439 let addons = get_addons(&mut stripped_filter_part);
440
441 if let Some(general_level) = try_into_log_level(stripped_filter_part.trim()) {
442 log_filter.general_logging_enabled = true;
443 log_filter.general_level = general_level;
444 log_filter.general_addons = addons;
445 } else if let Ok(module_filter) =
446 LogIdModuleFilter::try_from(&stripped_filter_part, ids, addons)
447 {
448 log_filter.allowed_modules.push(module_filter);
449 }
450 }
451 }
452
453 log_filter
454 }
455
456 fn replace(&mut self, other: Self) {
457 self.allowed_global_ids = other.allowed_global_ids;
458 self.allowed_modules = other.allowed_modules;
459 self.general_addons = other.general_addons;
460 self.general_level = other.general_level;
461 self.general_logging_enabled = other.general_logging_enabled;
462 }
463
464 pub fn allow_addon(&self, id: LogId, origin: &Origin, addon: &AddonKind) -> bool {
465 let addon_filter = AddonFilter::from(addon);
466
467 if self.general_logging_enabled && self.general_addons.contains(&addon_filter) {
468 return true;
469 }
470
471 addon_allowed(&self.allowed_global_ids, id, &addon_filter)
472 || addon_allowed_in_origin(&self.allowed_modules, id, origin, &addon_filter)
473 }
474
475 pub fn show_origin_info(&self, id: LogId, origin: &Origin) -> bool {
476 let addon_filter = AddonFilter::Origin;
477
478 if self.general_logging_enabled && self.general_addons.contains(&addon_filter) {
479 return true;
480 }
481
482 addon_allowed(&self.allowed_global_ids, id, &addon_filter)
483 || addon_allowed_in_origin(&self.allowed_modules, id, origin, &addon_filter)
484 }
485
486 pub fn show_id(&self, id: LogId, origin: &Origin) -> bool {
487 let addon_filter = AddonFilter::Id;
488
489 if self.general_logging_enabled && self.general_addons.contains(&addon_filter) {
490 return true;
491 }
492
493 addon_allowed(&self.allowed_global_ids, id, &addon_filter)
494 || addon_allowed_in_origin(&self.allowed_modules, id, origin, &addon_filter)
495 }
496
497 pub fn builder(log_level: LogLevel) -> FilterConfigBuilder {
498 FilterConfigBuilder::new(log_level)
499 }
500}
501
502impl evident::event::filter::Filter<LogId, LogMsg> for FilterConfig {
503 fn allow_entry(&self, entry: &impl evident::event::entry::EventEntry<LogId, LogMsg>) -> bool {
504 if entry
506 .get_origin()
507 .module_path
508 .starts_with("logid::event_handler")
509 {
510 return true;
511 }
512
513 if self.general_logging_enabled && self.general_level <= entry.get_event_id().log_level {
515 return true;
516 }
517
518 id_allowed(&self.allowed_global_ids, *entry.get_event_id())
519 || id_allowed_in_origin(
520 &self.allowed_modules,
521 *entry.get_event_id(),
522 entry.get_origin(),
523 )
524 }
525}
526
527impl<I> From<(LogLevel, I)> for FilterConfig
528where
529 I: IntoIterator<Item = AddonFilter>,
530{
531 fn from((level, addons): (LogLevel, I)) -> Self {
532 FilterConfig::builder(level).allowed_addons(addons).build()
533 }
534}
535
536#[derive(Debug, Clone)]
537pub enum FilterError {
538 ParsingLogId(String),
539 ParsingAddons(String),
540 ParsingModule(String),
541 SettingFilter,
542}
543
544impl std::error::Error for FilterError {}
545
546impl std::fmt::Display for FilterError {
547 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
548 match self {
549 FilterError::ParsingLogId(bad_id) => {
550 write!(f, "Could not parse '{}' as `LogId`.", bad_id)
551 }
552 FilterError::ParsingAddons(bad_addons) => {
553 write!(f, "Could not parse addons '{}'.", bad_addons)
554 }
555 FilterError::ParsingModule(bad_module) => {
556 write!(f, "Could not parse module '{}'.", bad_module)
557 }
558 FilterError::SettingFilter => {
559 write!(f, "Could not set the new filter configuration.")
560 }
561 }
562 }
563}
564
565fn id_allowed(ids: &Vec<LogIdAddonFilter>, id: LogId) -> bool {
566 for allowed_id in ids {
567 if allowed_id.log_id == id {
568 return true;
569 }
570 }
571
572 false
573}
574
575fn id_allowed_in_origin(modules: &Vec<LogIdModuleFilter>, id: LogId, origin: &Origin) -> bool {
576 for module in modules {
577 if module.event_allowed(id, origin) {
578 return true;
579 }
580 }
581
582 false
583}
584
585fn addon_allowed(ids: &Vec<LogIdAddonFilter>, id: LogId, addon: &AddonFilter) -> bool {
586 for allowed_id in ids {
587 if allowed_id.log_id == id && allowed_id.allowed_addons.contains(addon) {
588 return true;
589 }
590 }
591
592 false
593}
594
595fn addon_allowed_in_origin(
596 modules: &Vec<LogIdModuleFilter>,
597 id: LogId,
598 origin: &Origin,
599 addon: &AddonFilter,
600) -> bool {
601 for module in modules {
602 if module.addon_allowed(id, origin, addon) {
603 return true;
604 }
605 }
606
607 false
608}
609
610fn get_addons(s: &mut String) -> Vec<AddonFilter> {
611 let mut addons = Vec::new();
612
613 if let (Some(addon_start), Some(addon_end)) = (s.find('('), s.find(')')) {
614 if addon_start >= addon_end {
615 return addons;
616 }
617
618 if let Some(addons_part) = s.get((addon_start + 1)..addon_end) {
619 for addon_part in addons_part.split('&') {
620 if let Ok(addon) = AddonFilter::try_from(addon_part.trim()) {
621 if addon == AddonFilter::AllAllowed {
622 addons.push(AddonFilter::Id);
623 addons.push(AddonFilter::Origin);
624 addons.push(AddonFilter::Infos);
625 addons.push(AddonFilter::Debugs);
626 addons.push(AddonFilter::Traces);
627 addons.push(AddonFilter::Related);
628
629 #[cfg(feature = "hint_note")]
630 addons.push(AddonFilter::Hint);
631 #[cfg(feature = "hint_note")]
632 addons.push(AddonFilter::Note);
633
634 #[cfg(feature = "diagnostics")]
635 addons.push(AddonFilter::Diagnostics);
636
637 #[cfg(feature = "payloads")]
638 addons.push(AddonFilter::Payloads);
639 } else {
640 addons.push(addon);
641 }
642 }
643 }
644 }
645
646 s.replace_range(addon_start..(addon_end + 1), "");
647 }
648
649 addons
650}
651
652fn get_ids(s: &mut String) -> Vec<LogIdAddonFilter> {
653 let mut ids = Vec::new();
654
655 if let (Some(ids_start), Some(ids_end)) = (s.find('['), s.find(']')) {
656 if ids_start >= ids_end {
657 return ids;
658 }
659
660 if let Some(ids_part) = s.get((ids_start + 1)..ids_end) {
661 for id_part in ids_part.split('|') {
662 if let Ok(id) = LogIdAddonFilter::try_from(id_part.trim()) {
663 ids.push(id);
664 }
665 }
666 }
667
668 s.replace_range(ids_start..(ids_end + 1), "");
669 }
670
671 ids
672}
673
674fn try_into_log_level(s: &str) -> Option<LogLevel> {
675 match s.to_lowercase().as_str() {
676 "error" => Some(LogLevel::Error),
677 "warn" => Some(LogLevel::Warn),
678 "info" => Some(LogLevel::Info),
679 "debug" => Some(LogLevel::Debug),
680 "trace" | "on" => Some(LogLevel::Trace),
681 _ => None,
682 }
683}
684
685#[cfg(test)]
686mod tests {
687 use super::LogIdFilter;
688
689 #[test]
690 fn valid_log_id_filter() {
691 let log_id_filter = LogIdFilter::try_from("my_crate::my_id").unwrap();
692
693 assert_eq!(
694 log_id_filter.module_path, "my_crate",
695 "Module path extraction was not correct"
696 );
697 assert_eq!(
698 log_id_filter.identifier, "my_id",
699 "Identifier extraction was not correct"
700 );
701 }
702
703 #[test]
704 fn valid_log_id_filter_with_one_submodule() {
705 let log_id_filter = LogIdFilter::try_from("my_crate::my_module::my_id").unwrap();
706
707 assert_eq!(
708 log_id_filter.module_path, "my_crate::my_module",
709 "Module path extraction was not correct"
710 );
711 assert_eq!(
712 log_id_filter.identifier, "my_id",
713 "Identifier extraction was not correct"
714 );
715 }
716
717 #[test]
718 fn valid_log_id_filter_with_submodule() {
719 let log_id_filter =
720 LogIdFilter::try_from("my_crate::my_module::sub_module::my_id").unwrap();
721
722 assert_eq!(
723 log_id_filter.module_path, "my_crate::my_module::sub_module",
724 "Module path extraction was not correct"
725 );
726 assert_eq!(
727 log_id_filter.identifier, "my_id",
728 "Identifier extraction was not correct"
729 );
730 }
731
732 #[test]
733 fn invalid_log_id_filter_with_empty_module_path() {
734 let log_id_filter = LogIdFilter::try_from("::my_id");
735
736 assert!(
737 log_id_filter.is_err(),
738 "Parsing invalid LogIdFilter did not result in error."
739 );
740 }
741
742 #[test]
743 fn invalid_log_id_filter_with_empty_identifier() {
744 let log_id_filter = LogIdFilter::try_from("my_crate::");
745
746 assert!(
747 log_id_filter.is_err(),
748 "Parsing invalid LogIdFilter did not result in error."
749 );
750 }
751}