-
Notifications
You must be signed in to change notification settings - Fork 52
Expand file tree
/
Copy pathlogging.rs
More file actions
156 lines (129 loc) · 4.22 KB
/
logging.rs
File metadata and controls
156 lines (129 loc) · 4.22 KB
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
150
151
152
153
154
155
156
//! Setup for logging in tests.
use std::collections::VecDeque;
use std::io;
use std::sync::{Mutex, MutexGuard, Once, OnceLock};
use torrust_tracker_configuration::logging::TraceStyle;
use tracing::level_filters::LevelFilter;
use tracing_subscriber::fmt::MakeWriter;
static INIT: Once = Once::new();
/// A global buffer containing the latest lines captured from logs.
#[doc(hidden)]
pub fn captured_logs_buffer() -> &'static Mutex<CircularBuffer> {
static CAPTURED_LOGS_GLOBAL_BUFFER: OnceLock<Mutex<CircularBuffer>> = OnceLock::new();
CAPTURED_LOGS_GLOBAL_BUFFER.get_or_init(|| Mutex::new(CircularBuffer::new(10000, 200)))
}
pub fn setup() {
INIT.call_once(|| {
tracing_init(LevelFilter::ERROR, &TraceStyle::Default);
});
}
fn tracing_init(level_filter: LevelFilter, style: &TraceStyle) {
let mock_writer = LogCapturer::new(captured_logs_buffer());
let builder = tracing_subscriber::fmt()
.with_max_level(level_filter)
.with_ansi(true)
.with_test_writer()
.with_writer(mock_writer);
let () = match style {
TraceStyle::Default => builder.init(),
TraceStyle::Pretty(display_filename) => builder.pretty().with_file(*display_filename).init(),
TraceStyle::Compact => builder.compact().init(),
TraceStyle::Json => builder.json().init(),
};
tracing::info!("Logging initialized");
}
/// It returns true is there is a log line containing all the texts passed.
///
/// # Panics
///
/// Will panic if it can't get the lock for the global buffer or convert it into
/// a vec.
#[must_use]
#[allow(dead_code)]
pub fn logs_contains_a_line_with(texts: &[&str]) -> bool {
// code-review: we can search directly in the buffer instead of converting
// the buffer into a string but that would slow down the tests because
// cloning should be faster that locking the buffer for searching.
// Because the buffer is not big.
let logs = String::from_utf8(captured_logs_buffer().lock().unwrap().as_vec()).unwrap();
for line in logs.split('\n') {
if contains(line, texts) {
return true;
}
}
false
}
#[allow(dead_code)]
fn contains(text: &str, texts: &[&str]) -> bool {
texts.iter().all(|&word| text.contains(word))
}
/// A tracing writer which captures the latests logs lines into a buffer.
/// It's used to capture the logs in the tests.
#[derive(Debug)]
pub struct LogCapturer<'a> {
logs: &'a Mutex<CircularBuffer>,
}
impl<'a> LogCapturer<'a> {
pub fn new(buf: &'a Mutex<CircularBuffer>) -> Self {
Self { logs: buf }
}
fn buf(&self) -> io::Result<MutexGuard<'a, CircularBuffer>> {
self.logs.lock().map_err(|_| io::Error::from(io::ErrorKind::Other))
}
}
impl io::Write for LogCapturer<'_> {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
print!("{}", String::from_utf8(buf.to_vec()).unwrap());
let mut target = self.buf()?;
target.write(buf)
}
fn flush(&mut self) -> io::Result<()> {
self.buf()?.flush()
}
}
impl MakeWriter<'_> for LogCapturer<'_> {
type Writer = Self;
fn make_writer(&self) -> Self::Writer {
LogCapturer::new(self.logs)
}
}
#[derive(Debug)]
pub struct CircularBuffer {
max_size: usize,
buffer: VecDeque<u8>,
}
impl CircularBuffer {
#[must_use]
pub fn new(max_lines: usize, average_line_size: usize) -> Self {
Self {
max_size: max_lines * average_line_size,
buffer: VecDeque::with_capacity(max_lines * average_line_size),
}
}
/// # Errors
///
/// Won't return any error.
#[allow(clippy::unnecessary_wraps)]
pub fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
for &byte in buf {
if self.buffer.len() == self.max_size {
// Remove oldest byte to make space
self.buffer.pop_front();
}
self.buffer.push_back(byte);
}
Ok(buf.len())
}
/// # Errors
///
/// Won't return any error.
#[allow(clippy::unnecessary_wraps)]
#[allow(clippy::unused_self)]
pub fn flush(&mut self) -> io::Result<()> {
Ok(())
}
#[must_use]
pub fn as_vec(&self) -> Vec<u8> {
self.buffer.iter().copied().collect()
}
}