wasmer_cli/
logging.rs

1//! Logging functions for the debug feature.
2
3use is_terminal::IsTerminal;
4use tracing::level_filters::LevelFilter;
5use tracing_subscriber::{EnvFilter, fmt, layer::SubscriberExt, util::SubscriberInitExt};
6
7const WHITELISTED_LOG_TARGETS: &[&str] = &["wasmer", "wasmer_wasix", "virtual_fs"];
8
9/// Control the output generated by the CLI.
10#[derive(Debug, Default, Clone, PartialEq, clap::Parser)]
11pub struct Output {
12    /// Generate verbose output (repeat for more verbosity)
13    #[clap(short, long, action = clap::ArgAction::Count, global = true, conflicts_with = "quiet")]
14    pub verbose: u8,
15    /// Do not print progress messages.
16    #[clap(short, long, global = true, conflicts_with = "verbose")]
17    pub quiet: bool,
18    /// The format to use when generating logs.
19    #[clap(long, global = true, env, default_value = "text")]
20    pub log_format: LogFormat,
21    /// Which span events to log.
22    #[clap(long, global = true, env, default_value = "close")]
23    pub log_events: LogEvents,
24    /// When to display colored output.
25    #[clap(long, default_value_t = clap::ColorChoice::Auto, global = true)]
26    pub color: clap::ColorChoice,
27}
28
29impl Output {
30    /// Has the `--verbose` flag been set?
31    pub fn is_verbose(&self) -> bool {
32        self.verbose > 0
33    }
34
35    /// Initialize logging based on the `$RUST_LOG` environment variable and
36    /// command-line flags.
37    pub fn initialize_logging(&self) {
38        let fmt_layer = fmt::layer()
39            .with_target(true)
40            .with_ansi(self.should_emit_colors())
41            .with_thread_ids(true)
42            .with_writer(std::io::stderr);
43
44        let fmt_layer = match self.log_events {
45            LogEvents::New => fmt_layer.with_span_events(fmt::format::FmtSpan::NEW),
46            LogEvents::Close => fmt_layer.with_span_events(fmt::format::FmtSpan::CLOSE),
47            LogEvents::All => {
48                fmt_layer.with_span_events(fmt::format::FmtSpan::NEW | fmt::format::FmtSpan::CLOSE)
49            }
50        };
51
52        let filter_layer = self.log_filter();
53
54        match self.log_format {
55            LogFormat::Text => tracing_subscriber::registry()
56                .with(filter_layer)
57                .with(fmt_layer.compact().with_target(true))
58                .init(),
59            LogFormat::Json => tracing_subscriber::registry()
60                .with(filter_layer)
61                .with(fmt_layer.json().with_target(true))
62                .init(),
63        }
64    }
65
66    fn log_filter(&self) -> EnvFilter {
67        let default_filters = [
68            LevelFilter::OFF,
69            LevelFilter::WARN,
70            LevelFilter::INFO,
71            LevelFilter::DEBUG,
72        ];
73
74        // First, we set up the default log level.
75        let default_level = default_filters
76            .get(self.verbose as usize)
77            .copied()
78            .unwrap_or(LevelFilter::TRACE);
79        let mut filter = EnvFilter::builder()
80            .with_default_directive(default_level.into())
81            .from_env_lossy();
82
83        // Next we add level-specific directives, where verbosity=0 means don't
84        // override anything. Note that these are shifted one level up so we'll
85        // get something like RUST_LOG="warn,wasmer_wasix=info"
86        let specific_filters = [LevelFilter::WARN, LevelFilter::INFO, LevelFilter::DEBUG];
87        if self.verbose > 0 {
88            let level = specific_filters
89                .get(self.verbose as usize)
90                .copied()
91                .unwrap_or(LevelFilter::TRACE);
92
93            for target in WHITELISTED_LOG_TARGETS {
94                let directive = format!("{target}={level}").parse().unwrap();
95                filter = filter.add_directive(directive);
96            }
97        }
98
99        filter
100    }
101
102    /// Check whether we should emit ANSI escape codes for log formatting.
103    ///
104    /// The `tracing-subscriber` crate doesn't have native support for
105    /// "--color=always|never|auto", so we implement a poor man's version.
106    ///
107    /// For more, see https://github.com/tokio-rs/tracing/issues/2388
108    fn should_emit_colors(&self) -> bool {
109        match self.color {
110            clap::ColorChoice::Auto => std::io::stderr().is_terminal(),
111            clap::ColorChoice::Always => true,
112            clap::ColorChoice::Never => false,
113        }
114    }
115
116    /// Get the draw target to be used with the `indicatif` crate.
117    ///
118    /// Progress indicators won't draw anything if the user passed the `--quiet`
119    /// flag.
120    pub fn draw_target(&self) -> indicatif::ProgressDrawTarget {
121        if self.quiet {
122            return indicatif::ProgressDrawTarget::hidden();
123        }
124
125        indicatif::ProgressDrawTarget::stderr()
126    }
127}
128
129/// The format used when generating logs.
130#[derive(Debug, Default, Copy, Clone, PartialEq, clap::ValueEnum)]
131pub enum LogFormat {
132    /// Human-readable logs.
133    #[default]
134    Text,
135    /// Machine-readable logs.
136    Json,
137}
138
139/// Which span events to log.
140#[derive(Debug, Default, Copy, Clone, PartialEq, clap::ValueEnum)]
141pub enum LogEvents {
142    // Only log at the start of a new span.
143    New,
144    // Only log at the end of a span.
145    #[default]
146    Close,
147    // Log at the start and end of a span.
148    All,
149}