wasmer_cli/
logging.rs

1//! Logging functions for the debug feature.
2
3use 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/// 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    /// Returns true if either the `--quiet` flag is set or stderr is not a TTY.
36    pub fn is_quiet_or_no_tty(&self) -> bool {
37        self.quiet || !std::io::stderr().is_terminal()
38    }
39
40    /// Initialize logging based on the `$RUST_LOG` environment variable and
41    /// command-line flags.
42    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        // First, we set up the default log level.
88        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        // Next we add level-specific directives, where verbosity=0 means don't
97        // override anything. Note that these are shifted one level up so we'll
98        // get something like RUST_LOG="warn,wasmer_wasix=info"
99        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    /// Check whether we should emit ANSI escape codes for log formatting.
116    ///
117    /// The `tracing-subscriber` crate doesn't have native support for
118    /// "--color=always|never|auto", so we implement a poor man's version.
119    ///
120    /// For more, see https://github.com/tokio-rs/tracing/issues/2388
121    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    /// Get the draw target to be used with the `indicatif` crate.
130    ///
131    /// Progress indicators won't draw anything if the user passed the `--quiet`
132    /// flag.
133    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/// The format used when generating logs.
143#[derive(Debug, Default, Copy, Clone, PartialEq, clap::ValueEnum)]
144pub enum LogFormat {
145    /// Human-readable logs.
146    #[default]
147    Text,
148    /// Machine-readable logs.
149    Json,
150}
151
152/// Which span events to log.
153#[derive(Debug, Default, Copy, Clone, PartialEq, clap::ValueEnum)]
154pub enum LogEvents {
155    // Only log at the start of a new span.
156    New,
157    // Only log at the end of a span.
158    #[default]
159    Close,
160    // Log at the start and end of a span.
161    All,
162}