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:
2026-08-03 12:12:12 -04:00
parent 3f48912ad7
commit 4e1d7cd94a
29 changed files with 2344 additions and 55 deletions
+119
View File
@@ -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)
}