summaryrefslogtreecommitdiffhomepage
path: root/test/test-runner/src/logging.rs
blob: 3c9aad9d15c61c7ec57ce082b95cf27ceaf670fd (plain)
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
use log::{Level, LevelFilter, Metadata, Record, SetLoggerError};
use once_cell::sync::Lazy;
use std::path::{Path, PathBuf};
use test_rpc::logging::Error;
use test_rpc::logging::{LogFile, LogOutput, Output};
use tokio::{
    fs::read_to_string,
    sync::{
        broadcast::{channel, Receiver, Sender},
        Mutex,
    },
};

const MAX_OUTPUT_BUFFER: usize = 10_000;

pub static LOGGER: Lazy<StdOutBuffer> = Lazy::new(|| {
    let (sender, listener) = channel(MAX_OUTPUT_BUFFER);
    StdOutBuffer(Mutex::new(listener), sender)
});

pub struct StdOutBuffer(pub Mutex<Receiver<Output>>, pub Sender<Output>);

impl log::Log for StdOutBuffer {
    fn enabled(&self, metadata: &Metadata) -> bool {
        metadata.level() <= Level::Info
    }

    fn log(&self, record: &Record) {
        if self.enabled(record.metadata()) {
            match record.metadata().level() {
                Level::Error => {
                    self.1
                        .send(Output::Error(format!("{}", record.args())))
                        .unwrap();
                }
                Level::Warn => {
                    self.1
                        .send(Output::Warning(format!("{}", record.args())))
                        .unwrap();
                }
                Level::Info => {
                    if !record.metadata().target().contains("tarpc") {
                        self.1
                            .send(Output::Info(format!("{}", record.args())))
                            .unwrap();
                    }
                }
                _ => (),
            }
            println!("{}", record.args());
        }
    }

    fn flush(&self) {}
}

pub fn init_logger() -> Result<(), SetLoggerError> {
    log::set_logger(&*LOGGER).map(|()| log::set_max_level(LevelFilter::Info))
}

pub async fn get_mullvad_app_logs() -> LogOutput {
    LogOutput {
        settings_json: read_settings_file().await,
        log_files: read_log_files().await,
    }
}

async fn read_settings_file() -> Result<String, Error> {
    let mut settings_path = mullvad_paths::get_default_settings_dir()
        .map_err(|error| Error::Logs(format!("{}", error)))?;
    settings_path.push("settings.json");
    read_to_string(&settings_path)
        .await
        .map_err(|error| Error::Logs(format!("{}: {}", settings_path.display(), error)))
}

async fn read_log_files() -> Result<Vec<Result<LogFile, Error>>, Error> {
    let log_dir =
        mullvad_paths::get_default_log_dir().map_err(|error| Error::Logs(format!("{}", error)))?;
    let paths = list_logs(log_dir)
        .await
        .map_err(|error| Error::Logs(format!("{}", error)))?;
    let mut log_files = Vec::new();
    for path in paths {
        let log_file = read_to_string(&path)
            .await
            .map_err(|error| Error::Logs(format!("{}: {}", path.display(), error)))
            .map(|content| LogFile {
                content,
                name: path,
            });
        log_files.push(log_file);
    }
    Ok(log_files)
}

async fn list_logs<T: AsRef<Path>>(log_dir: T) -> Result<Vec<PathBuf>, Error> {
    let mut dir_entries = tokio::fs::read_dir(&log_dir)
        .await
        .map_err(|e| Error::Logs(format!("{}: {}", log_dir.as_ref().display(), e)))?;
    let log_extension = Some(std::ffi::OsStr::new("log"));

    let mut paths = Vec::new();
    while let Ok(Some(entry)) = dir_entries.next_entry().await {
        let path = entry.path();
        if path.extension() == log_extension {
            paths.push(path);
        }
    }
    Ok(paths)
}