package logger import ( "fmt" "io" "log" "os" "path/filepath" "runtime" "strings" "sync" "time" ) // LogLevel defines the severity of log messages type LogLevel int const ( // LevelError is for critical errors that should always be displayed LevelError LogLevel = iota // LevelWarning is for important warnings LevelWarning // LevelInfo is for informational messages LevelInfo // LevelDebug is for detailed debugging information LevelDebug // LevelTrace is for very detailed tracing information LevelTrace ) var levelNames = map[LogLevel]string{ LevelError: "ERROR", LevelWarning: "WARNING", LevelInfo: "INFO", LevelDebug: "DEBUG", LevelTrace: "TRACE", } var levelColors = map[LogLevel]string{ LevelError: "\033[1;31m", // Bold Red LevelWarning: "\033[1;33m", // Bold Yellow LevelInfo: "\033[1;32m", // Bold Green LevelDebug: "\033[1;36m", // Bold Cyan LevelTrace: "\033[1;35m", // Bold Magenta } // ResetColor is the ANSI code to reset text color const ResetColor = "\033[0m" // Logger is our custom logger with level support type Logger struct { mu sync.Mutex out io.Writer currentLevel LogLevel prefix string flag int useColors bool callerOffset int defaultFields map[string]interface{} } var ( // DefaultLogger is the global logger instance DefaultLogger *Logger // defaultLogLevel is the default log level if not specified defaultLogLevel = LevelInfo // Global mutex for DefaultLogger initialization initMutex sync.Mutex ) // ParseLevel converts a string log level to LogLevel func ParseLevel(levelStr string) LogLevel { switch strings.ToUpper(levelStr) { case "ERROR": return LevelError case "WARNING", "WARN": return LevelWarning case "INFO": return LevelInfo case "DEBUG": return LevelDebug case "TRACE": return LevelTrace default: return defaultLogLevel } } // String returns the string representation of the log level func (l LogLevel) String() string { if name, ok := levelNames[l]; ok { return name } return fmt.Sprintf("Level(%d)", l) } // New creates a new Logger instance func New(out io.Writer, prefix string, flag int) *Logger { return &Logger{ out: out, currentLevel: defaultLogLevel, prefix: prefix, flag: flag, useColors: true, callerOffset: 0, defaultFields: make(map[string]interface{}), } } // Init initializes the DefaultLogger func Init(level LogLevel) { initMutex.Lock() defer initMutex.Unlock() if DefaultLogger == nil { DefaultLogger = New(os.Stdout, "", log.Lmicroseconds|log.Lshortfile) } DefaultLogger.SetLevel(level) } // SetLevel sets the current log level func (l *Logger) SetLevel(level LogLevel) { l.mu.Lock() defer l.mu.Unlock() l.currentLevel = level } // GetLevel returns the current log level func (l *Logger) GetLevel() LogLevel { l.mu.Lock() defer l.mu.Unlock() return l.currentLevel } // SetCallerOffset sets the caller offset for correct file and line reporting func (l *Logger) SetCallerOffset(offset int) { l.mu.Lock() defer l.mu.Unlock() l.callerOffset = offset } // WithField adds a field to the logger's context func (l *Logger) WithField(key string, value interface{}) *Logger { newLogger := &Logger{ out: l.out, currentLevel: l.currentLevel, prefix: l.prefix, flag: l.flag, useColors: l.useColors, callerOffset: l.callerOffset, defaultFields: make(map[string]interface{}), } // Copy existing fields for k, v := range l.defaultFields { newLogger.defaultFields[k] = v } // Add new field newLogger.defaultFields[key] = value return newLogger } // WithFields adds multiple fields to the logger's context func (l *Logger) WithFields(fields map[string]interface{}) *Logger { newLogger := &Logger{ out: l.out, currentLevel: l.currentLevel, prefix: l.prefix, flag: l.flag, useColors: l.useColors, callerOffset: l.callerOffset, defaultFields: make(map[string]interface{}), } // Copy existing fields for k, v := range l.defaultFields { newLogger.defaultFields[k] = v } // Add new fields for k, v := range fields { newLogger.defaultFields[k] = v } return newLogger } // formatMessage formats a log message with level, time, file, and line information func (l *Logger) formatMessage(level LogLevel, format string, args ...interface{}) string { var msg string if len(args) > 0 { msg = fmt.Sprintf(format, args...) } else { msg = format } // Format default fields if any var fields string if len(l.defaultFields) > 0 { var pairs []string for k, v := range l.defaultFields { pairs = append(pairs, fmt.Sprintf("%s=%v", k, v)) } fields = " " + strings.Join(pairs, " ") } var levelColor, resetColor string if l.useColors { levelColor = levelColors[level] resetColor = ResetColor } var caller string if l.flag&log.Lshortfile != 0 || l.flag&log.Llongfile != 0 { _, file, line, ok := runtime.Caller(3 + l.callerOffset) if !ok { file = "???" line = 0 } if l.flag&log.Lshortfile != 0 { file = filepath.Base(file) } caller = fmt.Sprintf("%s:%d ", file, line) } var timeStr string if l.flag&(log.Ldate|log.Ltime|log.Lmicroseconds) != 0 { t := time.Now() if l.flag&log.Ldate != 0 { timeStr += fmt.Sprintf("%04d/%02d/%02d ", t.Year(), t.Month(), t.Day()) } if l.flag&(log.Ltime|log.Lmicroseconds) != 0 { timeStr += fmt.Sprintf("%02d:%02d:%02d", t.Hour(), t.Minute(), t.Second()) if l.flag&log.Lmicroseconds != 0 { timeStr += fmt.Sprintf(".%06d", t.Nanosecond()/1000) } timeStr += " " } } return fmt.Sprintf("%s%s%s%s[%s%s%s]%s %s\n", l.prefix, timeStr, caller, levelColor, levelNames[level], resetColor, fields, resetColor, msg) } // log logs a message at the specified level func (l *Logger) log(level LogLevel, format string, args ...interface{}) { if level > l.currentLevel { return } l.mu.Lock() defer l.mu.Unlock() msg := l.formatMessage(level, format, args...) fmt.Fprint(l.out, msg) } // Error logs an error message func (l *Logger) Error(format string, args ...interface{}) { l.log(LevelError, format, args...) } // Warning logs a warning message func (l *Logger) Warning(format string, args ...interface{}) { l.log(LevelWarning, format, args...) } // Info logs an informational message func (l *Logger) Info(format string, args ...interface{}) { l.log(LevelInfo, format, args...) } // Debug logs a debug message func (l *Logger) Debug(format string, args ...interface{}) { l.log(LevelDebug, format, args...) } // Trace logs a trace message func (l *Logger) Trace(format string, args ...interface{}) { l.log(LevelTrace, format, args...) } // Global log functions that use DefaultLogger // Error logs an error message using the default logger func Error(format string, args ...interface{}) { if DefaultLogger == nil { Init(defaultLogLevel) } DefaultLogger.Error(format, args...) } // Warning logs a warning message using the default logger func Warning(format string, args ...interface{}) { if DefaultLogger == nil { Init(defaultLogLevel) } DefaultLogger.Warning(format, args...) } // Info logs an informational message using the default logger func Info(format string, args ...interface{}) { if DefaultLogger == nil { Init(defaultLogLevel) } DefaultLogger.Info(format, args...) } // Debug logs a debug message using the default logger func Debug(format string, args ...interface{}) { if DefaultLogger == nil { Init(defaultLogLevel) } DefaultLogger.Debug(format, args...) } // Trace logs a trace message using the default logger func Trace(format string, args ...interface{}) { if DefaultLogger == nil { Init(defaultLogLevel) } DefaultLogger.Trace(format, args...) } // SetLevel sets the log level for the default logger func SetLevel(level LogLevel) { if DefaultLogger == nil { Init(level) return } DefaultLogger.SetLevel(level) } // GetLevel gets the log level for the default logger func GetLevel() LogLevel { if DefaultLogger == nil { Init(defaultLogLevel) } return DefaultLogger.GetLevel() } // WithField returns a new logger with the field added to the default logger's context func WithField(key string, value interface{}) *Logger { if DefaultLogger == nil { Init(defaultLogLevel) } return DefaultLogger.WithField(key, value) } // WithFields returns a new logger with the fields added to the default logger's context func WithFields(fields map[string]interface{}) *Logger { if DefaultLogger == nil { Init(defaultLogLevel) } return DefaultLogger.WithFields(fields) }