rust_kanban/io/
logger.rs

1// This logger implementation is highly inspired by the logger implementation in https://github.com/gin66/tui-logger
2
3use 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}