mithril_common/test/logging/
memory_logger.rs1use std::fmt;
2use std::io;
3use std::sync::{Arc, RwLock};
4
5use slog::{Drain, KV, OwnedKVList, Record};
6
7#[derive(Default, Clone)]
26pub struct MemoryDrainForTest {
27 records: Arc<RwLock<Vec<String>>>,
28}
29
30impl MemoryDrainForTest {
31 pub fn new() -> (Self, MemoryDrainForTestInspector) {
33 let drain = Self::default();
34 let inspector = MemoryDrainForTestInspector::new(&drain);
35 (drain, inspector)
36 }
37}
38
39pub struct MemoryDrainForTestInspector {
41 records: Arc<RwLock<Vec<String>>>,
42}
43
44impl MemoryDrainForTestInspector {
45 fn new(memory_drain: &MemoryDrainForTest) -> Self {
46 Self {
47 records: memory_drain.records.clone(),
48 }
49 }
50
51 pub fn search_logs(&self, text: &str) -> Vec<String> {
56 self.records
57 .read()
58 .unwrap()
59 .iter()
60 .filter(|record| record.contains(text))
61 .cloned()
62 .collect()
63 }
64
65 pub fn contains_log(&self, text: &str) -> bool {
70 self.records
71 .read()
72 .unwrap()
73 .iter()
74 .any(|record| record.contains(text))
75 }
76}
77
78impl fmt::Display for MemoryDrainForTestInspector {
79 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
80 write!(f, "{}", self.records.read().unwrap().join("\n"))
81 }
82}
83
84impl Drain for MemoryDrainForTest {
85 type Ok = ();
86 type Err = io::Error;
87
88 fn log(&self, record: &Record, values: &OwnedKVList) -> Result<Self::Ok, Self::Err> {
89 let mut kv_serializer = KVSerializer::default();
90 record.kv().serialize(record, &mut kv_serializer)?;
91 values.serialize(record, &mut kv_serializer)?;
92
93 let msg = format!(
94 "{} {}; {}",
95 record.level().as_str(),
96 record.msg(),
97 kv_serializer.content
98 );
99 println!("{msg}");
101
102 self.records.write().unwrap().push(msg);
103 Ok(())
104 }
105}
106
107#[derive(Default)]
108struct KVSerializer {
109 content: String,
110}
111
112impl slog::Serializer for KVSerializer {
113 fn emit_arguments(&mut self, key: slog::Key, val: &std::fmt::Arguments) -> slog::Result {
114 use std::fmt::Write;
115
116 let prefix = if self.content.is_empty() { "" } else { ", " };
117 write!(self.content, "{prefix}{key}={val:?}")
118 .map_err(|_| io::Error::other("Failed to serialize log"))?;
119 Ok(())
120 }
121}
122
123#[cfg(test)]
124mod tests {
125 use slog::info;
126
127 use super::*;
128
129 #[test]
130 fn test_log_format() {
131 let (drain, log_inspector) = MemoryDrainForTest::new();
132 let logger = slog::Logger::root(drain.clone().fuse(), slog::o!("shared" => "shared"));
133
134 info!(logger, "test format"; "key_3" => "value three", "key_2" => "value two", "key_1" => "value one");
136
137 let results = log_inspector.search_logs("test format");
138 assert_eq!(
139 "INFO test format; key_1=value one, key_2=value two, key_3=value three, shared=shared",
140 results[0]
141 );
142 }
143
144 #[test]
145 fn displaying_inspector_returns_all_log_messages() {
146 let (drain, log_inspector) = MemoryDrainForTest::new();
147 let logger = slog::Logger::root(drain.fuse(), slog::o!());
148
149 info!(logger, "message one"; "key" => "value1");
150 info!(logger, "message two"; "key" => "value2");
151
152 let display = format!("{log_inspector}");
153 assert_eq!(
154 display,
155 "INFO message one; key=value1\nINFO message two; key=value2"
156 );
157 }
158
159 #[test]
160 fn can_search_for_log_messages() {
161 let (drain, log_inspector) = MemoryDrainForTest::new();
162 let logger = slog::Logger::root(drain.clone().fuse(), slog::o!());
163
164 info!(logger, "test message"; "key" => "value");
165 info!(logger, "another message"; "key2" => "value2");
166
167 let results = log_inspector.search_logs("test");
168 assert_eq!(results.len(), 1);
169 assert!(results[0].contains("test message"));
170 assert!(log_inspector.contains_log("test message"));
171 }
172
173 #[tokio::test]
174 async fn test_concurrent_logging_from_two_tasks() {
175 let (drain, log_inspector) = MemoryDrainForTest::new();
176 let drain_clone1 = drain.clone();
177 let drain_clone2 = drain.clone();
178
179 let handle1 = tokio::spawn(async move {
180 let logger = slog::Logger::root(drain_clone1.fuse(), slog::o!());
181 info!(logger, "async test 1"; "key" => "value");
182 });
183 let handle2 = tokio::spawn(async move {
184 let logger = slog::Logger::root(drain_clone2.fuse(), slog::o!());
185 info!(logger, "async test 2"; "key" => "value");
186 });
187
188 handle1.await.unwrap();
189 handle2.await.unwrap();
190
191 let results = log_inspector.search_logs("async test");
192 assert_eq!(results.len(), 2);
193 assert!(results.iter().any(|r| r.contains("async test 1")));
194 assert!(results.iter().any(|r| r.contains("async test 2")));
195 }
196
197 #[tokio::test(flavor = "multi_thread", worker_threads = 4)]
198 async fn test_concurrent_logging_from_multiple_threads() {
199 let (drain, log_inspector) = MemoryDrainForTest::new();
200 let mut join_set = tokio::task::JoinSet::new();
201
202 for i in 0..10 {
203 let drain_clone = drain.clone();
204 join_set.spawn(async move {
205 let logger = slog::Logger::root(drain_clone.fuse(), slog::o!());
206 info!(logger, "multi thread test {i}"; "thread_id" => i);
207 });
208 }
209
210 join_set.join_all().await;
211
212 let results = log_inspector.search_logs("multi thread test");
213 assert_eq!(results.len(), 10);
214 for i in 0..10 {
215 assert!(results.iter().any(|r| r.contains(&format!("multi thread test {i}"))));
216 }
217 }
218}