use super::{ControlPanelState, LogEntry};
use std::sync::Arc;
pub(crate) async fn logs_json(
axum::extract::State(state): axum::extract::State<Arc<ControlPanelState>>,
) -> axum::Json<Vec<LogEntry>> {
let mut logs = state.recent_logs(200);
if logs.len() < 50 {
let config = state.config.load();
if let Some(ref log_dir) = config.telemetry.log_dir {
let today = chrono::Utc::now().format("%Y-%m-%d").to_string();
let log_file = std::path::Path::new(log_dir)
.join(format!("adk-gateway.log.{}", today));
if let Ok(content) = std::fs::read_to_string(&log_file) {
let file_logs: Vec<LogEntry> = content
.lines()
.rev()
.take(200)
.filter_map(parse_log_line)
.collect();
let mut merged = file_logs;
merged.extend(logs);
merged.dedup_by(|a, b| a.timestamp == b.timestamp && a.message == b.message);
merged.truncate(200);
logs = merged;
}
}
}
axum::Json(logs)
}
fn parse_log_line(line: &str) -> Option<LogEntry> {
let line = line.trim();
if line.is_empty() {
return None;
}
let parts: Vec<&str> = line.splitn(2, " ").collect();
if parts.len() < 2 {
return Some(LogEntry {
timestamp: String::new(),
level: "INFO".into(),
message: line.to_string(),
target: None,
});
}
let timestamp = parts[0].trim().to_string();
let rest = parts[1].trim();
let (level, remainder) = if let Some(r) = rest.strip_prefix("INFO ") {
("INFO", r)
} else if let Some(r) = rest.strip_prefix("WARN ") {
("WARN", r)
} else if let Some(r) = rest.strip_prefix("ERROR ") {
("ERROR", r)
} else if let Some(r) = rest.strip_prefix("DEBUG ") {
("DEBUG", r)
} else {
("INFO", rest)
};
let (target, message) = if let Some(colon_pos) = remainder.find(": ") {
let t = &remainder[..colon_pos];
let m = &remainder[colon_pos + 2..];
(Some(t.to_string()), m.to_string())
} else {
(None, remainder.to_string())
};
Some(LogEntry {
timestamp,
level: level.to_string(),
message,
target,
})
}