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