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