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 .path()
335 .last()
336 .map(|segment| {
337 convert_to_lowercase_if_needed(&segment.to_string(), case_sensitive)
338 .contains(&*query_tag)
339 })
340 .unwrap_or(false),
341 ExtendedMoniker::ComponentManager => {
342 EXTENDED_MONIKER_COMPONENT_MANAGER_STR.contains(&*query_tag)
343 }
344 }
345}
346
347#[cfg(test)]
348mod test {
349 use diagnostics_data::{ExtendedMoniker, Timestamp};
350 use selectors::parse_log_interest_selector;
351
352 use crate::log_socket_stream::OneOrMany;
353 use crate::{DumpCommand, LogSubCommand};
354
355 use super::*;
356
357 fn empty_dump_command() -> LogCommand {
358 LogCommand {
359 sub_command: Some(LogSubCommand::Dump(DumpCommand {})),
360 ..LogCommand::default()
361 }
362 }
363
364 fn make_log_entry(log_data: LogData) -> LogEntry {
365 LogEntry { data: log_data }
366 }
367
368 #[fuchsia::test]
369 async fn test_criteria_tag_filter_filters_moniker() {
370 let cmd = LogCommand { tag: vec!["testcomponent".to_string()], ..empty_dump_command() };
371 let criteria = LogFilterCriteria::from(cmd);
372
373 assert!(criteria.matches(&make_log_entry(
374 diagnostics_data::LogsDataBuilder::new(diagnostics_data::BuilderArgs {
375 timestamp: Timestamp::from_nanos(0),
376 component_url: Some("".into()),
377 moniker: "my/testcomponent".try_into().unwrap(),
378 severity: diagnostics_data::Severity::Error,
379 })
380 .set_message("included")
381 .add_tag("tag1")
382 .add_tag("tag2")
383 .build()
384 .into()
385 )));
386 }
387
388 #[fuchsia::test]
389 async fn test_criteria_exclude_tag_filters_moniker() {
390 let cmd =
391 LogCommand { exclude_tags: vec!["testcomponent".to_string()], ..empty_dump_command() };
392 let criteria = LogFilterCriteria::from(cmd);
393 assert!(!criteria.matches(&make_log_entry(
394 diagnostics_data::LogsDataBuilder::new(diagnostics_data::BuilderArgs {
395 timestamp: Timestamp::from_nanos(0),
396 component_url: Some("".into()),
397 moniker: "my/testcomponent".try_into().unwrap(),
398 severity: diagnostics_data::Severity::Error,
399 })
400 .set_message("excluded")
401 .add_tag("tag1")
402 .add_tag("tag2")
403 .build()
404 .into()
405 )));
406 }
407
408 #[fuchsia::test]
409 async fn test_criteria_tag_filter() {
410 let cmd = LogCommand {
411 tag: vec!["tag1".to_string()],
412 exclude_tags: vec!["tag3".to_string()],
413 ..empty_dump_command()
414 };
415 let criteria = LogFilterCriteria::from(cmd);
416
417 assert!(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 assert!(!criteria.matches(&make_log_entry(
432 diagnostics_data::LogsDataBuilder::new(diagnostics_data::BuilderArgs {
433 timestamp: Timestamp::from_nanos(0),
434 component_url: Some("".into()),
435 moniker: ExtendedMoniker::ComponentManager,
436 severity: diagnostics_data::Severity::Error,
437 })
438 .set_message("included")
439 .add_tag("tag2")
440 .build()
441 .into()
442 )));
443 assert!(!criteria.matches(&make_log_entry(
444 diagnostics_data::LogsDataBuilder::new(diagnostics_data::BuilderArgs {
445 timestamp: Timestamp::from_nanos(0),
446 component_url: Some("".into()),
447 moniker: ExtendedMoniker::ComponentManager,
448 severity: diagnostics_data::Severity::Error,
449 })
450 .set_message("included")
451 .add_tag("tag1")
452 .add_tag("tag3")
453 .build()
454 .into()
455 )));
456 }
457
458 #[fuchsia::test]
459 async fn test_per_component_severity() {
460 let cmd = LogCommand {
461 sub_command: Some(LogSubCommand::Dump(DumpCommand {})),
462 set_severity: vec![OneOrMany::One(
463 parse_log_interest_selector("test_selector#DEBUG").unwrap(),
464 )],
465 ..LogCommand::default()
466 };
467 let expectations = [
468 ("test_selector", diagnostics_data::Severity::Debug, true),
469 ("other_selector", diagnostics_data::Severity::Debug, false),
470 ("other_selector", diagnostics_data::Severity::Info, true),
471 ];
472 let criteria = LogFilterCriteria::from(cmd);
473 assert_eq!(criteria.min_severity, Severity::Info);
474 for (moniker, severity, is_included) in expectations {
475 let entry = make_log_entry(
476 diagnostics_data::LogsDataBuilder::new(diagnostics_data::BuilderArgs {
477 timestamp: Timestamp::from_nanos(0),
478 component_url: Some("".into()),
479 moniker: moniker.try_into().unwrap(),
480 severity,
481 })
482 .set_message("message")
483 .add_tag("tag1")
484 .add_tag("tag2")
485 .build()
486 .into(),
487 );
488 assert_eq!(criteria.matches(&entry), is_included);
489 }
490 }
491
492 #[fuchsia::test]
493 async fn test_per_component_severity_uses_min_match() {
494 let severities = [
495 diagnostics_data::Severity::Info,
496 diagnostics_data::Severity::Trace,
497 diagnostics_data::Severity::Debug,
498 ];
499
500 let cmd = LogCommand {
501 sub_command: Some(LogSubCommand::Dump(DumpCommand {})),
502 set_severity: vec![
503 OneOrMany::One(parse_log_interest_selector("test_selector#INFO").unwrap()),
504 OneOrMany::One(parse_log_interest_selector("test_selector#TRACE").unwrap()),
505 OneOrMany::One(parse_log_interest_selector("test_selector#DEBUG").unwrap()),
506 ],
507 ..LogCommand::default()
508 };
509 let criteria = LogFilterCriteria::from(cmd);
510
511 for severity in severities {
512 let entry = make_log_entry(
513 diagnostics_data::LogsDataBuilder::new(diagnostics_data::BuilderArgs {
514 timestamp: Timestamp::from_nanos(0),
515 component_url: Some("".into()),
516 moniker: "test_selector".try_into().unwrap(),
517 severity,
518 })
519 .set_message("message")
520 .add_tag("tag1")
521 .add_tag("tag2")
522 .build()
523 .into(),
524 );
525 assert!(criteria.matches(&entry));
526 }
527 }
528
529 #[fuchsia::test]
530 async fn test_criteria_tag_filter_legacy() {
531 let cmd = LogCommand {
532 tag: vec!["tag1".to_string()],
533 exclude_tags: vec!["tag3".to_string()],
534 ..empty_dump_command()
535 };
536 let criteria = LogFilterCriteria::from(cmd);
537
538 assert!(criteria.matches(&make_log_entry(
539 diagnostics_data::LogsDataBuilder::new(diagnostics_data::BuilderArgs {
540 timestamp: Timestamp::from_nanos(0),
541 component_url: Some("".into()),
542 moniker: ExtendedMoniker::ComponentManager,
543 severity: diagnostics_data::Severity::Error,
544 })
545 .set_message("included")
546 .add_tag("tag1")
547 .add_tag("tag2")
548 .build()
549 .into()
550 )));
551
552 assert!(!criteria.matches(&make_log_entry(
553 diagnostics_data::LogsDataBuilder::new(diagnostics_data::BuilderArgs {
554 timestamp: Timestamp::from_nanos(0),
555 component_url: Some("".into()),
556 moniker: ExtendedMoniker::ComponentManager,
557 severity: diagnostics_data::Severity::Error,
558 })
559 .set_message("included")
560 .add_tag("tag2")
561 .build()
562 .into()
563 )));
564 assert!(!criteria.matches(&make_log_entry(
565 diagnostics_data::LogsDataBuilder::new(diagnostics_data::BuilderArgs {
566 timestamp: Timestamp::from_nanos(0),
567 component_url: Some("".into()),
568 moniker: ExtendedMoniker::ComponentManager,
569 severity: diagnostics_data::Severity::Error,
570 })
571 .set_message("included")
572 .add_tag("tag1")
573 .add_tag("tag3")
574 .build()
575 .into()
576 )));
577 }
578
579 #[fuchsia::test]
580 async fn test_severity_filter_with_debug() {
581 let mut cmd = empty_dump_command();
582 cmd.severity = Severity::Trace;
583 let criteria = LogFilterCriteria::from(cmd);
584
585 assert!(criteria.matches(&make_log_entry(
586 diagnostics_data::LogsDataBuilder::new(diagnostics_data::BuilderArgs {
587 timestamp: Timestamp::from_nanos(0),
588 component_url: Some("".into()),
589 moniker: "included/moniker".try_into().unwrap(),
590 severity: diagnostics_data::Severity::Error,
591 })
592 .set_message("included message")
593 .build()
594 .into()
595 )));
596 assert!(criteria.matches(&make_log_entry(
597 diagnostics_data::LogsDataBuilder::new(diagnostics_data::BuilderArgs {
598 timestamp: Timestamp::from_nanos(0),
599 component_url: Some("".into()),
600 moniker: "included/moniker".try_into().unwrap(),
601 severity: diagnostics_data::Severity::Info,
602 })
603 .set_message("different message")
604 .build()
605 .into()
606 )));
607 assert!(criteria.matches(&make_log_entry(
608 diagnostics_data::LogsDataBuilder::new(diagnostics_data::BuilderArgs {
609 timestamp: Timestamp::from_nanos(0),
610 component_url: Some("".into()),
611 moniker: "other/moniker".try_into().unwrap(),
612 severity: diagnostics_data::Severity::Debug,
613 })
614 .set_message("included message")
615 .build()
616 .into()
617 )));
618 }
619
620 #[fuchsia::test]
621 async fn test_pid_filter() {
622 let mut cmd = empty_dump_command();
623 cmd.pid = Some(123);
624 let criteria = LogFilterCriteria::from(cmd);
625
626 assert!(criteria.matches(&make_log_entry(
627 diagnostics_data::LogsDataBuilder::new(diagnostics_data::BuilderArgs {
628 timestamp: Timestamp::from_nanos(0),
629 component_url: Some("".into()),
630 moniker: "included/moniker".try_into().unwrap(),
631 severity: diagnostics_data::Severity::Error,
632 })
633 .set_message("included message")
634 .set_pid(123)
635 .build()
636 .into()
637 )));
638 assert!(!criteria.matches(&make_log_entry(
639 diagnostics_data::LogsDataBuilder::new(diagnostics_data::BuilderArgs {
640 timestamp: Timestamp::from_nanos(0),
641 component_url: Some("".into()),
642 moniker: "included/moniker".try_into().unwrap(),
643 severity: diagnostics_data::Severity::Error,
644 })
645 .set_message("included message")
646 .set_pid(456)
647 .build()
648 .into()
649 )));
650 }
651
652 struct FakeInstanceGetter;
653 #[async_trait::async_trait(?Send)]
654 impl InstanceGetter for FakeInstanceGetter {
655 async fn get_monikers_from_query(
656 &self,
657 query: &str,
658 ) -> Result<Vec<moniker::Moniker>, LogError> {
659 Ok(vec![moniker::Moniker::try_from(query).unwrap()])
660 }
661 }
662
663 #[fuchsia::test]
664 async fn test_criteria_component_filter() {
665 let cmd = LogCommand {
666 component: vec!["/core/network/netstack".to_string()],
667 ..empty_dump_command()
668 };
669
670 let mut criteria = LogFilterCriteria::from(cmd);
671 criteria.expand_monikers(&FakeInstanceGetter).await.unwrap();
672
673 assert!(!criteria.matches(&make_log_entry(
674 diagnostics_data::LogsDataBuilder::new(diagnostics_data::BuilderArgs {
675 timestamp: Timestamp::from_nanos(0),
676 component_url: Some("".into()),
677 moniker: "bootstrap/archivist".try_into().unwrap(),
678 severity: diagnostics_data::Severity::Error,
679 })
680 .set_message("excluded")
681 .build()
682 .into()
683 )));
684
685 assert!(criteria.matches(&make_log_entry(
686 diagnostics_data::LogsDataBuilder::new(diagnostics_data::BuilderArgs {
687 timestamp: Timestamp::from_nanos(0),
688 component_url: Some("".into()),
689 moniker: "core/network/netstack".try_into().unwrap(),
690 severity: diagnostics_data::Severity::Error,
691 })
692 .set_message("included")
693 .build()
694 .into()
695 )));
696
697 assert!(!criteria.matches(&make_log_entry(
698 diagnostics_data::LogsDataBuilder::new(diagnostics_data::BuilderArgs {
699 timestamp: Timestamp::from_nanos(0),
700 component_url: Some("".into()),
701 moniker: "core/network/dhcp".try_into().unwrap(),
702 severity: diagnostics_data::Severity::Error,
703 })
704 .set_message("included")
705 .build()
706 .into()
707 )));
708 }
709
710 #[fuchsia::test]
711 async fn test_tid_filter() {
712 let mut cmd = empty_dump_command();
713 cmd.tid = Some(123);
714 let criteria = LogFilterCriteria::from(cmd);
715
716 assert!(criteria.matches(&make_log_entry(
717 diagnostics_data::LogsDataBuilder::new(diagnostics_data::BuilderArgs {
718 timestamp: Timestamp::from_nanos(0),
719 component_url: Some("".into()),
720 moniker: "included/moniker".try_into().unwrap(),
721 severity: diagnostics_data::Severity::Error,
722 })
723 .set_message("included message")
724 .set_tid(123)
725 .build()
726 .into()
727 )));
728 assert!(!criteria.matches(&make_log_entry(
729 diagnostics_data::LogsDataBuilder::new(diagnostics_data::BuilderArgs {
730 timestamp: Timestamp::from_nanos(0),
731 component_url: Some("".into()),
732 moniker: "included/moniker".try_into().unwrap(),
733 severity: diagnostics_data::Severity::Error,
734 })
735 .set_message("included message")
736 .set_tid(456)
737 .build()
738 .into()
739 )));
740 }
741
742 #[fuchsia::test]
743 async fn test_setter_functions() {
744 let mut filter = LogFilterCriteria::default();
745 filter.set_min_severity(Severity::Error);
746 assert_eq!(filter.min_severity, Severity::Error);
747 filter.set_tags(["tag1"]);
748 assert_eq!(filter.tags, ["tag1"]);
749 filter.set_exclude_tags(["tag2"]);
750 assert_eq!(filter.exclude_tags, ["tag2"]);
751 }
752
753 #[fuchsia::test]
754 async fn test_criteria_moniker_message_and_severity_matches() {
755 let cmd = LogCommand {
756 filter: vec!["included".to_string()],
757 exclude: vec!["not this".to_string()],
758 severity: Severity::Error,
759 ..empty_dump_command()
760 };
761 let criteria = LogFilterCriteria::from(cmd);
762
763 assert!(criteria.matches(&make_log_entry(
764 diagnostics_data::LogsDataBuilder::new(diagnostics_data::BuilderArgs {
765 timestamp: Timestamp::from_nanos(0),
766 component_url: Some("".into()),
767 moniker: "included/moniker".try_into().unwrap(),
768 severity: diagnostics_data::Severity::Error,
769 })
770 .set_message("included message")
771 .build()
772 .into()
773 )));
774 assert!(criteria.matches(&make_log_entry(
775 diagnostics_data::LogsDataBuilder::new(diagnostics_data::BuilderArgs {
776 timestamp: Timestamp::from_nanos(0),
777 component_url: Some("".into()),
778 moniker: "included/moniker".try_into().unwrap(),
779 severity: diagnostics_data::Severity::Fatal,
780 })
781 .set_message("included message")
782 .build()
783 .into()
784 )));
785 assert!(criteria.matches(&make_log_entry(
786 diagnostics_data::LogsDataBuilder::new(diagnostics_data::BuilderArgs {
787 timestamp: Timestamp::from_nanos(0),
788 component_url: Some("".into()),
789 moniker: "included/moniker".try_into().unwrap(),
791 severity: diagnostics_data::Severity::Fatal,
792 })
793 .set_message("included message")
794 .build()
795 .into()
796 )));
797 assert!(!criteria.matches(&make_log_entry(
798 diagnostics_data::LogsDataBuilder::new(diagnostics_data::BuilderArgs {
799 timestamp: Timestamp::from_nanos(0),
800 component_url: Some("".into()),
801 moniker: "not/this/moniker".try_into().unwrap(),
802 severity: diagnostics_data::Severity::Error,
803 })
804 .set_message("different message")
805 .build()
806 .into()
807 )));
808 assert!(!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::Warn,
814 })
815 .set_message("included message")
816 .build()
817 .into()
818 )));
819 assert!(!criteria.matches(&make_log_entry(
820 diagnostics_data::LogsDataBuilder::new(diagnostics_data::BuilderArgs {
821 timestamp: Timestamp::from_nanos(0),
822 component_url: Some("".into()),
823 moniker: "other/moniker".try_into().unwrap(),
824 severity: diagnostics_data::Severity::Error,
825 })
826 .set_message("not this message")
827 .build()
828 .into()
829 )));
830 assert!(!criteria.matches(&make_log_entry(
831 diagnostics_data::LogsDataBuilder::new(diagnostics_data::BuilderArgs {
832 timestamp: Timestamp::from_nanos(0),
833 component_url: Some("".into()),
834 moniker: "included/moniker".try_into().unwrap(),
835 severity: diagnostics_data::Severity::Error,
836 })
837 .set_message("not this message")
838 .build()
839 .into()
840 )));
841 }
842
843 #[fuchsia::test]
844 async fn test_criteria_klog_only() {
845 let cmd = LogCommand { tag: vec!["component_manager".into()], ..empty_dump_command() };
846 let criteria = LogFilterCriteria::from(cmd);
847
848 assert!(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: "klog".try_into().unwrap(),
853 severity: diagnostics_data::Severity::Error,
854 })
855 .set_message("[component_manager] included message")
856 .build()
857 .into()
858 )));
859 assert!(!criteria.matches(&make_log_entry(
860 diagnostics_data::LogsDataBuilder::new(diagnostics_data::BuilderArgs {
861 timestamp: Timestamp::from_nanos(0),
862 component_url: Some("".into()),
863 moniker: "klog".try_into().unwrap(),
864 severity: diagnostics_data::Severity::Error,
865 })
866 .set_message("excluded message[component_manager]")
867 .build()
868 .into()
869 )));
870 assert!(criteria.matches(&make_log_entry(
871 diagnostics_data::LogsDataBuilder::new(diagnostics_data::BuilderArgs {
872 timestamp: Timestamp::from_nanos(0),
873 component_url: Some("".into()),
874 moniker: "klog".try_into().unwrap(),
875 severity: diagnostics_data::Severity::Error,
876 })
877 .set_message("[tag0][component_manager] included message")
878 .build()
879 .into()
880 )));
881 assert!(!criteria.matches(&make_log_entry(
882 diagnostics_data::LogsDataBuilder::new(diagnostics_data::BuilderArgs {
883 timestamp: Timestamp::from_nanos(0),
884 component_url: Some("".into()),
885 moniker: "klog".try_into().unwrap(),
886 severity: diagnostics_data::Severity::Error,
887 })
888 .set_message("[other] excluded message")
889 .build()
890 .into()
891 )));
892 assert!(!criteria.matches(&make_log_entry(
893 diagnostics_data::LogsDataBuilder::new(diagnostics_data::BuilderArgs {
894 timestamp: Timestamp::from_nanos(0),
895 component_url: Some("".into()),
896 moniker: "klog".try_into().unwrap(),
897 severity: diagnostics_data::Severity::Error,
898 })
899 .set_message("no tags, excluded")
900 .build()
901 .into()
902 )));
903 assert!(!criteria.matches(&make_log_entry(
904 diagnostics_data::LogsDataBuilder::new(diagnostics_data::BuilderArgs {
905 timestamp: Timestamp::from_nanos(0),
906 component_url: Some("".into()),
907 moniker: "other/moniker".try_into().unwrap(),
908 severity: diagnostics_data::Severity::Error,
909 })
910 .set_message("[component_manager] excluded message")
911 .build()
912 .into()
913 )));
914 }
915
916 #[fuchsia::test]
917 async fn test_criteria_klog_tag_hack() {
918 let cmd = LogCommand { kernel: true, ..empty_dump_command() };
919 let mut criteria = LogFilterCriteria::from(cmd);
920
921 criteria.expand_monikers(&FakeInstanceGetter).await.unwrap();
922
923 assert!(criteria.matches(&make_log_entry(
924 diagnostics_data::LogsDataBuilder::new(diagnostics_data::BuilderArgs {
925 timestamp: Timestamp::from_nanos(0),
926 component_url: Some("".into()),
927 moniker: "klog".try_into().unwrap(),
928 severity: diagnostics_data::Severity::Error,
929 })
930 .set_message("included message")
931 .build()
932 .into()
933 )));
934 assert!(!criteria.matches(&make_log_entry(
935 diagnostics_data::LogsDataBuilder::new(diagnostics_data::BuilderArgs {
936 timestamp: Timestamp::from_nanos(0),
937 component_url: Some("".into()),
938 moniker: "other/moniker".try_into().unwrap(),
939 severity: diagnostics_data::Severity::Error,
940 })
941 .set_message("included message")
942 .build()
943 .into()
944 )));
945 }
946
947 #[test]
948 fn filter_fiters_filename() {
949 let cmd = LogCommand { filter: vec!["sometestfile".into()], ..empty_dump_command() };
950 let criteria = LogFilterCriteria::from(cmd);
951
952 assert!(criteria.matches(&make_log_entry(
953 diagnostics_data::LogsDataBuilder::new(diagnostics_data::BuilderArgs {
954 timestamp: Timestamp::from_nanos(0),
955 component_url: Some("".into()),
956 moniker: "core/last_segment".try_into().unwrap(),
957 severity: diagnostics_data::Severity::Error,
958 })
959 .set_file("sometestfile")
960 .set_message("hello world")
961 .build()
962 .into()
963 )));
964 }
965
966 #[fuchsia::test]
967 async fn test_empty_criteria() {
968 let cmd = empty_dump_command();
969 let criteria = LogFilterCriteria::from(cmd);
970
971 assert!(criteria.matches(&make_log_entry(
972 diagnostics_data::LogsDataBuilder::new(diagnostics_data::BuilderArgs {
973 timestamp: Timestamp::from_nanos(0),
974 component_url: Some("".into()),
975 moniker: "included/moniker".try_into().unwrap(),
976 severity: diagnostics_data::Severity::Error,
977 })
978 .set_message("included message")
979 .build()
980 .into()
981 )));
982 assert!(criteria.matches(&make_log_entry(
983 diagnostics_data::LogsDataBuilder::new(diagnostics_data::BuilderArgs {
984 timestamp: Timestamp::from_nanos(0),
985 component_url: Some("".into()),
986 moniker: "included/moniker".try_into().unwrap(),
987 severity: diagnostics_data::Severity::Info,
988 })
989 .set_message("different message")
990 .build()
991 .into()
992 )));
993 assert!(!criteria.matches(&make_log_entry(
994 diagnostics_data::LogsDataBuilder::new(diagnostics_data::BuilderArgs {
995 timestamp: Timestamp::from_nanos(0),
996 component_url: Some("".into()),
997 moniker: "other/moniker".try_into().unwrap(),
998 severity: diagnostics_data::Severity::Debug,
999 })
1000 .set_message("included message")
1001 .build()
1002 .into()
1003 )));
1004
1005 let entry = make_log_entry(
1006 diagnostics_data::LogsDataBuilder::new(diagnostics_data::BuilderArgs {
1007 timestamp: Timestamp::from_nanos(0),
1008 component_url: Some("".into()),
1009 moniker: "other/moniker".try_into().unwrap(),
1010 severity: diagnostics_data::Severity::Debug,
1011 })
1012 .set_message("included message")
1013 .build()
1014 .into(),
1015 );
1016
1017 assert!(!criteria.matches(&entry));
1018 }
1019
1020 #[test]
1021 fn filter_fiters_case_sensitivity() {
1022 let cmd = LogCommand { filter: vec!["sometestfile".into()], ..empty_dump_command() };
1024 let criteria = LogFilterCriteria::from(cmd);
1025
1026 let entry_0 = make_log_entry(
1027 diagnostics_data::LogsDataBuilder::new(diagnostics_data::BuilderArgs {
1028 timestamp: Timestamp::from_nanos(0),
1029 component_url: Some("".into()),
1030 moniker: "core/last_segment".try_into().unwrap(),
1031 severity: diagnostics_data::Severity::Error,
1032 })
1033 .set_file("sometestfile")
1034 .set_message("hello world")
1035 .build()
1036 .into(),
1037 );
1038
1039 let entry_1 = make_log_entry(
1040 diagnostics_data::LogsDataBuilder::new(diagnostics_data::BuilderArgs {
1041 timestamp: Timestamp::from_nanos(0),
1042 component_url: Some("".into()),
1043 moniker: "core/last_segment".try_into().unwrap(),
1044 severity: diagnostics_data::Severity::Error,
1045 })
1046 .set_file("someTESTfile")
1047 .set_message("hello world")
1048 .build()
1049 .into(),
1050 );
1051 assert!(criteria.matches(&entry_0));
1052 assert!(criteria.matches(&entry_1));
1053
1054 let cmd = LogCommand {
1056 filter: vec!["sometestfile".into()],
1057 case_sensitive: true,
1058 ..empty_dump_command()
1059 };
1060 let criteria = LogFilterCriteria::from(cmd);
1061
1062 assert!(criteria.matches(&entry_0));
1063 assert!(!criteria.matches(&entry_1));
1064 }
1065
1066 #[test]
1067 fn filter_fiters_case_sensitivity_for_tags() {
1068 let cmd = LogCommand { tag: vec!["someTAG".into()], ..empty_dump_command() };
1070 let criteria = LogFilterCriteria::from(cmd);
1071
1072 let entry_0 = make_log_entry(
1073 diagnostics_data::LogsDataBuilder::new(diagnostics_data::BuilderArgs {
1074 timestamp: Timestamp::from_nanos(0),
1075 component_url: Some("".into()),
1076 moniker: "core/last_segment".try_into().unwrap(),
1077 severity: diagnostics_data::Severity::Error,
1078 })
1079 .add_tag("someTAG")
1080 .set_message("hello world")
1081 .build()
1082 .into(),
1083 );
1084
1085 let entry_1 = make_log_entry(
1086 diagnostics_data::LogsDataBuilder::new(diagnostics_data::BuilderArgs {
1087 timestamp: Timestamp::from_nanos(0),
1088 component_url: Some("".into()),
1089 moniker: "core/last_segment".try_into().unwrap(),
1090 severity: diagnostics_data::Severity::Error,
1091 })
1092 .add_tag("SomeTaG")
1093 .set_message("hello world")
1094 .build()
1095 .into(),
1096 );
1097 assert!(criteria.matches(&entry_0));
1098 assert!(criteria.matches(&entry_1));
1099
1100 let cmd = LogCommand {
1102 tag: vec!["someTAG".into()],
1103 case_sensitive: true,
1104 ..empty_dump_command()
1105 };
1106 let criteria = LogFilterCriteria::from(cmd);
1107
1108 assert!(criteria.matches(&entry_0));
1109 assert!(!criteria.matches(&entry_1));
1110 }
1111
1112 #[test]
1113 fn filter_fiters_case_sensitivity_for_tags_including_moniker() {
1114 let cmd = LogCommand { tag: vec!["someTAG".into()], ..empty_dump_command() };
1116 let criteria = LogFilterCriteria::from(cmd);
1117
1118 let entry_0 = make_log_entry(
1119 diagnostics_data::LogsDataBuilder::new(diagnostics_data::BuilderArgs {
1120 timestamp: Timestamp::from_nanos(0),
1121 component_url: Some("".into()),
1122 moniker: "core/someTAG".try_into().unwrap(),
1123 severity: diagnostics_data::Severity::Error,
1124 })
1125 .set_message("hello world")
1126 .build()
1127 .into(),
1128 );
1129
1130 let entry_1 = make_log_entry(
1131 diagnostics_data::LogsDataBuilder::new(diagnostics_data::BuilderArgs {
1132 timestamp: Timestamp::from_nanos(0),
1133 component_url: Some("".into()),
1134 moniker: "core/SomeTaG".try_into().unwrap(),
1135 severity: diagnostics_data::Severity::Error,
1136 })
1137 .set_message("hello world")
1138 .build()
1139 .into(),
1140 );
1141 assert!(criteria.matches(&entry_0));
1142 assert!(criteria.matches(&entry_1));
1143
1144 let cmd = LogCommand {
1146 tag: vec!["someTAG".into()],
1147 case_sensitive: true,
1148 ..empty_dump_command()
1149 };
1150 let criteria = LogFilterCriteria::from(cmd);
1151
1152 assert!(criteria.matches(&entry_0));
1153 assert!(!criteria.matches(&entry_1));
1154 }
1155
1156 #[test]
1157 fn tag_matches_moniker_last_segment() {
1158 let cmd = LogCommand { tag: vec!["last_segment".to_string()], ..empty_dump_command() };
1160 let criteria = LogFilterCriteria::from(cmd);
1161
1162 assert!(criteria.matches(&make_log_entry(
1163 diagnostics_data::LogsDataBuilder::new(diagnostics_data::BuilderArgs {
1164 timestamp: Timestamp::from_nanos(0),
1165 component_url: Some("".into()),
1166 moniker: "core/last_segment".try_into().unwrap(),
1167 severity: diagnostics_data::Severity::Error,
1168 })
1169 .set_message("hello world")
1170 .build()
1171 .into()
1172 )));
1173 }
1174}