Skip to main content

oo_ide/
log_buffer.rs

1//! IDE-internal logging system.
2//!
3//! `ooLogger` hooks into the `log` crate and fans each record out to:
4//!   1. A fixed-capacity in-memory ring buffer (`LogBuffer`).
5//!   2. A `tokio::sync::broadcast` channel so the log view receives live entries.
6//!
7//! The file sink is handled by `simplelog::WriteLogger` via
8//! `simplelog::CombinedLogger`; this module only manages the in-process buffer.
9
10use std::collections::VecDeque;
11use std::sync::{Arc, Mutex};
12use std::time::SystemTime;
13
14use log::{Level, LevelFilter, Metadata, Record};
15use simplelog::{Config, SharedLogger};
16use tokio::sync::broadcast;
17
18// ---------------------------------------------------------------------------
19// Public types
20// ---------------------------------------------------------------------------
21
22/// One captured log record.
23#[derive(Clone, Debug)]
24pub struct LogEntry {
25    pub time: SystemTime,
26    pub level: Level,
27    /// Target reported by the `log` macro (e.g. `oo::lsp`).
28    pub target: String,
29    pub message: String,
30}
31
32/// Fixed-capacity FIFO that keeps the most recent N entries.
33pub struct LogBuffer {
34    capacity: usize,
35    entries: VecDeque<LogEntry>,
36}
37
38impl LogBuffer {
39    fn new(capacity: usize) -> Self {
40        Self {
41            capacity,
42            entries: VecDeque::with_capacity(capacity),
43        }
44    }
45
46    fn push(&mut self, entry: LogEntry) {
47        if self.entries.len() == self.capacity {
48            self.entries.pop_front();
49        }
50        self.entries.push_back(entry);
51    }
52
53    /// Snapshot for the log view to display on open.
54    pub fn snapshot(&self) -> Vec<LogEntry> {
55        self.entries.iter().cloned().collect()
56    }
57}
58
59/// Shared state between the custom logger and any subscribers (e.g. `LogView`).
60pub struct LogState {
61    pub buffer: Mutex<LogBuffer>,
62    pub tx: broadcast::Sender<LogEntry>,
63}
64
65impl LogState {
66    fn new(capacity: usize) -> Arc<Self> {
67        let (tx, _) = broadcast::channel(4096);
68        Arc::new(Self {
69            buffer: Mutex::new(LogBuffer::new(capacity)),
70            tx,
71        })
72    }
73
74    /// Subscribe to live log entries.
75    pub fn subscribe(&self) -> broadcast::Receiver<LogEntry> {
76        self.tx.subscribe()
77    }
78}
79
80// ---------------------------------------------------------------------------
81// In-memory logger — implements simplelog::SharedLogger
82// ---------------------------------------------------------------------------
83
84pub struct InMemoryLogger {
85    state: Arc<LogState>,
86    level: LevelFilter,
87}
88
89impl log::Log for InMemoryLogger {
90    fn enabled(&self, metadata: &Metadata) -> bool {
91        metadata.level() <= self.level
92    }
93
94    fn log(&self, record: &Record) {
95        if !self.enabled(record.metadata()) {
96            return;
97        }
98        let entry = LogEntry {
99            time: SystemTime::now(),
100            level: record.level(),
101            target: record.target().to_owned(),
102            message: record.args().to_string(),
103        };
104        if let Ok(mut buf) = self.state.buffer.lock() {
105            buf.push(entry.clone());
106        }
107        // Ignore send errors — no subscribers is fine.
108        let _ = self.state.tx.send(entry);
109    }
110
111    fn flush(&self) {}
112}
113
114impl SharedLogger for InMemoryLogger {
115    fn level(&self) -> LevelFilter {
116        self.level
117    }
118
119    fn config(&self) -> Option<&Config> {
120        None
121    }
122
123    fn as_log(self: Box<Self>) -> Box<dyn log::Log> {
124        self
125    }
126}
127
128// ---------------------------------------------------------------------------
129// Initialisation
130// ---------------------------------------------------------------------------
131
132/// Initialise a combined logger: `simplelog::WriteLogger` to `log_file` plus
133/// the in-memory logger.  Returns the `LogState` handle to thread into the app.
134///
135/// # Panics
136/// Panics if a global logger has already been set.
137pub fn init_combined_logger(log_file: std::fs::File, level: LevelFilter) -> Arc<LogState> {
138    let state = LogState::new(10_000);
139
140    let file_logger = simplelog::WriteLogger::new(level, Config::default(), log_file);
141
142    let mem_logger = Box::new(InMemoryLogger {
143        state: state.clone(),
144        level,
145    });
146
147    simplelog::CombinedLogger::init(vec![file_logger, mem_logger])
148        .expect("logger already initialised");
149
150    state
151}