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.tag,
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) -> bool {
217 let tags = Self::parse_tags(klog_str);
218 self.tags.iter().any(|f| tags.iter().any(|t| t.contains(f)))
219 }
220
221 fn matches_filter_by_moniker_string(filter_string: &str, log: &LogsData) -> bool {
223 let Ok(filter_moniker) = ExtendedMoniker::from_str(filter_string) else {
224 return false;
225 };
226 filter_moniker == log.moniker
227 }
228
229 fn match_filters_to_log_data(&self, data: &LogsData) -> bool {
231 let min_severity = self
232 .interest_selectors
233 .iter()
234 .filter(|s| data.moniker.matches_component_selector(&s.selector).unwrap_or(false))
235 .filter_map(|selector| selector.interest.min_severity)
236 .min()
237 .unwrap_or_else(|| self.min_severity.into());
238 if data.metadata.severity < min_severity {
239 return false;
240 }
241
242 if let Some(pid) = self.pid {
243 if data.pid() != Some(pid) {
244 return false;
245 }
246 }
247
248 if let Some(tid) = self.tid {
249 if data.tid() != Some(tid) {
250 return false;
251 }
252 }
253
254 if !self.moniker_filters.matched_monikers.is_empty()
255 && !self
256 .moniker_filters
257 .matched_monikers
258 .iter()
259 .any(|f| Self::matches_filter_by_moniker_string(f, data))
260 {
261 return false;
262 }
263
264 let msg = data.msg().unwrap_or("");
265
266 if !self.filters.is_empty()
267 && !self
268 .filters
269 .iter()
270 .any(|f| Self::matches_filter_string(f, msg, data, self.case_sensitive))
271 {
272 return false;
273 }
274
275 if self
276 .excludes
277 .iter()
278 .any(|f| Self::matches_filter_string(f, msg, data, self.case_sensitive))
279 {
280 return false;
281 }
282
283 if !self.tags.is_empty()
284 && !self.tags.iter().any(|query_tag| {
285 let has_tag = data.tags().map(|t| t.contains(query_tag)).unwrap_or(false);
286 let moniker_has_tag = moniker_contains_in_last_segment(&data.moniker, query_tag);
287 has_tag || moniker_has_tag
288 })
289 {
290 if data.moniker == *KLOG_MONIKER {
291 return self.match_synthetic_klog_tags(data.msg().unwrap_or(""));
292 }
293 return false;
294 }
295
296 if self.exclude_tags.iter().any(|excluded_tag| {
297 let has_tag = data.tags().map(|tag| tag.contains(excluded_tag)).unwrap_or(false);
298 let moniker_has_tag = moniker_contains_in_last_segment(&data.moniker, excluded_tag);
299 has_tag || moniker_has_tag
300 }) {
301 return false;
302 }
303
304 true
305 }
306}
307
308fn moniker_contains_in_last_segment(moniker: &ExtendedMoniker, query_tag: &str) -> bool {
309 match moniker {
310 ExtendedMoniker::ComponentInstance(moniker) => moniker
311 .path()
312 .last()
313 .map(|segment| segment.to_string().contains(query_tag))
314 .unwrap_or(false),
315 ExtendedMoniker::ComponentManager => {
316 EXTENDED_MONIKER_COMPONENT_MANAGER_STR.contains(query_tag)
317 }
318 }
319}
320
321#[cfg(test)]
322mod test {
323 use diagnostics_data::{ExtendedMoniker, Timestamp};
324 use selectors::parse_log_interest_selector;
325
326 use crate::log_socket_stream::OneOrMany;
327 use crate::{DumpCommand, LogSubCommand};
328
329 use super::*;
330
331 fn empty_dump_command() -> LogCommand {
332 LogCommand {
333 sub_command: Some(LogSubCommand::Dump(DumpCommand {})),
334 ..LogCommand::default()
335 }
336 }
337
338 fn make_log_entry(log_data: LogData) -> LogEntry {
339 LogEntry { data: log_data }
340 }
341
342 #[fuchsia::test]
343 async fn test_criteria_tag_filter_filters_moniker() {
344 let cmd = LogCommand { tag: vec!["testcomponent".to_string()], ..empty_dump_command() };
345 let criteria = LogFilterCriteria::from(cmd);
346
347 assert!(criteria.matches(&make_log_entry(
348 diagnostics_data::LogsDataBuilder::new(diagnostics_data::BuilderArgs {
349 timestamp: Timestamp::from_nanos(0),
350 component_url: Some("".into()),
351 moniker: "my/testcomponent".try_into().unwrap(),
352 severity: diagnostics_data::Severity::Error,
353 })
354 .set_message("included")
355 .add_tag("tag1")
356 .add_tag("tag2")
357 .build()
358 .into()
359 )));
360 }
361
362 #[fuchsia::test]
363 async fn test_criteria_exclude_tag_filters_moniker() {
364 let cmd =
365 LogCommand { exclude_tags: vec!["testcomponent".to_string()], ..empty_dump_command() };
366 let criteria = LogFilterCriteria::from(cmd);
367 assert!(!criteria.matches(&make_log_entry(
368 diagnostics_data::LogsDataBuilder::new(diagnostics_data::BuilderArgs {
369 timestamp: Timestamp::from_nanos(0),
370 component_url: Some("".into()),
371 moniker: "my/testcomponent".try_into().unwrap(),
372 severity: diagnostics_data::Severity::Error,
373 })
374 .set_message("excluded")
375 .add_tag("tag1")
376 .add_tag("tag2")
377 .build()
378 .into()
379 )));
380 }
381
382 #[fuchsia::test]
383 async fn test_criteria_tag_filter() {
384 let cmd = LogCommand {
385 tag: vec!["tag1".to_string()],
386 exclude_tags: vec!["tag3".to_string()],
387 ..empty_dump_command()
388 };
389 let criteria = LogFilterCriteria::from(cmd);
390
391 assert!(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: ExtendedMoniker::ComponentManager,
396 severity: diagnostics_data::Severity::Error,
397 })
398 .set_message("included")
399 .add_tag("tag1")
400 .add_tag("tag2")
401 .build()
402 .into()
403 )));
404
405 assert!(!criteria.matches(&make_log_entry(
406 diagnostics_data::LogsDataBuilder::new(diagnostics_data::BuilderArgs {
407 timestamp: Timestamp::from_nanos(0),
408 component_url: Some("".into()),
409 moniker: ExtendedMoniker::ComponentManager,
410 severity: diagnostics_data::Severity::Error,
411 })
412 .set_message("included")
413 .add_tag("tag2")
414 .build()
415 .into()
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("tag3")
427 .build()
428 .into()
429 )));
430 }
431
432 #[fuchsia::test]
433 async fn test_per_component_severity() {
434 let cmd = LogCommand {
435 sub_command: Some(LogSubCommand::Dump(DumpCommand {})),
436 set_severity: vec![OneOrMany::One(
437 parse_log_interest_selector("test_selector#DEBUG").unwrap(),
438 )],
439 ..LogCommand::default()
440 };
441 let expectations = [
442 ("test_selector", diagnostics_data::Severity::Debug, true),
443 ("other_selector", diagnostics_data::Severity::Debug, false),
444 ("other_selector", diagnostics_data::Severity::Info, true),
445 ];
446 let criteria = LogFilterCriteria::from(cmd);
447 assert_eq!(criteria.min_severity, Severity::Info);
448 for (moniker, severity, is_included) in expectations {
449 let entry = make_log_entry(
450 diagnostics_data::LogsDataBuilder::new(diagnostics_data::BuilderArgs {
451 timestamp: Timestamp::from_nanos(0),
452 component_url: Some("".into()),
453 moniker: moniker.try_into().unwrap(),
454 severity,
455 })
456 .set_message("message")
457 .add_tag("tag1")
458 .add_tag("tag2")
459 .build()
460 .into(),
461 );
462 assert_eq!(criteria.matches(&entry), is_included);
463 }
464 }
465
466 #[fuchsia::test]
467 async fn test_per_component_severity_uses_min_match() {
468 let severities = [
469 diagnostics_data::Severity::Info,
470 diagnostics_data::Severity::Trace,
471 diagnostics_data::Severity::Debug,
472 ];
473
474 let cmd = LogCommand {
475 sub_command: Some(LogSubCommand::Dump(DumpCommand {})),
476 set_severity: vec![
477 OneOrMany::One(parse_log_interest_selector("test_selector#INFO").unwrap()),
478 OneOrMany::One(parse_log_interest_selector("test_selector#TRACE").unwrap()),
479 OneOrMany::One(parse_log_interest_selector("test_selector#DEBUG").unwrap()),
480 ],
481 ..LogCommand::default()
482 };
483 let criteria = LogFilterCriteria::from(cmd);
484
485 for severity in severities {
486 let entry = make_log_entry(
487 diagnostics_data::LogsDataBuilder::new(diagnostics_data::BuilderArgs {
488 timestamp: Timestamp::from_nanos(0),
489 component_url: Some("".into()),
490 moniker: "test_selector".try_into().unwrap(),
491 severity,
492 })
493 .set_message("message")
494 .add_tag("tag1")
495 .add_tag("tag2")
496 .build()
497 .into(),
498 );
499 assert!(criteria.matches(&entry));
500 }
501 }
502
503 #[fuchsia::test]
504 async fn test_criteria_tag_filter_legacy() {
505 let cmd = LogCommand {
506 tag: vec!["tag1".to_string()],
507 exclude_tags: vec!["tag3".to_string()],
508 ..empty_dump_command()
509 };
510 let criteria = LogFilterCriteria::from(cmd);
511
512 assert!(criteria.matches(&make_log_entry(
513 diagnostics_data::LogsDataBuilder::new(diagnostics_data::BuilderArgs {
514 timestamp: Timestamp::from_nanos(0),
515 component_url: Some("".into()),
516 moniker: ExtendedMoniker::ComponentManager,
517 severity: diagnostics_data::Severity::Error,
518 })
519 .set_message("included")
520 .add_tag("tag1")
521 .add_tag("tag2")
522 .build()
523 .into()
524 )));
525
526 assert!(!criteria.matches(&make_log_entry(
527 diagnostics_data::LogsDataBuilder::new(diagnostics_data::BuilderArgs {
528 timestamp: Timestamp::from_nanos(0),
529 component_url: Some("".into()),
530 moniker: ExtendedMoniker::ComponentManager,
531 severity: diagnostics_data::Severity::Error,
532 })
533 .set_message("included")
534 .add_tag("tag2")
535 .build()
536 .into()
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("tag3")
548 .build()
549 .into()
550 )));
551 }
552
553 #[fuchsia::test]
554 async fn test_severity_filter_with_debug() {
555 let mut cmd = empty_dump_command();
556 cmd.severity = Severity::Trace;
557 let criteria = LogFilterCriteria::from(cmd);
558
559 assert!(criteria.matches(&make_log_entry(
560 diagnostics_data::LogsDataBuilder::new(diagnostics_data::BuilderArgs {
561 timestamp: Timestamp::from_nanos(0),
562 component_url: Some("".into()),
563 moniker: "included/moniker".try_into().unwrap(),
564 severity: diagnostics_data::Severity::Error,
565 })
566 .set_message("included message")
567 .build()
568 .into()
569 )));
570 assert!(criteria.matches(&make_log_entry(
571 diagnostics_data::LogsDataBuilder::new(diagnostics_data::BuilderArgs {
572 timestamp: Timestamp::from_nanos(0),
573 component_url: Some("".into()),
574 moniker: "included/moniker".try_into().unwrap(),
575 severity: diagnostics_data::Severity::Info,
576 })
577 .set_message("different message")
578 .build()
579 .into()
580 )));
581 assert!(criteria.matches(&make_log_entry(
582 diagnostics_data::LogsDataBuilder::new(diagnostics_data::BuilderArgs {
583 timestamp: Timestamp::from_nanos(0),
584 component_url: Some("".into()),
585 moniker: "other/moniker".try_into().unwrap(),
586 severity: diagnostics_data::Severity::Debug,
587 })
588 .set_message("included message")
589 .build()
590 .into()
591 )));
592 }
593
594 #[fuchsia::test]
595 async fn test_pid_filter() {
596 let mut cmd = empty_dump_command();
597 cmd.pid = Some(123);
598 let criteria = LogFilterCriteria::from(cmd);
599
600 assert!(criteria.matches(&make_log_entry(
601 diagnostics_data::LogsDataBuilder::new(diagnostics_data::BuilderArgs {
602 timestamp: Timestamp::from_nanos(0),
603 component_url: Some("".into()),
604 moniker: "included/moniker".try_into().unwrap(),
605 severity: diagnostics_data::Severity::Error,
606 })
607 .set_message("included message")
608 .set_pid(123)
609 .build()
610 .into()
611 )));
612 assert!(!criteria.matches(&make_log_entry(
613 diagnostics_data::LogsDataBuilder::new(diagnostics_data::BuilderArgs {
614 timestamp: Timestamp::from_nanos(0),
615 component_url: Some("".into()),
616 moniker: "included/moniker".try_into().unwrap(),
617 severity: diagnostics_data::Severity::Error,
618 })
619 .set_message("included message")
620 .set_pid(456)
621 .build()
622 .into()
623 )));
624 }
625
626 struct FakeInstanceGetter;
627 #[async_trait::async_trait(?Send)]
628 impl InstanceGetter for FakeInstanceGetter {
629 async fn get_monikers_from_query(
630 &self,
631 query: &str,
632 ) -> Result<Vec<moniker::Moniker>, LogError> {
633 Ok(vec![moniker::Moniker::try_from(query).unwrap()])
634 }
635 }
636
637 #[fuchsia::test]
638 async fn test_criteria_component_filter() {
639 let cmd = LogCommand {
640 component: vec!["/core/network/netstack".to_string()],
641 ..empty_dump_command()
642 };
643
644 let mut criteria = LogFilterCriteria::from(cmd);
645 criteria.expand_monikers(&FakeInstanceGetter).await.unwrap();
646
647 assert!(!criteria.matches(&make_log_entry(
648 diagnostics_data::LogsDataBuilder::new(diagnostics_data::BuilderArgs {
649 timestamp: Timestamp::from_nanos(0),
650 component_url: Some("".into()),
651 moniker: "bootstrap/archivist".try_into().unwrap(),
652 severity: diagnostics_data::Severity::Error,
653 })
654 .set_message("excluded")
655 .build()
656 .into()
657 )));
658
659 assert!(criteria.matches(&make_log_entry(
660 diagnostics_data::LogsDataBuilder::new(diagnostics_data::BuilderArgs {
661 timestamp: Timestamp::from_nanos(0),
662 component_url: Some("".into()),
663 moniker: "core/network/netstack".try_into().unwrap(),
664 severity: diagnostics_data::Severity::Error,
665 })
666 .set_message("included")
667 .build()
668 .into()
669 )));
670
671 assert!(!criteria.matches(&make_log_entry(
672 diagnostics_data::LogsDataBuilder::new(diagnostics_data::BuilderArgs {
673 timestamp: Timestamp::from_nanos(0),
674 component_url: Some("".into()),
675 moniker: "core/network/dhcp".try_into().unwrap(),
676 severity: diagnostics_data::Severity::Error,
677 })
678 .set_message("included")
679 .build()
680 .into()
681 )));
682 }
683
684 #[fuchsia::test]
685 async fn test_tid_filter() {
686 let mut cmd = empty_dump_command();
687 cmd.tid = Some(123);
688 let criteria = LogFilterCriteria::from(cmd);
689
690 assert!(criteria.matches(&make_log_entry(
691 diagnostics_data::LogsDataBuilder::new(diagnostics_data::BuilderArgs {
692 timestamp: Timestamp::from_nanos(0),
693 component_url: Some("".into()),
694 moniker: "included/moniker".try_into().unwrap(),
695 severity: diagnostics_data::Severity::Error,
696 })
697 .set_message("included message")
698 .set_tid(123)
699 .build()
700 .into()
701 )));
702 assert!(!criteria.matches(&make_log_entry(
703 diagnostics_data::LogsDataBuilder::new(diagnostics_data::BuilderArgs {
704 timestamp: Timestamp::from_nanos(0),
705 component_url: Some("".into()),
706 moniker: "included/moniker".try_into().unwrap(),
707 severity: diagnostics_data::Severity::Error,
708 })
709 .set_message("included message")
710 .set_tid(456)
711 .build()
712 .into()
713 )));
714 }
715
716 #[fuchsia::test]
717 async fn test_setter_functions() {
718 let mut filter = LogFilterCriteria::default();
719 filter.set_min_severity(Severity::Error);
720 assert_eq!(filter.min_severity, Severity::Error);
721 filter.set_tags(["tag1"]);
722 assert_eq!(filter.tags, ["tag1"]);
723 filter.set_exclude_tags(["tag2"]);
724 assert_eq!(filter.exclude_tags, ["tag2"]);
725 }
726
727 #[fuchsia::test]
728 async fn test_criteria_moniker_message_and_severity_matches() {
729 let cmd = LogCommand {
730 filter: vec!["included".to_string()],
731 exclude: vec!["not this".to_string()],
732 severity: Severity::Error,
733 ..empty_dump_command()
734 };
735 let criteria = LogFilterCriteria::from(cmd);
736
737 assert!(criteria.matches(&make_log_entry(
738 diagnostics_data::LogsDataBuilder::new(diagnostics_data::BuilderArgs {
739 timestamp: Timestamp::from_nanos(0),
740 component_url: Some("".into()),
741 moniker: "included/moniker".try_into().unwrap(),
742 severity: diagnostics_data::Severity::Error,
743 })
744 .set_message("included message")
745 .build()
746 .into()
747 )));
748 assert!(criteria.matches(&make_log_entry(
749 diagnostics_data::LogsDataBuilder::new(diagnostics_data::BuilderArgs {
750 timestamp: Timestamp::from_nanos(0),
751 component_url: Some("".into()),
752 moniker: "included/moniker".try_into().unwrap(),
753 severity: diagnostics_data::Severity::Fatal,
754 })
755 .set_message("included message")
756 .build()
757 .into()
758 )));
759 assert!(criteria.matches(&make_log_entry(
760 diagnostics_data::LogsDataBuilder::new(diagnostics_data::BuilderArgs {
761 timestamp: Timestamp::from_nanos(0),
762 component_url: Some("".into()),
763 moniker: "included/moniker".try_into().unwrap(),
765 severity: diagnostics_data::Severity::Fatal,
766 })
767 .set_message("included message")
768 .build()
769 .into()
770 )));
771 assert!(!criteria.matches(&make_log_entry(
772 diagnostics_data::LogsDataBuilder::new(diagnostics_data::BuilderArgs {
773 timestamp: Timestamp::from_nanos(0),
774 component_url: Some("".into()),
775 moniker: "not/this/moniker".try_into().unwrap(),
776 severity: diagnostics_data::Severity::Error,
777 })
778 .set_message("different message")
779 .build()
780 .into()
781 )));
782 assert!(!criteria.matches(&make_log_entry(
783 diagnostics_data::LogsDataBuilder::new(diagnostics_data::BuilderArgs {
784 timestamp: Timestamp::from_nanos(0),
785 component_url: Some("".into()),
786 moniker: "included/moniker".try_into().unwrap(),
787 severity: diagnostics_data::Severity::Warn,
788 })
789 .set_message("included message")
790 .build()
791 .into()
792 )));
793 assert!(!criteria.matches(&make_log_entry(
794 diagnostics_data::LogsDataBuilder::new(diagnostics_data::BuilderArgs {
795 timestamp: Timestamp::from_nanos(0),
796 component_url: Some("".into()),
797 moniker: "other/moniker".try_into().unwrap(),
798 severity: diagnostics_data::Severity::Error,
799 })
800 .set_message("not this message")
801 .build()
802 .into()
803 )));
804 assert!(!criteria.matches(&make_log_entry(
805 diagnostics_data::LogsDataBuilder::new(diagnostics_data::BuilderArgs {
806 timestamp: Timestamp::from_nanos(0),
807 component_url: Some("".into()),
808 moniker: "included/moniker".try_into().unwrap(),
809 severity: diagnostics_data::Severity::Error,
810 })
811 .set_message("not this message")
812 .build()
813 .into()
814 )));
815 }
816
817 #[fuchsia::test]
818 async fn test_criteria_klog_only() {
819 let cmd = LogCommand { tag: vec!["component_manager".into()], ..empty_dump_command() };
820 let criteria = LogFilterCriteria::from(cmd);
821
822 assert!(criteria.matches(&make_log_entry(
823 diagnostics_data::LogsDataBuilder::new(diagnostics_data::BuilderArgs {
824 timestamp: Timestamp::from_nanos(0),
825 component_url: Some("".into()),
826 moniker: "klog".try_into().unwrap(),
827 severity: diagnostics_data::Severity::Error,
828 })
829 .set_message("[component_manager] included message")
830 .build()
831 .into()
832 )));
833 assert!(!criteria.matches(&make_log_entry(
834 diagnostics_data::LogsDataBuilder::new(diagnostics_data::BuilderArgs {
835 timestamp: Timestamp::from_nanos(0),
836 component_url: Some("".into()),
837 moniker: "klog".try_into().unwrap(),
838 severity: diagnostics_data::Severity::Error,
839 })
840 .set_message("excluded message[component_manager]")
841 .build()
842 .into()
843 )));
844 assert!(criteria.matches(&make_log_entry(
845 diagnostics_data::LogsDataBuilder::new(diagnostics_data::BuilderArgs {
846 timestamp: Timestamp::from_nanos(0),
847 component_url: Some("".into()),
848 moniker: "klog".try_into().unwrap(),
849 severity: diagnostics_data::Severity::Error,
850 })
851 .set_message("[tag0][component_manager] included message")
852 .build()
853 .into()
854 )));
855 assert!(!criteria.matches(&make_log_entry(
856 diagnostics_data::LogsDataBuilder::new(diagnostics_data::BuilderArgs {
857 timestamp: Timestamp::from_nanos(0),
858 component_url: Some("".into()),
859 moniker: "klog".try_into().unwrap(),
860 severity: diagnostics_data::Severity::Error,
861 })
862 .set_message("[other] excluded message")
863 .build()
864 .into()
865 )));
866 assert!(!criteria.matches(&make_log_entry(
867 diagnostics_data::LogsDataBuilder::new(diagnostics_data::BuilderArgs {
868 timestamp: Timestamp::from_nanos(0),
869 component_url: Some("".into()),
870 moniker: "klog".try_into().unwrap(),
871 severity: diagnostics_data::Severity::Error,
872 })
873 .set_message("no tags, excluded")
874 .build()
875 .into()
876 )));
877 assert!(!criteria.matches(&make_log_entry(
878 diagnostics_data::LogsDataBuilder::new(diagnostics_data::BuilderArgs {
879 timestamp: Timestamp::from_nanos(0),
880 component_url: Some("".into()),
881 moniker: "other/moniker".try_into().unwrap(),
882 severity: diagnostics_data::Severity::Error,
883 })
884 .set_message("[component_manager] excluded message")
885 .build()
886 .into()
887 )));
888 }
889
890 #[fuchsia::test]
891 async fn test_criteria_klog_tag_hack() {
892 let cmd = LogCommand { kernel: true, ..empty_dump_command() };
893 let mut criteria = LogFilterCriteria::from(cmd);
894
895 criteria.expand_monikers(&FakeInstanceGetter).await.unwrap();
896
897 assert!(criteria.matches(&make_log_entry(
898 diagnostics_data::LogsDataBuilder::new(diagnostics_data::BuilderArgs {
899 timestamp: Timestamp::from_nanos(0),
900 component_url: Some("".into()),
901 moniker: "klog".try_into().unwrap(),
902 severity: diagnostics_data::Severity::Error,
903 })
904 .set_message("included message")
905 .build()
906 .into()
907 )));
908 assert!(!criteria.matches(&make_log_entry(
909 diagnostics_data::LogsDataBuilder::new(diagnostics_data::BuilderArgs {
910 timestamp: Timestamp::from_nanos(0),
911 component_url: Some("".into()),
912 moniker: "other/moniker".try_into().unwrap(),
913 severity: diagnostics_data::Severity::Error,
914 })
915 .set_message("included message")
916 .build()
917 .into()
918 )));
919 }
920
921 #[test]
922 fn filter_fiters_filename() {
923 let cmd = LogCommand { filter: vec!["sometestfile".into()], ..empty_dump_command() };
924 let criteria = LogFilterCriteria::from(cmd);
925
926 assert!(criteria.matches(&make_log_entry(
927 diagnostics_data::LogsDataBuilder::new(diagnostics_data::BuilderArgs {
928 timestamp: Timestamp::from_nanos(0),
929 component_url: Some("".into()),
930 moniker: "core/last_segment".try_into().unwrap(),
931 severity: diagnostics_data::Severity::Error,
932 })
933 .set_file("sometestfile")
934 .set_message("hello world")
935 .build()
936 .into()
937 )));
938 }
939
940 #[fuchsia::test]
941 async fn test_empty_criteria() {
942 let cmd = empty_dump_command();
943 let criteria = LogFilterCriteria::from(cmd);
944
945 assert!(criteria.matches(&make_log_entry(
946 diagnostics_data::LogsDataBuilder::new(diagnostics_data::BuilderArgs {
947 timestamp: Timestamp::from_nanos(0),
948 component_url: Some("".into()),
949 moniker: "included/moniker".try_into().unwrap(),
950 severity: diagnostics_data::Severity::Error,
951 })
952 .set_message("included message")
953 .build()
954 .into()
955 )));
956 assert!(criteria.matches(&make_log_entry(
957 diagnostics_data::LogsDataBuilder::new(diagnostics_data::BuilderArgs {
958 timestamp: Timestamp::from_nanos(0),
959 component_url: Some("".into()),
960 moniker: "included/moniker".try_into().unwrap(),
961 severity: diagnostics_data::Severity::Info,
962 })
963 .set_message("different message")
964 .build()
965 .into()
966 )));
967 assert!(!criteria.matches(&make_log_entry(
968 diagnostics_data::LogsDataBuilder::new(diagnostics_data::BuilderArgs {
969 timestamp: Timestamp::from_nanos(0),
970 component_url: Some("".into()),
971 moniker: "other/moniker".try_into().unwrap(),
972 severity: diagnostics_data::Severity::Debug,
973 })
974 .set_message("included message")
975 .build()
976 .into()
977 )));
978
979 let entry = make_log_entry(
980 diagnostics_data::LogsDataBuilder::new(diagnostics_data::BuilderArgs {
981 timestamp: Timestamp::from_nanos(0),
982 component_url: Some("".into()),
983 moniker: "other/moniker".try_into().unwrap(),
984 severity: diagnostics_data::Severity::Debug,
985 })
986 .set_message("included message")
987 .build()
988 .into(),
989 );
990
991 assert!(!criteria.matches(&entry));
992 }
993
994 #[test]
995 fn filter_fiters_case_sensitivity() {
996 let cmd = LogCommand { filter: vec!["sometestfile".into()], ..empty_dump_command() };
998 let criteria = LogFilterCriteria::from(cmd);
999
1000 let entry_0 = make_log_entry(
1001 diagnostics_data::LogsDataBuilder::new(diagnostics_data::BuilderArgs {
1002 timestamp: Timestamp::from_nanos(0),
1003 component_url: Some("".into()),
1004 moniker: "core/last_segment".try_into().unwrap(),
1005 severity: diagnostics_data::Severity::Error,
1006 })
1007 .set_file("sometestfile")
1008 .set_message("hello world")
1009 .build()
1010 .into(),
1011 );
1012
1013 let entry_1 = make_log_entry(
1014 diagnostics_data::LogsDataBuilder::new(diagnostics_data::BuilderArgs {
1015 timestamp: Timestamp::from_nanos(0),
1016 component_url: Some("".into()),
1017 moniker: "core/last_segment".try_into().unwrap(),
1018 severity: diagnostics_data::Severity::Error,
1019 })
1020 .set_file("someTESTfile")
1021 .set_message("hello world")
1022 .build()
1023 .into(),
1024 );
1025 assert!(criteria.matches(&entry_0));
1026 assert!(criteria.matches(&entry_1));
1027
1028 let cmd = LogCommand {
1030 filter: vec!["sometestfile".into()],
1031 case_sensitive: true,
1032 ..empty_dump_command()
1033 };
1034 let criteria = LogFilterCriteria::from(cmd);
1035
1036 assert!(criteria.matches(&entry_0));
1037 assert!(!criteria.matches(&entry_1));
1038 }
1039
1040 #[test]
1041 fn tag_matches_moniker_last_segment() {
1042 let cmd = LogCommand { tag: vec!["last_segment".to_string()], ..empty_dump_command() };
1044 let criteria = LogFilterCriteria::from(cmd);
1045
1046 assert!(criteria.matches(&make_log_entry(
1047 diagnostics_data::LogsDataBuilder::new(diagnostics_data::BuilderArgs {
1048 timestamp: Timestamp::from_nanos(0),
1049 component_url: Some("".into()),
1050 moniker: "core/last_segment".try_into().unwrap(),
1051 severity: diagnostics_data::Severity::Error,
1052 })
1053 .set_message("hello world")
1054 .build()
1055 .into()
1056 )));
1057 }
1058}