package logger import ( "fmt" "io" "os" "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" colorWhite = "\033[37m" colorGray = "\033[90m" ) // Log levels const ( LevelDebug = iota LevelInfo LevelWarning LevelError LevelServer LevelFatal ) // Level names and colors var levelProps = map[int]struct { tag string color string }{ LevelDebug: {"DEBUG", colorCyan}, LevelInfo: {" INFO", colorBlue}, LevelWarning: {" WARN", colorYellow}, LevelError: {"ERROR", colorRed}, LevelServer: {" SYS", colorGreen}, LevelFatal: {"FATAL", colorPurple}, } // Time format for log messages const timeFormat = "15:04:05" // Default rate limiting settings const ( defaultMaxLogs = 1000 // Max logs per second before rate limiting defaultRateLimitTime = 10 * time.Second // How long to pause during rate limiting ) // Single global logger instance with mutex for safe initialization var ( globalLogger *Logger globalLoggerOnce sync.Once ) // Logger handles logging operations type Logger struct { writer io.Writer level int useColors bool timeFormat string mu sync.Mutex // Mutex for thread-safe writing debugMode atomic.Bool // Force debug logging regardless of level // Simple rate limiting logCount atomic.Int64 // Number of logs in current window logCountStart atomic.Int64 // Start time of current counting window rateLimited atomic.Bool // Whether we're currently rate limited rateLimitUntil atomic.Int64 // Timestamp when rate limiting ends maxLogsPerSec int64 // Maximum logs per second before limiting limitDuration time.Duration // How long to pause logging when rate limited } // GetLogger returns the global logger instance, creating it if needed func GetLogger() *Logger { globalLoggerOnce.Do(func() { globalLogger = newLogger(LevelInfo, true) }) return globalLogger } // InitGlobalLogger initializes the global logger with custom settings func InitGlobalLogger(minLevel int, useColors bool) { globalLoggerOnce.Do(func() { globalLogger = newLogger(minLevel, useColors) }) } // newLogger creates a new logger instance (internal use) func newLogger(minLevel int, useColors bool) *Logger { logger := &Logger{ writer: os.Stdout, level: minLevel, useColors: useColors, timeFormat: timeFormat, maxLogsPerSec: defaultMaxLogs, limitDuration: defaultRateLimitTime, } // Initialize counters logger.resetCounters() return logger } // New creates a new logger (deprecated - use GetLogger() instead) func New(minLevel int, useColors bool) *Logger { return newLogger(minLevel, useColors) } // resetCounters resets the rate limiting counters func (l *Logger) resetCounters() { l.logCount.Store(0) l.logCountStart.Store(time.Now().Unix()) l.rateLimited.Store(false) l.rateLimitUntil.Store(0) } // 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 } // EnableDebug forces debug logs to be shown regardless of level func (l *Logger) EnableDebug() { l.debugMode.Store(true) } // DisableDebug stops forcing debug logs func (l *Logger) DisableDebug() { l.debugMode.Store(false) } // IsDebugEnabled returns true if debug mode is enabled func (l *Logger) IsDebugEnabled() bool { return l.debugMode.Load() } // 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%s %s\n", colorGray, now, colorReset, props.color, props.tag, colorReset, message) } else { logLine = fmt.Sprintf("%s %s %s\n", now, props.tag, message) } } // Synchronously write the log message l.mu.Lock() _, _ = fmt.Fprint(l.writer, logLine) // For fatal errors, ensure we sync immediately if level == LevelFatal { if f, ok := l.writer.(*os.File); ok { _ = f.Sync() } } l.mu.Unlock() } // checkRateLimit checks if we should rate limit logging // Returns true if the message should be logged, false if it should be dropped func (l *Logger) checkRateLimit(level int) bool { // High priority messages are never rate limited if level >= LevelWarning { return true } // Check if we're currently in a rate-limited period if l.rateLimited.Load() { now := time.Now().Unix() limitUntil := l.rateLimitUntil.Load() if now >= limitUntil { // Rate limiting period is over l.rateLimited.Store(false) l.resetCounters() } else { // Still in rate limiting period, drop the message return false } } // If not rate limited, check if we should start rate limiting count := l.logCount.Add(1) // Check if we need to reset the counter for a new second now := time.Now().Unix() start := l.logCountStart.Load() if now > start { // New second, reset counter l.logCount.Store(1) // Count this message l.logCountStart.Store(now) return true } // Check if we've exceeded our threshold if count > l.maxLogsPerSec { // Start rate limiting l.rateLimited.Store(true) l.rateLimitUntil.Store(now + int64(l.limitDuration.Seconds())) // Log a warning about rate limiting l.writeMessage(LevelServer, fmt.Sprintf("Rate limiting logger temporarily due to high demand (%d logs/sec exceeded)", count), false) return false } return true } // log handles the core logging logic with level filtering func (l *Logger) log(level int, format string, args ...any) { // Check if we should log this message // Either level is high enough OR (it's a debug message AND debug mode is enabled) if level < l.level && !(level == LevelDebug && l.debugMode.Load()) { return } // Check rate limiting - always log high priority messages if !l.checkRateLimit(level) { return } // Format message 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 } // Check rate limiting if !l.checkRateLimit(LevelInfo) { 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() } // Server logs a server message func (l *Logger) Server(format string, args ...any) { l.log(LevelServer, format, args...) } // Global helper functions that use the global logger // Debug logs a debug message to the global logger func Debug(format string, args ...any) { GetLogger().Debug(format, args...) } // Info logs an informational message to the global logger func Info(format string, args ...any) { GetLogger().Info(format, args...) } // Warning logs a warning message to the global logger func Warning(format string, args ...any) { GetLogger().Warning(format, args...) } // Error logs an error message to the global logger func Error(format string, args ...any) { GetLogger().Error(format, args...) } // Fatal logs a fatal error message to the global logger and exits func Fatal(format string, args ...any) { GetLogger().Fatal(format, args...) } // Server logs a server message to the global logger func Server(format string, args ...any) { GetLogger().Server(format, args...) } // LogRaw logs a raw message to the global logger func LogRaw(format string, args ...any) { GetLogger().LogRaw(format, args...) } // SetLevel changes the minimum log level of the global logger func SetLevel(level int) { GetLogger().SetLevel(level) } // SetOutput changes the output destination of the global logger func SetOutput(w io.Writer) { GetLogger().SetOutput(w) } // TimeFormat returns the current time format of the global logger func TimeFormat() string { return GetLogger().TimeFormat() } // EnableDebug enables debug messages regardless of log level func EnableDebug() { GetLogger().EnableDebug() } // DisableDebug disables forced debug messages func DisableDebug() { GetLogger().DisableDebug() } // IsDebugEnabled returns true if debug mode is enabled func IsDebugEnabled() bool { return GetLogger().IsDebugEnabled() }