1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
//! Logging functions for the debug feature.

use is_terminal::IsTerminal;
use tracing::level_filters::LevelFilter;
use tracing_subscriber::{fmt, layer::SubscriberExt, util::SubscriberInitExt, EnvFilter};

const WHITELISTED_LOG_TARGETS: &[&str] = &["wasmer", "wasmer_wasix", "virtual_fs"];

/// Control the output generated by the CLI.
#[derive(Debug, Default, Clone, PartialEq, clap::Parser)]
pub struct Output {
    /// Generate verbose output (repeat for more verbosity)
    #[clap(short, long, action = clap::ArgAction::Count, global = true, conflicts_with = "quiet")]
    pub verbose: u8,
    /// Do not print progress messages.
    #[clap(short, long, global = true, conflicts_with = "verbose")]
    pub quiet: bool,
    /// The format to use when generating logs.
    #[clap(long, global = true, env, default_value = "text")]
    pub log_format: LogFormat,
    /// Which span events to log.
    #[clap(long, global = true, env, default_value = "close")]
    pub log_events: LogEvents,
    /// When to display colored output.
    #[clap(long, default_value_t = clap::ColorChoice::Auto, global = true)]
    pub color: clap::ColorChoice,
}

impl Output {
    /// Has the `--verbose` flag been set?
    pub fn is_verbose(&self) -> bool {
        self.verbose > 0
    }

    /// Initialize logging based on the `$RUST_LOG` environment variable and
    /// command-line flags.
    pub fn initialize_logging(&self) {
        let fmt_layer = fmt::layer()
            .with_target(true)
            .with_ansi(self.should_emit_colors())
            .with_thread_ids(true)
            .with_writer(std::io::stderr);

        let fmt_layer = match self.log_events {
            LogEvents::New => fmt_layer.with_span_events(fmt::format::FmtSpan::NEW),
            LogEvents::Close => fmt_layer.with_span_events(fmt::format::FmtSpan::CLOSE),
            LogEvents::All => {
                fmt_layer.with_span_events(fmt::format::FmtSpan::NEW | fmt::format::FmtSpan::CLOSE)
            }
        };

        let filter_layer = self.log_filter();

        match self.log_format {
            LogFormat::Text => tracing_subscriber::registry()
                .with(filter_layer)
                .with(fmt_layer.compact().with_target(true))
                .init(),
            LogFormat::Json => tracing_subscriber::registry()
                .with(filter_layer)
                .with(fmt_layer.json().with_target(true))
                .init(),
        }
    }

    fn log_filter(&self) -> EnvFilter {
        let default_filters = [
            LevelFilter::OFF,
            LevelFilter::WARN,
            LevelFilter::INFO,
            LevelFilter::DEBUG,
        ];

        // First, we set up the default log level.
        let default_level = default_filters
            .get(self.verbose as usize)
            .copied()
            .unwrap_or(LevelFilter::TRACE);
        let mut filter = EnvFilter::builder()
            .with_default_directive(default_level.into())
            .from_env_lossy();

        // Next we add level-specific directives, where verbosity=0 means don't
        // override anything. Note that these are shifted one level up so we'll
        // get something like RUST_LOG="warn,wasmer_wasix=info"
        let specific_filters = [LevelFilter::WARN, LevelFilter::INFO, LevelFilter::DEBUG];
        if self.verbose > 0 {
            let level = specific_filters
                .get(self.verbose as usize)
                .copied()
                .unwrap_or(LevelFilter::TRACE);

            for target in WHITELISTED_LOG_TARGETS {
                let directive = format!("{target}={level}").parse().unwrap();
                filter = filter.add_directive(directive);
            }
        }

        filter
    }

    /// Check whether we should emit ANSI escape codes for log formatting.
    ///
    /// The `tracing-subscriber` crate doesn't have native support for
    /// "--color=always|never|auto", so we implement a poor man's version.
    ///
    /// For more, see https://github.com/tokio-rs/tracing/issues/2388
    fn should_emit_colors(&self) -> bool {
        match self.color {
            clap::ColorChoice::Auto => std::io::stderr().is_terminal(),
            clap::ColorChoice::Always => true,
            clap::ColorChoice::Never => false,
        }
    }

    /// Get the draw target to be used with the `indicatif` crate.
    ///
    /// Progress indicators won't draw anything if the user passed the `--quiet`
    /// flag.
    pub fn draw_target(&self) -> indicatif::ProgressDrawTarget {
        if self.quiet {
            return indicatif::ProgressDrawTarget::hidden();
        }

        indicatif::ProgressDrawTarget::stderr()
    }
}

/// The format used when generating logs.
#[derive(Debug, Default, Copy, Clone, PartialEq, clap::ValueEnum)]
pub enum LogFormat {
    /// Human-readable logs.
    #[default]
    Text,
    /// Machine-readable logs.
    Json,
}

/// Which span events to log.
#[derive(Debug, Default, Copy, Clone, PartialEq, clap::ValueEnum)]
pub enum LogEvents {
    // Only log at the start of a new span.
    New,
    // Only log at the end of a span.
    #[default]
    Close,
    // Log at the start and end of a span.
    All,
}