mirror of
https://codeberg.org/icewind/logsmash.git
synced 2026-06-03 10:04:12 +02:00
exception matching
This commit is contained in:
parent
28c93c935c
commit
4bf687461f
7 changed files with 185 additions and 23 deletions
3
Cargo.lock
generated
3
Cargo.lock
generated
|
|
@ -167,6 +167,9 @@ dependencies = [
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cloud-log-analyser-data"
|
name = "cloud-log-analyser-data"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "constant_time_eq"
|
name = "constant_time_eq"
|
||||||
|
|
|
||||||
58
data/Cargo.lock
generated
58
data/Cargo.lock
generated
|
|
@ -5,3 +5,61 @@ version = 3
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cloud-log-analyser-data"
|
name = "cloud-log-analyser-data"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "proc-macro2"
|
||||||
|
version = "1.0.86"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77"
|
||||||
|
dependencies = [
|
||||||
|
"unicode-ident",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "quote"
|
||||||
|
version = "1.0.36"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde"
|
||||||
|
version = "1.0.204"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "bc76f558e0cbb2a839d37354c575f1dc3fdc6546b5be373ba43d95f231bf7c12"
|
||||||
|
dependencies = [
|
||||||
|
"serde_derive",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde_derive"
|
||||||
|
version = "1.0.204"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e0cd7e117be63d3c3678776753929474f3b04a43a080c744d6b0ae2a8c28e222"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "syn"
|
||||||
|
version = "2.0.72"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "dc4b9b9bf2add8093d3f2c0204471e951b2285580335de42f9d2534f3ae7a8af"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"unicode-ident",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicode-ident"
|
||||||
|
version = "1.0.12"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
|
||||||
|
|
|
||||||
|
|
@ -5,4 +5,5 @@ version = "0.1.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
serde = { version = "1.0.204", features = ["derive"] }
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
|
use serde::Deserialize;
|
||||||
use std::fmt::{Display, Formatter};
|
use std::fmt::{Display, Formatter};
|
||||||
|
|
||||||
#[derive(Debug, Default, PartialEq, Clone, Copy)]
|
#[derive(Debug, Default, PartialEq, Clone, Copy, Deserialize)]
|
||||||
|
#[serde(from = "i64")]
|
||||||
pub enum LogLevel {
|
pub enum LogLevel {
|
||||||
Debug,
|
Debug,
|
||||||
Info,
|
Info,
|
||||||
|
|
@ -28,6 +30,12 @@ impl From<i64> for LogLevel {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl LogLevel {
|
||||||
|
pub fn matches(&self, matcher_level: LogLevel) -> bool {
|
||||||
|
matcher_level == *self || matcher_level == LogLevel::Exception || *self == LogLevel::Unknown
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Clone)]
|
#[derive(Debug, PartialEq, Clone)]
|
||||||
pub struct LoggingStatement {
|
pub struct LoggingStatement {
|
||||||
pub level: LogLevel,
|
pub level: LogLevel,
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,13 @@
|
||||||
|
use cloud_log_analyser_data::LogLevel;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
pub struct LogLine<'a> {
|
pub struct LogLine<'a> {
|
||||||
pub version: &'a str,
|
pub version: &'a str,
|
||||||
pub level: i64,
|
pub level: LogLevel,
|
||||||
pub message: Cow<'a, str>,
|
pub message: Cow<'a, str>,
|
||||||
|
pub exception: Option<Exception<'a>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LogLine<'_> {
|
impl LogLine<'_> {
|
||||||
|
|
@ -18,3 +20,12 @@ impl LogLine<'_> {
|
||||||
major.parse().ok()
|
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>>>,
|
||||||
|
}
|
||||||
|
|
|
||||||
27
src/main.rs
27
src/main.rs
|
|
@ -50,7 +50,7 @@ fn main() -> MainResult {
|
||||||
continue;
|
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);
|
counts.entry(index).or_default().add_assign(1);
|
||||||
} else {
|
} else {
|
||||||
unmatched += 1;
|
unmatched += 1;
|
||||||
|
|
@ -62,13 +62,24 @@ fn main() -> MainResult {
|
||||||
counts.reverse();
|
counts.reverse();
|
||||||
for (index, count) in counts {
|
for (index, count) in counts {
|
||||||
let statement = &statements[index];
|
let statement = &statements[index];
|
||||||
println!(
|
if let Some(exception) = statement.exception {
|
||||||
"{}: {} line {}: {}",
|
println!(
|
||||||
statement.message(),
|
"{}({}): {} line {}: {}",
|
||||||
statement.path,
|
exception,
|
||||||
statement.line,
|
statement.message(),
|
||||||
count
|
statement.path,
|
||||||
);
|
statement.line,
|
||||||
|
count
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
println!(
|
||||||
|
"{}: {} line {}: {}",
|
||||||
|
statement.message(),
|
||||||
|
statement.path,
|
||||||
|
statement.line,
|
||||||
|
count
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if unmatched > 0 {
|
if unmatched > 0 {
|
||||||
eprintln!("{unmatched} lines couldn't be matched");
|
eprintln!("{unmatched} lines couldn't be matched");
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
use crate::logline::LogLine;
|
||||||
use cloud_log_analyser_data::{LogLevel, LoggingStatement};
|
use cloud_log_analyser_data::{LogLevel, LoggingStatement};
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
|
|
||||||
|
|
@ -7,6 +8,8 @@ pub struct LogMatch {
|
||||||
pattern_length: usize,
|
pattern_length: usize,
|
||||||
has_meaningful_message: bool,
|
has_meaningful_message: bool,
|
||||||
exception: Option<&'static str>,
|
exception: Option<&'static str>,
|
||||||
|
path: &'static str,
|
||||||
|
line: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LogMatch {
|
impl LogMatch {
|
||||||
|
|
@ -19,6 +22,8 @@ impl LogMatch {
|
||||||
.regex
|
.regex
|
||||||
.contains(|c: char| c.is_ascii_alphanumeric()),
|
.contains(|c: char| c.is_ascii_alphanumeric()),
|
||||||
exception: statement.exception,
|
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_match = None;
|
||||||
let mut best_length = 0;
|
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() {
|
for (i, log_match) in self.matches.iter().enumerate() {
|
||||||
if log_match.has_meaningful_message {
|
if log_match.has_meaningful_message {
|
||||||
if (log_match.level == level
|
if log.level.matches(log_match.level)
|
||||||
|| log_match.level == LogLevel::Exception
|
&& log_match.pattern.is_match(log.message.as_ref())
|
||||||
|| level == LogLevel::Unknown)
|
|
||||||
&& log_match.pattern.is_match(message)
|
|
||||||
&& log_match.pattern_length > best_length
|
&& log_match.pattern_length > best_length
|
||||||
{
|
{
|
||||||
best_match = Some(i);
|
best_match = Some(i);
|
||||||
|
|
@ -52,12 +66,16 @@ impl Matcher {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// todo: handle translated log messages
|
||||||
|
|
||||||
best_match
|
best_match
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_matcher() {
|
fn test_matcher() {
|
||||||
|
use crate::logline::Exception;
|
||||||
|
|
||||||
let statements = &[
|
let statements = &[
|
||||||
LoggingStatement {
|
LoggingStatement {
|
||||||
line: 68,
|
line: 68,
|
||||||
|
|
@ -65,6 +83,7 @@ fn test_matcher() {
|
||||||
path: "foo",
|
path: "foo",
|
||||||
placeholders: &[],
|
placeholders: &[],
|
||||||
regex: "^Not allowed to rename a shared album$",
|
regex: "^Not allowed to rename a shared album$",
|
||||||
|
exception: None,
|
||||||
},
|
},
|
||||||
LoggingStatement {
|
LoggingStatement {
|
||||||
line: 69,
|
line: 69,
|
||||||
|
|
@ -72,6 +91,7 @@ fn test_matcher() {
|
||||||
path: "bar",
|
path: "bar",
|
||||||
placeholders: &[],
|
placeholders: &[],
|
||||||
regex: "^You are not allowed to edit link shares that you don't own$",
|
regex: "^You are not allowed to edit link shares that you don't own$",
|
||||||
|
exception: None,
|
||||||
},
|
},
|
||||||
LoggingStatement {
|
LoggingStatement {
|
||||||
line: 69,
|
line: 69,
|
||||||
|
|
@ -79,6 +99,7 @@ fn test_matcher() {
|
||||||
path: "asd",
|
path: "asd",
|
||||||
placeholders: &["$mimeType"],
|
placeholders: &["$mimeType"],
|
||||||
regex: r#"^Unsupported query value for mimetype: (.*), only values in the format "mime/type" or "mime/%" are supported$"#,
|
regex: r#"^Unsupported query value for mimetype: (.*), only values in the format "mime/type" or "mime/%" are supported$"#,
|
||||||
|
exception: None,
|
||||||
},
|
},
|
||||||
LoggingStatement {
|
LoggingStatement {
|
||||||
line: 68,
|
line: 68,
|
||||||
|
|
@ -86,30 +107,79 @@ fn test_matcher() {
|
||||||
path: "short",
|
path: "short",
|
||||||
placeholders: &["$path"],
|
placeholders: &["$path"],
|
||||||
regex: "^Not allowed to rename (.*)$",
|
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);
|
let matcher = Matcher::new(statements);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
Some(0),
|
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!(
|
assert_eq!(
|
||||||
Some(3),
|
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!(
|
assert_eq!(
|
||||||
Some(1),
|
Some(1),
|
||||||
matcher.match_log(
|
matcher.match_log(&LogLine {
|
||||||
LogLevel::Error,
|
version: "29",
|
||||||
"You are not allowed to edit link shares that you don't own"
|
level: LogLevel::Error,
|
||||||
)
|
message: "You are not allowed to edit link shares that you don't own".into(),
|
||||||
|
exception: None,
|
||||||
|
})
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
None,
|
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(
|
matcher.match_log(
|
||||||
LogLevel::Info,
|
&LogLine {
|
||||||
"You are not allowed to edit link shares that you don't own"
|
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"));
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue