package logger import ( "fmt" "io" "os" "strings" "sync" "sync/atomic" "time" ) // ANSI color codes const ( colorReset = "\033[0m" colorRed = "\033[31m" colorGreen = "\033[32m" colorYellow = "\033[33m" colorBlue = "\033[34m" colorPurple = "\033[35m" colorCyan = "\033[36m" colorGray = "\033[90m" ) // Log types const ( TypeNone = iota TypeDebug TypeInfo TypeWarning TypeError TypeServer TypeFatal ) // Type properties var typeProps = map[int]struct { tag string color string }{ TypeDebug: {"D", colorCyan}, TypeInfo: {"I", colorBlue}, TypeWarning: {"W", colorYellow}, TypeError: {"E", colorRed}, TypeServer: {"S", colorGreen}, TypeFatal: {"F", colorPurple}, } const timeFormat = "15:04:05" var ( globalLogger *Logger globalLoggerOnce sync.Once ) type Logger struct { writer io.Writer useColors bool timeFormat string showTimestamp bool mu sync.Mutex debugMode atomic.Bool } func GetLogger() *Logger { globalLoggerOnce.Do(func() { globalLogger = newLogger(true, true) }) return globalLogger } func InitGlobalLogger(useColors bool, showTimestamp bool) { globalLogger = newLogger(useColors, showTimestamp) } func newLogger(useColors bool, showTimestamp bool) *Logger { return &Logger{ writer: os.Stdout, useColors: useColors, timeFormat: timeFormat, showTimestamp: showTimestamp, } } func New(useColors bool, showTimestamp bool) *Logger { return newLogger(useColors, showTimestamp) } func (l *Logger) SetOutput(w io.Writer) { l.mu.Lock() l.writer = w l.mu.Unlock() } func (l *Logger) TimeFormat() string { return l.timeFormat } func (l *Logger) SetTimeFormat(format string) { l.mu.Lock() l.timeFormat = format l.mu.Unlock() } func (l *Logger) EnableTimestamp() { l.showTimestamp = true } func (l *Logger) DisableTimestamp() { l.showTimestamp = false } func (l *Logger) EnableColors() { l.useColors = true } func (l *Logger) DisableColors() { l.useColors = false } func (l *Logger) EnableDebug() { l.debugMode.Store(true) } func (l *Logger) DisableDebug() { l.debugMode.Store(false) } func (l *Logger) IsDebugEnabled() bool { return l.debugMode.Load() } func (l *Logger) applyColor(text, color string) string { if l.useColors { return color + text + colorReset } return text } func stripAnsiColors(s string) string { result := "" inEscape := false for _, c := range s { if inEscape { if c == 'm' { inEscape = false } continue } if c == '\033' { inEscape = true continue } result += string(c) } return result } func (l *Logger) writeMessage(logType int, message string, rawMode bool) { if rawMode { l.mu.Lock() _, _ = fmt.Fprint(l.writer, message+"\n") l.mu.Unlock() return } parts := []string{} if l.showTimestamp { timestamp := time.Now().Format(l.timeFormat) if l.useColors { timestamp = l.applyColor(timestamp, colorGray) } parts = append(parts, timestamp) } if logType != TypeNone { props := typeProps[logType] tag := "[" + props.tag + "]" if l.useColors { tag = l.applyColor(tag, props.color) } parts = append(parts, tag) } parts = append(parts, message) logLine := strings.Join(parts, " ") + "\n" l.mu.Lock() _, _ = fmt.Fprint(l.writer, logLine) if logType == TypeFatal { if f, ok := l.writer.(*os.File); ok { _ = f.Sync() } } l.mu.Unlock() } func (l *Logger) log(logType int, format string, args ...any) { // Only filter debug messages if logType == TypeDebug && !l.debugMode.Load() { return } var message string if len(args) > 0 { message = fmt.Sprintf(format, args...) } else { message = format } l.writeMessage(logType, message, false) if logType == TypeFatal { os.Exit(1) } } func (l *Logger) LogRaw(format string, args ...any) { var message string if len(args) > 0 { message = fmt.Sprintf(format, args...) } else { message = format } if !l.useColors { message = stripAnsiColors(message) } l.writeMessage(TypeInfo, message, true) } func (l *Logger) Debug(format string, args ...any) { l.log(TypeDebug, format, args...) } func (l *Logger) Info(format string, args ...any) { l.log(TypeInfo, format, args...) } func (l *Logger) Warning(format string, args ...any) { l.log(TypeWarning, format, args...) } func (l *Logger) Error(format string, args ...any) { l.log(TypeError, format, args...) } func (l *Logger) Fatal(format string, args ...any) { l.log(TypeFatal, format, args...) } func (l *Logger) Server(format string, args ...any) { l.log(TypeServer, format, args...) } func (l *Logger) LogRequest(statusCode int, method, path string, duration time.Duration) { var statusColor string switch { case statusCode < 300: statusColor = colorGreen case statusCode < 400: statusColor = colorCyan case statusCode < 500: statusColor = colorYellow default: statusColor = colorRed } var durationStr string micros := duration.Microseconds() if micros < 1000 { durationStr = fmt.Sprintf("%.0fµs", float64(micros)) } else if micros < 1000000 { durationStr = fmt.Sprintf("%.1fms", float64(micros)/1000) } else { durationStr = fmt.Sprintf("%.2fs", duration.Seconds()) } message := fmt.Sprintf("%s %s %s %s", l.applyColor("["+method+"]", colorGray), l.applyColor(fmt.Sprintf("%d", statusCode), statusColor), l.applyColor(path, colorGray), l.applyColor(durationStr, colorGray), ) l.writeMessage(TypeNone, message, false) } // Global functions func Debug(format string, args ...any) { GetLogger().Debug(format, args...) } func Info(format string, args ...any) { GetLogger().Info(format, args...) } func Warning(format string, args ...any) { GetLogger().Warning(format, args...) } func Error(format string, args ...any) { GetLogger().Error(format, args...) } func Fatal(format string, args ...any) { GetLogger().Fatal(format, args...) } func Server(format string, args ...any) { GetLogger().Server(format, args...) } func LogRaw(format string, args ...any) { GetLogger().LogRaw(format, args...) } func SetOutput(w io.Writer) { GetLogger().SetOutput(w) } func TimeFormat() string { return GetLogger().TimeFormat() } func EnableDebug() { GetLogger().EnableDebug() } func DisableDebug() { GetLogger().DisableDebug() } func IsDebugEnabled() bool { return GetLogger().IsDebugEnabled() } func EnableTimestamp() { GetLogger().EnableTimestamp() } func DisableTimestamp() { GetLogger().DisableTimestamp() } func LogRequest(statusCode int, method, path string, duration time.Duration) { GetLogger().LogRequest(statusCode, method, path, duration) }