-
Notifications
You must be signed in to change notification settings - Fork 389
/
Copy pathlogging.rs
141 lines (128 loc) · 4.5 KB
/
logging.rs
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
use log::{Level, LevelFilter, Metadata, Record, SetLoggerError};
use std::{
ffi::OsStr,
path::{Path, PathBuf},
sync::LazyLock,
};
use test_rpc::logging::{Error, LogFile, LogOutput, Output};
use tokio::{
fs::File,
io::{self, AsyncBufReadExt, BufReader},
sync::{
broadcast::{channel, Receiver, Sender},
Mutex,
},
};
const MAX_OUTPUT_BUFFER: usize = 10_000;
/// Only consider files that end with ".log"
const INCLUDE_LOG_FILE_EXT: &str = "log";
/// Ignore log files that contain ".old"
const EXCLUDE_LOG_FILE_CONTAIN: &str = ".old";
/// Maximum number of lines that each log file may contain
const TRUNCATE_LOG_FILE_LINES: usize = 100;
pub static LOGGER: LazyLock<StdOutBuffer> = LazyLock::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_truncated(&settings_path, None)
.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_truncated(&path, Some(TRUNCATE_LOG_FILE_LINES))
.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 mut paths = Vec::new();
while let Ok(Some(entry)) = dir_entries.next_entry().await {
let path = entry.path();
if let Some(u8_path) = path.to_str() {
if u8_path.contains(EXCLUDE_LOG_FILE_CONTAIN) {
continue;
}
}
if path.extension() == Some(OsStr::new(INCLUDE_LOG_FILE_EXT)) {
paths.push(path);
}
}
Ok(paths)
}
/// Read the contents of a file to string, optionally truncating the result by given amount of lines.
async fn read_truncated<T: AsRef<Path>>(
path: T,
truncate_lines: Option<usize>,
) -> io::Result<String> {
let mut output = vec![];
let reader = BufReader::new(File::open(path).await?);
let mut lines = reader.lines();
while let Some(line) = lines.next_line().await? {
output.push(line);
}
if let Some(max_number_of_lines) = truncate_lines {
output.truncate(max_number_of_lines);
}
Ok(output.join("\n"))
}