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