log_analyzer/stores/
analysis_store.rs

1use crate::models::log_line::LogLine;
2use parking_lot::{lock_api::RwLockReadGuard, RawRwLock, RwLock};
3
4/// Store for managing processed logs.
5///
6/// Stores both the combined filtered log and the search log
7pub trait AnalysisStore {
8    /// Add a list of processed lines
9    fn add_lines(&self, lines: &[LogLine]);
10    /// Add a list of searched lines
11    fn add_search_lines(&self, lines: &[LogLine]);
12    /// Change the search query
13    fn add_search_query(&self, query: &str);
14    /// Get the current search query
15    fn get_search_query(&self) -> Option<String>;
16    /// Clear the processed log
17    fn reset_log(&self);
18    /// Clear the searched log
19    fn reset_search(&self);
20    /// Get a RwLock to the current processed log to avoid copying
21    fn fetch_log(&self) -> RwLockReadGuard<RawRwLock, Vec<LogLine>>;
22    /// Get a RwLock to the current searched log to avoid copying
23    fn fetch_search(&self) -> RwLockReadGuard<RawRwLock, Vec<LogLine>>;
24    /// Get a copy of a window of lines. Is safe to query out of bounds
25    fn get_log_lines(&self, from: usize, to: usize) -> Vec<LogLine>;
26    /// Get a copy of a window of search lines. Is safe to query out of bounds
27    fn get_search_lines(&self, from: usize, to: usize) -> Vec<LogLine>;
28    /// Get a window of `elements` number of lines centered around the target `line`
29    ///
30    /// Returns (list of lines, offset from start, index of target)
31    fn get_log_lines_containing(
32        &self,
33        index: usize,
34        elements: usize,
35    ) -> (Vec<LogLine>, usize, usize);
36    /// Get a window of `elements` number of lines centered around the target `line`
37    ///
38    /// Returns (list of lines, offset from start, index of target)
39    fn get_search_lines_containing(
40        &self,
41        index: usize,
42        elements: usize,
43    ) -> (Vec<LogLine>, usize, usize);
44    /// Count the total number of lines
45    fn get_total_filtered_lines(&self) -> usize;
46    /// Count the total number of search lines
47    fn get_total_searched_lines(&self) -> usize;
48}
49pub struct InMemmoryAnalysisStore {
50    log: RwLock<Vec<LogLine>>,
51    search_query: RwLock<Option<String>>,
52    search_log: RwLock<Vec<LogLine>>,
53}
54
55impl InMemmoryAnalysisStore {
56    pub fn new() -> Self {
57        Self {
58            log: RwLock::new(Vec::new()),
59            search_query: RwLock::new(None),
60            search_log: RwLock::new(Vec::new()),
61        }
62    }
63}
64
65impl Default for InMemmoryAnalysisStore {
66    fn default() -> Self {
67        Self::new()
68    }
69}
70
71impl AnalysisStore for InMemmoryAnalysisStore {
72    fn add_lines(&self, lines: &[LogLine]) {
73        let mut w = self.log.write();
74        for line in lines {
75            let index = w.len();
76
77            let mut line = line.clone();
78            line.index = index.to_string();
79
80            w.push(line);
81        }
82    }
83
84    fn add_search_lines(&self, lines: &[LogLine]) {
85        let mut w = self.search_log.write();
86        for line in lines {
87            w.push(line.clone());
88        }
89    }
90
91    fn add_search_query(&self, query: &str) {
92        let mut w = self.search_query.write();
93        *w = Some(query.to_string());
94    }
95
96    fn get_search_query(&self) -> Option<String> {
97        let r = self.search_query.read();
98        r.clone()
99    }
100
101    fn fetch_log(&self) -> RwLockReadGuard<RawRwLock, Vec<LogLine>> {
102        self.log.read()
103    }
104
105    fn fetch_search(&self) -> RwLockReadGuard<RawRwLock, Vec<LogLine>> {
106        self.search_log.read()
107    }
108
109    fn get_log_lines(&self, from: usize, to: usize) -> Vec<LogLine> {
110        let log = self.log.read();
111        log[from.min(log.len())..to.min(log.len())].to_vec()
112    }
113
114    fn get_search_lines(&self, from: usize, to: usize) -> Vec<LogLine> {
115        let log = self.search_log.read();
116        log[from.min(log.len())..to.min(log.len())].to_vec()
117    }
118
119    fn get_log_lines_containing(
120        &self,
121        index: usize,
122        elements: usize,
123    ) -> (Vec<LogLine>, usize, usize) {
124        let log = self.log.read();
125        InMemmoryAnalysisStore::find_rolling_window(&log, index, elements)
126    }
127
128    fn get_search_lines_containing(
129        &self,
130        index: usize,
131        elements: usize,
132    ) -> (Vec<LogLine>, usize, usize) {
133        let search_log = self.search_log.read();
134        InMemmoryAnalysisStore::find_rolling_window(&search_log, index, elements)
135    }
136
137    fn reset_log(&self) {
138        let mut w = self.log.write();
139        w.clear();
140    }
141
142    fn reset_search(&self) {
143        let mut w = self.search_log.write();
144        w.clear();
145    }
146
147    fn get_total_filtered_lines(&self) -> usize {
148        self.log.read().len()
149    }
150
151    fn get_total_searched_lines(&self) -> usize {
152        self.search_log.read().len()
153    }
154}
155
156impl InMemmoryAnalysisStore {
157    fn find_sorted_index(source: &[LogLine], index: usize) -> usize {
158        match source.binary_search_by(|e| {
159            e.index
160                .parse::<usize>()
161                .unwrap()
162                .cmp(&index)
163        }) {
164            Ok(i) => i,
165            Err(i) => i,
166        }
167    }
168
169    /// Find a window of elements containing the target in the middle
170    /// Returns (elements, offset, index)
171    fn find_rolling_window(
172        source: &[LogLine],
173        index: usize,
174        elements: usize,
175    ) -> (Vec<LogLine>, usize, usize) {
176        let closest = InMemmoryAnalysisStore::find_sorted_index(source, index);
177        let from = if (elements / 2) < closest {
178            closest - elements / 2
179        } else {
180            0
181        };
182        let to = (closest + elements / 2).min(source.len());
183
184        let lines = source[from..to].to_vec();
185        let index = InMemmoryAnalysisStore::find_sorted_index(&lines, index);
186        (lines, from, index)
187    }
188}
189
190#[cfg(test)]
191mod tests {
192    use super::*;
193
194    fn log_line_with_index(index: usize) -> LogLine {
195        LogLine {
196            index: index.to_string(),
197            ..Default::default()
198        }
199    }
200}