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