full line parse error page

This commit is contained in:
Robin Appelman 2025-05-27 22:30:45 +02:00
commit eec9d1aa00
4 changed files with 55 additions and 8 deletions

View file

@ -30,3 +30,16 @@ pub enum ReadError {
#[error("log file contained non-utf8 characters: {0:#}")] #[error("log file contained non-utf8 characters: {0:#}")]
Utf8(#[from] FromUtf8Error), Utf8(#[from] FromUtf8Error),
} }
#[derive(Debug, Error)]
#[error("Error while parsing log line '{line}': {err:#}")]
pub struct ParseError {
pub err: serde_json::Error,
pub line: String,
}
impl PartialEq for ParseError {
fn eq(&self, other: &Self) -> bool {
self.line == other.line
}
}

View file

@ -64,6 +64,7 @@ fn help(page: UiPage) -> &'static str {
"«Q» Exit - «Esc» Back - «R» Toggle raw - «C» Copy log line - «R» Show logs for request" "«Q» Exit - «Esc» Back - «R» Toggle raw - «C» Copy log line - «R» Show logs for request"
} }
UiPage::Errors => "«Q» Exit - «Esc» Back - «C» Copy log line", UiPage::Errors => "«Q» Exit - «Esc» Back - «C» Copy log line",
UiPage::Error => "«Q» Exit - «Esc» Back",
} }
} }

View file

@ -9,7 +9,7 @@ use crate::ui::match_list::match_list;
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::{ use crate::ui::state::{
ErrorState, GroupedLogsState, LogState, MatchListState, MatchState, UiState, ErrorLinesState, ErrorState, GroupedLogsState, LogState, MatchListState, MatchState, UiState,
}; };
use ratatui::crossterm::event::{DisableMouseCapture, EnableMouseCapture}; use ratatui::crossterm::event::{DisableMouseCapture, EnableMouseCapture};
use ratatui::crossterm::terminal::{ use ratatui::crossterm::terminal::{
@ -17,7 +17,9 @@ use ratatui::crossterm::terminal::{
}; };
use ratatui::crossterm::ExecutableCommand; use ratatui::crossterm::ExecutableCommand;
use ratatui::prelude::*; use ratatui::prelude::*;
use ratatui::widgets::Paragraph;
use ratatui::Terminal; use ratatui::Terminal;
use serde_json::Value;
use std::io; use std::io;
use std::io::stdout; use std::io::stdout;
use std::panic::{set_hook, take_hook}; use std::panic::{set_hook, take_hook};
@ -171,9 +173,17 @@ fn ui(frame: &mut Frame, app: &App, state: &mut UiState) {
); );
frame.render_widget(footer(app, state.footer_params()), layout[2]); frame.render_widget(footer(app, state.footer_params()), layout[2]);
} }
UiState::Errors(ErrorState { table_state, .. }) => { UiState::Errors(ErrorLinesState { table_state, .. }) => {
frame.render_stateful_widget(error_list(app), layout[0].union(layout[1]), table_state); frame.render_stateful_widget(error_list(app), layout[0].union(layout[1]), table_state);
frame.render_widget(footer(app, state.footer_params()), layout[2]); frame.render_widget(footer(app, state.footer_params()), layout[2]);
} }
UiState::Error(ErrorState { error, .. }) => {
let pretty =
serde_json::to_string_pretty(&serde_json::from_str::<Value>(&error.line).unwrap())
.unwrap();
let par = Paragraph::new(format!("{:#}\n\n{pretty}", error.err));
frame.render_widget(par, layout[0].union(layout[1]))
}
} }
} }

View file

@ -1,4 +1,5 @@
use crate::app::{App, Filter, LogMatch, EMPTY_FILTER}; use crate::app::{App, Filter, LogMatch, EMPTY_FILTER};
use crate::error::ParseError;
use crate::logfile::logline::{FullLogLine, LogLine}; use crate::logfile::logline::{FullLogLine, LogLine};
use crate::ui::footer::FooterParams; use crate::ui::footer::FooterParams;
use crate::ui::input::{PopMode, UiEvent}; use crate::ui::input::{PopMode, UiEvent};
@ -9,6 +10,7 @@ use derive_more::From;
use ratatui::widgets::TableState; use ratatui::widgets::TableState;
use std::borrow::Cow; use std::borrow::Cow;
use std::iter::once; use std::iter::once;
use std::sync::Arc;
#[derive(Clone, From, PartialEq)] #[derive(Clone, From, PartialEq)]
pub enum UiState<'a> { pub enum UiState<'a> {
@ -16,7 +18,8 @@ pub enum UiState<'a> {
Match(MatchState<'a>), Match(MatchState<'a>),
GroupedLogs(GroupedLogsState<'a>), GroupedLogs(GroupedLogsState<'a>),
Log(LogState<'a>), Log(LogState<'a>),
Errors(ErrorState<'a>), Errors(ErrorLinesState<'a>),
Error(ErrorState<'a>),
Quit, Quit,
} }
@ -159,7 +162,18 @@ impl<'a> GroupedLogsState<'a> {
fn enter(self, selected: usize, app: &'a App<'a>) -> UiState<'a> { fn enter(self, selected: usize, app: &'a App<'a>) -> UiState<'a> {
let log = self.get_selected(selected, app); let log = self.get_selected(selected, app);
let raw_line = app.get_line(log.index).unwrap(); let raw_line = app.get_line(log.index).unwrap();
let full_line = parse_line_full(raw_line).unwrap(); let full_line = match parse_line_full(raw_line) {
Ok(line) => line,
Err(err) => {
return UiState::Error(ErrorState {
error: Arc::new(ParseError {
err,
line: raw_line.into(),
}),
previous: Box::new(self.into()),
});
}
};
let trace_len = if let Some(exception) = &full_line.exception { let trace_len = if let Some(exception) = &full_line.exception {
exception.stack().map(|e| 1 + e.trace.len()).sum() exception.stack().map(|e| 1 + e.trace.len()).sum()
} else { } else {
@ -198,12 +212,12 @@ impl PartialEq for GroupedLogsState<'_> {
} }
#[derive(Clone)] #[derive(Clone)]
pub struct ErrorState<'a> { pub struct ErrorLinesState<'a> {
pub table_state: ScrollbarTableState, pub table_state: ScrollbarTableState,
pub previous: Box<UiState<'a>>, pub previous: Box<UiState<'a>>,
} }
impl PartialEq for ErrorState<'_> { impl PartialEq for ErrorLinesState<'_> {
fn eq(&self, _other: &Self) -> bool { fn eq(&self, _other: &Self) -> bool {
true true
} }
@ -258,6 +272,7 @@ impl<'a> UiState<'a> {
UiState::GroupedLogs(_) => UiPage::Logs, UiState::GroupedLogs(_) => UiPage::Logs,
UiState::Log(_) => UiPage::Log, UiState::Log(_) => UiPage::Log,
UiState::Errors(_) => UiPage::Errors, UiState::Errors(_) => UiPage::Errors,
UiState::Error(_) => UiPage::Error,
} }
} }
@ -373,6 +388,7 @@ impl<'a> UiState<'a> {
UiState::GroupedLogs(_) => UI_HEADER_SIZE + 1, UiState::GroupedLogs(_) => UI_HEADER_SIZE + 1,
UiState::Log(_) => 0, UiState::Log(_) => 0,
UiState::Errors(_) => 0, UiState::Errors(_) => 0,
UiState::Error(_) => 0,
UiState::Quit => 0, UiState::Quit => 0,
} }
} }
@ -422,7 +438,7 @@ impl<'a> UiState<'a> {
let table_state = ScrollbarTableState::new(app.error_lines.len()); let table_state = ScrollbarTableState::new(app.error_lines.len());
( (
true, true,
UiState::Errors(ErrorState { UiState::Errors(ErrorLinesState {
table_state, table_state,
previous: Box::new(state.into()), previous: Box::new(state.into()),
}), }),
@ -513,7 +529,7 @@ impl<'a> UiState<'a> {
UiState::Match(MatchState { previous, .. }) UiState::Match(MatchState { previous, .. })
| UiState::GroupedLogs(GroupedLogsState { previous, .. }) | UiState::GroupedLogs(GroupedLogsState { previous, .. })
| UiState::Log(LogState { previous, .. }) | UiState::Log(LogState { previous, .. })
| UiState::Errors(ErrorState { previous, .. }), | UiState::Errors(ErrorLinesState { previous, .. }),
UiEvent::Back, UiEvent::Back,
) => (true, *previous), ) => (true, *previous),
(state, _) => (false, state), (state, _) => (false, state),
@ -538,4 +554,11 @@ pub enum UiPage {
Logs, Logs,
Log, Log,
Errors, Errors,
Error,
}
#[derive(Clone, PartialEq)]
pub struct ErrorState<'a> {
pub error: Arc<ParseError>,
pub previous: Box<UiState<'a>>,
} }