borrowed line parsing

This commit is contained in:
Robin Appelman 2024-08-14 16:35:43 +02:00
commit 95e09f0e0c
12 changed files with 61 additions and 62 deletions

4
Cargo.lock generated
View file

@ -876,9 +876,9 @@ dependencies = [
[[package]]
name = "regex"
version = "1.10.5"
version = "1.10.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b91213439dad192326a0d7c6ee3955910425f441d7038e0d6933b0aec5c4517f"
checksum = "4219d74c6b67a3654a9fbebc4b419e22126d13d2f3c4a07ee0cb61ff79a79619"
dependencies = [
"aho-corasick",
"memchr",

View file

@ -103,7 +103,7 @@
releaseMatrix = buildMatrix releaseTargets;
devShells.default = mkShell {
nativeBuildInputs = with pkgs; [msrvToolchain rustc bacon cargo-msrv cargo-insta samply];
nativeBuildInputs = with pkgs; [msrvToolchain rustc bacon cargo-msrv cargo-insta samply hyperfine];
};
}
)

View file

@ -6,20 +6,20 @@ use logsmash_data::StatementList;
use std::collections::BTreeMap;
use time::OffsetDateTime;
pub struct App {
pub struct App<'a> {
pub first_date: OffsetDateTime,
pub last_date: OffsetDateTime,
pub lines: Vec<LogLine>,
pub lines: Vec<LogLine<'a>>,
pub log_statements: StatementList,
pub matches: Vec<LogMatch>,
pub error_count: usize,
pub all: LogMatch,
pub unmatched: LogMatch,
pub log_file: LogFile,
pub log_file: &'a LogFile,
pub error_lines: Vec<(String, serde_json::Error)>,
}
impl App {
impl<'a> App<'a> {
pub fn match_lines(&self) -> usize {
let unmatched_line_count = if self.unmatched.lines.is_empty() {
0
@ -29,7 +29,7 @@ impl App {
self.matches.len() + 1 + unmatched_line_count
}
pub fn get_line(&self, index: usize) -> Option<&str> {
pub fn get_line(&self, index: usize) -> Option<&'a str> {
self.log_file.nth(index)
}
}

View file

@ -2,6 +2,7 @@ use ahash::AHasher;
use logsmash_data::LogLevel;
use serde::Deserialize;
use serde_json::Value;
use std::borrow::Cow;
use std::fmt::{Display, Formatter};
use std::hash::{Hash, Hasher};
use time::format_description::well_known::iso8601::{Config, EncodedConfig, TimePrecision};
@ -16,14 +17,14 @@ pub const TIME_FORMAT: EncodedConfig = Config::DEFAULT
.encode();
#[derive(Deserialize)]
pub struct LogLine {
pub struct LogLine<'a> {
#[serde(default)]
pub index: usize,
pub version: TinyAsciiStr<16>,
pub version: &'a str,
pub level: LogLevel,
pub message: String,
pub exception: Option<Exception>,
pub app: TinyAsciiStr<32>,
pub message: Cow<'a, str>,
pub exception: Option<Exception<'a>>,
pub app: &'a str,
#[serde(with = "date")]
pub time: OffsetDateTime,
}
@ -76,21 +77,10 @@ mod date {
return Ok(date);
}
}
return Err(D::Error::custom(format_args!(
Err(D::Error::custom(format_args!(
"Failed to parse date: {}",
str
)));
}
}
impl LogLine {
pub fn identity(&self) -> u64 {
let mut hasher = AHasher::default();
self.message.hash(&mut hasher);
self.level.hash(&mut hasher);
self.exception.hash(&mut hasher);
self.app.hash(&mut hasher);
hasher.finish()
)))
}
}
@ -99,15 +89,24 @@ pub fn format_time(time: OffsetDateTime) -> String {
.unwrap_or_else(|_| "Invalid time".into())
}
impl LogLine {
pub fn display(&self) -> String {
impl<'a> LogLine<'a> {
pub fn identity(&self) -> u64 {
let mut hasher = AHasher::default();
self.message.hash(&mut hasher);
self.level.hash(&mut hasher);
self.exception.hash(&mut hasher);
self.app.hash(&mut hasher);
hasher.finish()
}
pub fn display(&'a self) -> Cow<'a, str> {
if let Some(exception) = self.exception.as_ref() {
format!(
Cow::Owned(format!(
"{}{}{}({}) - {} line {}",
if self.message.starts_with("Exception thrown:") {
""
} else {
self.message.as_str()
&self.message
},
if self.message.starts_with("Exception thrown:") {
""
@ -118,23 +117,23 @@ impl LogLine {
exception.message,
exception.file,
exception.line
)
))
} else {
self.message.clone()
Cow::Borrowed(&self.message)
}
}
}
#[derive(Deserialize, Debug)]
#[serde(rename_all = "PascalCase")]
pub struct Exception {
pub message: String,
pub exception: String,
pub file: String,
pub struct Exception<'a> {
pub message: Cow<'a, str>,
pub exception: Cow<'a, str>,
pub file: Cow<'a, str>,
pub line: usize,
}
impl Hash for Exception {
impl Hash for Exception<'_> {
fn hash<H: Hasher>(&self, state: &mut H) {
self.message.hash(state);
self.exception.hash(state);

View file

@ -143,7 +143,7 @@ fn main() -> MainResult {
unmatched,
all,
error_count,
log_file,
log_file: &log_file,
};
if args.profile {

View file

@ -57,7 +57,7 @@ impl Matcher {
if let Some(exception) = &log.exception {
for log_match in self.matches.iter() {
if log_match.line == exception.line
&& log_match.exception == Some(exception.exception.as_str())
&& log_match.exception == Some(exception.exception.as_ref())
&& exception.file.ends_with(log_match.path)
{
return Some(MatchResult::Single(log_match.index));
@ -68,7 +68,7 @@ impl Matcher {
for log_match in self.matches.iter() {
if log_match.has_meaningful_message
&& log.level.matches(log_match.level)
&& log_match.pattern.is_match(log.message.as_str())
&& log_match.pattern.is_match(&log.message)
&& log_match.pattern_length >= best_length
{
if log_match.pattern_length > best_length {
@ -147,7 +147,6 @@ impl MatchResult {
fn test_matcher() {
use crate::logline::Exception;
use time::OffsetDateTime;
use tinystr::TinyAsciiStr;
const STATEMENTS: &[LoggingStatement] = &[
LoggingStatement {
@ -195,8 +194,8 @@ fn test_matcher() {
assert_eq!(
Some(MatchResult::Single(0)),
matcher.match_log(&LogLine {
version: TinyAsciiStr::from_str("29").unwrap(),
app: TinyAsciiStr::from_str("core").unwrap(),
version: "29",
app: "core",
level: LogLevel::Error,
message: "Not allowed to rename a shared album".into(),
exception: None,
@ -207,8 +206,8 @@ fn test_matcher() {
assert_eq!(
Some(MatchResult::List(vec![3, 4])),
matcher.match_log(&LogLine {
version: TinyAsciiStr::from_str("29").unwrap(),
app: TinyAsciiStr::from_str("core").unwrap(),
version: "29",
app: "core",
level: LogLevel::Error,
message: "Not allowed to rename an album".into(),
exception: None,
@ -219,8 +218,8 @@ fn test_matcher() {
assert_eq!(
Some(MatchResult::Single(1)),
matcher.match_log(&LogLine {
version: TinyAsciiStr::from_str("29").unwrap(),
app: TinyAsciiStr::from_str("core").unwrap(),
version: "29",
app: "core",
level: LogLevel::Error,
message: "You are not allowed to edit link shares that you don't own".into(),
exception: None,
@ -231,8 +230,8 @@ fn test_matcher() {
assert_eq!(
None,
matcher.match_log(&LogLine {
version: TinyAsciiStr::from_str("29").unwrap(),
app: TinyAsciiStr::from_str("core").unwrap(),
version: "29",
app: "core",
level: LogLevel::Info,
message: "You are not allowed to edit link shares that you don't own".into(),
exception: None,
@ -244,8 +243,8 @@ fn test_matcher() {
Some(MatchResult::Single(2)),
matcher.match_log(
&LogLine {
version: TinyAsciiStr::from_str("29").unwrap(),
app: TinyAsciiStr::from_str("core").unwrap(),
version: "29",
app: "core",
level: LogLevel::Error,
message: "Unsupported query value for mimetype: %/text, only values in the format \"mime/type\" or \"mime/%\" are supported".into(),
exception: None,
@ -258,8 +257,8 @@ fn test_matcher() {
Some(MatchResult::Single(4)),
matcher.match_log(
&LogLine {
version: TinyAsciiStr::from_str("29").unwrap(),
app: TinyAsciiStr::from_str("core").unwrap(),
version: "29",
app: "core",
level: LogLevel::Error,
message: "Unsupported query value for mimetype: %/text, only values in the format \"mime/type\" or \"mime/%\" are supported".into(),
exception: Some(Exception {

View file

@ -5,7 +5,7 @@ use ratatui::layout::Constraint;
use ratatui::text::Text;
use ratatui::widgets::{Cell, Row};
pub fn error_list(app: &App) -> ScrollbarTable {
pub fn error_list<'a>(app: &'a App<'a>) -> ScrollbarTable<'a> {
let header = [Text::from("Error"), Text::from("Line")]
.into_iter()
.map(Cell::from)

View file

@ -3,9 +3,10 @@ use crate::ui::state::UiPage;
use ratatui::layout::Constraint;
use ratatui::prelude::Style;
use ratatui::style::palette::tailwind;
use ratatui::text::Text;
use ratatui::widgets::{Row, Table};
pub fn footer(app: &App, page: UiPage) -> Table {
pub fn footer<'a>(app: &App<'a>, page: UiPage) -> Table<'a> {
let footer_style = Style::default()
.bg(tailwind::BLACK)
.fg(tailwind::GREEN.c600);
@ -18,9 +19,9 @@ pub fn footer(app: &App, page: UiPage) -> Table {
Table::new(
[Row::new([
help(page).to_string(),
format!("{} unmatched items", app.unmatched.lines.len()),
format!("{} parse errors", app.error_count),
Text::from(help(page)),
Text::from(format!("{} unmatched items", app.unmatched.lines.len())),
Text::from(format!("{} parse errors", app.error_count)),
])],
widths,
)

View file

@ -7,7 +7,7 @@ use ratatui::widgets::{Cell, Row};
use std::fmt::Write;
use std::iter::{empty, once};
pub fn match_list(app: &App) -> ScrollbarTable {
pub fn match_list<'a>(app: &'a App<'a>) -> ScrollbarTable<'a> {
let header = [
Text::from("Statement"),
Text::from("File"),

View file

@ -6,7 +6,7 @@ use ratatui::layout::{Alignment, Constraint};
use ratatui::text::Text;
use ratatui::widgets::{Cell, Row};
pub fn raw_logs<'a>(app: &'a App, lines: &[usize]) -> ScrollbarTable<'a> {
pub fn raw_logs<'a>(app: &'a App<'a>, lines: &[usize]) -> ScrollbarTable<'a> {
let lines = lines.iter().copied().map(|i| &app.lines[i]);
let header = [
Text::from("Level"),
@ -29,10 +29,10 @@ pub fn raw_logs<'a>(app: &'a App, lines: &[usize]) -> ScrollbarTable<'a> {
ScrollbarTable::new(lines.map(log_row), widths).header(header)
}
fn log_row(line: &LogLine) -> Row {
fn log_row<'a>(line: &'a LogLine<'a>) -> Row<'a> {
Row::new([
Text::from(line.level.as_str()),
Text::from(line.app.as_str()),
Text::from(line.app),
Text::from(line.display()),
Text::from(format_time(line.time)).alignment(Alignment::Right),
])

View file

@ -5,7 +5,7 @@ use ratatui::layout::Constraint;
use ratatui::text::Text;
use ratatui::widgets::{Cell, Row};
pub fn grouped_lines<'a>(app: &'a App, log_match: &'a LogMatch) -> ScrollbarTable<'a> {
pub fn grouped_lines<'a>(app: &'a App<'a>, log_match: &'a LogMatch) -> ScrollbarTable<'a> {
let grouped = &log_match.grouped;
let header = [
Text::from("Level"),
@ -35,7 +35,7 @@ fn group_row<'a>(app: &'a App, group: &'a GroupedLines) -> Row<'a> {
Row::new([
Text::from(line.level.as_str()),
Text::from(line.app.as_str()),
Text::from(line.app),
Text::from(line.display()),
Text::from(group.sparkline.as_str()),
Text::from(group.len().to_string()),

View file

@ -61,7 +61,7 @@ impl<'a> LogsState<'a> {
#[derive(Clone)]
pub struct LogState<'a> {
pub trace_len: usize,
pub log: &'a LogLine,
pub log: &'a LogLine<'a>,
pub full_line: FullLogLine,
pub table_state: ScrollbarTableState,
pub previous: Box<UiState<'a>>,
@ -97,7 +97,7 @@ impl<'a> UiState<'a> {
}
}
pub fn process(self, event: UiEvent, app: &'a App) -> (bool, UiState) {
pub fn process(self, event: UiEvent, app: &'a App<'a>) -> (bool, UiState) {
match (self, event) {
(UiState::Quit, _) => (true, UiState::Quit),
(_, UiEvent::Quit) => (true, UiState::Quit),