1use std::io::IsTerminal as _;
4use tracing::level_filters::LevelFilter;
5use tracing_subscriber::{EnvFilter, Layer, fmt, layer::SubscriberExt, util::SubscriberInitExt};
6
7const WHITELISTED_LOG_TARGETS: &[&str] = &["wasmer", "wasmer_wasix", "virtual_fs"];
8
9#[derive(Debug, Default, Clone, PartialEq, clap::Parser)]
11pub struct Output {
12 #[clap(short, long, action = clap::ArgAction::Count, global = true, conflicts_with = "quiet")]
14 pub verbose: u8,
15 #[clap(short, long, global = true, conflicts_with = "verbose")]
17 pub quiet: bool,
18 #[clap(long, global = true, env, default_value = "text")]
20 pub log_format: LogFormat,
21 #[clap(long, global = true, env, default_value = "close")]
23 pub log_events: LogEvents,
24 #[clap(long, default_value_t = clap::ColorChoice::Auto, global = true)]
26 pub color: clap::ColorChoice,
27}
28
29impl Output {
30 pub fn is_verbose(&self) -> bool {
32 self.verbose > 0
33 }
34
35 pub fn is_quiet_or_no_tty(&self) -> bool {
37 self.quiet || !std::io::stderr().is_terminal()
38 }
39
40 pub fn initialize_logging(&self) {
43 let filter_layer = self.log_filter();
44
45 let fmt_layer = fmt::layer()
46 .with_target(true)
47 .with_ansi(self.should_emit_colors())
48 .with_thread_ids(true)
49 .with_writer(std::io::stderr);
50
51 let fmt_layer = match self.log_events {
52 LogEvents::New => fmt_layer.with_span_events(fmt::format::FmtSpan::NEW),
53 LogEvents::Close => fmt_layer.with_span_events(fmt::format::FmtSpan::CLOSE),
54 LogEvents::All => {
55 fmt_layer.with_span_events(fmt::format::FmtSpan::NEW | fmt::format::FmtSpan::CLOSE)
56 }
57 };
58
59 let registry = tracing_subscriber::registry();
60
61 #[cfg(feature = "tokio-subscriber")]
62 let registry = registry.with(console_subscriber::spawn());
63
64 match self.log_format {
65 LogFormat::Text => registry
66 .with(
67 fmt_layer
68 .compact()
69 .with_target(true)
70 .with_filter(filter_layer),
71 )
72 .init(),
73 LogFormat::Json => registry
74 .with(fmt_layer.json().with_target(true).with_filter(filter_layer))
75 .init(),
76 };
77 }
78
79 fn log_filter(&self) -> EnvFilter {
80 let default_filters = [
81 LevelFilter::OFF,
82 LevelFilter::WARN,
83 LevelFilter::INFO,
84 LevelFilter::DEBUG,
85 ];
86
87 let default_level = default_filters
89 .get(self.verbose as usize)
90 .copied()
91 .unwrap_or(LevelFilter::TRACE);
92 let mut filter = EnvFilter::builder()
93 .with_default_directive(default_level.into())
94 .from_env_lossy();
95
96 let specific_filters = [LevelFilter::WARN, LevelFilter::INFO, LevelFilter::DEBUG];
100 if self.verbose > 0 {
101 let level = specific_filters
102 .get(self.verbose as usize)
103 .copied()
104 .unwrap_or(LevelFilter::TRACE);
105
106 for target in WHITELISTED_LOG_TARGETS {
107 let directive = format!("{target}={level}").parse().unwrap();
108 filter = filter.add_directive(directive);
109 }
110 }
111
112 filter
113 }
114
115 fn should_emit_colors(&self) -> bool {
122 match self.color {
123 clap::ColorChoice::Auto => std::io::stderr().is_terminal(),
124 clap::ColorChoice::Always => true,
125 clap::ColorChoice::Never => false,
126 }
127 }
128
129 pub fn draw_target(&self) -> indicatif::ProgressDrawTarget {
134 if self.quiet {
135 return indicatif::ProgressDrawTarget::hidden();
136 }
137
138 indicatif::ProgressDrawTarget::stderr()
139 }
140}
141
142#[derive(Debug, Default, Copy, Clone, PartialEq, clap::ValueEnum)]
144pub enum LogFormat {
145 #[default]
147 Text,
148 Json,
150}
151
152#[derive(Debug, Default, Copy, Clone, PartialEq, clap::ValueEnum)]
154pub enum LogEvents {
155 New,
157 #[default]
159 Close,
160 All,
162}