package logger import ( "fmt" "io" "os" "sync" "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" colorWhite = "\033[37m" ) // Log levels const ( LevelDebug = iota LevelInfo LevelWarning LevelError LevelFatal ) // Level names and colors var levelProps = map[int]struct { tag string color string }{ LevelDebug: {"DBG", colorCyan}, LevelInfo: {"INF", colorBlue}, LevelWarning: {"WRN", colorYellow}, LevelError: {"ERR", colorRed}, LevelFatal: {"FTL", colorPurple}, } // Time format for log messages const timeFormat = "15:04:05" // Logger handles logging operations type Logger struct { writer io.Writer level int useColors bool timeFormat string mu sync.Mutex // Mutex for thread-safe writing } // New creates a new logger func New(minLevel int, useColors bool) *Logger { return &Logger{ writer: os.Stdout, level: minLevel, useColors: useColors, timeFormat: timeFormat, } } // SetOutput changes the output destination func (l *Logger) SetOutput(w io.Writer) { l.mu.Lock() defer l.mu.Unlock() l.writer = w } // TimeFormat returns the current time format func (l *Logger) TimeFormat() string { return l.timeFormat } // SetTimeFormat changes the time format string func (l *Logger) SetTimeFormat(format string) { l.timeFormat = format } // SetLevel changes the minimum log level func (l *Logger) SetLevel(level int) { l.level = level } // EnableColors enables ANSI color codes in the output func (l *Logger) EnableColors() { l.useColors = true } // DisableColors disables ANSI color codes in the output func (l *Logger) DisableColors() { l.useColors = false } // writeMessage writes a formatted log message directly to the writer func (l *Logger) writeMessage(level int, message string, rawMode bool) { var logLine string if rawMode { // Raw mode - message is already formatted, just append newline logLine = message + "\n" } else { // Standard format with timestamp, level tag, and message now := time.Now().Format(l.timeFormat) props := levelProps[level] if l.useColors { logLine = fmt.Sprintf("%s %s[%s]%s %s\n", now, props.color, props.tag, colorReset, message) } else { logLine = fmt.Sprintf("%s [%s] %s\n", now, props.tag, message) } } // Asynchronously write the log message go func(w io.Writer, data string) { l.mu.Lock() _, _ = fmt.Fprint(w, data) l.mu.Unlock() }(l.writer, logLine) // For fatal errors, ensure we sync immediately in the current goroutine if level == LevelFatal { l.mu.Lock() if f, ok := l.writer.(*os.File); ok { _ = f.Sync() } l.mu.Unlock() } } // log handles the core logging logic with level filtering func (l *Logger) log(level int, format string, args ...any) { if level < l.level { return } var message string if len(args) > 0 { message = fmt.Sprintf(format, args...) } else { message = format } l.writeMessage(level, message, false) // Exit on fatal errors if level == LevelFatal { os.Exit(1) } } // LogRaw logs a message with raw formatting, bypassing the standard format func (l *Logger) LogRaw(format string, args ...any) { // Use info level for filtering if LevelInfo < l.level { return } var message string if len(args) > 0 { message = fmt.Sprintf(format, args...) } else { message = format } // Don't apply colors if disabled if !l.useColors { // Strip ANSI color codes if colors are disabled message = removeAnsiColors(message) } l.writeMessage(LevelInfo, message, true) } // Simple helper to remove ANSI color codes func removeAnsiColors(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 } // Debug logs a debug message func (l *Logger) Debug(format string, args ...any) { l.log(LevelDebug, format, args...) } // Info logs an informational message func (l *Logger) Info(format string, args ...any) { l.log(LevelInfo, format, args...) } // Warning logs a warning message func (l *Logger) Warning(format string, args ...any) { l.log(LevelWarning, format, args...) } // Error logs an error message func (l *Logger) Error(format string, args ...any) { l.log(LevelError, format, args...) } // Fatal logs a fatal error message and exits func (l *Logger) Fatal(format string, args ...any) { l.log(LevelFatal, format, args...) // No need for os.Exit here as it's handled in log() } // Default global logger var defaultLogger = New(LevelInfo, true) // Debug logs a debug message to the default logger func Debug(format string, args ...any) { defaultLogger.Debug(format, args...) } // Info logs an informational message to the default logger func Info(format string, args ...any) { defaultLogger.Info(format, args...) } // Warning logs a warning message to the default logger func Warning(format string, args ...any) { defaultLogger.Warning(format, args...) } // Error logs an error message to the default logger func Error(format string, args ...any) { defaultLogger.Error(format, args...) } // Fatal logs a fatal error message to the default logger and exits func Fatal(format string, args ...any) { defaultLogger.Fatal(format, args...) } // LogRaw logs a raw message to the default logger func LogRaw(format string, args ...any) { defaultLogger.LogRaw(format, args...) } // SetLevel changes the minimum log level of the default logger func SetLevel(level int) { defaultLogger.SetLevel(level) } // SetOutput changes the output destination of the default logger func SetOutput(w io.Writer) { defaultLogger.SetOutput(w) }