Skip to main content

wapm_cli/
logging.rs

1//! Utilities for setting up logging
2
3use crate::config::Config;
4use crate::util;
5use fern::colors::{Color, ColoredLevelConfig};
6use std::fs;
7use std::io;
8use std::sync::atomic::{AtomicUsize, Ordering};
9use thiserror::Error;
10
11static STDOUT_LINE_COUNTER: AtomicUsize = AtomicUsize::new(0);
12
13fn get_num_stdout_lines_logged() -> usize {
14    STDOUT_LINE_COUNTER.load(Ordering::Acquire)
15}
16
17/// Updates counter with the lines printed to stdout
18pub(crate) fn add_lines_printed_to_stdout(num_lines: usize) {
19    STDOUT_LINE_COUNTER.fetch_add(num_lines, Ordering::AcqRel);
20}
21
22/// Note: this function doesn't lock the atomic, so using it from
23/// multiple threads may not work.
24pub(crate) fn clear_stdout() -> io::Result<()> {
25    use std::io::Write;
26
27    let stdout = io::stdout();
28    let mut f = stdout.lock();
29    let num_lines_to_clear = get_num_stdout_lines_logged();
30    for _ in 0..num_lines_to_clear {
31        // ANSI escape codes for:
32        // - go up one line: \x1B[<NUM LINES>A
33        // - clear the line: \x1B[2K
34        write!(f, "\x1B[1A\x1B[2K")?;
35    }
36    f.flush()?;
37    Ok(())
38}
39
40/// Subroutine to instantiate the loggers
41pub fn set_up_logging(count_lines: bool) -> anyhow::Result<()> {
42    let colors_line = ColoredLevelConfig::new()
43        .error(Color::Red)
44        .warn(Color::Yellow)
45        .trace(Color::BrightBlack);
46    let should_color = util::wapm_should_print_color();
47
48    let colors_level = colors_line.info(Color::Green);
49    let dispatch = fern::Dispatch::new()
50        // stdout and stderr logging
51        .chain({
52            let base = if should_color {
53                fern::Dispatch::new()
54                    .level(log::LevelFilter::Info)
55                    .filter(|metadata| metadata.target().starts_with("wapm_cli"))
56                    .format(move |out, message, record| {
57                        if count_lines && record.level() == log::Level::Info {
58                            let num_lines = message.to_string().lines().count();
59                            add_lines_printed_to_stdout(num_lines);
60                        }
61                        out.finish(format_args!(
62                            "{color_line}[{level}{color_line}]{ansi_close} {message}",
63                            color_line = format_args!(
64                                "\x1B[{}m",
65                                colors_line.get_color(&record.level()).to_fg_str()
66                            ),
67                            level = colors_level.color(record.level()),
68                            ansi_close = "\x1B[0m",
69                            message = message,
70                        ));
71                    })
72            } else {
73                // default formatter without color
74                fern::Dispatch::new()
75                    .level(log::LevelFilter::Info)
76                    .filter(|metadata| metadata.target().starts_with("wapm_cli"))
77                    .format(move |out, message, record| {
78                        if count_lines && record.level() == log::Level::Info {
79                            let num_lines = message.to_string().lines().count();
80                            add_lines_printed_to_stdout(num_lines);
81                        }
82                        out.finish(format_args!(
83                            "[{level}] {message}",
84                            level = record.level(),
85                            message = message,
86                        ));
87                    })
88            };
89            base
90                // stdout
91                .chain(
92                    fern::Dispatch::new()
93                        .filter(|metadata| metadata.level() == log::LevelFilter::Info)
94                        .chain(std::io::stdout()),
95                )
96                // stderr
97                .chain(
98                    fern::Dispatch::new()
99                        .filter(|metadata| {
100                            // lower is higher priority
101                            metadata.level() <= log::LevelFilter::Warn
102                                && metadata.target().starts_with("wapm_cli")
103                        })
104                        .chain(std::io::stderr()),
105                )
106        });
107
108    // verbose logging to file
109    let dispatch = if let Ok(wasmer_dir) = Config::get_folder() {
110        let log_out = wasmer_dir.join("wapm.log");
111        dispatch.chain(
112            fern::Dispatch::new()
113                .level(log::LevelFilter::Debug)
114                .level_for("hyper", log::LevelFilter::Info)
115                .level_for("tokio_reactor", log::LevelFilter::Info)
116                .format(move |out, message, record| {
117                    out.finish(format_args!(
118                        "[{date}][{level}][{target}][{file}:{line}] {message}",
119                        date = chrono::Local::now().format("%Y-%m-%d %H:%M:%S"),
120                        target = record.target(),
121                        level = record.level(),
122                        message = message,
123                        file = record.file().unwrap_or(""),
124                        line = record
125                            .line()
126                            .map(|line| format!("{}", line))
127                            .unwrap_or_else(|| "".to_string()),
128                    ));
129                })
130                .chain(
131                    fs::OpenOptions::new()
132                        .write(true)
133                        .create(true)
134                        .truncate(true)
135                        .open(log_out.clone())
136                        .map_err(|e| {
137                            LoggingError::FailedToOpenLoggingFile(format!(
138                                "log_out: {:?}, error type: {:?}",
139                                log_out,
140                                e.kind()
141                            ))
142                        })?,
143                ),
144        )
145    } else {
146        dispatch
147    };
148
149    dispatch
150        .apply()
151        .map_err(|e| LoggingError::FailedToInstantiateLogger(format!("{}", e)))?;
152
153    trace!("Logging set up");
154    Ok(())
155}
156
157#[derive(Debug, Error)]
158pub enum LoggingError {
159    #[error("Failed to open logging file in WASMER_DIR: {0}")]
160    FailedToOpenLoggingFile(String),
161    #[error("Something went wrong setting up logging: {0}")]
162    FailedToInstantiateLogger(String),
163}