1use anyhow::format_err;
6use argh::{ArgsInfo, FromArgs, TopLevelCommand};
7use chrono::{DateTime, Local};
8use chrono_english::{parse_date_string, Dialect};
9use component_debug::query::get_instances_from_query;
10use diagnostics_data::Severity;
11use errors::{ffx_bail, FfxError};
12use fidl_fuchsia_diagnostics::{LogInterestSelector, LogSettingsProxy};
13use fidl_fuchsia_sys2::RealmQueryProxy;
14pub use log_socket_stream::OneOrMany;
15use moniker::Moniker;
16use selectors::{sanitize_moniker_for_selectors, SelectorExt};
17use std::borrow::Cow;
18use std::io::Write;
19use std::ops::Deref;
20use std::str::FromStr;
21use std::string::FromUtf8Error;
22use std::time::Duration;
23use thiserror::Error;
24mod filter;
25mod log_formatter;
26mod log_socket_stream;
27pub use log_formatter::{
28 dump_logs_from_socket, BootTimeAccessor, DefaultLogFormatter, FormatterError, LogData,
29 LogEntry, Symbolize, Timestamp, WriterContainer, TIMESTAMP_FORMAT,
30};
31pub use log_socket_stream::{JsonDeserializeError, LogsDataStream};
32
33#[derive(ArgsInfo, FromArgs, Clone, PartialEq, Debug)]
35#[argh(subcommand)]
36pub enum LogSubCommand {
37 Watch(WatchCommand),
38 Dump(DumpCommand),
39 SetSeverity(SetSeverityCommand),
40}
41
42#[derive(ArgsInfo, FromArgs, Clone, PartialEq, Debug, Default)]
43#[argh(subcommand, name = "set-severity")]
45pub struct SetSeverityCommand {
46 #[argh(switch)]
50 pub no_persist: bool,
51
52 #[argh(switch)]
56 pub force: bool,
57
58 #[argh(positional, from_str_fn(log_interest_selector))]
65 pub interest_selector: Vec<OneOrMany<LogInterestSelector>>,
66}
67
68#[derive(ArgsInfo, FromArgs, Clone, PartialEq, Debug)]
69#[argh(subcommand, name = "watch")]
71pub struct WatchCommand {}
72
73#[derive(ArgsInfo, FromArgs, Clone, PartialEq, Debug)]
74#[argh(subcommand, name = "dump")]
76pub struct DumpCommand {}
77
78pub fn parse_time(value: &str) -> Result<DetailedDateTime, String> {
79 parse_date_string(value, Local::now(), Dialect::Us)
80 .map(|time| DetailedDateTime { time, is_now: value == "now" })
81 .map_err(|e| format!("invalid date string: {e}"))
82}
83
84pub fn parse_seconds_string_as_duration(value: &str) -> Result<Duration, String> {
87 Ok(Duration::from_secs(
88 value.parse().map_err(|e| format!("value '{value}' is not a number: {e}"))?,
89 ))
90}
91
92#[derive(Clone, Debug, PartialEq)]
94pub enum TimeFormat {
95 Utc,
97 Local,
99 Boot,
101}
102
103impl std::str::FromStr for TimeFormat {
104 type Err = String;
105
106 fn from_str(s: &str) -> Result<Self, Self::Err> {
107 let lower = s.to_ascii_lowercase();
108 match lower.as_str() {
109 "local" => Ok(TimeFormat::Local),
110 "utc" => Ok(TimeFormat::Utc),
111 "boot" => Ok(TimeFormat::Boot),
112 _ => Err(format!("'{s}' is not a valid value: must be one of 'local', 'utc', 'boot'")),
113 }
114 }
115}
116
117#[derive(PartialEq, Clone, Debug)]
121pub struct DetailedDateTime {
122 pub time: DateTime<Local>,
125 pub is_now: bool,
129}
130
131impl Deref for DetailedDateTime {
132 type Target = DateTime<Local>;
133
134 fn deref(&self) -> &Self::Target {
135 &self.time
136 }
137}
138
139#[derive(Clone, PartialEq, Debug)]
140pub enum SymbolizeMode {
141 Off,
143 Pretty,
145 Classic,
147}
148
149impl SymbolizeMode {
150 pub fn is_prettification_disabled(&self) -> bool {
151 matches!(self, SymbolizeMode::Classic)
152 }
153
154 pub fn is_symbolize_disabled(&self) -> bool {
155 matches!(self, SymbolizeMode::Off)
156 }
157}
158
159#[derive(ArgsInfo, FromArgs, Clone, Debug, PartialEq)]
160#[argh(
161 subcommand,
162 name = "log",
163 description = "Display logs from a target device",
164 note = "Logs are retrieve from the target at the moment this command is called.
165
166You may see some additional information attached to the log line:
167
168- `dropped=N`: this means that N logs attributed to the component were dropped when the component
169 wrote to the log socket. This can happen when archivist cannot keep up with the rate of logs being
170 emitted by the component and the component filled the log socket buffer in the kernel.
171
172- `rolled=N`: this means that N logs rolled out from the archivist buffer and ffx never saw them.
173 This can happen when more logs are being ingested by the archivist across all components and the
174 ffx couldn't retrieve them fast enough.
175
176Symbolization is performed in the background using the symbolizer host tool. You can pass
177additional arguments to the symbolizer tool (for example, to add a remote symbol server) using:
178 $ ffx config set proactive_log.symbolize.extra_args \"--symbol-server gs://some-url/path --symbol-server gs://some-other-url/path ...\"
179
180To learn more about configuring the log viewer, visit https://fuchsia.dev/fuchsia-src/development/tools/ffx/commands/log",
181 example = "\
182Dump the most recent logs and stream new ones as they happen:
183 $ ffx log
184
185Stream new logs starting from the current time, filtering for severity of at least \"WARN\":
186 $ ffx log --severity warn --since now
187
188Stream logs where the source moniker, component url and message do not include \"sys\":
189 $ ffx log --exclude sys
190
191Stream ERROR logs with source moniker, component url or message containing either
192\"netstack\" or \"remote-control.cm\", but not containing \"sys\":
193 $ ffx log --severity error --filter netstack --filter remote-control.cm --exclude sys
194
195Dump all available logs where the source moniker, component url, or message contains
196\"remote-control\":
197 $ ffx log --filter remote-control dump
198
199Dump all logs from the last 30 minutes logged before 5 minutes ago:
200 $ ffx log --since \"30m ago\" --until \"5m ago\" dump
201
202Enable DEBUG logs from the \"core/audio\" component while logs are streaming:
203 $ ffx log --set-severity core/audio#DEBUG"
204)]
205pub struct LogCommand {
206 #[argh(subcommand)]
207 pub sub_command: Option<LogSubCommand>,
208
209 #[argh(option)]
212 pub filter: Vec<String>,
213
214 #[argh(option)]
216 pub moniker: Vec<String>,
217
218 #[argh(option)]
221 pub component: Vec<String>,
222
223 #[argh(option)]
226 pub exclude: Vec<String>,
227
228 #[argh(option)]
230 pub tag: Vec<String>,
231
232 #[argh(option)]
234 pub exclude_tags: Vec<String>,
235
236 #[argh(option, default = "Severity::Info")]
239 pub severity: Severity,
240
241 #[argh(switch)]
243 pub kernel: bool,
244
245 #[argh(option, from_str_fn(parse_time))]
247 pub since: Option<DetailedDateTime>,
248
249 #[argh(option, from_str_fn(parse_seconds_string_as_duration))]
252 pub since_boot: Option<Duration>,
253
254 #[argh(option, from_str_fn(parse_time))]
256 pub until: Option<DetailedDateTime>,
257
258 #[argh(option, from_str_fn(parse_seconds_string_as_duration))]
261 pub until_boot: Option<Duration>,
262
263 #[argh(switch)]
265 pub hide_tags: bool,
266
267 #[argh(switch)]
269 pub hide_file: bool,
270
271 #[argh(switch)]
275 pub no_color: bool,
276
277 #[argh(switch)]
280 pub case_sensitive: bool,
281
282 #[argh(switch)]
284 pub show_metadata: bool,
285
286 #[argh(switch)]
289 pub show_full_moniker: bool,
290
291 #[argh(option, default = "TimeFormat::Boot")]
295 pub clock: TimeFormat,
296
297 #[cfg(not(target_os = "fuchsia"))]
302 #[argh(option, default = "SymbolizeMode::Pretty")]
303 pub symbolize: SymbolizeMode,
304
305 #[argh(option, from_str_fn(log_interest_selector))]
314 pub set_severity: Vec<OneOrMany<LogInterestSelector>>,
315
316 #[argh(option)]
318 pub pid: Option<u64>,
319
320 #[argh(option)]
322 pub tid: Option<u64>,
323
324 #[argh(switch)]
329 pub force_set_severity: bool,
330
331 #[cfg(target_os = "fuchsia")]
333 #[argh(switch)]
334 pub json: bool,
335}
336
337impl Default for LogCommand {
338 fn default() -> Self {
339 LogCommand {
340 filter: vec![],
341 moniker: vec![],
342 component: vec![],
343 exclude: vec![],
344 tag: vec![],
345 exclude_tags: vec![],
346 hide_tags: false,
347 hide_file: false,
348 clock: TimeFormat::Boot,
349 no_color: false,
350 kernel: false,
351 severity: Severity::Info,
352 show_metadata: false,
353 force_set_severity: false,
354 since: None,
355 since_boot: None,
356 until: None,
357 case_sensitive: false,
358 until_boot: None,
359 sub_command: None,
360 set_severity: vec![],
361 show_full_moniker: false,
362 pid: None,
363 tid: None,
364 #[cfg(target_os = "fuchsia")]
365 json: false,
366 #[cfg(not(target_os = "fuchsia"))]
367 symbolize: SymbolizeMode::Pretty,
368 }
369 }
370}
371
372#[derive(PartialEq, Debug)]
374pub enum LogProcessingResult {
375 Exit,
377 Continue,
379}
380
381impl FromStr for SymbolizeMode {
382 type Err = anyhow::Error;
383
384 fn from_str(s: &str) -> Result<Self, Self::Err> {
385 let s = s.to_lowercase();
386 match s.as_str() {
387 "off" => Ok(SymbolizeMode::Off),
388 "pretty" => Ok(SymbolizeMode::Pretty),
389 "classic" => Ok(SymbolizeMode::Classic),
390 other => Err(format_err!("invalid symbolize flag: {}", other)),
391 }
392 }
393}
394
395#[derive(Error, Debug)]
396pub enum LogError {
397 #[error(transparent)]
398 UnknownError(#[from] anyhow::Error),
399 #[error("No boot timestamp")]
400 NoBootTimestamp,
401 #[error(transparent)]
402 IOError(#[from] std::io::Error),
403 #[error("Cannot use dump with --since now")]
404 DumpWithSinceNow,
405 #[error("No symbolizer configuration provided")]
406 NoSymbolizerConfig,
407 #[error(transparent)]
408 FfxError(#[from] FfxError),
409 #[error(transparent)]
410 Utf8Error(#[from] FromUtf8Error),
411 #[error(transparent)]
412 FidlError(#[from] fidl::Error),
413 #[error(transparent)]
414 FormatterError(#[from] FormatterError),
415 #[error("Deprecated flag: `{flag}`, use: `{new_flag}`")]
416 DeprecatedFlag { flag: &'static str, new_flag: &'static str },
417 #[error(
418 "Fuzzy matching failed due to too many matches, please re-try with one of these:\n{0}"
419 )]
420 FuzzyMatchTooManyMatches(String),
421 #[error("No running components were found matching {0}")]
422 SearchParameterNotFound(String),
423}
424
425impl LogError {
426 fn too_many_fuzzy_matches(matches: impl Iterator<Item = String>) -> Self {
427 let mut result = String::new();
428 for component in matches {
429 result.push_str(&component);
430 result.push('\n');
431 }
432
433 Self::FuzzyMatchTooManyMatches(result)
434 }
435}
436
437#[async_trait::async_trait(?Send)]
439pub trait InstanceGetter {
440 async fn get_monikers_from_query(&self, query: &str) -> Result<Vec<Moniker>, LogError>;
441}
442
443#[async_trait::async_trait(?Send)]
444impl InstanceGetter for RealmQueryProxy {
445 async fn get_monikers_from_query(&self, query: &str) -> Result<Vec<Moniker>, LogError> {
446 Ok(get_instances_from_query(query, self)
447 .await?
448 .into_iter()
449 .map(|value| value.moniker)
450 .collect())
451 }
452}
453
454impl LogCommand {
455 async fn map_interest_selectors<'a>(
456 realm_query: &impl InstanceGetter,
457 interest_selectors: impl Iterator<Item = &'a LogInterestSelector>,
458 ) -> Result<impl Iterator<Item = Cow<'a, LogInterestSelector>>, LogError> {
459 let selectors = Self::get_selectors_and_monikers(interest_selectors);
460 let mut translated_selectors = vec![];
461 for (moniker, selector) in selectors {
462 let instances = realm_query.get_monikers_from_query(moniker.as_str()).await?;
464 if instances.len() == 1 {
466 let mut translated_selector = selector.clone();
467 translated_selector.selector = instances[0].clone().into_component_selector();
468 translated_selectors.push((Cow::Owned(translated_selector), instances));
469 } else {
470 translated_selectors.push((Cow::Borrowed(selector), instances));
471 }
472 }
473 if translated_selectors.iter().any(|(_, matches)| matches.len() > 1) {
474 let mut err_output = vec![];
475 writeln!(
476 &mut err_output,
477 "WARN: One or more of your selectors appears to be ambiguous"
478 )?;
479 writeln!(&mut err_output, "and may not match any components on your system.\n")?;
480 writeln!(
481 &mut err_output,
482 "If this is unintentional you can explicitly match using the"
483 )?;
484 writeln!(&mut err_output, "following command:\n")?;
485 writeln!(&mut err_output, "ffx log \\")?;
486 let mut output = vec![];
487 for (oselector, instances) in translated_selectors {
488 for selector in instances {
489 writeln!(
490 output,
491 "\t--set-severity {}#{} \\",
492 sanitize_moniker_for_selectors(selector.to_string().as_str())
493 .replace("\\", "\\\\"),
494 format!("{:?}", oselector.interest.min_severity.unwrap()).to_uppercase()
495 )?;
496 }
497 }
498 let _ = output.pop();
500 let _ = output.pop();
501 let _ = output.pop();
502
503 writeln!(&mut err_output, "{}", String::from_utf8(output).unwrap())?;
504 writeln!(&mut err_output, "\nIf this is intentional, you can disable this with")?;
505 writeln!(&mut err_output, "ffx log --force-set-severity.")?;
506
507 ffx_bail!("{}", String::from_utf8(err_output)?);
508 }
509 Ok(translated_selectors.into_iter().map(|(selector, _)| selector))
510 }
511
512 pub fn validate_cmd_flags_with_warnings(&mut self) -> Result<Vec<&'static str>, LogError> {
513 let mut warnings = vec![];
514
515 if !self.moniker.is_empty() {
516 warnings.push("WARNING: --moniker is deprecated, use --component instead");
517 if self.component.is_empty() {
518 self.component = std::mem::take(&mut self.moniker);
519 } else {
520 warnings.push("WARNING: ignoring --moniker arguments in favor of --component");
521 }
522 }
523
524 Ok(warnings)
525 }
526
527 pub async fn maybe_set_interest(
531 &self,
532 log_settings_client: &LogSettingsProxy,
533 realm_query: &impl InstanceGetter,
534 ) -> Result<(), LogError> {
535 let (set_severity, force_set_severity, persist) =
536 if let Some(LogSubCommand::SetSeverity(options)) = &self.sub_command {
537 let default_cmd = LogCommand {
539 sub_command: Some(LogSubCommand::SetSeverity(options.clone())),
540 ..Default::default()
541 };
542 if &default_cmd != self {
543 ffx_bail!("Cannot combine set-severity with other options.");
544 }
545 (&options.interest_selector, options.force, !options.no_persist)
546 } else {
547 (&self.set_severity, self.force_set_severity, false)
548 };
549 if persist {
550 let selectors = if force_set_severity {
551 set_severity.clone().into_iter().flatten().collect::<Vec<_>>()
552 } else {
553 let new_selectors =
554 Self::map_interest_selectors(realm_query, set_severity.iter().flatten())
555 .await?
556 .map(|s| s.into_owned())
557 .collect::<Vec<_>>();
558 if new_selectors.is_empty() {
559 set_severity.clone().into_iter().flatten().collect::<Vec<_>>()
560 } else {
561 new_selectors
562 }
563 };
564 log_settings_client
565 .set_component_interest(
566 &fidl_fuchsia_diagnostics::LogSettingsSetComponentInterestRequest {
567 selectors: Some(selectors),
568 persist: Some(true),
569 ..Default::default()
570 },
571 )
572 .await?;
573 } else if !set_severity.is_empty() {
574 let selectors = if force_set_severity {
575 set_severity.clone().into_iter().flatten().collect::<Vec<_>>()
576 } else {
577 let new_selectors =
578 Self::map_interest_selectors(realm_query, set_severity.iter().flatten())
579 .await?
580 .map(|s| s.into_owned())
581 .collect::<Vec<_>>();
582 if new_selectors.is_empty() {
583 set_severity.clone().into_iter().flatten().collect::<Vec<_>>()
584 } else {
585 new_selectors
586 }
587 };
588
589 log_settings_client.set_interest(&selectors).await?;
590 }
591
592 Ok(())
593 }
594
595 fn get_selectors_and_monikers<'a>(
596 interest_selectors: impl Iterator<Item = &'a LogInterestSelector>,
597 ) -> Vec<(String, &'a LogInterestSelector)> {
598 let mut selectors = vec![];
599 for selector in interest_selectors {
600 let segments = selector.selector.moniker_segments.as_ref().unwrap();
601 let mut full_moniker = String::new();
602 for segment in segments {
603 match segment {
604 fidl_fuchsia_diagnostics::StringSelector::ExactMatch(segment) => {
605 if full_moniker.is_empty() {
606 full_moniker.push_str(segment);
607 } else {
608 full_moniker.push('/');
609 full_moniker.push_str(segment);
610 }
611 }
612 _ => {
613 return vec![];
616 }
617 }
618 }
619 selectors.push((full_moniker, selector));
620 }
621 selectors
622 }
623}
624
625impl TopLevelCommand for LogCommand {}
626
627fn log_interest_selector(s: &str) -> Result<OneOrMany<LogInterestSelector>, String> {
628 if s.contains(",") {
629 let many: Result<Vec<LogInterestSelector>, String> = s
630 .split(",")
631 .map(|value| selectors::parse_log_interest_selector(value).map_err(|e| e.to_string()))
632 .collect();
633 Ok(OneOrMany::Many(many?))
634 } else {
635 Ok(OneOrMany::One(selectors::parse_log_interest_selector(s).map_err(|s| s.to_string())?))
636 }
637}
638
639#[cfg(test)]
640mod test {
641 use super::*;
642 use assert_matches::assert_matches;
643 use async_trait::async_trait;
644 use fidl::endpoints::create_proxy;
645 use fidl_fuchsia_diagnostics::{LogSettingsMarker, LogSettingsRequest};
646 use futures_util::future::Either;
647 use futures_util::stream::FuturesUnordered;
648 use futures_util::StreamExt;
649 use selectors::parse_log_interest_selector;
650
651 #[derive(Default)]
652 struct FakeInstanceGetter {
653 output: Vec<Moniker>,
654 expected_selector: Option<String>,
655 }
656
657 #[async_trait(?Send)]
658 impl InstanceGetter for FakeInstanceGetter {
659 async fn get_monikers_from_query(&self, query: &str) -> Result<Vec<Moniker>, LogError> {
660 if let Some(expected) = &self.expected_selector {
661 assert_eq!(expected, query);
662 }
663 Ok(self.output.clone())
664 }
665 }
666
667 #[fuchsia::test]
668 async fn test_symbolize_mode_from_str() {
669 assert_matches!(SymbolizeMode::from_str("off"), Ok(value) if value == SymbolizeMode::Off);
670 assert_matches!(
671 SymbolizeMode::from_str("pretty"),
672 Ok(value) if value == SymbolizeMode::Pretty
673 );
674 assert_matches!(
675 SymbolizeMode::from_str("classic"),
676 Ok(value) if value == SymbolizeMode::Classic
677 );
678 }
679
680 #[fuchsia::test]
681 async fn maybe_set_interest_errors_additional_arguments_passed_to_set_interest() {
682 let (settings_proxy, settings_server) = create_proxy::<LogSettingsMarker>();
683 let getter = FakeInstanceGetter {
684 expected_selector: Some("ambiguous_selector".into()),
685 output: vec![
686 Moniker::try_from("core/some/ambiguous_selector:thing/test").unwrap(),
687 Moniker::try_from("core/other/ambiguous_selector:thing/test").unwrap(),
688 ],
689 };
690 let cmd = LogCommand {
693 sub_command: Some(LogSubCommand::SetSeverity(SetSeverityCommand {
694 interest_selector: vec![OneOrMany::One(
695 parse_log_interest_selector("ambiguous_selector#INFO").unwrap(),
696 )],
697 force: false,
698 no_persist: false,
699 })),
700 hide_file: true,
701 ..LogCommand::default()
702 };
703 let mut set_interest_result = None;
704
705 let mut scheduler = FuturesUnordered::new();
706 scheduler.push(Either::Left(async {
707 set_interest_result = Some(cmd.maybe_set_interest(&settings_proxy, &getter).await);
708 drop(settings_proxy);
709 }));
710 scheduler.push(Either::Right(async {
711 let request = settings_server.into_stream().next().await;
712 assert_matches!(request, None);
714 }));
715 while scheduler.next().await.is_some() {}
716 drop(scheduler);
717
718 let error = format!("{}", set_interest_result.unwrap().unwrap_err());
719
720 const EXPECTED_INTEREST_ERROR: &str = "Cannot combine set-severity with other options.";
721 assert_eq!(error, EXPECTED_INTEREST_ERROR);
722 }
723
724 #[fuchsia::test]
725 async fn maybe_set_interest_errors_if_ambiguous_selector() {
726 let (settings_proxy, settings_server) = create_proxy::<LogSettingsMarker>();
727 let getter = FakeInstanceGetter {
728 expected_selector: Some("ambiguous_selector".into()),
729 output: vec![
730 Moniker::try_from("core/some/ambiguous_selector:thing/test").unwrap(),
731 Moniker::try_from("core/other/ambiguous_selector:thing/test").unwrap(),
732 ],
733 };
734 let cmd = LogCommand {
737 sub_command: Some(LogSubCommand::Dump(DumpCommand {})),
738 set_severity: vec![OneOrMany::One(
739 parse_log_interest_selector("ambiguous_selector#INFO").unwrap(),
740 )],
741 ..LogCommand::default()
742 };
743 let mut set_interest_result = None;
744
745 let mut scheduler = FuturesUnordered::new();
746 scheduler.push(Either::Left(async {
747 set_interest_result = Some(cmd.maybe_set_interest(&settings_proxy, &getter).await);
748 drop(settings_proxy);
749 }));
750 scheduler.push(Either::Right(async {
751 let request = settings_server.into_stream().next().await;
752 assert_matches!(request, None);
754 }));
755 while scheduler.next().await.is_some() {}
756 drop(scheduler);
757
758 let error = format!("{}", set_interest_result.unwrap().unwrap_err());
759
760 const EXPECTED_INTEREST_ERROR: &str = r#"WARN: One or more of your selectors appears to be ambiguous
761and may not match any components on your system.
762
763If this is unintentional you can explicitly match using the
764following command:
765
766ffx log \
767 --set-severity core/some/ambiguous_selector\\:thing/test#INFO \
768 --set-severity core/other/ambiguous_selector\\:thing/test#INFO
769
770If this is intentional, you can disable this with
771ffx log --force-set-severity.
772"#;
773 assert_eq!(error, EXPECTED_INTEREST_ERROR);
774 }
775
776 #[fuchsia::test]
777 async fn logger_translates_selector_if_one_match() {
778 let cmd = LogCommand {
779 sub_command: Some(LogSubCommand::Dump(DumpCommand {})),
780 set_severity: vec![OneOrMany::One(
781 parse_log_interest_selector("ambiguous_selector#INFO").unwrap(),
782 )],
783 ..LogCommand::default()
784 };
785 let mut set_interest_result = None;
786 let getter = FakeInstanceGetter {
787 expected_selector: Some("ambiguous_selector".into()),
788 output: vec![Moniker::try_from("core/some/ambiguous_selector").unwrap()],
789 };
790 let mut scheduler = FuturesUnordered::new();
791 let (settings_proxy, settings_server) = create_proxy::<LogSettingsMarker>();
792 scheduler.push(Either::Left(async {
793 set_interest_result = Some(cmd.maybe_set_interest(&settings_proxy, &getter).await);
794 drop(settings_proxy);
795 }));
796 scheduler.push(Either::Right(async {
797 let request = settings_server.into_stream().next().await;
798 let (selectors, responder) = assert_matches!(
799 request,
800 Some(Ok(LogSettingsRequest::SetInterest { selectors, responder })) =>
801 (selectors, responder)
802 );
803 responder.send().unwrap();
804 assert_eq!(
805 selectors,
806 vec![parse_log_interest_selector("core/some/ambiguous_selector#INFO").unwrap()]
807 );
808 }));
809 while scheduler.next().await.is_some() {}
810 drop(scheduler);
811 assert_matches!(set_interest_result, Some(Ok(())));
812 }
813
814 #[fuchsia::test]
815 async fn logger_uses_specified_selectors_if_no_results_returned() {
816 let cmd = LogCommand {
817 sub_command: Some(LogSubCommand::Dump(DumpCommand {})),
818 set_severity: vec![OneOrMany::One(
819 parse_log_interest_selector("core/something/a:b/elements:main/otherstuff:*#DEBUG")
820 .unwrap(),
821 )],
822 ..LogCommand::default()
823 };
824 let mut set_interest_result = None;
825 let getter = FakeInstanceGetter {
826 expected_selector: Some("core/something/a:b/elements:main/otherstuff:*#DEBUG".into()),
827 output: vec![],
828 };
829 let scheduler = FuturesUnordered::new();
830 let (settings_proxy, settings_server) = create_proxy::<LogSettingsMarker>();
831 scheduler.push(Either::Left(async {
832 set_interest_result = Some(cmd.maybe_set_interest(&settings_proxy, &getter).await);
833 drop(settings_proxy);
834 }));
835 scheduler.push(Either::Right(async {
836 let request = settings_server.into_stream().next().await;
837 let (selectors, responder) = assert_matches!(
838 request,
839 Some(Ok(LogSettingsRequest::SetInterest { selectors, responder })) =>
840 (selectors, responder)
841 );
842 responder.send().unwrap();
843 assert_eq!(
844 selectors,
845 vec![parse_log_interest_selector(
846 "core/something/a:b/elements:main/otherstuff:*#DEBUG"
847 )
848 .unwrap()]
849 );
850 }));
851 scheduler.map(|_| Ok(())).forward(futures::sink::drain()).await.unwrap();
852 assert_matches!(set_interest_result, Some(Ok(())));
853 }
854
855 #[fuchsia::test]
856 async fn logger_prints_ignores_ambiguity_if_force_set_severity_is_used() {
857 let cmd = LogCommand {
858 sub_command: Some(LogSubCommand::SetSeverity(SetSeverityCommand {
859 no_persist: true,
860 interest_selector: vec![OneOrMany::One(
861 parse_log_interest_selector("ambiguous_selector#INFO").unwrap(),
862 )],
863 force: true,
864 })),
865 ..LogCommand::default()
866 };
867 let getter = FakeInstanceGetter {
868 expected_selector: Some("ambiguous_selector".into()),
869 output: vec![
870 Moniker::try_from("core/some/ambiguous_selector:thing/test").unwrap(),
871 Moniker::try_from("core/other/ambiguous_selector:thing/test").unwrap(),
872 ],
873 };
874 let mut set_interest_result = None;
875 let mut scheduler = FuturesUnordered::new();
876 let (settings_proxy, settings_server) = create_proxy::<LogSettingsMarker>();
877 scheduler.push(Either::Left(async {
878 set_interest_result = Some(cmd.maybe_set_interest(&settings_proxy, &getter).await);
879 drop(settings_proxy);
880 }));
881 scheduler.push(Either::Right(async {
882 let request = settings_server.into_stream().next().await;
883 let (selectors, responder) = assert_matches!(
884 request,
885 Some(Ok(LogSettingsRequest::SetInterest { selectors, responder })) =>
886 (selectors, responder)
887 );
888 responder.send().unwrap();
889 assert_eq!(
890 selectors,
891 vec![parse_log_interest_selector("ambiguous_selector#INFO").unwrap()]
892 );
893 }));
894 while scheduler.next().await.is_some() {}
895 drop(scheduler);
896 assert_matches!(set_interest_result, Some(Ok(())));
897 }
898
899 #[fuchsia::test]
900 async fn logger_prints_ignores_ambiguity_if_force_set_severity_is_used_persistent() {
901 let cmd = LogCommand {
902 sub_command: Some(LogSubCommand::SetSeverity(SetSeverityCommand {
903 no_persist: false,
904 interest_selector: vec![log_socket_stream::OneOrMany::One(
905 parse_log_interest_selector("ambiguous_selector#INFO").unwrap(),
906 )],
907 force: true,
908 })),
909 ..LogCommand::default()
910 };
911 let getter = FakeInstanceGetter {
912 expected_selector: Some("ambiguous_selector".into()),
913 output: vec![
914 Moniker::try_from("core/some/ambiguous_selector:thing/test").unwrap(),
915 Moniker::try_from("core/other/ambiguous_selector:thing/test").unwrap(),
916 ],
917 };
918 let mut set_interest_result = None;
919 let mut scheduler = FuturesUnordered::new();
920 let (settings_proxy, settings_server) = create_proxy::<LogSettingsMarker>();
921 scheduler.push(Either::Left(async {
922 set_interest_result = Some(cmd.maybe_set_interest(&settings_proxy, &getter).await);
923 drop(settings_proxy);
924 }));
925 scheduler.push(Either::Right(async {
926 let request = settings_server.into_stream().next().await;
927 let (payload, responder) = assert_matches!(
928 request,
929 Some(Ok(LogSettingsRequest::SetComponentInterest { payload, responder })) =>
930 (payload, responder)
931 );
932 assert_eq!(payload.persist, Some(true));
933 responder.send().unwrap();
934 assert_eq!(
935 payload.selectors.unwrap(),
936 vec![parse_log_interest_selector("ambiguous_selector#INFO").unwrap()]
937 );
938 }));
939 while scheduler.next().await.is_some() {}
940 drop(scheduler);
941 assert_matches!(set_interest_result, Some(Ok(())));
942 }
943
944 #[fuchsia::test]
945 async fn logger_prints_ignores_ambiguity_if_machine_output_is_used() {
946 let cmd = LogCommand {
947 sub_command: Some(LogSubCommand::Dump(DumpCommand {})),
948 set_severity: vec![OneOrMany::One(
949 parse_log_interest_selector("ambiguous_selector#INFO").unwrap(),
950 )],
951 force_set_severity: true,
952 ..LogCommand::default()
953 };
954 let getter = FakeInstanceGetter {
955 expected_selector: Some("ambiguous_selector".into()),
956 output: vec![
957 Moniker::try_from("core/some/collection:thing/test").unwrap(),
958 Moniker::try_from("core/other/collection:thing/test").unwrap(),
959 ],
960 };
961 let mut set_interest_result = None;
962 let mut scheduler = FuturesUnordered::new();
963 let (settings_proxy, settings_server) = create_proxy::<LogSettingsMarker>();
964 scheduler.push(Either::Left(async {
965 set_interest_result = Some(cmd.maybe_set_interest(&settings_proxy, &getter).await);
966 drop(settings_proxy);
967 }));
968 scheduler.push(Either::Right(async {
969 let request = settings_server.into_stream().next().await;
970 let (selectors, responder) = assert_matches!(
971 request,
972 Some(Ok(LogSettingsRequest::SetInterest { selectors, responder })) =>
973 (selectors, responder)
974 );
975 responder.send().unwrap();
976 assert_eq!(
977 selectors,
978 vec![parse_log_interest_selector("ambiguous_selector#INFO").unwrap()]
979 );
980 }));
981 while scheduler.next().await.is_some() {}
982 drop(scheduler);
983 assert_matches!(set_interest_result, Some(Ok(())));
984 }
985 #[test]
986 fn test_parse_selector() {
987 assert_eq!(
988 log_interest_selector("core/audio#DEBUG").unwrap(),
989 OneOrMany::One(parse_log_interest_selector("core/audio#DEBUG").unwrap())
990 );
991 }
992
993 #[test]
994 fn test_parse_selector_with_commas() {
995 assert_eq!(
996 log_interest_selector("core/audio#DEBUG,bootstrap/archivist#TRACE").unwrap(),
997 OneOrMany::Many(vec![
998 parse_log_interest_selector("core/audio#DEBUG").unwrap(),
999 parse_log_interest_selector("bootstrap/archivist#TRACE").unwrap()
1000 ])
1001 );
1002 }
1003
1004 #[test]
1005 fn test_parse_time() {
1006 assert!(parse_time("now").unwrap().is_now);
1007 let date_string = "04/20/2020";
1008 let res = parse_time(date_string).unwrap();
1009 assert!(!res.is_now);
1010 assert_eq!(
1011 res.date_naive(),
1012 parse_date_string(date_string, Local::now(), Dialect::Us).unwrap().date_naive()
1013 );
1014 }
1015}