Add YAML config loading with validation, env overrides for secrets, and a web dashboard exposing health, stats, and recent events.
120 lines
2.6 KiB
V
120 lines
2.6 KiB
V
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)
|
||
}
|