446 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			446 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
package logger
 | 
						|
 | 
						|
import (
 | 
						|
	"bytes"
 | 
						|
	"fmt"
 | 
						|
	"io"
 | 
						|
	"log"
 | 
						|
	"os"
 | 
						|
	"path/filepath"
 | 
						|
	"runtime"
 | 
						|
	"strconv"
 | 
						|
	"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{}
 | 
						|
	showGoroutine bool
 | 
						|
}
 | 
						|
 | 
						|
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{}),
 | 
						|
		showGoroutine: true,
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// 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
 | 
						|
}
 | 
						|
 | 
						|
// SetShowGoroutine sets whether to include goroutine ID in log messages
 | 
						|
func (l *Logger) SetShowGoroutine(show bool) {
 | 
						|
	l.mu.Lock()
 | 
						|
	defer l.mu.Unlock()
 | 
						|
	l.showGoroutine = show
 | 
						|
}
 | 
						|
 | 
						|
// ShowGoroutine returns whether goroutine ID is included in log messages
 | 
						|
func (l *Logger) ShowGoroutine() bool {
 | 
						|
	l.mu.Lock()
 | 
						|
	defer l.mu.Unlock()
 | 
						|
	return l.showGoroutine
 | 
						|
}
 | 
						|
 | 
						|
// 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{}),
 | 
						|
		showGoroutine: l.showGoroutine,
 | 
						|
	}
 | 
						|
 | 
						|
	// 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{}),
 | 
						|
		showGoroutine: l.showGoroutine,
 | 
						|
	}
 | 
						|
 | 
						|
	// 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
 | 
						|
}
 | 
						|
 | 
						|
// GetGoroutineID extracts the goroutine ID from the runtime stack
 | 
						|
func GetGoroutineID() string {
 | 
						|
	buf := make([]byte, 64)
 | 
						|
	n := runtime.Stack(buf, false)
 | 
						|
	// Format of first line is "goroutine N [state]:"
 | 
						|
	// We only need the N part
 | 
						|
	buf = buf[:n]
 | 
						|
	idField := bytes.Fields(bytes.Split(buf, []byte{':'})[0])[1]
 | 
						|
	return string(idField)
 | 
						|
}
 | 
						|
 | 
						|
// 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 {
 | 
						|
		// Find the actual caller by scanning up the stack
 | 
						|
		// until we find a function outside the logger package
 | 
						|
		var file string
 | 
						|
		var line int
 | 
						|
		var ok bool
 | 
						|
 | 
						|
		// Start at a reasonable depth and scan up to 10 frames
 | 
						|
		for depth := 4; depth < 15; depth++ {
 | 
						|
			_, file, line, ok = runtime.Caller(depth)
 | 
						|
			if !ok {
 | 
						|
				break
 | 
						|
			}
 | 
						|
 | 
						|
			// If the caller is not in the logger package, we found our caller
 | 
						|
			if !strings.Contains(file, "logger/logger.go") {
 | 
						|
				break
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		if !ok {
 | 
						|
			file = "???"
 | 
						|
			line = 0
 | 
						|
		}
 | 
						|
 | 
						|
		if l.flag&log.Lshortfile != 0 {
 | 
						|
			file = filepath.Base(file)
 | 
						|
		}
 | 
						|
		caller = fmt.Sprintf("%-25s ", file+":"+strconv.Itoa(line))
 | 
						|
	}
 | 
						|
 | 
						|
	// Format the timestamp with fixed width
 | 
						|
	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 = fmt.Sprintf("%-15s ", timeStr)
 | 
						|
	}
 | 
						|
 | 
						|
	// Add goroutine ID if enabled, with fixed width
 | 
						|
	var goroutineStr string
 | 
						|
	if l.showGoroutine {
 | 
						|
		goroutineID := GetGoroutineID()
 | 
						|
		goroutineStr = fmt.Sprintf("[g:%-4s] ", goroutineID)
 | 
						|
	}
 | 
						|
 | 
						|
	// Create a colored level indicator with both brackets colored
 | 
						|
	levelStr := fmt.Sprintf("%s[%s]%s", levelColor, levelNames[level], levelColor)
 | 
						|
	// Add a space after the level and before the reset color
 | 
						|
	levelColumn := fmt.Sprintf("%s %s", levelStr, resetColor)
 | 
						|
 | 
						|
	return fmt.Sprintf("%s%s%s%s%s%s%s\n",
 | 
						|
		l.prefix, timeStr, caller, goroutineStr, levelColumn, msg, fields)
 | 
						|
}
 | 
						|
 | 
						|
// 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...)
 | 
						|
}
 | 
						|
 | 
						|
// LogPanic logs a panic error and its stack trace
 | 
						|
func LogPanic(r interface{}) {
 | 
						|
	if DefaultLogger == nil {
 | 
						|
		Init(defaultLogLevel)
 | 
						|
	}
 | 
						|
	stack := make([]byte, 4096)
 | 
						|
	n := runtime.Stack(stack, false)
 | 
						|
	DefaultLogger.Error("PANIC: %v\n%s", r, stack[:n])
 | 
						|
}
 | 
						|
 | 
						|
// 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)
 | 
						|
}
 | 
						|
 | 
						|
// SetShowGoroutine enables or disables goroutine ID display in the default logger
 | 
						|
func SetShowGoroutine(show bool) {
 | 
						|
	if DefaultLogger == nil {
 | 
						|
		Init(defaultLogLevel)
 | 
						|
	}
 | 
						|
	DefaultLogger.SetShowGoroutine(show)
 | 
						|
}
 | 
						|
 | 
						|
// ShowGoroutine returns whether goroutine ID is included in default logger's messages
 | 
						|
func ShowGoroutine() bool {
 | 
						|
	if DefaultLogger == nil {
 | 
						|
		Init(defaultLogLevel)
 | 
						|
	}
 | 
						|
	return DefaultLogger.ShowGoroutine()
 | 
						|
}
 |