1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
use super::{LogParser, ParsedLogLine};
use serde_json::Value;
/// Parser for JSON formatted logs
pub struct JsonLogParser;
impl LogParser for JsonLogParser {
fn name(&self) -> &'static str {
"JSON"
}
fn can_parse(&self, sample_lines: &[&str]) -> bool {
// Check if lines look like valid JSON objects
if sample_lines.is_empty() {
return false;
}
let valid_count = sample_lines
.iter()
.filter(|line| {
let trimmed = line.trim();
trimmed.starts_with('{') &&
trimmed.ends_with('}') &&
serde_json::from_str::<Value>(trimmed).is_ok() &&
// Be more flexible about field names
(trimmed.contains("timestamp") ||
trimmed.contains("time") ||
trimmed.contains("@timestamp")) &&
(trimmed.contains("level") ||
trimmed.contains("severity") ||
trimmed.contains("log_level"))
})
.count();
// Require at least 40% of lines to be valid JSON with somewhat relaxed field requirements
valid_count * 100 / sample_lines.len() >= 40
}
fn parse_line(&self, line: &str) -> ParsedLogLine {
let mut parsed = ParsedLogLine {
message: Some(line.to_string()),
..Default::default()
};
// Try to parse as JSON
if let Ok(json) = serde_json::from_str::<Value>(line.trim()) {
if let Some(obj) = json.as_object() {
// Fill the fields HashMap with owned String values
for (key, value) in obj {
match value {
Value::String(s) => {
parsed.fields.insert(key.clone(), s.clone());
}
Value::Number(n) => {
parsed.fields.insert(key.clone(), n.to_string());
}
Value::Bool(b) => {
parsed.fields.insert(key.clone(), b.to_string());
}
_ => {} // Ignore other types for now
}
}
// Timestamp detection - use owned strings
for key in &["timestamp", "time", "@timestamp", "date", "datetime"] {
if let Some(json_str) = obj.get(*key).and_then(|v| v.as_str()) {
// Try to find this string in the original line
if let Some(pos) = line.find(json_str) {
parsed.timestamp = Some(line[pos..pos + json_str.len()].to_string());
break;
}
}
}
// Level detection - use owned strings
for key in &["level", "severity", "loglevel", "log_level", "@level"] {
if let Some(json_str) = obj.get(*key).and_then(|v| v.as_str()) {
// Try to find this string in the original line
if let Some(pos) = line.find(json_str) {
parsed.level = Some(line[pos..pos + json_str.len()].to_string());
break;
}
}
}
// Message detection - use owned strings
for key in &["message", "msg", "text", "description", "content"] {
if let Some(json_str) = obj.get(*key).and_then(|v| v.as_str()) {
// Try to find this string in the original line
if let Some(pos) = line.find(json_str) {
parsed.message = Some(line[pos..pos + json_str.len()].to_string());
break;
}
}
}
}
}
parsed
}
}