remove logging statement indirection

This commit is contained in:
Robin Appelman 2025-08-09 17:24:54 +02:00
commit ea695e8460
8 changed files with 120 additions and 126 deletions

View file

@ -24,15 +24,11 @@ impl StatementList {
StatementList { statements }
}
pub fn iter(
&self,
) -> impl Iterator<Item = (LogStatementIndex, &'static LoggingStatement)> + Send + '_ {
self.statements
.iter()
.copied()
.flat_map(|(_, list)| list.iter())
.enumerate()
.map(|(index, statement)| (LogStatementIndex(index), statement))
pub fn iter(&self) -> impl Iterator<Item = LoggingStatementWithPathPrefix> + Send + '_ {
self.statements.iter().copied().flat_map(|(prefix, list)| {
list.iter()
.map(|statement| statement.with_path_prefix(prefix))
})
}
pub fn get(&self, mut index: LogStatementIndex) -> Option<LoggingStatementWithPathPrefix> {

View file

@ -1,5 +1,7 @@
use serde::Deserialize;
use std::cmp::Ordering;
use std::fmt::{Display, Formatter};
use std::hash::{Hash, Hasher};
#[derive(Debug, Default, PartialEq, Clone, Copy, Deserialize, Hash, PartialOrd, Ord, Eq)]
#[serde(from = "i64")]
@ -57,7 +59,7 @@ impl LogLevel {
}
}
#[derive(Debug, PartialEq, Clone)]
#[derive(Debug, PartialEq, Clone, Eq, Ord)]
pub struct LoggingStatement {
pub level: LogLevel,
pub path: &'static str,
@ -68,24 +70,31 @@ pub struct LoggingStatement {
pub has_meaningful_message: bool,
}
impl PartialOrd for LoggingStatement {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
(self.path, self.line).partial_cmp(&(other.path, other.line))
}
}
impl Hash for LoggingStatement {
fn hash<H: Hasher>(&self, state: &mut H) {
(self.path, self.line).hash(state)
}
}
impl LoggingStatement {
pub fn with_path_prefix(&self, path_prefix: &'static str) -> LoggingStatementWithPathPrefix {
pub fn with_path_prefix(
&'static self,
path_prefix: &'static str,
) -> LoggingStatementWithPathPrefix {
LoggingStatementWithPathPrefix {
level: self.level,
path_prefix,
path: self.path,
line: self.line,
placeholders: self.placeholders,
exception: self.exception,
pattern: self.pattern,
has_meaningful_message: self.has_meaningful_message,
statement: self,
}
}
pub fn message(&self) -> impl Display + '_ {
LoggingMessage {
message: self.clone(),
}
LoggingMessage { message: &self }
}
pub fn pattern_len(&self) -> usize {
@ -93,50 +102,34 @@ impl LoggingStatement {
}
}
#[derive(Debug, PartialEq, Clone)]
#[derive(Debug, PartialEq, Clone, Eq, Ord, PartialOrd, Hash)]
pub struct LoggingStatementWithPathPrefix {
pub level: LogLevel,
pub statement: &'static LoggingStatement,
pub path_prefix: &'static str,
pub path: &'static str,
pub line: usize,
pub placeholders: &'static [&'static str],
pub exception: Option<&'static str>,
pub pattern: &'static str,
pub has_meaningful_message: bool,
}
impl From<&LoggingStatementWithPathPrefix> for LoggingStatement {
fn from(value: &LoggingStatementWithPathPrefix) -> Self {
LoggingStatement {
level: value.level,
path: value.path,
line: value.line,
placeholders: value.placeholders,
exception: value.exception,
pattern: value.pattern,
has_meaningful_message: value.has_meaningful_message,
}
}
}
impl LoggingStatementWithPathPrefix {
fn raw_message(&self) -> LoggingMessage {
fn raw_message(&self) -> LoggingMessage<'static> {
LoggingMessage {
message: self.into(),
message: self.statement,
}
}
pub fn path(&self) -> impl Display {
LoggingStatementPath {
path_prefix: self.path_prefix,
path: self.path,
path: self.statement.path,
}
}
pub fn line(&self) -> usize {
self.statement.line
}
pub fn message(&self) -> impl Display {
LoggingStatementMessage {
message: self.raw_message(),
exception: self.exception,
exception: self.statement.exception,
}
}
}
@ -153,7 +146,7 @@ impl Display for LoggingStatementPath {
}
struct LoggingStatementMessage {
pub message: LoggingMessage,
pub message: LoggingMessage<'static>,
pub exception: Option<&'static str>,
}
@ -174,16 +167,16 @@ impl Display for LoggingStatementWithPathPrefix {
"«{}» {} line {}",
self.raw_message(),
self.path(),
self.line
self.statement.line
)
}
}
struct LoggingMessage {
message: LoggingStatement,
struct LoggingMessage<'a> {
message: &'a LoggingStatement,
}
impl Display for LoggingMessage {
impl Display for LoggingMessage<'_> {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
if self.message.pattern.is_empty() {
return Ok(());

View file

@ -1,3 +1,4 @@
use crate::grouping::group_lines_by;
use crate::logfile::{LogFile, LogLine, LogLineNumber};
use crate::logs::ParsedLogs;
use crate::matcher::MatchResult;
@ -6,7 +7,6 @@ use logsmash_data::{LoggingStatementWithPathPrefix, StatementList};
use regex::{escape, Regex, RegexBuilder};
use serde_json::Error as JsonError;
use std::cell::OnceCell;
use std::collections::BTreeMap;
use std::fmt::Display;
use time::OffsetDateTime;
@ -64,7 +64,7 @@ pub struct LogMatch<'logs> {
impl<'logs> LogMatch<'logs> {
pub fn new(result: Option<MatchResult>, lines: Vec<&'logs LogLine<'logs>>) -> Self {
let count = lines.len();
let grouped = group_lines(lines.iter().copied());
let grouped = group_lines_by(lines.iter().copied(), LogLine::identity);
let all = LineSet::new(lines);
LogMatch {
@ -96,33 +96,31 @@ impl<'logs> LogMatch<'logs> {
}
pub fn row_count(&self) -> usize {
self.result.as_ref().map(|res| res.len()).unwrap_or(1)
self.result.as_ref().map(|res| res.count()).unwrap_or(1)
}
pub fn statements<'a>(
&'a self,
app: &'a App,
) -> impl Iterator<Item = LoggingStatementWithPathPrefix> + 'a {
self.result
.iter()
.flat_map(|res| res.iter())
.filter_map(|index| app.log_statements.get(index))
) -> impl Iterator<Item = &'a LoggingStatementWithPathPrefix> + use<'a> {
self.result.iter().flat_map(|res| res.iter())
}
pub fn matches(&self, app: &App, filter: &Filter) -> bool {
pub fn matches(&self, filter: &Filter) -> bool {
if filter.is_empty() {
return true;
}
self.statements(app).any(|statement| {
self.statements().any(|statement| {
filter.parts().all(|filter_part| {
filter_part.is_match(statement.pattern)
|| filter_part.is_match(statement.path)
filter_part.is_match(statement.statement.pattern)
|| filter_part.is_match(statement.statement.path)
|| filter_part.is_match(statement.path_prefix)
|| statement
.statement
.placeholders
.iter()
.any(|placeholder| filter_part.is_match(placeholder))
|| statement
.statement
.exception
.filter(|exception| filter_part.is_match(exception))
.is_some()
@ -135,21 +133,6 @@ impl<'logs> LogMatch<'logs> {
}
}
fn group_lines<'logs, I: Iterator<Item = &'logs LogLine<'logs>>>(
indices: I,
) -> Vec<LineSet<'logs>> {
let mut map: BTreeMap<u64, Vec<&'logs LogLine<'logs>>> = BTreeMap::new();
for line in indices {
map.entry(line.identity()).or_default().push(line);
}
let mut list: Vec<_> = map.into_values().map(LineSet::new).collect();
list.sort_by_key(|list| list.len());
list.reverse();
list
}
pub struct LineSet<'logs> {
pub lines: Vec<&'logs LogLine<'logs>>,
pub histogram: OnceCell<TimeGraph>,

21
src/grouping.rs Normal file
View file

@ -0,0 +1,21 @@
use crate::app::LineSet;
use crate::logfile::LogLine;
use std::collections::BTreeMap;
pub fn group_lines_by<'logs, I, F, K>(indices: I, f: F) -> Vec<LineSet<'logs>>
where
I: Iterator<Item = &'logs LogLine<'logs>>,
K: Ord,
F: Fn(&'logs LogLine<'logs>) -> K,
{
let mut map: BTreeMap<K, Vec<&'logs LogLine<'logs>>> = BTreeMap::new();
for line in indices {
map.entry(f(line)).or_default().push(line);
}
let mut list: Vec<_> = map.into_values().map(LineSet::new).collect();
list.sort_by_key(|list| list.len());
list.reverse();
list
}

View file

@ -22,6 +22,7 @@ use std::sync::RwLock;
mod app;
mod error;
pub mod grouping;
mod logfile;
mod logs;
mod matcher;

View file

@ -1,7 +1,7 @@
use crate::logfile::logline::{Exception, LogLine};
use crate::logfile::LineNumber;
use itertools::Either;
use logsmash_data::{LogLevel, LogStatementIndex, LoggingStatement, StatementList};
use logsmash_data::{LogLevel, LoggingStatementWithPathPrefix, StatementList};
use std::hash::{Hash, Hasher};
use std::iter::once;
use std::ops::Range;
@ -13,22 +13,22 @@ pub struct LogMatch {
exception: Option<&'static str>,
path: &'static str,
line: LineNumber,
index: LogStatementIndex,
statement: LoggingStatementWithPathPrefix,
}
impl LogMatch {
pub fn new(index: LogStatementIndex, statement: &LoggingStatement) -> LogMatch {
pub fn new(statement: LoggingStatementWithPathPrefix) -> LogMatch {
LogMatch {
level: statement.level,
pattern: if statement.has_meaningful_message {
statement.pattern
level: statement.statement.level,
pattern: if statement.statement.has_meaningful_message {
statement.statement.pattern
} else {
""
},
exception: statement.exception,
path: statement.path,
line: statement.line.into(),
index,
exception: statement.statement.exception,
path: statement.statement.path,
line: statement.statement.line.into(),
statement,
}
}
@ -44,10 +44,7 @@ pub struct Matcher {
impl Matcher {
pub fn new(statements: &StatementList) -> Matcher {
let mut matches: Vec<_> = statements
.iter()
.map(|(index, statement)| LogMatch::new(index, statement))
.collect();
let mut matches: Vec<_> = statements.iter().map(LogMatch::new).collect();
matches.sort_by(|a, b| {
// sort first by level, then by longest pattern
a.level
@ -106,13 +103,13 @@ impl Matcher {
best_length = log_match.pattern_len();
best_match = Some(match best_match {
Some(MatchResult::Single(res)) => {
MatchResult::List(vec![res, log_match.index])
MatchResult::List(vec![res, log_match.statement.clone()])
}
Some(MatchResult::List(mut list)) => {
list.push(log_match.index);
list.push(log_match.statement.clone());
MatchResult::List(list)
}
None => MatchResult::Single(log_match.index),
None => MatchResult::Single(log_match.statement.clone()),
});
}
}
@ -123,13 +120,13 @@ impl Matcher {
best_match
}
fn match_exception(&self, exception: &Exception) -> Option<LogStatementIndex> {
fn match_exception(&self, exception: &Exception) -> Option<LoggingStatementWithPathPrefix> {
for log_match in self.matches.iter() {
if log_match.line == exception.line
&& log_match.exception == Some(exception.exception.as_ref())
&& exception.file.ends_with(log_match.path)
{
return Some(log_match.index);
return Some(log_match.statement.clone());
}
}
None
@ -148,8 +145,8 @@ pub fn match_single(pattern: &str, text: &str) -> bool {
#[derive(Debug, Clone, Eq)]
pub enum MatchResult {
Single(LogStatementIndex),
List(Vec<LogStatementIndex>),
Single(LoggingStatementWithPathPrefix),
List(Vec<LoggingStatementWithPathPrefix>),
}
impl PartialEq for MatchResult {
@ -182,30 +179,30 @@ impl Hash for MatchResult {
}
impl MatchResult {
pub fn len(&self) -> usize {
pub fn iter(&self) -> impl Iterator<Item = &LoggingStatementWithPathPrefix> + '_ {
match self {
MatchResult::Single(statement) => Either::Left(once(statement)),
MatchResult::List(list) => Either::Right(list.iter()),
}
}
pub fn count(&self) -> usize {
match self {
MatchResult::Single(_) => 1,
MatchResult::List(list) => list.len(),
}
}
pub fn iter(&self) -> impl Iterator<Item = LogStatementIndex> + '_ {
match self {
MatchResult::Single(index) => Either::Left(once(*index)),
MatchResult::List(list) => Either::Right(list.iter().copied()),
}
impl From<LoggingStatementWithPathPrefix> for MatchResult {
fn from(value: LoggingStatementWithPathPrefix) -> Self {
MatchResult::Single(value)
}
}
impl From<usize> for MatchResult {
fn from(value: usize) -> Self {
MatchResult::Single(LogStatementIndex::from(value))
}
}
impl From<Vec<usize>> for MatchResult {
fn from(value: Vec<usize>) -> Self {
MatchResult::List(value.into_iter().map(LogStatementIndex::from).collect())
impl From<Vec<LoggingStatementWithPathPrefix>> for MatchResult {
fn from(value: Vec<LoggingStatementWithPathPrefix>) -> Self {
MatchResult::List(value)
}
}
@ -278,6 +275,7 @@ fn test_matcher() {
use crate::logfile::logline::Exception;
use crate::logfile::LogLineNumber;
use crate::logs::LogIndex;
use logsmash_data::LoggingStatement;
use std::str::FromStr;
use time::OffsetDateTime;
use tinystr::TinyAsciiStr;
@ -357,7 +355,7 @@ fn test_matcher() {
let matcher = Matcher::new(&StatementList::new(vec![("", STATEMENTS)]));
assert_eq!(
Some(MatchResult::from(0)),
Some(MatchResult::from(STATEMENTS[0].with_path_prefix(""))),
matcher.match_log(&LogLine {
version: "29",
app: "core".into(),
@ -367,7 +365,10 @@ fn test_matcher() {
})
);
assert_eq!(
Some(MatchResult::from(vec![3, 4])),
Some(MatchResult::from(vec![
STATEMENTS[3].with_path_prefix(""),
STATEMENTS[4].with_path_prefix("")
])),
matcher.match_log(&LogLine {
version: "29",
app: "core".into(),
@ -377,7 +378,7 @@ fn test_matcher() {
})
);
assert_eq!(
Some(MatchResult::from(1)),
Some(MatchResult::from(STATEMENTS[1].with_path_prefix(""))),
matcher.match_log(&LogLine {
version: "29",
app: "core".into(),
@ -397,7 +398,7 @@ fn test_matcher() {
})
);
assert_eq!(
Some(MatchResult::from(2)),
Some(MatchResult::from(STATEMENTS[2].with_path_prefix(""))),
matcher.match_log(
&LogLine {
version: "29",
@ -409,7 +410,7 @@ fn test_matcher() {
)
);
assert_eq!(
Some(MatchResult::from(4)),
Some(MatchResult::from(STATEMENTS[4].with_path_prefix(""))),
matcher.match_log(
&LogLine {
version: "29",
@ -427,7 +428,7 @@ fn test_matcher() {
)
);
assert_eq!(
Some(MatchResult::from(5)),
Some(MatchResult::from(STATEMENTS[5].with_path_prefix(""))),
matcher.match_log(&LogLine {
version: "29",
app: "core".into(),

View file

@ -41,7 +41,7 @@ pub fn match_list<'a>(app: &'a App<'a>, filter: &Filter) -> ScrollbarTable<'a> {
.chain(
app.matches
.iter()
.filter(|result| result.matches(app, filter))
.filter(|result| result.matches(filter))
.map(|result| log_row(result, app, "")),
)
.chain(unmatched),
@ -55,11 +55,10 @@ fn log_row<'a>(result: &'a LogMatch, app: &'a App, name: &'static str) -> Row<'a
let mut message = String::new();
let mut paths = String::new();
let mut lines = String::new();
for index in match_result.iter() {
let statement = app.log_statements.get(index).expect("invalid match index");
for statement in match_result.iter() {
writeln!(&mut message, "{}", statement.message()).ok();
writeln!(&mut paths, "{}", statement.path()).ok();
writeln!(&mut lines, "{}", statement.line).ok();
writeln!(&mut lines, "{}", statement.line()).ok();
}
Row::new([
Text::from(message),
@ -68,7 +67,7 @@ fn log_row<'a>(result: &'a LogMatch, app: &'a App, name: &'static str) -> Row<'a
Text::from(result.sparkline(app)),
Text::from(result.count().to_string()),
])
.height(match_result.len() as u16)
.height(match_result.count() as u16)
} else {
Row::new([
Text::from(name),

View file

@ -51,7 +51,7 @@ impl<'a> MatchListState<'a> {
} else {
app.matches
.iter()
.filter(|log_match| log_match.matches(app, &self.filter))
.filter(|log_match| log_match.matches(&self.filter))
.nth(selected - 1)
.unwrap_or(&app.unmatched)
}
@ -356,7 +356,7 @@ impl<'a> UiState<'a> {
let match_row_counts = app
.matches
.iter()
.filter(|m| m.matches(app, filter))
.filter(|m| m.matches(filter))
.map(|m| m.row_count());
for (index, row_count) in once(1)
.chain(match_row_counts)