0.34.6_repo_cleanup
This commit is contained in:
1 parent
ce1a90397c
commit
133d2f0f91
273 files changed
+5969
-6926
No files matched your search
@@ -50,8 +50,6 @@ fn record_matches(value: &Value, query: &FileLogQuery, search: &[FileLogSearchCl
|
||||
true
|
||||
}
|
||||
|
||||
// Order the entire bounded matching scan before applying offset/limit. Sorting
|
||||
// only the current browser page would give misleading navigation results.
|
||||
#[derive(Debug)]
|
||||
enum FileLogSortKey {
|
||||
Number(serde_json::Number),
|
||||
@@ -59,29 +57,43 @@ enum FileLogSortKey {
|
||||
}
|
||||
|
||||
fn stored_log_summary(value: &Value) -> String {
|
||||
let field = |name| value.get(name).and_then(Value::as_str).filter(|text| !text.is_empty());
|
||||
let field = |name| {
|
||||
value
|
||||
.get(name)
|
||||
.and_then(Value::as_str)
|
||||
.filter(|text| !text.is_empty())
|
||||
};
|
||||
if let Some(reason) = field("reason").or_else(|| field("protocol_error")) {
|
||||
return reason.to_owned();
|
||||
}
|
||||
if field("method").is_some() || field("path").is_some() {
|
||||
return format!("{} {}{}", field("method").unwrap_or(""),
|
||||
return format!(
|
||||
"{} {}{}",
|
||||
field("method").unwrap_or(""),
|
||||
field("host").or_else(|| field("authority")).unwrap_or(""),
|
||||
field("path").unwrap_or("")).trim().to_owned();
|
||||
field("path").unwrap_or("")
|
||||
)
|
||||
.trim()
|
||||
.to_owned();
|
||||
}
|
||||
for key in ["error", "error_detail", "message", "raw", "outcome"] {
|
||||
if let Some(text) = field(key) { return text.to_owned(); }
|
||||
if let Some(text) = field(key) {
|
||||
return text.to_owned();
|
||||
}
|
||||
}
|
||||
String::new()
|
||||
}
|
||||
|
||||
fn stored_log_sort_key(record: &Value, field: &str) -> Option<FileLogSortKey> {
|
||||
if field == "record" {
|
||||
return Some(FileLogSortKey::Text(stored_log_summary(record).to_lowercase()));
|
||||
return Some(FileLogSortKey::Text(
|
||||
stored_log_summary(record).to_lowercase(),
|
||||
));
|
||||
}
|
||||
// Prefer millisecond timestamps for numeric chronological sorting. RAW
|
||||
// file timestamps use the existing ISO/text fallback.
|
||||
let value = if field == "time" {
|
||||
record.get("time_ms").filter(|value| value.is_number())
|
||||
record
|
||||
.get("time_ms")
|
||||
.filter(|value| value.is_number())
|
||||
.or_else(|| record.get("timestamp"))
|
||||
} else {
|
||||
file_log_field(record, field)
|
||||
@@ -91,39 +103,51 @@ fn stored_log_sort_key(record: &Value, field: &str) -> Option<FileLogSortKey> {
|
||||
Value::Number(number) => Some(FileLogSortKey::Number(number.clone())),
|
||||
Value::String(text) if !text.is_empty() => Some(FileLogSortKey::Text(text.to_lowercase())),
|
||||
Value::Bool(boolean) => Some(FileLogSortKey::Text(boolean.to_string())),
|
||||
Value::Array(_) | Value::Object(_) => Some(FileLogSortKey::Text(value.to_string().to_lowercase())),
|
||||
Value::Array(_) | Value::Object(_) => {
|
||||
Some(FileLogSortKey::Text(value.to_string().to_lowercase()))
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn file_log_timestamp_cmp_desc(left: &Value, right: &Value) -> std::cmp::Ordering {
|
||||
match (left.get("time_ms").and_then(Value::as_u64), right.get("time_ms").and_then(Value::as_u64)) {
|
||||
match (
|
||||
left.get("time_ms").and_then(Value::as_u64),
|
||||
right.get("time_ms").and_then(Value::as_u64),
|
||||
) {
|
||||
(Some(a), Some(b)) => b.cmp(&a),
|
||||
_ => right.get("timestamp").and_then(Value::as_str).unwrap_or("")
|
||||
_ => right
|
||||
.get("timestamp")
|
||||
.and_then(Value::as_str)
|
||||
.unwrap_or("")
|
||||
.cmp(left.get("timestamp").and_then(Value::as_str).unwrap_or("")),
|
||||
}
|
||||
}
|
||||
|
||||
fn sort_file_log_records(records: &mut Vec<Value>, field: &str, descending: bool) {
|
||||
use std::cmp::Ordering;
|
||||
// Build one key per record; do not repeatedly allocate/parse strings in
|
||||
// the O(n log n) comparison loop. The scan itself is limited to 20k lines.
|
||||
let mut keyed = std::mem::take(records).into_iter().enumerate()
|
||||
let mut keyed = std::mem::take(records)
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
.map(|(ordinal, record)| (stored_log_sort_key(&record, field), ordinal, record))
|
||||
.collect::<Vec<_>>();
|
||||
keyed.sort_by(|left, right| {
|
||||
let order = match (&left.0, &right.0) {
|
||||
(None, None) => Ordering::Equal,
|
||||
(None, Some(_)) => return Ordering::Greater, // missing values always last
|
||||
(None, Some(_)) => return Ordering::Greater,
|
||||
(Some(_), None) => return Ordering::Less,
|
||||
(Some(FileLogSortKey::Number(a)), Some(FileLogSortKey::Number(b))) =>
|
||||
(Some(FileLogSortKey::Number(a)), Some(FileLogSortKey::Number(b))) => {
|
||||
match (a.as_i64(), b.as_i64()) {
|
||||
(Some(left), Some(right)) => left.cmp(&right),
|
||||
_ => match (a.as_u64(), b.as_u64()) {
|
||||
(Some(left), Some(right)) => left.cmp(&right),
|
||||
_ => a.as_f64().partial_cmp(&b.as_f64()).unwrap_or(Ordering::Equal),
|
||||
_ => a
|
||||
.as_f64()
|
||||
.partial_cmp(&b.as_f64())
|
||||
.unwrap_or(Ordering::Equal),
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
(Some(FileLogSortKey::Text(a)), Some(FileLogSortKey::Text(b))) => a.cmp(b),
|
||||
(Some(FileLogSortKey::Number(_)), Some(FileLogSortKey::Text(_))) => Ordering::Less,
|
||||
(Some(FileLogSortKey::Text(_)), Some(FileLogSortKey::Number(_))) => Ordering::Greater,
|
||||
@@ -145,10 +169,20 @@ fn validate_file_log_query(query: &FileLogQuery) -> Result<(), ApiError> {
|
||||
|| !text_ok(&query.q, 512)
|
||||
|| !text_ok(&query.client_ip, 128)
|
||||
|| !text_ok(&query.vhost, 256)
|
||||
|| query.aggregate_field.as_ref().is_some_and(|field| !valid_raw_field_key(field))
|
||||
|| query.sort_by.as_ref().is_some_and(|field| !valid_raw_field_key(field))
|
||||
|| query.aggregate_limit.is_some_and(|value| value == 0 || value > FILE_LOG_MAX_AGGREGATE_RESULTS)
|
||||
|| query.aggregate_min_count.is_some_and(|value| value < 2 || value > FILE_LOG_SCAN_LINES)
|
||||
|| query
|
||||
.aggregate_field
|
||||
.as_ref()
|
||||
.is_some_and(|field| !valid_raw_field_key(field))
|
||||
|| query
|
||||
.sort_by
|
||||
.as_ref()
|
||||
.is_some_and(|field| !valid_raw_field_key(field))
|
||||
|| query
|
||||
.aggregate_limit
|
||||
.is_some_and(|value| value == 0 || value > FILE_LOG_MAX_AGGREGATE_RESULTS)
|
||||
|| query
|
||||
.aggregate_min_count
|
||||
.is_some_and(|value| value < 2 || value > FILE_LOG_SCAN_LINES)
|
||||
|| query.offset.unwrap_or(0) > FILE_LOG_MAX_OFFSET
|
||||
|| query
|
||||
.limit
|
||||
@@ -187,8 +221,11 @@ async fn file_logs_query(
|
||||
let requested = query.source.as_deref().unwrap_or("all");
|
||||
let mut truncated = false;
|
||||
if requested != "all" {
|
||||
let consolidate_vhosts = query.consolidate_vhosts.unwrap_or(false) && requested.starts_with("vhost:");
|
||||
sources.retain(|source| source.id == requested || (consolidate_vhosts && source.group_id == requested));
|
||||
let consolidate_vhosts =
|
||||
query.consolidate_vhosts.unwrap_or(false) && requested.starts_with("vhost:");
|
||||
sources.retain(|source| {
|
||||
source.id == requested || (consolidate_vhosts && source.group_id == requested)
|
||||
});
|
||||
if sources.is_empty() {
|
||||
return Err(ApiError(
|
||||
StatusCode::BAD_REQUEST,
|
||||
@@ -210,9 +247,15 @@ async fn file_logs_query(
|
||||
let mut scanned_lines = 0usize;
|
||||
let mut records = Vec::new();
|
||||
let mut fields = std::collections::BTreeSet::new();
|
||||
let aggregate_field = query.aggregate_field.as_deref().map(str::to_ascii_lowercase);
|
||||
let aggregate_field = query
|
||||
.aggregate_field
|
||||
.as_deref()
|
||||
.map(str::to_ascii_lowercase);
|
||||
let aggregate_min_count = query.aggregate_min_count.unwrap_or(2);
|
||||
let aggregate_limit = query.aggregate_limit.unwrap_or(50).clamp(1, FILE_LOG_MAX_AGGREGATE_RESULTS);
|
||||
let aggregate_limit = query
|
||||
.aggregate_limit
|
||||
.unwrap_or(50)
|
||||
.clamp(1, FILE_LOG_MAX_AGGREGATE_RESULTS);
|
||||
let mut aggregate_counts = std::collections::HashMap::<String, usize>::new();
|
||||
let mut aggregate_truncated = false;
|
||||
for (index, source) in sources.iter().enumerate() {
|
||||
@@ -241,8 +284,13 @@ async fn file_logs_query(
|
||||
let mut values = Vec::new();
|
||||
file_log_aggregate_values(field_value, &mut values);
|
||||
for aggregate_value in values {
|
||||
if aggregate_counts.contains_key(&aggregate_value) || aggregate_counts.len() < FILE_LOG_MAX_AGGREGATE_VALUES {
|
||||
*aggregate_counts.entry(aggregate_value).or_insert(0) += 1;
|
||||
if aggregate_counts.contains_key(&aggregate_value)
|
||||
|| aggregate_counts.len()
|
||||
< FILE_LOG_MAX_AGGREGATE_VALUES
|
||||
{
|
||||
*aggregate_counts
|
||||
.entry(aggregate_value)
|
||||
.or_insert(0) += 1;
|
||||
} else {
|
||||
aggregate_truncated = true;
|
||||
}
|
||||
@@ -257,9 +305,13 @@ async fn file_logs_query(
|
||||
Err(_) => truncated = true,
|
||||
}
|
||||
}
|
||||
sort_file_log_records(&mut records, query.sort_by.as_deref().unwrap_or("time"),
|
||||
query.sort_descending.unwrap_or(true));
|
||||
let mut aggregation = aggregate_counts.into_iter()
|
||||
sort_file_log_records(
|
||||
&mut records,
|
||||
query.sort_by.as_deref().unwrap_or("time"),
|
||||
query.sort_descending.unwrap_or(true),
|
||||
);
|
||||
let mut aggregation = aggregate_counts
|
||||
.into_iter()
|
||||
.filter(|(_, count)| *count >= aggregate_min_count)
|
||||
.map(|(value, count)| json!({"value": value, "count": count}))
|
||||
.collect::<Vec<_>>();
|
||||
@@ -267,7 +319,9 @@ async fn file_logs_query(
|
||||
let left_count = left.get("count").and_then(Value::as_u64).unwrap_or(0);
|
||||
let right_count = right.get("count").and_then(Value::as_u64).unwrap_or(0);
|
||||
right_count.cmp(&left_count).then_with(|| {
|
||||
left.get("value").and_then(Value::as_str).unwrap_or("")
|
||||
left.get("value")
|
||||
.and_then(Value::as_str)
|
||||
.unwrap_or("")
|
||||
.cmp(right.get("value").and_then(Value::as_str).unwrap_or(""))
|
||||
})
|
||||
});
|
||||
|
||||
Reference in new issue
Block a user