exception matching

This commit is contained in:
Robin Appelman 2024-07-22 22:56:40 +02:00
commit 4bf687461f
7 changed files with 185 additions and 23 deletions

View file

@ -1,11 +1,13 @@
use cloud_log_analyser_data::LogLevel;
use serde::Deserialize;
use std::borrow::Cow;
#[derive(Deserialize)]
pub struct LogLine<'a> {
pub version: &'a str,
pub level: i64,
pub level: LogLevel,
pub message: Cow<'a, str>,
pub exception: Option<Exception<'a>>,
}
impl LogLine<'_> {
@ -18,3 +20,12 @@ impl LogLine<'_> {
major.parse().ok()
}
}
#[derive(Deserialize)]
#[serde(rename_all = "PascalCase")]
pub struct Exception<'a> {
pub exception: Cow<'a, str>,
pub file: Cow<'a, str>,
pub line: usize,
pub previous: Option<Box<Exception<'a>>>,
}

View file

@ -50,7 +50,7 @@ fn main() -> MainResult {
continue;
}
};
if let Some(index) = matcher.match_log(parsed.level.into(), parsed.message.as_ref()) {
if let Some(index) = matcher.match_log(&parsed) {
counts.entry(index).or_default().add_assign(1);
} else {
unmatched += 1;
@ -62,13 +62,24 @@ fn main() -> MainResult {
counts.reverse();
for (index, count) in counts {
let statement = &statements[index];
println!(
"{}: {} line {}: {}",
statement.message(),
statement.path,
statement.line,
count
);
if let Some(exception) = statement.exception {
println!(
"{}({}): {} line {}: {}",
exception,
statement.message(),
statement.path,
statement.line,
count
);
} else {
println!(
"{}: {} line {}: {}",
statement.message(),
statement.path,
statement.line,
count
);
}
}
if unmatched > 0 {
eprintln!("{unmatched} lines couldn't be matched");

View file

@ -1,3 +1,4 @@
use crate::logline::LogLine;
use cloud_log_analyser_data::{LogLevel, LoggingStatement};
use regex::Regex;
@ -7,6 +8,8 @@ pub struct LogMatch {
pattern_length: usize,
has_meaningful_message: bool,
exception: Option<&'static str>,
path: &'static str,
line: usize,
}
impl LogMatch {
@ -19,6 +22,8 @@ impl LogMatch {
.regex
.contains(|c: char| c.is_ascii_alphanumeric()),
exception: statement.exception,
path: statement.path,
line: statement.line,
}
}
}
@ -34,16 +39,25 @@ impl Matcher {
}
}
pub fn match_log(&self, level: LogLevel, message: &str) -> Option<usize> {
pub fn match_log(&self, log: &LogLine) -> Option<usize> {
let mut best_match = None;
let mut best_length = 0;
if let Some(exception) = &log.exception {
for (i, log_match) in self.matches.iter().enumerate() {
if log_match.line == exception.line
&& log_match.exception.as_deref() == Some(exception.exception.as_ref())
&& log_match.path == exception.file.as_ref()
{
return Some(i);
}
}
}
for (i, log_match) in self.matches.iter().enumerate() {
if log_match.has_meaningful_message {
if (log_match.level == level
|| log_match.level == LogLevel::Exception
|| level == LogLevel::Unknown)
&& log_match.pattern.is_match(message)
if log.level.matches(log_match.level)
&& log_match.pattern.is_match(log.message.as_ref())
&& log_match.pattern_length > best_length
{
best_match = Some(i);
@ -52,12 +66,16 @@ impl Matcher {
}
}
// todo: handle translated log messages
best_match
}
}
#[test]
fn test_matcher() {
use crate::logline::Exception;
let statements = &[
LoggingStatement {
line: 68,
@ -65,6 +83,7 @@ fn test_matcher() {
path: "foo",
placeholders: &[],
regex: "^Not allowed to rename a shared album$",
exception: None,
},
LoggingStatement {
line: 69,
@ -72,6 +91,7 @@ fn test_matcher() {
path: "bar",
placeholders: &[],
regex: "^You are not allowed to edit link shares that you don't own$",
exception: None,
},
LoggingStatement {
line: 69,
@ -79,6 +99,7 @@ fn test_matcher() {
path: "asd",
placeholders: &["$mimeType"],
regex: r#"^Unsupported query value for mimetype: (.*), only values in the format "mime/type" or "mime/%" are supported$"#,
exception: None,
},
LoggingStatement {
line: 68,
@ -86,30 +107,79 @@ fn test_matcher() {
path: "short",
placeholders: &["$path"],
regex: "^Not allowed to rename (.*)$",
exception: None,
},
LoggingStatement {
line: 68,
level: LogLevel::Exception,
path: "short",
placeholders: &["$path"],
regex: "^Not allowed to rename (.*)$",
exception: "Bar\\FooException".into(),
},
];
let matcher = Matcher::new(statements);
assert_eq!(
Some(0),
matcher.match_log(LogLevel::Error, "Not allowed to rename a shared album")
matcher.match_log(&LogLine {
version: "29",
level: LogLevel::Error,
message: "Not allowed to rename a shared album".into(),
exception: None,
})
);
assert_eq!(
Some(3),
matcher.match_log(LogLevel::Error, "Not allowed to rename an album")
matcher.match_log(&LogLine {
version: "29",
level: LogLevel::Error,
message: "Not allowed to rename an album".into(),
exception: None,
})
);
assert_eq!(
Some(1),
matcher.match_log(
LogLevel::Error,
"You are not allowed to edit link shares that you don't own"
)
matcher.match_log(&LogLine {
version: "29",
level: LogLevel::Error,
message: "You are not allowed to edit link shares that you don't own".into(),
exception: None,
})
);
assert_eq!(
None,
matcher.match_log(&LogLine {
version: "29",
level: LogLevel::Info,
message: "You are not allowed to edit link shares that you don't own".into(),
exception: None,
})
);
assert_eq!(
Some(2),
matcher.match_log(
LogLevel::Info,
"You are not allowed to edit link shares that you don't own"
&LogLine {
version: "29",
level: LogLevel::Error,
message: "Unsupported query value for mimetype: %/text, only values in the format \"mime/type\" or \"mime/%\" are supported".into(),
exception: None,
}
)
);
assert_eq!(
Some(4),
matcher.match_log(
&LogLine {
version: "29",
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 {
exception: "Bar\\FooException".into(),
file: "short".into(),
line: 68,
previous: None,
}),
}
)
);
assert_eq!(Some(2), matcher.match_log(LogLevel::Error, "Unsupported query value for mimetype: %/text, only values in the format \"mime/type\" or \"mime/%\" are supported"));
}