1use chrono::{DateTime, Local};
4use log::{Level, LevelFilter, Log, Metadata, Record};
5use parking_lot::Mutex;
6use ratatui::widgets::TableState;
7use std::{
8 collections::{
9 hash_map::{Iter, Keys},
10 HashMap,
11 },
12 iter, mem,
13};
14
15#[derive(Clone, Debug)]
16pub struct CircularBuffer<T> {
17 pub buffer: Vec<T>,
18 next_write_pos: usize,
19}
20
21#[derive(Default, Debug)]
22pub struct LevelConfig {
23 config: HashMap<String, LevelFilter>,
24 generation: u64,
25 default_display_level: Option<LevelFilter>,
26}
27
28#[derive(Clone, Debug)]
29pub struct ExtLogRecord {
30 pub timestamp: DateTime<Local>,
31 pub level: Level,
32 pub msg: String,
33}
34
35#[derive(Debug)]
36struct HotSelect {
37 hash_table: HashMap<u64, LevelFilter>,
38 default: LevelFilter,
39}
40
41#[derive(Debug)]
42pub struct HotLog {
43 pub events: CircularBuffer<ExtLogRecord>,
44 pub state: TableState,
45}
46
47#[derive(Debug)]
48pub struct RustKanbanLoggerInner {
49 hot_depth: usize,
50 pub events: CircularBuffer<ExtLogRecord>,
51 pub total_events: usize,
52 default: LevelFilter,
53 targets: LevelConfig,
54}
55
56#[derive(Debug)]
57pub struct RustKanbanLogger {
58 hot_select: Mutex<HotSelect>,
59 pub hot_log: Mutex<HotLog>,
60 pub inner: Mutex<RustKanbanLoggerInner>,
61}
62
63impl RustKanbanLogger {
64 pub fn move_events(&self) {
65 if self.hot_log.lock().events.total_elements() == 0 {
66 return;
67 }
68
69 let mut received_events = {
70 let new_circular = CircularBuffer::new(self.inner.lock().hot_depth);
71 let mut hl = self.hot_log.lock();
72 mem::replace(&mut hl.events, new_circular)
73 };
74 let mut tli = self.inner.lock();
75 let total = received_events.total_elements();
76 let elements = received_events.len();
77 tli.total_events += total;
78 let mut consumed = received_events.take();
79 let mut reversed = Vec::with_capacity(consumed.len() + 1);
80 while let Some(log_entry) = consumed.pop() {
81 reversed.push(log_entry);
82 }
83 if total > elements {
84 let new_log_entry = ExtLogRecord {
85 timestamp: reversed[reversed.len() - 1].timestamp,
86 level: Level::Warn,
87 msg: format!(
88 "There have been {} events lost, {} recorded out of {}",
89 total - elements,
90 elements,
91 total
92 ),
93 };
94 reversed.push(new_log_entry);
95 }
96 while let Some(log_entry) = reversed.pop() {
97 tli.events.push(log_entry);
98 }
99 }
100}
101
102impl<T> CircularBuffer<T> {
103 pub fn new(max_depth: usize) -> CircularBuffer<T> {
104 CircularBuffer {
105 buffer: Vec::with_capacity(max_depth),
106 next_write_pos: 0,
107 }
108 }
109 pub fn len(&self) -> usize {
110 self.buffer.len()
111 }
112 pub fn is_empty(&self) -> bool {
113 self.buffer.is_empty()
114 }
115 pub fn push(&mut self, elem: T) {
116 let max_depth = self.buffer.capacity();
117 if self.buffer.len() < max_depth {
118 self.buffer.push(elem);
119 } else {
120 self.buffer[self.next_write_pos % max_depth] = elem;
121 }
122 self.next_write_pos += 1;
123 }
124 pub fn take(&mut self) -> Vec<T> {
125 let mut consumed = vec![];
126 let max_depth = self.buffer.capacity();
127 if self.buffer.len() < max_depth {
128 consumed.append(&mut self.buffer);
129 } else {
130 let pos = self.next_write_pos % max_depth;
131 let mut remaining_buffer = self.buffer.split_off(pos);
132 consumed.append(&mut remaining_buffer);
133 consumed.append(&mut self.buffer)
134 }
135 self.next_write_pos = 0;
136 consumed
137 }
138 pub fn total_elements(&self) -> usize {
139 self.next_write_pos
140 }
141 pub fn has_wrapped(&self) -> bool {
142 self.next_write_pos > self.buffer.capacity()
143 }
144 pub fn iter(&mut self) -> iter::Chain<std::slice::Iter<T>, std::slice::Iter<T>> {
145 let max_depth = self.buffer.capacity();
146 if self.next_write_pos <= max_depth {
147 self.buffer.iter().chain(self.buffer[..0].iter())
148 } else {
149 let wrap = self.next_write_pos % max_depth;
150 let it_end = self.buffer[..wrap].iter();
151 let it_start = self.buffer[wrap..].iter();
152 it_start.chain(it_end)
153 }
154 }
155 pub fn rev_iter(
156 &mut self,
157 ) -> iter::Chain<std::iter::Rev<std::slice::Iter<T>>, std::iter::Rev<std::slice::Iter<T>>> {
158 let max_depth = self.buffer.capacity();
159 if self.next_write_pos <= max_depth {
160 self.buffer
161 .iter()
162 .rev()
163 .chain(self.buffer[..0].iter().rev())
164 } else {
165 let wrap = self.next_write_pos % max_depth;
166 let it_end = self.buffer[..wrap].iter().rev();
167 let it_start = self.buffer[wrap..].iter().rev();
168 it_end.chain(it_start)
169 }
170 }
171}
172
173impl LevelConfig {
174 pub fn new() -> LevelConfig {
175 LevelConfig {
176 config: HashMap::new(),
177 generation: 0,
178 default_display_level: None,
179 }
180 }
181 pub fn set(&mut self, target: &str, level: LevelFilter) {
182 if let Some(lev) = self.config.get_mut(target) {
183 if *lev != level {
184 *lev = level;
185 self.generation += 1;
186 }
187 return;
188 }
189 self.config.insert(target.to_string(), level);
190 self.generation += 1;
191 }
192 pub fn set_default_display_level(&mut self, level: LevelFilter) {
193 self.default_display_level = Some(level);
194 }
195 pub fn keys(&self) -> Keys<String, LevelFilter> {
196 self.config.keys()
197 }
198 pub fn get(&self, target: &str) -> Option<&LevelFilter> {
199 self.config.get(target)
200 }
201 pub fn iter(&self) -> Iter<String, LevelFilter> {
202 self.config.iter()
203 }
204}
205
206pub fn set_hot_buffer_depth(depth: usize) {
207 RUST_KANBAN_LOGGER.inner.lock().hot_depth = depth;
208}
209
210pub fn move_events() {
211 RUST_KANBAN_LOGGER.move_events();
212}
213
214pub fn set_default_level(level_filter: LevelFilter) {
215 RUST_KANBAN_LOGGER.hot_select.lock().default = level_filter;
216 RUST_KANBAN_LOGGER.inner.lock().default = level_filter;
217}
218
219pub fn set_level_for_target(target: &str, level_filter: LevelFilter) {
220 let h = fxhash::hash64(&target);
221 RUST_KANBAN_LOGGER
222 .inner
223 .lock()
224 .targets
225 .set(target, level_filter);
226 let mut hs = RUST_KANBAN_LOGGER.hot_select.lock();
227 hs.hash_table.insert(h, level_filter);
228}
229
230impl RustKanbanLogger {
231 fn raw_log(&self, record: &Record) {
232 let log_entry = ExtLogRecord {
233 timestamp: chrono::Local::now(),
234 level: record.level(),
235 msg: format!("{}", record.args()),
236 };
237 let mut hot_log = self.hot_log.lock();
238 hot_log.events.push(log_entry);
239 let last_index = hot_log.events.len() - 1;
240 hot_log.state.select(Some(last_index));
241 }
242}
243
244impl Log for RustKanbanLogger {
245 fn enabled(&self, metadata: &Metadata) -> bool {
246 let h = fxhash::hash64(metadata.target());
247 let hs = self.hot_select.lock();
248 if let Some(&level_filter) = hs.hash_table.get(&h) {
249 metadata.level() <= level_filter
250 } else {
251 metadata.level() <= hs.default
252 }
253 }
254
255 fn log(&self, record: &Record) {
256 if self.enabled(record.metadata()) {
257 self.raw_log(record)
258 }
259 }
260
261 fn flush(&self) {}
262}
263
264lazy_static! {
265 pub static ref RUST_KANBAN_LOGGER: RustKanbanLogger = {
266 let hs = HotSelect {
267 hash_table: HashMap::with_capacity(1000),
268 default: LevelFilter::Info,
269 };
270 let hl = HotLog {
271 events: CircularBuffer::new(1000),
272 state: TableState::default(),
273 };
274 let tli = RustKanbanLoggerInner {
275 hot_depth: 1000,
276 events: CircularBuffer::new(10000),
277 total_events: 0,
278 default: LevelFilter::Info,
279 targets: LevelConfig::new(),
280 };
281 RustKanbanLogger {
282 hot_select: Mutex::new(hs),
283 hot_log: Mutex::new(hl),
284 inner: Mutex::new(tli),
285 }
286 };
287}
288
289pub fn init_logger(max_level: LevelFilter) -> Result<(), log::SetLoggerError> {
290 log::set_max_level(max_level);
291 log::set_logger(&*RUST_KANBAN_LOGGER)
292}
293
294pub fn get_logs() -> CircularBuffer<ExtLogRecord> {
295 RUST_KANBAN_LOGGER.hot_log.lock().events.clone()
296}
297
298pub fn get_selected_index() -> usize {
299 RUST_KANBAN_LOGGER
300 .hot_log
301 .lock()
302 .state
303 .selected()
304 .unwrap_or(0)
305}