Add config validation and dashboard
Add YAML config loading with validation, env overrides for secrets, and a web dashboard exposing health, stats, and recent events.
This commit is contained in:
@@ -0,0 +1,119 @@
|
||||
module main
|
||||
|
||||
import time
|
||||
|
||||
pub enum LogLevel {
|
||||
debug
|
||||
info
|
||||
warn
|
||||
error
|
||||
}
|
||||
|
||||
pub struct Logger {
|
||||
level LogLevel
|
||||
json_mode bool
|
||||
}
|
||||
|
||||
pub fn new_logger(level LogLevel, json_mode bool) Logger {
|
||||
return Logger{
|
||||
level: level
|
||||
json_mode: json_mode
|
||||
}
|
||||
}
|
||||
|
||||
fn level_string(level LogLevel) string {
|
||||
return match level {
|
||||
.debug { 'DEBUG' }
|
||||
.info { 'INFO' }
|
||||
.warn { 'WARN' }
|
||||
.error { 'ERROR' }
|
||||
}
|
||||
}
|
||||
|
||||
fn level_icon(level LogLevel) string {
|
||||
return match level {
|
||||
.debug { '🔍' }
|
||||
.info { 'ℹ️ ' }
|
||||
.warn { '⚠️ ' }
|
||||
.error { '❌' }
|
||||
}
|
||||
}
|
||||
|
||||
fn (l Logger) should_log(level LogLevel) bool {
|
||||
return int(level) >= int(l.level)
|
||||
}
|
||||
|
||||
pub fn (l Logger) log(level LogLevel, source string, event string, message string) {
|
||||
if !l.should_log(level) {
|
||||
return
|
||||
}
|
||||
|
||||
if l.json_mode {
|
||||
l.log_json(level, source, event, message)
|
||||
} else {
|
||||
l.log_human(level, source, event, message)
|
||||
}
|
||||
}
|
||||
|
||||
fn (l Logger) log_json(level LogLevel, source string, event string, message string) {
|
||||
timestamp := time.now().format_rfc3339()
|
||||
level_str := level_string(level)
|
||||
// Manual JSON construction — simple enough to avoid importing json for each log line
|
||||
// Escape backslashes and quotes in message
|
||||
escaped := escape_json(message)
|
||||
output := '{"timestamp":"${timestamp}","level":"${level_str}","source":"${source}","event":"${event}","message":"${escaped}"}'
|
||||
if level == .error {
|
||||
eprintln(output)
|
||||
} else {
|
||||
println(output)
|
||||
}
|
||||
}
|
||||
|
||||
fn escape_json(s string) string {
|
||||
mut result := s.clone()
|
||||
result = result.replace('\\', '\\\\')
|
||||
result = result.replace('"', '\\"')
|
||||
result = result.replace('\n', '\\n')
|
||||
result = result.replace('\r', '\\r')
|
||||
result = result.replace('\t', '\\t')
|
||||
return result
|
||||
}
|
||||
|
||||
fn (l Logger) log_human(level LogLevel, source string, _event string, message string) {
|
||||
icon := level_icon(level)
|
||||
ts := time.now()
|
||||
ts_str := format_time_compact(ts)
|
||||
output := '${ts_str} ${icon} [${source}] ${message}'
|
||||
|
||||
if level == .error {
|
||||
eprintln(output)
|
||||
} else if level == .warn {
|
||||
eprintln(output)
|
||||
} else {
|
||||
println(output)
|
||||
}
|
||||
}
|
||||
|
||||
// format_time_compact returns "HH:MM:SS" (compact time for console output)
|
||||
fn format_time_compact(t time.Time) string {
|
||||
h := int(t.hour)
|
||||
m := int(t.minute)
|
||||
s := int(t.second)
|
||||
return '${h:02d}:${m:02d}:${s:02d}'
|
||||
}
|
||||
|
||||
pub fn (l Logger) debug(source string, event string, message string) {
|
||||
l.log(.debug, source, event, message)
|
||||
}
|
||||
|
||||
pub fn (l Logger) info(source string, event string, message string) {
|
||||
l.log(.info, source, event, message)
|
||||
}
|
||||
|
||||
pub fn (l Logger) warn(source string, event string, message string) {
|
||||
l.log(.warn, source, event, message)
|
||||
}
|
||||
|
||||
pub fn (l Logger) error(source string, event string, message string) {
|
||||
l.log(.error, source, event, message)
|
||||
}
|
||||
Reference in New Issue
Block a user