package logger import ( "fmt" "io" "os" "strings" "sync" "sync/atomic" "time" "Moonshark/color" ) // Log levels const ( LevelDebug = iota LevelInfo LevelWarn LevelError LevelFatal ) // Level config var levels = map[int]struct { tag string color func(string) string }{ LevelDebug: {"D", color.Cyan}, LevelInfo: {"I", color.Blue}, LevelWarn: {"W", color.Yellow}, LevelError: {"E", color.Red}, LevelFatal: {"F", color.Purple}, } var ( global *Logger globalOnce sync.Once ) type Logger struct { out io.Writer enabled atomic.Bool timestamp atomic.Bool debug atomic.Bool colors atomic.Bool mu sync.Mutex } func init() { globalOnce.Do(func() { global = &Logger{out: os.Stdout} global.enabled.Store(true) global.timestamp.Store(true) global.colors.Store(true) }) } func applyColor(text string, colorFunc func(string) string) string { if global.colors.Load() { return colorFunc(text) } return text } func write(level int, msg string) { if !global.enabled.Load() { return } if level == LevelDebug && !global.debug.Load() { return } var parts []string if global.timestamp.Load() { ts := applyColor(time.Now().Format("3:04PM"), color.Gray) parts = append(parts, ts) } if cfg, ok := levels[level]; ok { tag := applyColor("["+cfg.tag+"]", cfg.color) parts = append(parts, tag) } parts = append(parts, msg) line := strings.Join(parts, " ") + "\n" global.mu.Lock() fmt.Fprint(global.out, line) if level == LevelFatal { if f, ok := global.out.(*os.File); ok { f.Sync() } } global.mu.Unlock() if level == LevelFatal { os.Exit(1) } } func log(level int, format string, args ...any) { var msg string if len(args) > 0 { msg = fmt.Sprintf(format, args...) } else { msg = format } write(level, msg) } func Debugf(format string, args ...any) { log(LevelDebug, format, args...) } func Infof(format string, args ...any) { log(LevelInfo, format, args...) } func Warnf(format string, args ...any) { log(LevelWarn, format, args...) } func Errorf(format string, args ...any) { log(LevelError, format, args...) } func Fatalf(format string, args ...any) { log(LevelFatal, format, args...) } func Raw(format string, args ...any) { if !global.enabled.Load() { return } var msg string if len(args) > 0 { msg = fmt.Sprintf(format, args...) } else { msg = format } global.mu.Lock() fmt.Fprint(global.out, msg+"\n") global.mu.Unlock() } func Request(status int, method, path string, duration time.Duration) { if !global.enabled.Load() { return } var statusColor func(string) string switch { case status < 300: statusColor = color.Green case status < 400: statusColor = color.Cyan case status < 500: statusColor = color.Yellow default: statusColor = color.Red } var dur string us := duration.Microseconds() switch { case us < 1000: dur = fmt.Sprintf("%.0fµs", float64(us)) case us < 1000000: dur = fmt.Sprintf("%.1fms", float64(us)/1000) default: dur = fmt.Sprintf("%.2fs", duration.Seconds()) } var parts []string if global.timestamp.Load() { ts := applyColor(time.Now().Format("3:04PM"), color.Gray) parts = append(parts, ts) } parts = append(parts, applyColor("["+method+"]", color.Gray), applyColor(fmt.Sprintf("%d", status), statusColor), applyColor(path, color.Gray), applyColor(dur, color.Gray), ) msg := strings.Join(parts, " ") global.mu.Lock() fmt.Fprint(global.out, msg+"\n") global.mu.Unlock() } func SetOutput(w io.Writer) { global.mu.Lock() global.out = w global.mu.Unlock() } func Enable() { global.enabled.Store(true) } func Disable() { global.enabled.Store(false) } func IsEnabled() bool { return global.enabled.Load() } func EnableColors() { global.colors.Store(true) } func DisableColors() { global.colors.Store(false) } func ColorsEnabled() bool { return global.colors.Load() } func Timestamp(enabled bool) { global.timestamp.Store(enabled) } func Debug(enabled bool) { global.debug.Store(enabled) } func IsDebug() bool { return global.debug.Load() }