466 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			466 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
 | |
| 	// LevelLua is specifically for output from Lua scripts
 | |
| 	LevelLua
 | |
| )
 | |
| 
 | |
| var levelNames = map[LogLevel]string{
 | |
| 	LevelError:   "ERROR",
 | |
| 	LevelWarning: "WARNING",
 | |
| 	LevelInfo:    "INFO",
 | |
| 	LevelDebug:   "DEBUG",
 | |
| 	LevelTrace:   "TRACE",
 | |
| 	LevelLua:     "LUA",
 | |
| }
 | |
| 
 | |
| 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
 | |
| 	LevelLua:     "\033[1;34m", // Bold Blue
 | |
| }
 | |
| 
 | |
| // 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
 | |
| 	case "LUA":
 | |
| 		return LevelLua
 | |
| 	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{}) {
 | |
| 	// Always show LUA level logs regardless of the current log level
 | |
| 	if level != LevelLua && 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...)
 | |
| }
 | |
| 
 | |
| // Lua logs a Lua message
 | |
| func (l *Logger) Lua(format string, args ...interface{}) {
 | |
| 	l.log(LevelLua, 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...)
 | |
| }
 | |
| 
 | |
| // Lua logs a Lua message using the default logger
 | |
| func Lua(format string, args ...interface{}) {
 | |
| 	if DefaultLogger == nil {
 | |
| 		Init(defaultLogLevel)
 | |
| 	}
 | |
| 	DefaultLogger.Lua(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()
 | |
| }
 |