package logger import ( "context" "os" "strings" "sync" "git.hujye.com/infrastructure/go-web-gin/config" "git.hujye.com/infrastructure/go-web-gin/env" "git.hujye.com/infrastructure/go-web-gin/web" "go.uber.org/zap" "go.uber.org/zap/zapcore" "gopkg.in/natefinch/lumberjack.v2" ) type Logger struct { logger *zap.Logger } var ( instance *Logger once sync.Once ) // parseLogLevel converts string log level to zapcore.Level func parseLogLevel(level string) zapcore.Level { switch strings.ToLower(level) { case "debug": return zapcore.DebugLevel case "info": return zapcore.InfoLevel case "warn", "warning": return zapcore.WarnLevel case "error": return zapcore.ErrorLevel case "fatal": return zapcore.FatalLevel default: return zapcore.InfoLevel } } // GetLogger returns the singleton Logger instance // Logger configuration is based on RUN_ENV environment variable and config file func GetLogger() *Logger { once.Do(func() { runEnv := env.GetRunEnv() cfg := config.Get() // Production and Development use JSON encoder // Local uses console encoder for better readability var encoderType EncoderType if runEnv == env.Production || runEnv == env.Development { encoderType = JSONEncoder } else { encoderType = ConsoleEncoder } // Create encoder encoder := NewEncoder(encoderType) // Create writer syncs (console + file if enabled) var writerSyncs zapcore.WriteSyncer if cfg.Log.OutputToFile { // Create rotating file writer with lumberjack fileWriter := &lumberjack.Logger{ Filename: cfg.Log.Filename, MaxSize: cfg.Log.MaxSize, MaxBackups: cfg.Log.MaxBackups, MaxAge: cfg.Log.MaxAge, Compress: cfg.Log.Compress, } // Write to both console and file writerSyncs = zapcore.NewMultiWriteSyncer(zapcore.AddSync(os.Stderr), zapcore.AddSync(fileWriter)) } else { // Write to console only writerSyncs = zapcore.AddSync(os.Stderr) } // Parse log level from config logLevel := parseLogLevel(cfg.App.LogLevel) // Create core with custom encoder core := zapcore.NewCore( encoder, writerSyncs, logLevel, ) logger := zap.New(core, zap.AddCaller(), zap.AddCallerSkip(1), zap.AddStacktrace(zapcore.ErrorLevel)) instance = &Logger{ logger: logger, } }) return instance } // getUserFields extracts user information from context and returns zap fields func getUserFields(ctx context.Context) []zap.Field { if ctx == nil { return nil } fields := []zap.Field{} if userID := web.GetUserID(ctx); userID != "" { fields = append(fields, zap.String("user_id", userID)) } if userName := web.GetUserName(ctx); userName != "" { fields = append(fields, zap.String("user_name", userName)) } if trace := web.GetTrace(ctx); trace != "" { fields = append(fields, zap.String("trace", trace)) } if fromIP := web.GetFromIP(ctx); fromIP != "" { fields = append(fields, zap.String("from_ip", fromIP)) } if province := web.GetProvince(ctx); province != "" { fields = append(fields, zap.String("province", province)) } if city := web.GetCity(ctx); city != "" { fields = append(fields, zap.String("city", city)) } if len(fields) == 0 { return nil } return fields } // Info logs an info message func (l *Logger) Info(ctx context.Context, msg string, keyValues ...interface{}) { allFields := getUserFields(ctx) for i := 0; i < len(keyValues); i += 2 { if i+1 < len(keyValues) { if key, ok := keyValues[i].(string); ok { allFields = append(allFields, zap.Any(key, keyValues[i+1])) } } } l.logger.Info(msg, allFields...) } // Error logs an error message func (l *Logger) Error(ctx context.Context, msg string, keyValues ...interface{}) { allFields := getUserFields(ctx) for i := 0; i < len(keyValues); i += 2 { if i+1 < len(keyValues) { if key, ok := keyValues[i].(string); ok { allFields = append(allFields, zap.Any(key, keyValues[i+1])) } } } l.logger.Error(msg, allFields...) } // Debug logs a debug message func (l *Logger) Debug(ctx context.Context, msg string, keyValues ...interface{}) { allFields := getUserFields(ctx) for i := 0; i < len(keyValues); i += 2 { if i+1 < len(keyValues) { if key, ok := keyValues[i].(string); ok { allFields = append(allFields, zap.Any(key, keyValues[i+1])) } } } l.logger.Debug(msg, allFields...) } // Warn logs a warning message func (l *Logger) Warn(ctx context.Context, msg string, keyValues ...interface{}) { allFields := getUserFields(ctx) for i := 0; i < len(keyValues); i += 2 { if i+1 < len(keyValues) { if key, ok := keyValues[i].(string); ok { allFields = append(allFields, zap.Any(key, keyValues[i+1])) } } } l.logger.Warn(msg, allFields...) } // Fatal logs a fatal message and exits func (l *Logger) Fatal(ctx context.Context, msg string, keyValues ...interface{}) { allFields := getUserFields(ctx) for i := 0; i < len(keyValues); i += 2 { if i+1 < len(keyValues) { if key, ok := keyValues[i].(string); ok { allFields = append(allFields, zap.Any(key, keyValues[i+1])) } } } l.logger.Fatal(msg, allFields...) } // Sync flushes any buffered log entries func (l *Logger) Sync() error { return l.logger.Sync() }