use crate::runtime::values::Value;
use std::collections::HashMap;
use std::env;
use std::sync::Mutex;
use std::time::{SystemTime, UNIX_EPOCH};
#[derive(Debug, Clone)]
pub struct LogEntry {
pub timestamp: i64,
pub level: LogLevel,
pub message: String,
pub data: HashMap<String, Value>,
pub source: String,
}
#[derive(Debug, Clone, PartialEq)]
pub enum LogLevel {
Info,
Warning,
Error,
Audit,
Debug,
}
lazy_static::lazy_static! {
static ref LOG_STORAGE: Mutex<Vec<LogEntry>> = Mutex::new(Vec::new());
}
pub fn info(message: &str, data: HashMap<String, Value>, source: Option<&str>) {
let source_str = source.unwrap_or("system").to_string();
log_message(LogLevel::Info, message, data, source_str);
}
pub fn warning(message: &str, data: HashMap<String, Value>, source: Option<&str>) {
let source_str = source.unwrap_or("system").to_string();
log_message(LogLevel::Warning, message, data, source_str);
}
pub fn error(message: &str, data: HashMap<String, Value>, source: Option<&str>) {
let source_str = source.unwrap_or("system").to_string();
log_message(LogLevel::Error, message, data, source_str);
}
pub fn audit(event: &str, data: HashMap<String, Value>, source: Option<&str>) {
let source_str = source.unwrap_or("audit").to_string();
log_message(LogLevel::Audit, event, data, source_str);
}
pub fn debug(message: &str, data: HashMap<String, Value>, source: Option<&str>) {
let source_str = source.unwrap_or("debug").to_string();
log_message(LogLevel::Debug, message, data, source_str);
}
fn log_level_order(level: &LogLevel) -> u8 {
match level {
LogLevel::Debug => 0,
LogLevel::Info | LogLevel::Audit => 1,
LogLevel::Warning => 2,
LogLevel::Error => 3,
}
}
fn max_entries() -> usize {
env::var("LOG_MAX_ENTRIES")
.ok()
.and_then(|s| s.trim().parse().ok())
.unwrap_or(1000)
}
fn sink_console() -> bool {
match env::var("LOG_SINK").as_deref() {
Ok("none") => false,
_ => true,
}
}
fn min_level_order() -> u8 {
match env::var("LOG_LEVEL").as_deref() {
Ok("debug") => 0,
Ok("info") | Ok("audit") => 1,
Ok("warning") | Ok("warn") => 2,
Ok("error") => 3,
_ => 0,
}
}
fn log_message(level: LogLevel, message: &str, data: HashMap<String, Value>, source: String) {
let timestamp = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or_default()
.as_secs() as i64;
let entry = LogEntry {
timestamp,
level: level.clone(),
message: message.to_string(),
data: data.clone(),
source: source.clone(),
};
if let Ok(mut storage) = LOG_STORAGE.lock() {
storage.push(entry.clone());
let cap = max_entries();
while storage.len() > cap {
storage.remove(0);
}
}
if sink_console() && log_level_order(&level) >= min_level_order() {
let level_str = match level {
LogLevel::Info => "INFO",
LogLevel::Warning => "WARN",
LogLevel::Error => "ERROR",
LogLevel::Audit => "AUDIT",
LogLevel::Debug => "DEBUG",
};
println!("[{}] {}: {} - {:?}", level_str, source, message, data);
}
}
pub fn get_entries() -> Vec<LogEntry> {
if let Ok(storage) = LOG_STORAGE.lock() {
storage.clone()
} else {
Vec::new()
}
}
pub fn get_entries_by_level(level: LogLevel) -> Vec<LogEntry> {
get_entries().into_iter()
.filter(|entry| entry.level == level)
.collect()
}
pub fn get_entries_by_source(source: &str) -> Vec<LogEntry> {
get_entries().into_iter()
.filter(|entry| entry.source == source)
.collect()
}
pub fn clear() {
if let Ok(mut storage) = LOG_STORAGE.lock() {
storage.clear();
}
}
pub fn get_stats() -> HashMap<String, Value> {
let entries = get_entries();
let mut stats = HashMap::new();
stats.insert("total_entries".to_string(), Value::Int(entries.len() as i64));
let mut level_counts = HashMap::new();
for entry in &entries {
let level_str = match entry.level {
LogLevel::Info => "info",
LogLevel::Warning => "warning",
LogLevel::Error => "error",
LogLevel::Audit => "audit",
LogLevel::Debug => "debug",
};
let count = level_counts.entry(level_str.to_string()).or_insert(0);
*count += 1;
}
for (level, count) in level_counts {
stats.insert(format!("count_{}", level), Value::Int(count));
}
let mut source_counts = HashMap::new();
for entry in &entries {
let count = source_counts.entry(entry.source.clone()).or_insert(0);
*count += 1;
}
for (source, count) in source_counts {
stats.insert(format!("source_{}", source), Value::Int(count));
}
stats
}