add option to show parse errors

This commit is contained in:
Robin Appelman 2024-08-09 23:08:48 +02:00
commit d061e7b606
6 changed files with 97 additions and 19 deletions

View file

@ -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 {

View file

@ -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
View 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())])
}

View file

@ -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",
} }
} }

View file

@ -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]);
}
} }
} }

View file

@ -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,
} }