package logger import ( "encoding/json" "fmt" "io" "os" "sync" "sync/atomic" "time" ) // Level represents log level. type Level int const ( LevelDebug Level = iota LevelInfo LevelWarn LevelError ) func (l Level) String() string { switch l { case LevelDebug: return "DEBUG" case LevelInfo: return "INFO" case LevelWarn: return "WARN" case LevelError: return "ERROR" default: return "INFO" } } // Logger is a simple JSON logger compatible with Go 1.20. type Logger struct { level Level writer io.Writer mu sync.Mutex } // Default is the global default logger. var Default atomic.Value func init() { Default.Store(&Logger{level: LevelInfo, writer: os.Stderr}) } func getDefault() *Logger { if l, ok := Default.Load().(*Logger); ok && l != nil { return l } return &Logger{level: LevelInfo, writer: os.Stderr} } // Init initializes the logger with the specified log level. func Init(level string) { InitWithWriter(level, os.Stdout) } // InitWithWriter initializes the logger with the specified log level and writer. func InitWithWriter(level string, writer io.Writer) { var lvl Level switch level { case "debug": lvl = LevelDebug case "info": lvl = LevelInfo case "warn": lvl = LevelWarn case "error": lvl = LevelError default: lvl = LevelInfo } if writer == nil { writer = os.Stdout } Default.Store(&Logger{level: lvl, writer: writer}) } func (l *Logger) log(level Level, msg string, args ...any) { if level < l.level { return } record := map[string]any{ "time": time.Now().Format(time.RFC3339Nano), "level": level.String(), "msg": msg, } for i := 0; i < len(args)-1; i += 2 { key, ok := args[i].(string) if !ok { continue } record[key] = args[i+1] } l.mu.Lock() defer l.mu.Unlock() data, _ := json.Marshal(record) fmt.Fprintln(l.writer, string(data)) } // Debug logs a message at Debug level. func Debug(msg string, args ...any) { getDefault().log(LevelDebug, msg, args...) } // Info logs a message at Info level. func Info(msg string, args ...any) { getDefault().log(LevelInfo, msg, args...) } // Warn logs a message at Warn level. func Warn(msg string, args ...any) { getDefault().log(LevelWarn, msg, args...) } // Error logs a message at Error level. func Error(msg string, args ...any) { getDefault().log(LevelError, msg, args...) } // Fatal logs a message at Error level and exits. func Fatal(msg string, args ...any) { getDefault().log(LevelError, msg, args...) os.Exit(1) }