This commit is contained in:
Mateusz Gruszczyński committed 2026-10-05 19:05:55 +02:00
1 parent ddc5c6593f
commit b9d137eee1
63 files changed
+1232 -445

No files matched your search

+75 -6
View File
@@ -229,12 +229,62 @@ fn read_tail(
Ok((lines, bytes, truncated || iter.next().is_some()))
}
fn normalized_record(source: &FileLogSource, line: &str) -> Value {
fn copy_mapped_field(
object: &mut serde_json::Map<String, Value>,
format: &crate::logging::JsonFormat,
canonical: &str,
) {
if object.contains_key(canonical) {
return;
}
let mapped = format
.fields
.iter()
.find_map(|(output, source)| (source == canonical).then_some(output));
if let Some(value) = mapped.and_then(|output| object.get(output)).cloned() {
object.insert(canonical.to_owned(), value);
}
}
fn normalized_record(
source: &FileLogSource,
line: &str,
format: &crate::logging::JsonFormat,
) -> Value {
let mut value = serde_json::from_str::<Value>(line).unwrap_or_else(|_| json!({"raw": line}));
if !value.is_object() {
value = json!({"value": value});
}
let object = value.as_object_mut().expect("record normalized to object");
if source.kind == "access" {
for field in [
"event", "time_ms", "status", "client_ip", "vhost", "method", "host", "path",
"transport", "http_version", "protocol_error", "error_detail", "tls_sni", "reason",
"error", "sni", "authority", "matched_by",
] {
copy_mapped_field(object, format, field);
}
if !object.contains_key("client_ip") {
if let Some(value) = object.get("source_ip").cloned() {
object.insert("client_ip".into(), value);
}
}
if !object.contains_key("reason") {
if let Some(value) = object.get("protocol_error").cloned() {
object.insert("reason".into(), value);
}
}
if !object.contains_key("error") {
if let Some(value) = object.get("error_detail").cloned() {
object.insert("error".into(), value);
}
}
if !object.contains_key("sni") {
if let Some(value) = object.get("tls_sni").cloned() {
object.insert("sni".into(), value);
}
}
}
object.entry("event").or_insert_with(|| {
Value::String(
if source.kind == "error" {
@@ -256,12 +306,27 @@ fn contains_ci(value: Option<&Value>, needle: &str) -> bool {
.is_some_and(|text| text.to_ascii_lowercase().contains(needle))
}
fn append_search_values(value: &Value, output: &mut String) {
match value {
Value::Null => {}
Value::Bool(value) => { output.push_str(if *value { " true" } else { " false" }); }
Value::Number(value) => { output.push(' '); output.push_str(&value.to_string()); }
Value::String(value) => { output.push(' '); output.push_str(&value.to_ascii_lowercase()); }
Value::Array(values) => values.iter().for_each(|value| append_search_values(value, output)),
Value::Object(values) => values.values().for_each(|value| append_search_values(value, output)),
}
}
fn record_matches(value: &Value, query: &FileLogQuery, q: &str) -> bool {
let Some(object) = value.as_object() else {
return false;
};
if !q.is_empty() && !value.to_string().to_ascii_lowercase().contains(q) {
return false;
if !q.is_empty() {
let mut searchable = String::new();
append_search_values(value, &mut searchable);
if !q.split_whitespace().all(|term| searchable.contains(term)) {
return false;
}
}
if let Some(event) = query
.event
@@ -345,7 +410,9 @@ async fn file_logs_query(
let config = state.config.load().envelope.config.clone();
tokio::task::spawn_blocking(move || {
let mut sources = configured_file_log_sources(&config);
let log_format = config.logging.format.clone();
let requested = query.source.as_deref().unwrap_or("all");
let mut truncated = false;
if requested != "all" {
sources.retain(|source| source.id == requested);
if sources.is_empty() {
@@ -356,7 +423,10 @@ async fn file_logs_query(
}
} else {
sources.sort_by(|a, b| b.modified_ms.cmp(&a.modified_ms));
sources.truncate(FILE_LOG_MAX_SOURCES);
if sources.len() > FILE_LOG_MAX_SOURCES {
truncated = true;
sources.truncate(FILE_LOG_MAX_SOURCES);
}
}
let q = query.q.clone().unwrap_or_default().to_ascii_lowercase();
let offset = query.offset.unwrap_or(0);
@@ -365,7 +435,6 @@ async fn file_logs_query(
let mut scanned_bytes = 0u64;
let mut remaining_lines = FILE_LOG_SCAN_LINES;
let mut scanned_lines = 0usize;
let mut truncated = false;
let mut records = Vec::new();
for (index, source) in sources.iter().enumerate() {
if remaining_bytes == 0 || remaining_lines == 0 {
@@ -385,7 +454,7 @@ async fn file_logs_query(
if line.is_empty() {
continue;
}
let value = normalized_record(source, &line);
let value = normalized_record(source, &line, &log_format);
if record_matches(&value, &query, &q) {
records.push(value);
}