Moonshark/utils/logger/logger.go

201 lines
4.0 KiB
Go

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() }