1use crate::log_formatter::{LogData, LogEntry};
6use crate::{InstanceGetter, LogCommand, LogError};
7use diagnostics_data::{LogsData, Severity};
8use fidl_fuchsia_diagnostics::LogInterestSelector;
9use moniker::{EXTENDED_MONIKER_COMPONENT_MANAGER_STR, ExtendedMoniker};
10use selectors::SelectorExt;
11use std::borrow::Cow;
12use std::str::FromStr;
13use std::sync::LazyLock;
14use zx_types::zx_koid_t;
15
16static KLOG: &str = "klog";
17static KLOG_MONIKER: LazyLock<ExtendedMoniker> =
18 LazyLock::new(|| ExtendedMoniker::try_from(KLOG).unwrap());
19
20struct MonikerFilters {
21 queries: Vec<String>,
22 matched_monikers: Vec<String>,
23}
24
25impl MonikerFilters {
26 fn new(queries: Vec<String>) -> Self {
27 Self { queries, matched_monikers: vec![] }
28 }
29
30 async fn expand_monikers(&mut self, getter: &impl InstanceGetter) -> Result<(), LogError> {
31 self.matched_monikers = vec![];
32 self.matched_monikers.reserve(self.queries.len());
33 for query in &self.queries {
34 if query == KLOG {
35 self.matched_monikers.push(query.clone());
36 continue;
37 }
38
39 let mut instances = getter.get_monikers_from_query(query).await?;
40 if instances.len() > 1 {
41 return Err(LogError::too_many_fuzzy_matches(
42 instances.into_iter().map(|i| i.to_string()),
43 ));
44 }
45 match instances.pop() {
46 Some(instance) => self.matched_monikers.push(instance.to_string()),
47 None => return Err(LogError::SearchParameterNotFound(query.to_string())),
48 }
49 }
50
51 Ok(())
52 }
53}
54
55pub struct LogFilterCriteria {
57 min_severity: Severity,
59 filters: Vec<String>,
61 moniker_filters: MonikerFilters,
63 excludes: Vec<String>,
65 tags: Vec<String>,
67 exclude_tags: Vec<String>,
69 pid: Option<zx_koid_t>,
71 tid: Option<zx_koid_t>,
73 interest_selectors: Vec<LogInterestSelector>,
77 case_sensitive: bool,
79}
80
81impl Default for LogFilterCriteria {
82 fn default() -> Self {
83 Self {
84 min_severity: Severity::Info,
85 filters: vec![],
86 excludes: vec![],
87 tags: vec![],
88 moniker_filters: MonikerFilters::new(vec![]),
89 exclude_tags: vec![],
90 pid: None,
91 tid: None,
92 case_sensitive: false,
93 interest_selectors: vec![],
94 }
95 }
96}
97
98fn convert_to_lowercase_if_needed<'a>(input: &'a str, case_sensitive: bool) -> Cow<'a, str> {
101 if case_sensitive { Cow::Borrowed(input) } else { Cow::Owned(input.to_lowercase()) }
102}
103
104impl From<LogCommand> for LogFilterCriteria {
105 fn from(mut cmd: LogCommand) -> Self {
106 Self {
107 min_severity: cmd.severity,
108 filters: cmd.filter,
109 tags: cmd
110 .tag
111 .into_iter()
112 .map(|value| convert_to_lowercase_if_needed(&value, cmd.case_sensitive).to_string())
113 .collect(),
114 excludes: cmd.exclude,
115 moniker_filters: if cmd.kernel {
116 cmd.component.push(KLOG.to_string());
117 MonikerFilters::new(cmd.component)
118 } else {
119 MonikerFilters::new(cmd.component)
120 },
121 exclude_tags: cmd.exclude_tags,
122 pid: cmd.pid,
123 case_sensitive: cmd.case_sensitive,
124 tid: cmd.tid,
125 interest_selectors: cmd.set_severity.into_iter().flatten().collect(),
126 }
127 }
128}
129
130impl LogFilterCriteria {
131 pub fn set_min_severity(&mut self, severity: Severity) {
133 self.min_severity = severity;
134 }
135
136 pub async fn expand_monikers(&mut self, getter: &impl InstanceGetter) -> Result<(), LogError> {
137 self.moniker_filters.expand_monikers(getter).await
138 }
139
140 pub fn set_tags<I, S>(&mut self, tags: I)
142 where
143 I: IntoIterator<Item = S>,
144 S: Into<String>,
145 {
146 self.tags = tags.into_iter().map(|value| value.into()).collect();
147 }
148
149 pub fn set_exclude_tags<I, S>(&mut self, tags: I)
151 where
152 I: IntoIterator<Item = S>,
153 S: Into<String>,
154 {
155 self.exclude_tags = tags.into_iter().map(|value| value.into()).collect();
156 }
157
158 pub fn matches(&self, entry: &LogEntry) -> bool {
160 match entry {
161 LogEntry { data: LogData::TargetLog(data), .. } => self.match_filters_to_log_data(data),
162 }
163 }
164
165 fn matches_filter_string(
168 filter_string: &str,
169 message: &str,
170 log: &LogsData,
171 case_sensitive: bool,
172 ) -> bool {
173 let filter_string = convert_to_lowercase_if_needed(filter_string, case_sensitive);
175 let message = convert_to_lowercase_if_needed(message, case_sensitive);
176 let file_path =
177 log.file_path().map(|value| convert_to_lowercase_if_needed(value, case_sensitive));
178 let component_url = log
179 .metadata
180 .component_url
181 .as_ref()
182 .map(|value| convert_to_lowercase_if_needed(value.as_str(), case_sensitive));
183 let moniker_str = log.moniker.to_string();
184 let moniker = convert_to_lowercase_if_needed(&moniker_str, case_sensitive);
185
186 message.contains(&*filter_string)
187 || file_path.is_some_and(|s| s.contains(&*filter_string))
188 || component_url.as_ref().is_some_and(|s| s.contains(&*filter_string))
189 || moniker.contains(&*filter_string)
190 }
191
192 fn parse_tags(value: &str) -> Vec<&str> {
194 let mut tags = Vec::new();
195 let mut current = value;
196 if !current.starts_with('[') {
197 return tags;
198 }
199 loop {
200 match current.find('[') {
201 Some(opening_index) => {
202 current = ¤t[opening_index + 1..];
203 }
204 None => return tags,
205 }
206 match current.find(']') {
207 Some(closing_index) => {
208 tags.push(¤t[..closing_index]);
209 current = ¤t[closing_index + 1..];
210 }
211 None => return tags,
212 }
213 }
214 }
215
216 fn match_synthetic_klog_tags(&self, klog_str: &str, case_sensitive: bool) -> bool {
217 let tags = Self::parse_tags(klog_str)
218 .into_iter()
219 .map(|value| convert_to_lowercase_if_needed(value, case_sensitive))
220 .collect::<Vec<_>>();
221 self.tags.iter().any(|f| {
222 tags.iter().any(|t| convert_to_lowercase_if_needed(t, case_sensitive).contains(f))
223 })
224 }
225
226 fn matches_filter_by_moniker_string(filter_string: &str, log: &LogsData) -> bool {
228 let Ok(filter_moniker) = ExtendedMoniker::from_str(filter_string) else {
229 return false;
230 };
231 filter_moniker == log.moniker
232 }
233
234 fn match_filters_to_log_data(&self, data: &LogsData) -> bool {
236 let min_severity = self
237 .interest_selectors
238 .iter()
239 .filter(|s| data.moniker.matches_component_selector(&s.selector).unwrap_or(false))
240 .filter_map(|selector| selector.interest.min_severity)
241 .min()
242 .unwrap_or_else(|| self.min_severity.into());
243 if data.metadata.severity < min_severity {
244 return false;
245 }
246
247 if let Some(pid) = self.pid
248 && data.pid() != Some(pid)
249 {
250 return false;
251 }
252
253 if let Some(tid) = self.tid
254 && data.tid() != Some(tid)
255 {
256 return false;
257 }
258
259 if !self.moniker_filters.matched_monikers.is_empty()
260 && !self
261 .moniker_filters
262 .matched_monikers
263 .iter()
264 .any(|f| Self::matches_filter_by_moniker_string(f, data))
265 {
266 return false;
267 }
268
269 let msg = data.msg().unwrap_or("");
270
271 if !self.filters.is_empty()
272 && !self
273 .filters
274 .iter()
275 .any(|f| Self::matches_filter_string(f, msg, data, self.case_sensitive))
276 {
277 return false;
278 }
279
280 if self
281 .excludes
282 .iter()
283 .any(|f| Self::matches_filter_string(f, msg, data, self.case_sensitive))
284 {
285 return false;
286 }
287 if !self.tags.is_empty()
288 && !self.tags.iter().any(|query_tag| {
289 let has_tag = data
290 .tags()
291 .map(|t| {
292 t.iter().any(|value| {
293 convert_to_lowercase_if_needed(value, self.case_sensitive) == *query_tag
294 })
295 })
296 .unwrap_or(false);
297 let moniker_has_tag =
298 moniker_contains_in_last_segment(&data.moniker, query_tag, self.case_sensitive);
299 has_tag || moniker_has_tag
300 })
301 {
302 if data.moniker == *KLOG_MONIKER {
303 return self
304 .match_synthetic_klog_tags(data.msg().unwrap_or(""), self.case_sensitive);
305 }
306 return false;
307 }
308
309 if self.exclude_tags.iter().any(|excluded_tag| {
310 let has_tag = data.tags().map(|tag| tag.contains(excluded_tag)).unwrap_or(false);
311 let moniker_has_tag =
312 moniker_contains_in_last_segment(&data.moniker, excluded_tag, self.case_sensitive);
313 has_tag || moniker_has_tag
314 }) {
315 return false;
316 }
317
318 true
319 }
320}
321
322fn moniker_contains_in_last_segment(
323 moniker: &ExtendedMoniker,
324 query_tag: &str,
325 case_sensitive: bool,
326) -> bool {
327 let query_tag = convert_to_lowercase_if_needed(query_tag, case_sensitive);
328 match moniker {
329 ExtendedMoniker::ComponentInstance(moniker) => moniker
330 .leaf()
331 .map(|segment| {
332 convert_to_lowercase_if_needed(segment.as_ref(), case_sensitive)
333 .contains(&*query_tag)
334 })
335 .unwrap_or(false),
336 ExtendedMoniker::ComponentManager => {
337 EXTENDED_MONIKER_COMPONENT_MANAGER_STR.contains(&*query_tag)
338 }
339 }
340}
341
342#[cfg(test)]
343mod test {
344 use diagnostics_data::{ExtendedMoniker, Timestamp};
345 use selectors::parse_log_interest_selector;
346
347 use crate::log_socket_stream::OneOrMany;
348 use crate::{DumpCommand, LogSubCommand};
349
350 use super::*;
351
352 fn empty_dump_command() -> LogCommand {
353 LogCommand {
354 sub_command: Some(LogSubCommand::Dump(DumpCommand {})),
355 ..LogCommand::default()
356 }
357 }
358
359 fn make_log_entry(log_data: LogData) -> LogEntry {
360 LogEntry { data: log_data }
361 }
362
363 #[fuchsia::test]
364 async fn test_criteria_tag_filter_filters_moniker() {
365 let cmd = LogCommand { tag: vec!["testcomponent".to_string()], ..empty_dump_command() };
366 let criteria = LogFilterCriteria::from(cmd);
367
368 assert!(
369 criteria.matches(&make_log_entry(
370 diagnostics_data::LogsDataBuilder::new(diagnostics_data::BuilderArgs {
371 timestamp: Timestamp::from_nanos(0),
372 component_url: Some("".into()),
373 moniker: "my/testcomponent".try_into().unwrap(),
374 severity: diagnostics_data::Severity::Error,
375 })
376 .set_message("included")
377 .add_tag("tag1")
378 .add_tag("tag2")
379 .build()
380 .into()
381 ))
382 );
383 }
384
385 #[fuchsia::test]
386 async fn test_criteria_exclude_tag_filters_moniker() {
387 let cmd =
388 LogCommand { exclude_tags: vec!["testcomponent".to_string()], ..empty_dump_command() };
389 let criteria = LogFilterCriteria::from(cmd);
390 assert!(
391 !criteria.matches(&make_log_entry(
392 diagnostics_data::LogsDataBuilder::new(diagnostics_data::BuilderArgs {
393 timestamp: Timestamp::from_nanos(0),
394 component_url: Some("".into()),
395 moniker: "my/testcomponent".try_into().unwrap(),
396 severity: diagnostics_data::Severity::Error,
397 })
398 .set_message("excluded")
399 .add_tag("tag1")
400 .add_tag("tag2")
401 .build()
402 .into()
403 ))
404 );
405 }
406
407 #[fuchsia::test]
408 async fn test_criteria_tag_filter() {
409 let cmd = LogCommand {
410 tag: vec!["tag1".to_string()],
411 exclude_tags: vec!["tag3".to_string()],
412 ..empty_dump_command()
413 };
414 let criteria = LogFilterCriteria::from(cmd);
415
416 assert!(
417 criteria.matches(&make_log_entry(
418 diagnostics_data::LogsDataBuilder::new(diagnostics_data::BuilderArgs {
419 timestamp: Timestamp::from_nanos(0),
420 component_url: Some("".into()),
421 moniker: ExtendedMoniker::ComponentManager,
422 severity: diagnostics_data::Severity::Error,
423 })
424 .set_message("included")
425 .add_tag("tag1")
426 .add_tag("tag2")
427 .build()
428 .into()
429 ))
430 );
431
432 assert!(
433 !criteria.matches(&make_log_entry(
434 diagnostics_data::LogsDataBuilder::new(diagnostics_data::BuilderArgs {
435 timestamp: Timestamp::from_nanos(0),
436 component_url: Some("".into()),
437 moniker: ExtendedMoniker::ComponentManager,
438 severity: diagnostics_data::Severity::Error,
439 })
440 .set_message("included")
441 .add_tag("tag2")
442 .build()
443 .into()
444 ))
445 );
446 assert!(
447 !criteria.matches(&make_log_entry(
448 diagnostics_data::LogsDataBuilder::new(diagnostics_data::BuilderArgs {
449 timestamp: Timestamp::from_nanos(0),
450 component_url: Some("".into()),
451 moniker: ExtendedMoniker::ComponentManager,
452 severity: diagnostics_data::Severity::Error,
453 })
454 .set_message("included")
455 .add_tag("tag1")
456 .add_tag("tag3")
457 .build()
458 .into()
459 ))
460 );
461 }
462
463 #[fuchsia::test]
464 async fn test_per_component_severity() {
465 let cmd = LogCommand {
466 sub_command: Some(LogSubCommand::Dump(DumpCommand {})),
467 set_severity: vec![OneOrMany::One(
468 parse_log_interest_selector("test_selector#DEBUG").unwrap(),
469 )],
470 ..LogCommand::default()
471 };
472 let expectations = [
473 ("test_selector", diagnostics_data::Severity::Debug, true),
474 ("other_selector", diagnostics_data::Severity::Debug, false),
475 ("other_selector", diagnostics_data::Severity::Info, true),
476 ];
477 let criteria = LogFilterCriteria::from(cmd);
478 assert_eq!(criteria.min_severity, Severity::Info);
479 for (moniker, severity, is_included) in expectations {
480 let entry = make_log_entry(
481 diagnostics_data::LogsDataBuilder::new(diagnostics_data::BuilderArgs {
482 timestamp: Timestamp::from_nanos(0),
483 component_url: Some("".into()),
484 moniker: moniker.try_into().unwrap(),
485 severity,
486 })
487 .set_message("message")
488 .add_tag("tag1")
489 .add_tag("tag2")
490 .build()
491 .into(),
492 );
493 assert_eq!(criteria.matches(&entry), is_included);
494 }
495 }
496
497 #[fuchsia::test]
498 async fn test_per_component_severity_uses_min_match() {
499 let severities = [
500 diagnostics_data::Severity::Info,
501 diagnostics_data::Severity::Trace,
502 diagnostics_data::Severity::Debug,
503 ];
504
505 let cmd = LogCommand {
506 sub_command: Some(LogSubCommand::Dump(DumpCommand {})),
507 set_severity: vec![
508 OneOrMany::One(parse_log_interest_selector("test_selector#INFO").unwrap()),
509 OneOrMany::One(parse_log_interest_selector("test_selector#TRACE").unwrap()),
510 OneOrMany::One(parse_log_interest_selector("test_selector#DEBUG").unwrap()),
511 ],
512 ..LogCommand::default()
513 };
514 let criteria = LogFilterCriteria::from(cmd);
515
516 for severity in severities {
517 let entry = make_log_entry(
518 diagnostics_data::LogsDataBuilder::new(diagnostics_data::BuilderArgs {
519 timestamp: Timestamp::from_nanos(0),
520 component_url: Some("".into()),
521 moniker: "test_selector".try_into().unwrap(),
522 severity,
523 })
524 .set_message("message")
525 .add_tag("tag1")
526 .add_tag("tag2")
527 .build()
528 .into(),
529 );
530 assert!(criteria.matches(&entry));
531 }
532 }
533
534 #[fuchsia::test]
535 async fn test_criteria_tag_filter_legacy() {
536 let cmd = LogCommand {
537 tag: vec!["tag1".to_string()],
538 exclude_tags: vec!["tag3".to_string()],
539 ..empty_dump_command()
540 };
541 let criteria = LogFilterCriteria::from(cmd);
542
543 assert!(
544 criteria.matches(&make_log_entry(
545 diagnostics_data::LogsDataBuilder::new(diagnostics_data::BuilderArgs {
546 timestamp: Timestamp::from_nanos(0),
547 component_url: Some("".into()),
548 moniker: ExtendedMoniker::ComponentManager,
549 severity: diagnostics_data::Severity::Error,
550 })
551 .set_message("included")
552 .add_tag("tag1")
553 .add_tag("tag2")
554 .build()
555 .into()
556 ))
557 );
558
559 assert!(
560 !criteria.matches(&make_log_entry(
561 diagnostics_data::LogsDataBuilder::new(diagnostics_data::BuilderArgs {
562 timestamp: Timestamp::from_nanos(0),
563 component_url: Some("".into()),
564 moniker: ExtendedMoniker::ComponentManager,
565 severity: diagnostics_data::Severity::Error,
566 })
567 .set_message("included")
568 .add_tag("tag2")
569 .build()
570 .into()
571 ))
572 );
573 assert!(
574 !criteria.matches(&make_log_entry(
575 diagnostics_data::LogsDataBuilder::new(diagnostics_data::BuilderArgs {
576 timestamp: Timestamp::from_nanos(0),
577 component_url: Some("".into()),
578 moniker: ExtendedMoniker::ComponentManager,
579 severity: diagnostics_data::Severity::Error,
580 })
581 .set_message("included")
582 .add_tag("tag1")
583 .add_tag("tag3")
584 .build()
585 .into()
586 ))
587 );
588 }
589
590 #[fuchsia::test]
591 async fn test_severity_filter_with_debug() {
592 let mut cmd = empty_dump_command();
593 cmd.severity = Severity::Trace;
594 let criteria = LogFilterCriteria::from(cmd);
595
596 assert!(
597 criteria.matches(&make_log_entry(
598 diagnostics_data::LogsDataBuilder::new(diagnostics_data::BuilderArgs {
599 timestamp: Timestamp::from_nanos(0),
600 component_url: Some("".into()),
601 moniker: "included/moniker".try_into().unwrap(),
602 severity: diagnostics_data::Severity::Error,
603 })
604 .set_message("included message")
605 .build()
606 .into()
607 ))
608 );
609 assert!(
610 criteria.matches(&make_log_entry(
611 diagnostics_data::LogsDataBuilder::new(diagnostics_data::BuilderArgs {
612 timestamp: Timestamp::from_nanos(0),
613 component_url: Some("".into()),
614 moniker: "included/moniker".try_into().unwrap(),
615 severity: diagnostics_data::Severity::Info,
616 })
617 .set_message("different message")
618 .build()
619 .into()
620 ))
621 );
622 assert!(
623 criteria.matches(&make_log_entry(
624 diagnostics_data::LogsDataBuilder::new(diagnostics_data::BuilderArgs {
625 timestamp: Timestamp::from_nanos(0),
626 component_url: Some("".into()),
627 moniker: "other/moniker".try_into().unwrap(),
628 severity: diagnostics_data::Severity::Debug,
629 })
630 .set_message("included message")
631 .build()
632 .into()
633 ))
634 );
635 }
636
637 #[fuchsia::test]
638 async fn test_pid_filter() {
639 let mut cmd = empty_dump_command();
640 cmd.pid = Some(123);
641 let criteria = LogFilterCriteria::from(cmd);
642
643 assert!(
644 criteria.matches(&make_log_entry(
645 diagnostics_data::LogsDataBuilder::new(diagnostics_data::BuilderArgs {
646 timestamp: Timestamp::from_nanos(0),
647 component_url: Some("".into()),
648 moniker: "included/moniker".try_into().unwrap(),
649 severity: diagnostics_data::Severity::Error,
650 })
651 .set_message("included message")
652 .set_pid(123)
653 .build()
654 .into()
655 ))
656 );
657 assert!(
658 !criteria.matches(&make_log_entry(
659 diagnostics_data::LogsDataBuilder::new(diagnostics_data::BuilderArgs {
660 timestamp: Timestamp::from_nanos(0),
661 component_url: Some("".into()),
662 moniker: "included/moniker".try_into().unwrap(),
663 severity: diagnostics_data::Severity::Error,
664 })
665 .set_message("included message")
666 .set_pid(456)
667 .build()
668 .into()
669 ))
670 );
671 }
672
673 struct FakeInstanceGetter;
674 #[async_trait::async_trait(?Send)]
675 impl InstanceGetter for FakeInstanceGetter {
676 async fn get_monikers_from_query(
677 &self,
678 query: &str,
679 ) -> Result<Vec<moniker::Moniker>, LogError> {
680 Ok(vec![moniker::Moniker::try_from(query).unwrap()])
681 }
682 }
683
684 #[fuchsia::test]
685 async fn test_criteria_component_filter() {
686 let cmd = LogCommand {
687 component: vec!["/core/network/netstack".to_string()],
688 ..empty_dump_command()
689 };
690
691 let mut criteria = LogFilterCriteria::from(cmd);
692 criteria.expand_monikers(&FakeInstanceGetter).await.unwrap();
693
694 assert!(
695 !criteria.matches(&make_log_entry(
696 diagnostics_data::LogsDataBuilder::new(diagnostics_data::BuilderArgs {
697 timestamp: Timestamp::from_nanos(0),
698 component_url: Some("".into()),
699 moniker: "bootstrap/archivist".try_into().unwrap(),
700 severity: diagnostics_data::Severity::Error,
701 })
702 .set_message("excluded")
703 .build()
704 .into()
705 ))
706 );
707
708 assert!(
709 criteria.matches(&make_log_entry(
710 diagnostics_data::LogsDataBuilder::new(diagnostics_data::BuilderArgs {
711 timestamp: Timestamp::from_nanos(0),
712 component_url: Some("".into()),
713 moniker: "core/network/netstack".try_into().unwrap(),
714 severity: diagnostics_data::Severity::Error,
715 })
716 .set_message("included")
717 .build()
718 .into()
719 ))
720 );
721
722 assert!(
723 !criteria.matches(&make_log_entry(
724 diagnostics_data::LogsDataBuilder::new(diagnostics_data::BuilderArgs {
725 timestamp: Timestamp::from_nanos(0),
726 component_url: Some("".into()),
727 moniker: "core/network/dhcp".try_into().unwrap(),
728 severity: diagnostics_data::Severity::Error,
729 })
730 .set_message("included")
731 .build()
732 .into()
733 ))
734 );
735 }
736
737 #[fuchsia::test]
738 async fn test_tid_filter() {
739 let mut cmd = empty_dump_command();
740 cmd.tid = Some(123);
741 let criteria = LogFilterCriteria::from(cmd);
742
743 assert!(
744 criteria.matches(&make_log_entry(
745 diagnostics_data::LogsDataBuilder::new(diagnostics_data::BuilderArgs {
746 timestamp: Timestamp::from_nanos(0),
747 component_url: Some("".into()),
748 moniker: "included/moniker".try_into().unwrap(),
749 severity: diagnostics_data::Severity::Error,
750 })
751 .set_message("included message")
752 .set_tid(123)
753 .build()
754 .into()
755 ))
756 );
757 assert!(
758 !criteria.matches(&make_log_entry(
759 diagnostics_data::LogsDataBuilder::new(diagnostics_data::BuilderArgs {
760 timestamp: Timestamp::from_nanos(0),
761 component_url: Some("".into()),
762 moniker: "included/moniker".try_into().unwrap(),
763 severity: diagnostics_data::Severity::Error,
764 })
765 .set_message("included message")
766 .set_tid(456)
767 .build()
768 .into()
769 ))
770 );
771 }
772
773 #[fuchsia::test]
774 async fn test_setter_functions() {
775 let mut filter = LogFilterCriteria::default();
776 filter.set_min_severity(Severity::Error);
777 assert_eq!(filter.min_severity, Severity::Error);
778 filter.set_tags(["tag1"]);
779 assert_eq!(filter.tags, ["tag1"]);
780 filter.set_exclude_tags(["tag2"]);
781 assert_eq!(filter.exclude_tags, ["tag2"]);
782 }
783
784 #[fuchsia::test]
785 async fn test_criteria_moniker_message_and_severity_matches() {
786 let cmd = LogCommand {
787 filter: vec!["included".to_string()],
788 exclude: vec!["not this".to_string()],
789 severity: Severity::Error,
790 ..empty_dump_command()
791 };
792 let criteria = LogFilterCriteria::from(cmd);
793
794 assert!(
795 criteria.matches(&make_log_entry(
796 diagnostics_data::LogsDataBuilder::new(diagnostics_data::BuilderArgs {
797 timestamp: Timestamp::from_nanos(0),
798 component_url: Some("".into()),
799 moniker: "included/moniker".try_into().unwrap(),
800 severity: diagnostics_data::Severity::Error,
801 })
802 .set_message("included message")
803 .build()
804 .into()
805 ))
806 );
807 assert!(
808 criteria.matches(&make_log_entry(
809 diagnostics_data::LogsDataBuilder::new(diagnostics_data::BuilderArgs {
810 timestamp: Timestamp::from_nanos(0),
811 component_url: Some("".into()),
812 moniker: "included/moniker".try_into().unwrap(),
813 severity: diagnostics_data::Severity::Fatal,
814 })
815 .set_message("included message")
816 .build()
817 .into()
818 ))
819 );
820 assert!(
821 criteria.matches(&make_log_entry(
822 diagnostics_data::LogsDataBuilder::new(diagnostics_data::BuilderArgs {
823 timestamp: Timestamp::from_nanos(0),
824 component_url: Some("".into()),
825 moniker: "included/moniker".try_into().unwrap(),
827 severity: diagnostics_data::Severity::Fatal,
828 })
829 .set_message("included message")
830 .build()
831 .into()
832 ))
833 );
834 assert!(
835 !criteria.matches(&make_log_entry(
836 diagnostics_data::LogsDataBuilder::new(diagnostics_data::BuilderArgs {
837 timestamp: Timestamp::from_nanos(0),
838 component_url: Some("".into()),
839 moniker: "not/this/moniker".try_into().unwrap(),
840 severity: diagnostics_data::Severity::Error,
841 })
842 .set_message("different message")
843 .build()
844 .into()
845 ))
846 );
847 assert!(
848 !criteria.matches(&make_log_entry(
849 diagnostics_data::LogsDataBuilder::new(diagnostics_data::BuilderArgs {
850 timestamp: Timestamp::from_nanos(0),
851 component_url: Some("".into()),
852 moniker: "included/moniker".try_into().unwrap(),
853 severity: diagnostics_data::Severity::Warn,
854 })
855 .set_message("included message")
856 .build()
857 .into()
858 ))
859 );
860 assert!(
861 !criteria.matches(&make_log_entry(
862 diagnostics_data::LogsDataBuilder::new(diagnostics_data::BuilderArgs {
863 timestamp: Timestamp::from_nanos(0),
864 component_url: Some("".into()),
865 moniker: "other/moniker".try_into().unwrap(),
866 severity: diagnostics_data::Severity::Error,
867 })
868 .set_message("not this message")
869 .build()
870 .into()
871 ))
872 );
873 assert!(
874 !criteria.matches(&make_log_entry(
875 diagnostics_data::LogsDataBuilder::new(diagnostics_data::BuilderArgs {
876 timestamp: Timestamp::from_nanos(0),
877 component_url: Some("".into()),
878 moniker: "included/moniker".try_into().unwrap(),
879 severity: diagnostics_data::Severity::Error,
880 })
881 .set_message("not this message")
882 .build()
883 .into()
884 ))
885 );
886 }
887
888 #[fuchsia::test]
889 async fn test_criteria_klog_only() {
890 let cmd = LogCommand { tag: vec!["component_manager".into()], ..empty_dump_command() };
891 let criteria = LogFilterCriteria::from(cmd);
892
893 assert!(
894 criteria.matches(&make_log_entry(
895 diagnostics_data::LogsDataBuilder::new(diagnostics_data::BuilderArgs {
896 timestamp: Timestamp::from_nanos(0),
897 component_url: Some("".into()),
898 moniker: "klog".try_into().unwrap(),
899 severity: diagnostics_data::Severity::Error,
900 })
901 .set_message("[component_manager] included message")
902 .build()
903 .into()
904 ))
905 );
906 assert!(
907 !criteria.matches(&make_log_entry(
908 diagnostics_data::LogsDataBuilder::new(diagnostics_data::BuilderArgs {
909 timestamp: Timestamp::from_nanos(0),
910 component_url: Some("".into()),
911 moniker: "klog".try_into().unwrap(),
912 severity: diagnostics_data::Severity::Error,
913 })
914 .set_message("excluded message[component_manager]")
915 .build()
916 .into()
917 ))
918 );
919 assert!(
920 criteria.matches(&make_log_entry(
921 diagnostics_data::LogsDataBuilder::new(diagnostics_data::BuilderArgs {
922 timestamp: Timestamp::from_nanos(0),
923 component_url: Some("".into()),
924 moniker: "klog".try_into().unwrap(),
925 severity: diagnostics_data::Severity::Error,
926 })
927 .set_message("[tag0][component_manager] included message")
928 .build()
929 .into()
930 ))
931 );
932 assert!(
933 !criteria.matches(&make_log_entry(
934 diagnostics_data::LogsDataBuilder::new(diagnostics_data::BuilderArgs {
935 timestamp: Timestamp::from_nanos(0),
936 component_url: Some("".into()),
937 moniker: "klog".try_into().unwrap(),
938 severity: diagnostics_data::Severity::Error,
939 })
940 .set_message("[other] excluded message")
941 .build()
942 .into()
943 ))
944 );
945 assert!(
946 !criteria.matches(&make_log_entry(
947 diagnostics_data::LogsDataBuilder::new(diagnostics_data::BuilderArgs {
948 timestamp: Timestamp::from_nanos(0),
949 component_url: Some("".into()),
950 moniker: "klog".try_into().unwrap(),
951 severity: diagnostics_data::Severity::Error,
952 })
953 .set_message("no tags, excluded")
954 .build()
955 .into()
956 ))
957 );
958 assert!(
959 !criteria.matches(&make_log_entry(
960 diagnostics_data::LogsDataBuilder::new(diagnostics_data::BuilderArgs {
961 timestamp: Timestamp::from_nanos(0),
962 component_url: Some("".into()),
963 moniker: "other/moniker".try_into().unwrap(),
964 severity: diagnostics_data::Severity::Error,
965 })
966 .set_message("[component_manager] excluded message")
967 .build()
968 .into()
969 ))
970 );
971 }
972
973 #[fuchsia::test]
974 async fn test_criteria_klog_tag_hack() {
975 let cmd = LogCommand { kernel: true, ..empty_dump_command() };
976 let mut criteria = LogFilterCriteria::from(cmd);
977
978 criteria.expand_monikers(&FakeInstanceGetter).await.unwrap();
979
980 assert!(
981 criteria.matches(&make_log_entry(
982 diagnostics_data::LogsDataBuilder::new(diagnostics_data::BuilderArgs {
983 timestamp: Timestamp::from_nanos(0),
984 component_url: Some("".into()),
985 moniker: "klog".try_into().unwrap(),
986 severity: diagnostics_data::Severity::Error,
987 })
988 .set_message("included message")
989 .build()
990 .into()
991 ))
992 );
993 assert!(
994 !criteria.matches(&make_log_entry(
995 diagnostics_data::LogsDataBuilder::new(diagnostics_data::BuilderArgs {
996 timestamp: Timestamp::from_nanos(0),
997 component_url: Some("".into()),
998 moniker: "other/moniker".try_into().unwrap(),
999 severity: diagnostics_data::Severity::Error,
1000 })
1001 .set_message("included message")
1002 .build()
1003 .into()
1004 ))
1005 );
1006 }
1007
1008 #[test]
1009 fn filter_fiters_filename() {
1010 let cmd = LogCommand { filter: vec!["sometestfile".into()], ..empty_dump_command() };
1011 let criteria = LogFilterCriteria::from(cmd);
1012
1013 assert!(
1014 criteria.matches(&make_log_entry(
1015 diagnostics_data::LogsDataBuilder::new(diagnostics_data::BuilderArgs {
1016 timestamp: Timestamp::from_nanos(0),
1017 component_url: Some("".into()),
1018 moniker: "core/last_segment".try_into().unwrap(),
1019 severity: diagnostics_data::Severity::Error,
1020 })
1021 .set_file("sometestfile")
1022 .set_message("hello world")
1023 .build()
1024 .into()
1025 ))
1026 );
1027 }
1028
1029 #[fuchsia::test]
1030 async fn test_empty_criteria() {
1031 let cmd = empty_dump_command();
1032 let criteria = LogFilterCriteria::from(cmd);
1033
1034 assert!(
1035 criteria.matches(&make_log_entry(
1036 diagnostics_data::LogsDataBuilder::new(diagnostics_data::BuilderArgs {
1037 timestamp: Timestamp::from_nanos(0),
1038 component_url: Some("".into()),
1039 moniker: "included/moniker".try_into().unwrap(),
1040 severity: diagnostics_data::Severity::Error,
1041 })
1042 .set_message("included message")
1043 .build()
1044 .into()
1045 ))
1046 );
1047 assert!(
1048 criteria.matches(&make_log_entry(
1049 diagnostics_data::LogsDataBuilder::new(diagnostics_data::BuilderArgs {
1050 timestamp: Timestamp::from_nanos(0),
1051 component_url: Some("".into()),
1052 moniker: "included/moniker".try_into().unwrap(),
1053 severity: diagnostics_data::Severity::Info,
1054 })
1055 .set_message("different message")
1056 .build()
1057 .into()
1058 ))
1059 );
1060 assert!(
1061 !criteria.matches(&make_log_entry(
1062 diagnostics_data::LogsDataBuilder::new(diagnostics_data::BuilderArgs {
1063 timestamp: Timestamp::from_nanos(0),
1064 component_url: Some("".into()),
1065 moniker: "other/moniker".try_into().unwrap(),
1066 severity: diagnostics_data::Severity::Debug,
1067 })
1068 .set_message("included message")
1069 .build()
1070 .into()
1071 ))
1072 );
1073
1074 let entry = make_log_entry(
1075 diagnostics_data::LogsDataBuilder::new(diagnostics_data::BuilderArgs {
1076 timestamp: Timestamp::from_nanos(0),
1077 component_url: Some("".into()),
1078 moniker: "other/moniker".try_into().unwrap(),
1079 severity: diagnostics_data::Severity::Debug,
1080 })
1081 .set_message("included message")
1082 .build()
1083 .into(),
1084 );
1085
1086 assert!(!criteria.matches(&entry));
1087 }
1088
1089 #[test]
1090 fn filter_fiters_case_sensitivity() {
1091 let cmd = LogCommand { filter: vec!["sometestfile".into()], ..empty_dump_command() };
1093 let criteria = LogFilterCriteria::from(cmd);
1094
1095 let entry_0 = make_log_entry(
1096 diagnostics_data::LogsDataBuilder::new(diagnostics_data::BuilderArgs {
1097 timestamp: Timestamp::from_nanos(0),
1098 component_url: Some("".into()),
1099 moniker: "core/last_segment".try_into().unwrap(),
1100 severity: diagnostics_data::Severity::Error,
1101 })
1102 .set_file("sometestfile")
1103 .set_message("hello world")
1104 .build()
1105 .into(),
1106 );
1107
1108 let entry_1 = make_log_entry(
1109 diagnostics_data::LogsDataBuilder::new(diagnostics_data::BuilderArgs {
1110 timestamp: Timestamp::from_nanos(0),
1111 component_url: Some("".into()),
1112 moniker: "core/last_segment".try_into().unwrap(),
1113 severity: diagnostics_data::Severity::Error,
1114 })
1115 .set_file("someTESTfile")
1116 .set_message("hello world")
1117 .build()
1118 .into(),
1119 );
1120 assert!(criteria.matches(&entry_0));
1121 assert!(criteria.matches(&entry_1));
1122
1123 let cmd = LogCommand {
1125 filter: vec!["sometestfile".into()],
1126 case_sensitive: true,
1127 ..empty_dump_command()
1128 };
1129 let criteria = LogFilterCriteria::from(cmd);
1130
1131 assert!(criteria.matches(&entry_0));
1132 assert!(!criteria.matches(&entry_1));
1133 }
1134
1135 #[test]
1136 fn filter_fiters_case_sensitivity_for_tags() {
1137 let cmd = LogCommand { tag: vec!["someTAG".into()], ..empty_dump_command() };
1139 let criteria = LogFilterCriteria::from(cmd);
1140
1141 let entry_0 = make_log_entry(
1142 diagnostics_data::LogsDataBuilder::new(diagnostics_data::BuilderArgs {
1143 timestamp: Timestamp::from_nanos(0),
1144 component_url: Some("".into()),
1145 moniker: "core/last_segment".try_into().unwrap(),
1146 severity: diagnostics_data::Severity::Error,
1147 })
1148 .add_tag("someTAG")
1149 .set_message("hello world")
1150 .build()
1151 .into(),
1152 );
1153
1154 let entry_1 = make_log_entry(
1155 diagnostics_data::LogsDataBuilder::new(diagnostics_data::BuilderArgs {
1156 timestamp: Timestamp::from_nanos(0),
1157 component_url: Some("".into()),
1158 moniker: "core/last_segment".try_into().unwrap(),
1159 severity: diagnostics_data::Severity::Error,
1160 })
1161 .add_tag("SomeTaG")
1162 .set_message("hello world")
1163 .build()
1164 .into(),
1165 );
1166 assert!(criteria.matches(&entry_0));
1167 assert!(criteria.matches(&entry_1));
1168
1169 let cmd = LogCommand {
1171 tag: vec!["someTAG".into()],
1172 case_sensitive: true,
1173 ..empty_dump_command()
1174 };
1175 let criteria = LogFilterCriteria::from(cmd);
1176
1177 assert!(criteria.matches(&entry_0));
1178 assert!(!criteria.matches(&entry_1));
1179 }
1180
1181 #[test]
1182 fn filter_fiters_case_sensitivity_for_tags_including_moniker() {
1183 let cmd = LogCommand { tag: vec!["someTAG".into()], ..empty_dump_command() };
1185 let criteria = LogFilterCriteria::from(cmd);
1186
1187 let entry_0 = make_log_entry(
1188 diagnostics_data::LogsDataBuilder::new(diagnostics_data::BuilderArgs {
1189 timestamp: Timestamp::from_nanos(0),
1190 component_url: Some("".into()),
1191 moniker: "core/someTAG".try_into().unwrap(),
1192 severity: diagnostics_data::Severity::Error,
1193 })
1194 .set_message("hello world")
1195 .build()
1196 .into(),
1197 );
1198
1199 let entry_1 = make_log_entry(
1200 diagnostics_data::LogsDataBuilder::new(diagnostics_data::BuilderArgs {
1201 timestamp: Timestamp::from_nanos(0),
1202 component_url: Some("".into()),
1203 moniker: "core/SomeTaG".try_into().unwrap(),
1204 severity: diagnostics_data::Severity::Error,
1205 })
1206 .set_message("hello world")
1207 .build()
1208 .into(),
1209 );
1210 assert!(criteria.matches(&entry_0));
1211 assert!(criteria.matches(&entry_1));
1212
1213 let cmd = LogCommand {
1215 tag: vec!["someTAG".into()],
1216 case_sensitive: true,
1217 ..empty_dump_command()
1218 };
1219 let criteria = LogFilterCriteria::from(cmd);
1220
1221 assert!(criteria.matches(&entry_0));
1222 assert!(!criteria.matches(&entry_1));
1223 }
1224
1225 #[test]
1226 fn tag_matches_moniker_last_segment() {
1227 let cmd = LogCommand { tag: vec!["last_segment".to_string()], ..empty_dump_command() };
1229 let criteria = LogFilterCriteria::from(cmd);
1230
1231 assert!(
1232 criteria.matches(&make_log_entry(
1233 diagnostics_data::LogsDataBuilder::new(diagnostics_data::BuilderArgs {
1234 timestamp: Timestamp::from_nanos(0),
1235 component_url: Some("".into()),
1236 moniker: "core/last_segment".try_into().unwrap(),
1237 severity: diagnostics_data::Severity::Error,
1238 })
1239 .set_message("hello world")
1240 .build()
1241 .into()
1242 ))
1243 );
1244 }
1245}