mirror of
https://codeberg.org/icewind/logsmash.git
synced 2026-06-03 18:14:11 +02:00
add option to show parse errors
This commit is contained in:
parent
d04da3b11d
commit
d061e7b606
6 changed files with 97 additions and 19 deletions
|
|
@ -16,6 +16,7 @@ pub struct App {
|
||||||
pub all: LogMatch,
|
pub all: LogMatch,
|
||||||
pub unmatched: LogMatch,
|
pub unmatched: LogMatch,
|
||||||
pub log_file: LogFile,
|
pub log_file: LogFile,
|
||||||
|
pub error_lines: Vec<(String, serde_json::Error)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl App {
|
impl App {
|
||||||
|
|
|
||||||
52
src/main.rs
52
src/main.rs
|
|
@ -72,35 +72,48 @@ fn main() -> MainResult {
|
||||||
let mut results: Vec<_> = lines
|
let mut results: Vec<_> = lines
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.par_bridge()
|
.par_bridge()
|
||||||
.flat_map(|(index, line)| {
|
.map(|(index, line)| {
|
||||||
let mut parsed = parse_line(line).ok()?;
|
let mut parsed = parse_line(line);
|
||||||
parsed.index = index;
|
if let Ok(parsed) = parsed.as_mut() {
|
||||||
Some(parsed)
|
parsed.index = index;
|
||||||
|
};
|
||||||
|
parsed.map_err(|err| (index, line, err))
|
||||||
})
|
})
|
||||||
.map(|parsed| {
|
.map(|parsed| {
|
||||||
let log_match = matcher.match_log(&parsed);
|
parsed.map(|parsed| {
|
||||||
(parsed, log_match)
|
let log_match = matcher.match_log(&parsed);
|
||||||
|
(parsed, log_match)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
results.sort_by_key(|(line, _)| line.index);
|
results.sort_by_key(|res| match res {
|
||||||
|
Ok((line, _)) => line.index,
|
||||||
|
Err((index, _, _)) => *index,
|
||||||
|
});
|
||||||
|
|
||||||
|
let mut error_lines = Vec::with_capacity(32);
|
||||||
let mut parsed_lines = Vec::with_capacity(1024);
|
let mut parsed_lines = Vec::with_capacity(1024);
|
||||||
let mut unmatched_lines = Vec::with_capacity(256);
|
let mut unmatched_lines = Vec::with_capacity(256);
|
||||||
|
|
||||||
for (parsed_index, result) in results.into_iter().enumerate() {
|
let mut parsed_index = 0;
|
||||||
let parsed = match result {
|
|
||||||
(parsed, Some(match_result)) => {
|
|
||||||
counts.entry(match_result).or_default().push(parsed_index);
|
|
||||||
parsed
|
|
||||||
}
|
|
||||||
(parsed, None) => {
|
|
||||||
unmatched_lines.push(parsed_index);
|
|
||||||
parsed
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
parsed_lines.push(parsed);
|
for result in results.into_iter() {
|
||||||
|
match result {
|
||||||
|
Ok((parsed, Some(match_result))) => {
|
||||||
|
counts.entry(match_result).or_default().push(parsed_index);
|
||||||
|
parsed_lines.push(parsed);
|
||||||
|
parsed_index += 1;
|
||||||
|
}
|
||||||
|
Ok((parsed, None)) => {
|
||||||
|
unmatched_lines.push(parsed_index);
|
||||||
|
parsed_lines.push(parsed);
|
||||||
|
parsed_index += 1;
|
||||||
|
}
|
||||||
|
Err((_index, line, e)) => {
|
||||||
|
error_lines.push((line.to_string(), e));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
let error_count = log_file.iter().count() - parsed_lines.len();
|
let error_count = log_file.iter().count() - parsed_lines.len();
|
||||||
|
|
||||||
|
|
@ -125,6 +138,7 @@ fn main() -> MainResult {
|
||||||
last_date: parsed_lines.last().unwrap().time,
|
last_date: parsed_lines.last().unwrap().time,
|
||||||
lines: parsed_lines,
|
lines: parsed_lines,
|
||||||
log_statements: statements,
|
log_statements: statements,
|
||||||
|
error_lines,
|
||||||
matches,
|
matches,
|
||||||
unmatched,
|
unmatched,
|
||||||
all,
|
all,
|
||||||
|
|
|
||||||
22
src/ui/error_list.rs
Normal file
22
src/ui/error_list.rs
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
use crate::app::App;
|
||||||
|
use crate::ui::style::TABLE_HEADER_STYLE;
|
||||||
|
use crate::ui::table::ScrollbarTable;
|
||||||
|
use ratatui::layout::Constraint;
|
||||||
|
use ratatui::text::Text;
|
||||||
|
use ratatui::widgets::{Cell, Row};
|
||||||
|
|
||||||
|
pub fn error_list(app: &App) -> ScrollbarTable {
|
||||||
|
let header = [Text::from("Error"), Text::from("Line")]
|
||||||
|
.into_iter()
|
||||||
|
.map(Cell::from)
|
||||||
|
.collect::<Row>()
|
||||||
|
.style(TABLE_HEADER_STYLE)
|
||||||
|
.height(1);
|
||||||
|
|
||||||
|
let widths = [Constraint::Percentage(50), Constraint::Percentage(50)];
|
||||||
|
ScrollbarTable::new(app.error_lines.iter().map(error_row), widths).header(header)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn error_row((line, err): &(String, serde_json::Error)) -> Row {
|
||||||
|
Row::new([Text::from(format!("{err}")), Text::from(line.as_str())])
|
||||||
|
}
|
||||||
|
|
@ -29,9 +29,10 @@ pub fn footer(app: &App, page: UiPage) -> Table {
|
||||||
|
|
||||||
fn help(page: UiPage) -> &'static str {
|
fn help(page: UiPage) -> &'static str {
|
||||||
match page {
|
match page {
|
||||||
UiPage::MatchList => "«Q» Exit - «Enter» Select",
|
UiPage::MatchList => "«Q» Exit - «Enter» Select - «E» Show parse errors",
|
||||||
UiPage::Match => "«Q» Exit - «Enter» Select - «Esc» Back",
|
UiPage::Match => "«Q» Exit - «Enter» Select - «Esc» Back",
|
||||||
UiPage::Logs => "«Q» Exit - «Esc» Back - «C» Copy log line",
|
UiPage::Logs => "«Q» Exit - «Esc» Back - «C» Copy log line",
|
||||||
UiPage::Log => "«Q» Exit - «Esc» Back - «R» Toggle raw - «C» Copy log line",
|
UiPage::Log => "«Q» Exit - «Esc» Back - «R» Toggle raw - «C» Copy log line",
|
||||||
|
UiPage::Errors => "«Q» Exit - «Esc» Back - «C» Copy log line",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,15 @@
|
||||||
use crate::app::App;
|
use crate::app::App;
|
||||||
use crate::error::UiError;
|
use crate::error::UiError;
|
||||||
|
use crate::ui::error_list::error_list;
|
||||||
use crate::ui::footer::footer;
|
use crate::ui::footer::footer;
|
||||||
use crate::ui::histogram::UiHistogram;
|
use crate::ui::histogram::UiHistogram;
|
||||||
use crate::ui::match_list::match_list;
|
use crate::ui::match_list::match_list;
|
||||||
use crate::ui::raw_logs::raw_logs;
|
use crate::ui::raw_logs::raw_logs;
|
||||||
use crate::ui::single_log::single_log;
|
use crate::ui::single_log::single_log;
|
||||||
use crate::ui::single_match::grouped_lines;
|
use crate::ui::single_match::grouped_lines;
|
||||||
use crate::ui::state::{LogState, LogsState, MatchListState, MatchState, UiEvent, UiPage, UiState};
|
use crate::ui::state::{
|
||||||
|
ErrorState, LogState, LogsState, MatchListState, MatchState, UiEvent, UiPage, UiState,
|
||||||
|
};
|
||||||
use ratatui::crossterm::event::{Event, KeyCode, KeyModifiers};
|
use ratatui::crossterm::event::{Event, KeyCode, KeyModifiers};
|
||||||
use ratatui::crossterm::terminal::{
|
use ratatui::crossterm::terminal::{
|
||||||
disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen,
|
disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen,
|
||||||
|
|
@ -17,6 +20,7 @@ use ratatui::Terminal;
|
||||||
use std::io;
|
use std::io;
|
||||||
use std::io::stdout;
|
use std::io::stdout;
|
||||||
|
|
||||||
|
mod error_list;
|
||||||
mod footer;
|
mod footer;
|
||||||
mod histogram;
|
mod histogram;
|
||||||
mod match_list;
|
mod match_list;
|
||||||
|
|
@ -61,6 +65,7 @@ fn handle_events(page: UiPage) -> io::Result<Option<UiEvent>> {
|
||||||
}
|
}
|
||||||
KeyCode::Char('q') => Some(UiEvent::Quit),
|
KeyCode::Char('q') => Some(UiEvent::Quit),
|
||||||
KeyCode::Esc => Some(UiEvent::Back),
|
KeyCode::Esc => Some(UiEvent::Back),
|
||||||
|
KeyCode::Char('e') if page == UiPage::MatchList => Some(UiEvent::Errors),
|
||||||
KeyCode::Left if page != UiPage::MatchList => Some(UiEvent::Back),
|
KeyCode::Left if page != UiPage::MatchList => Some(UiEvent::Back),
|
||||||
KeyCode::Down => Some(UiEvent::Down(1)),
|
KeyCode::Down => Some(UiEvent::Down(1)),
|
||||||
KeyCode::Up => Some(UiEvent::Up(1)),
|
KeyCode::Up => Some(UiEvent::Up(1)),
|
||||||
|
|
@ -137,5 +142,9 @@ fn ui(frame: &mut Frame, app: &App, state: &mut UiState) {
|
||||||
);
|
);
|
||||||
frame.render_widget(footer(app, page), layout[2]);
|
frame.render_widget(footer(app, page), layout[2]);
|
||||||
}
|
}
|
||||||
|
UiState::Errors(ErrorState { table_state, .. }) => {
|
||||||
|
frame.render_stateful_widget(error_list(app), layout[0].union(layout[1]), table_state);
|
||||||
|
frame.render_widget(footer(app, page), layout[2]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ pub enum UiState<'a> {
|
||||||
Match(MatchState<'a>),
|
Match(MatchState<'a>),
|
||||||
Logs(LogsState<'a>),
|
Logs(LogsState<'a>),
|
||||||
Log(LogState<'a>),
|
Log(LogState<'a>),
|
||||||
|
Errors(ErrorState<'a>),
|
||||||
Quit,
|
Quit,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -45,6 +46,12 @@ pub struct LogsState<'a> {
|
||||||
pub previous: Box<UiState<'a>>,
|
pub previous: Box<UiState<'a>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct ErrorState<'a> {
|
||||||
|
pub table_state: ScrollbarTableState,
|
||||||
|
pub previous: Box<UiState<'a>>,
|
||||||
|
}
|
||||||
|
|
||||||
impl<'a> LogsState<'a> {
|
impl<'a> LogsState<'a> {
|
||||||
fn selected(&self) -> usize {
|
fn selected(&self) -> usize {
|
||||||
self.table_state.selected()
|
self.table_state.selected()
|
||||||
|
|
@ -75,6 +82,7 @@ impl<'a> UiState<'a> {
|
||||||
UiState::Match(_) => UiPage::Match,
|
UiState::Match(_) => UiPage::Match,
|
||||||
UiState::Logs(_) => UiPage::Logs,
|
UiState::Logs(_) => UiPage::Logs,
|
||||||
UiState::Log(_) => UiPage::Log,
|
UiState::Log(_) => UiPage::Log,
|
||||||
|
UiState::Errors(_) => UiPage::Errors,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -84,6 +92,7 @@ impl<'a> UiState<'a> {
|
||||||
UiState::Match(state) => Some(&mut state.table_state),
|
UiState::Match(state) => Some(&mut state.table_state),
|
||||||
UiState::Logs(state) => Some(&mut state.table_state),
|
UiState::Logs(state) => Some(&mut state.table_state),
|
||||||
UiState::Log(state) => Some(&mut state.table_state),
|
UiState::Log(state) => Some(&mut state.table_state),
|
||||||
|
UiState::Errors(state) => Some(&mut state.table_state),
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -125,6 +134,16 @@ impl<'a> UiState<'a> {
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
(UiState::MatchList(state), UiEvent::Errors) => {
|
||||||
|
let table_state = ScrollbarTableState::new(app.error_lines.len());
|
||||||
|
(
|
||||||
|
true,
|
||||||
|
UiState::Errors(ErrorState {
|
||||||
|
table_state,
|
||||||
|
previous: Box::new(state.into()),
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
}
|
||||||
(UiState::Match(state), UiEvent::Select) => {
|
(UiState::Match(state), UiEvent::Select) => {
|
||||||
let selected = state.selected();
|
let selected = state.selected();
|
||||||
let mut table_state = TableState::default();
|
let mut table_state = TableState::default();
|
||||||
|
|
@ -181,10 +200,20 @@ impl<'a> UiState<'a> {
|
||||||
copy_osc(raw);
|
copy_osc(raw);
|
||||||
(false, UiState::Log(state))
|
(false, UiState::Log(state))
|
||||||
}
|
}
|
||||||
|
(UiState::Errors(state), UiEvent::Copy) => {
|
||||||
|
let raw = app
|
||||||
|
.error_lines
|
||||||
|
.get(state.table_state.selected())
|
||||||
|
.map(|(line, _)| line.as_str())
|
||||||
|
.unwrap_or_default();
|
||||||
|
copy_osc(raw);
|
||||||
|
(false, UiState::Errors(state))
|
||||||
|
}
|
||||||
(
|
(
|
||||||
UiState::Match(MatchState { previous, .. })
|
UiState::Match(MatchState { previous, .. })
|
||||||
| UiState::Logs(LogsState { previous, .. })
|
| UiState::Logs(LogsState { previous, .. })
|
||||||
| UiState::Log(LogState { previous, .. }),
|
| UiState::Log(LogState { previous, .. })
|
||||||
|
| UiState::Errors(ErrorState { previous, .. }),
|
||||||
UiEvent::Back,
|
UiEvent::Back,
|
||||||
) => (true, *previous),
|
) => (true, *previous),
|
||||||
(state, _) => (false, state),
|
(state, _) => (false, state),
|
||||||
|
|
@ -197,6 +226,7 @@ pub enum UiEvent {
|
||||||
Back,
|
Back,
|
||||||
Up(usize),
|
Up(usize),
|
||||||
Down(usize),
|
Down(usize),
|
||||||
|
Errors,
|
||||||
Select,
|
Select,
|
||||||
Copy,
|
Copy,
|
||||||
}
|
}
|
||||||
|
|
@ -207,4 +237,5 @@ pub enum UiPage {
|
||||||
Match,
|
Match,
|
||||||
Logs,
|
Logs,
|
||||||
Log,
|
Log,
|
||||||
|
Errors,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue