mirror of
https://codeberg.org/icewind/logsmash.git
synced 2026-06-03 10:04:12 +02:00
show additional log data
This commit is contained in:
parent
43fea7cb16
commit
a7a8bf60da
4 changed files with 101 additions and 9 deletions
|
|
@ -1,7 +1,7 @@
|
||||||
use crate::app::Filter;
|
use crate::app::Filter;
|
||||||
use crate::logfile::LogLineNumber;
|
use crate::logfile::LogLineNumber;
|
||||||
use crate::logs::LogIndex;
|
use crate::logs::LogIndex;
|
||||||
use ahash::AHasher;
|
use ahash::{AHasher, HashMap};
|
||||||
use derive_more::{Display, From};
|
use derive_more::{Display, From};
|
||||||
use logsmash_data::LogLevel;
|
use logsmash_data::LogLevel;
|
||||||
use serde::{Deserialize, Deserializer};
|
use serde::{Deserialize, Deserializer};
|
||||||
|
|
@ -247,6 +247,19 @@ pub struct FullLogLine {
|
||||||
pub user_agent: String,
|
pub user_agent: String,
|
||||||
pub version: TinyAsciiStr<16>,
|
pub version: TinyAsciiStr<16>,
|
||||||
pub exception: Option<FullException>,
|
pub exception: Option<FullException>,
|
||||||
|
#[serde(default)]
|
||||||
|
pub data: HashMap<String, String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FullLogLine {
|
||||||
|
pub fn has_data(&self) -> bool {
|
||||||
|
self.data.keys().any(|key| key.as_str() != "app")
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn data(&self) -> impl Iterator<Item = (&str, &str)> {
|
||||||
|
self.data.iter().map(|(key, value)| (key.as_str(), value.as_str()))
|
||||||
|
.filter(|(key, _)| *key != "app")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Debug, Clone)]
|
#[derive(Deserialize, Debug, Clone)]
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ use crate::logfile::logline::{format_time, FullException, FullLogLine, Trace};
|
||||||
use crate::ui::style::TABLE_HEADER_STYLE;
|
use crate::ui::style::TABLE_HEADER_STYLE;
|
||||||
use crate::ui::table::{ScrollbarTable, ScrollbarTableState};
|
use crate::ui::table::{ScrollbarTable, ScrollbarTableState};
|
||||||
use ratatui::prelude::*;
|
use ratatui::prelude::*;
|
||||||
use ratatui::widgets::{Cell, Paragraph, Row, Wrap};
|
use ratatui::widgets::{Block, BorderType, Borders, Cell, Paragraph, Row, Wrap};
|
||||||
use std::fmt::Write;
|
use std::fmt::Write;
|
||||||
use std::iter::once;
|
use std::iter::once;
|
||||||
|
|
||||||
|
|
@ -13,6 +13,7 @@ pub fn single_log(line: &FullLogLine) -> SingleLog {
|
||||||
pub struct SingleLog<'a> {
|
pub struct SingleLog<'a> {
|
||||||
line: &'a FullLogLine,
|
line: &'a FullLogLine,
|
||||||
path_prefix_length: usize,
|
path_prefix_length: usize,
|
||||||
|
data_length: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> SingleLog<'a> {
|
impl<'a> SingleLog<'a> {
|
||||||
|
|
@ -22,9 +23,11 @@ impl<'a> SingleLog<'a> {
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map(|ex| find_path_prefix_length(ex.trace.iter().map(|t| t.file.as_str())))
|
.map(|ex| find_path_prefix_length(ex.trace.iter().map(|t| t.file.as_str())))
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
|
let data_length = line.data().count();
|
||||||
SingleLog {
|
SingleLog {
|
||||||
line,
|
line,
|
||||||
path_prefix_length,
|
path_prefix_length,
|
||||||
|
data_length,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -58,11 +61,31 @@ impl StatefulWidget for SingleLog<'_> {
|
||||||
|
|
||||||
par.render(layout[0], buf);
|
par.render(layout[0], buf);
|
||||||
|
|
||||||
|
let (exception_area, mut exception_state) = if line.has_data() {
|
||||||
|
let (data_table, height) = render_data(line);
|
||||||
|
let layout = Layout::default()
|
||||||
|
.direction(Direction::Vertical)
|
||||||
|
.constraints(vec![Constraint::Min(height + 2), Constraint::Percentage(100)])
|
||||||
|
.split(layout[1]);
|
||||||
|
|
||||||
|
let block = Block::new()
|
||||||
|
.borders(Borders::BOTTOM.union(Borders::TOP))
|
||||||
|
.border_type(BorderType::Thick)
|
||||||
|
.border_style(TABLE_HEADER_STYLE)
|
||||||
|
.title("Data");
|
||||||
|
|
||||||
|
let (mut data_state, exception_state) = state.split(self.data_length);
|
||||||
|
StatefulWidget::render(data_table.block(block), layout[0], buf, &mut data_state);
|
||||||
|
(layout[1], exception_state)
|
||||||
|
} else {
|
||||||
|
(layout[1], state.clone())
|
||||||
|
};
|
||||||
|
|
||||||
if let Some(exception) = &line.exception {
|
if let Some(exception) = &line.exception {
|
||||||
if line.message.contains(&exception.message) {
|
if line.message.contains(&exception.message) {
|
||||||
StatefulWidget::render(
|
StatefulWidget::render(
|
||||||
render_exception(exception, self.path_prefix_length),
|
render_exception(exception, self.path_prefix_length),
|
||||||
layout[1],
|
exception_area,
|
||||||
buf,
|
buf,
|
||||||
state,
|
state,
|
||||||
);
|
);
|
||||||
|
|
@ -83,17 +106,17 @@ impl StatefulWidget for SingleLog<'_> {
|
||||||
let ex_layout = Layout::default()
|
let ex_layout = Layout::default()
|
||||||
.direction(Direction::Vertical)
|
.direction(Direction::Vertical)
|
||||||
.constraints(vec![
|
.constraints(vec![
|
||||||
Constraint::Min(ex_par.line_count(layout[1].width) as u16 + 1),
|
Constraint::Min(ex_par.line_count(exception_area.width) as u16 + 1),
|
||||||
Constraint::Percentage(100),
|
Constraint::Percentage(100),
|
||||||
])
|
])
|
||||||
.split(layout[1]);
|
.split(exception_area);
|
||||||
|
|
||||||
ex_par.render(ex_layout[0], buf);
|
ex_par.render(ex_layout[0], buf);
|
||||||
StatefulWidget::render(
|
StatefulWidget::render(
|
||||||
render_exception(exception, self.path_prefix_length),
|
render_exception(exception, self.path_prefix_length),
|
||||||
ex_layout[1],
|
ex_layout[1],
|
||||||
buf,
|
buf,
|
||||||
state,
|
&mut exception_state,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -181,3 +204,29 @@ fn find_path_prefix_length<'a, I: Iterator<Item = &'a str>>(paths: I) -> usize {
|
||||||
}
|
}
|
||||||
0
|
0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn render_data(log: &FullLogLine) -> (ScrollbarTable, u16) {
|
||||||
|
let header = [
|
||||||
|
Text::from("Key"),
|
||||||
|
Text::from("Value"),
|
||||||
|
]
|
||||||
|
.into_iter()
|
||||||
|
.map(Cell::from)
|
||||||
|
.collect::<Row>()
|
||||||
|
.style(TABLE_HEADER_STYLE)
|
||||||
|
.height(1);
|
||||||
|
|
||||||
|
|
||||||
|
let max_key_width = log.data().map(|(key, _)| key).map(str::len).max().unwrap_or_default();
|
||||||
|
let lines =log.data().count();
|
||||||
|
|
||||||
|
let rows = log.data()
|
||||||
|
.map(|(key, value)| Row::new([Cell::new(Text::from(key)), Cell::new(Text::from(value))]));
|
||||||
|
|
||||||
|
|
||||||
|
let widths = [
|
||||||
|
Constraint::Min(max_key_width as u16),
|
||||||
|
Constraint::Percentage(100),
|
||||||
|
];
|
||||||
|
(ScrollbarTable::new(rows, widths).header(header), lines as u16 + 1)
|
||||||
|
}
|
||||||
|
|
@ -285,7 +285,9 @@ impl<'a> DistinctLogsState<'a> {
|
||||||
0
|
0
|
||||||
};
|
};
|
||||||
|
|
||||||
let table_state = ScrollbarTableState::new(trace_len);
|
let data_len = full_line.data().count();
|
||||||
|
|
||||||
|
let table_state = ScrollbarTableState::new(trace_len + data_len);
|
||||||
UiState::Log(LogState {
|
UiState::Log(LogState {
|
||||||
log,
|
log,
|
||||||
full_line: Box::new(full_line),
|
full_line: Box::new(full_line),
|
||||||
|
|
|
||||||
|
|
@ -35,13 +35,22 @@ impl<'a> ScrollbarTable<'a> {
|
||||||
self.table = self.table.header(header);
|
self.table = self.table.header(header);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use = "method moves the value of self and returns the modified value"]
|
||||||
|
pub fn block(self, block: Block<'a>) -> Self {
|
||||||
|
ScrollbarTable {
|
||||||
|
table: self.table.block(block),
|
||||||
|
scrollbar: self.scrollbar,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct ScrollbarTableState {
|
pub struct ScrollbarTableState {
|
||||||
count: usize,
|
count: usize,
|
||||||
table: TableState,
|
table: TableState,
|
||||||
scrollbar: ScrollbarState,
|
scrollbar: ScrollbarState,
|
||||||
|
active: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ScrollbarTableState {
|
impl ScrollbarTableState {
|
||||||
|
|
@ -52,6 +61,7 @@ impl ScrollbarTableState {
|
||||||
count,
|
count,
|
||||||
table,
|
table,
|
||||||
scrollbar: ScrollbarState::new(count),
|
scrollbar: ScrollbarState::new(count),
|
||||||
|
active: true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -113,13 +123,31 @@ impl ScrollbarTableState {
|
||||||
self.table.select(Some(selected));
|
self.table.select(Some(selected));
|
||||||
self.scrollbar = self.scrollbar.position(selected);
|
self.scrollbar = self.scrollbar.position(selected);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn split(&self, len: usize) -> (Self, Self) {
|
||||||
|
let mut a = ScrollbarTableState::new(len);
|
||||||
|
let mut b = ScrollbarTableState::new(self.count - len);
|
||||||
|
if self.selected() < len {
|
||||||
|
a.select(self.selected());
|
||||||
|
b.active = false;
|
||||||
|
} else {
|
||||||
|
a.active = false;
|
||||||
|
b.select(self.selected() - len);
|
||||||
|
}
|
||||||
|
(a, b)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl StatefulWidget for ScrollbarTable<'_> {
|
impl StatefulWidget for ScrollbarTable<'_> {
|
||||||
type State = ScrollbarTableState;
|
type State = ScrollbarTableState;
|
||||||
|
|
||||||
fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
|
fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
|
||||||
|
if state.active {
|
||||||
StatefulWidget::render(self.table, area, buf, &mut state.table);
|
StatefulWidget::render(self.table, area, buf, &mut state.table);
|
||||||
|
} else {
|
||||||
|
let mut table_state = TableState::default();
|
||||||
|
StatefulWidget::render(self.table, area, buf, &mut table_state);
|
||||||
|
}
|
||||||
StatefulWidget::render(
|
StatefulWidget::render(
|
||||||
self.scrollbar,
|
self.scrollbar,
|
||||||
area.inner(Margin {
|
area.inner(Margin {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue