package logger import ( "os" "go.uber.org/zap" "go.uber.org/zap/zapcore" ) // Logger wraps zap.Logger for structured logging type Logger struct { *zap.Logger } // NewLogger creates a new logger instance func NewLogger(service string) *Logger { config := zap.NewProductionConfig() config.EncoderConfig.TimeKey = "timestamp" config.EncoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder config.EncoderConfig.MessageKey = "message" config.EncoderConfig.LevelKey = "level" // Use JSON format by default, can be overridden via env logFormat := os.Getenv("CALYPSO_LOG_FORMAT") if logFormat == "text" { config.Encoding = "console" config.EncoderConfig.EncodeLevel = zapcore.CapitalColorLevelEncoder } // Set log level from environment logLevel := os.Getenv("CALYPSO_LOG_LEVEL") if logLevel != "" { var level zapcore.Level if err := level.UnmarshalText([]byte(logLevel)); err == nil { config.Level = zap.NewAtomicLevelAt(level) } } zapLogger, err := config.Build( zap.AddCaller(), zap.AddStacktrace(zapcore.ErrorLevel), zap.Fields(zap.String("service", service)), ) if err != nil { panic(err) } return &Logger{zapLogger} } // WithFields adds structured fields to the logger func (l *Logger) WithFields(fields ...zap.Field) *Logger { return &Logger{l.Logger.With(fields...)} } // Info logs an info message with optional fields func (l *Logger) Info(msg string, fields ...interface{}) { zapFields := toZapFields(fields...) l.Logger.Info(msg, zapFields...) } // Error logs an error message with optional fields func (l *Logger) Error(msg string, fields ...interface{}) { zapFields := toZapFields(fields...) l.Logger.Error(msg, zapFields...) } // Warn logs a warning message with optional fields func (l *Logger) Warn(msg string, fields ...interface{}) { zapFields := toZapFields(fields...) l.Logger.Warn(msg, zapFields...) } // Debug logs a debug message with optional fields func (l *Logger) Debug(msg string, fields ...interface{}) { zapFields := toZapFields(fields...) l.Logger.Debug(msg, zapFields...) } // Fatal logs a fatal message and exits func (l *Logger) Fatal(msg string, fields ...interface{}) { zapFields := toZapFields(fields...) l.Logger.Fatal(msg, zapFields...) } // toZapFields converts key-value pairs to zap fields func toZapFields(fields ...interface{}) []zap.Field { zapFields := make([]zap.Field, 0, len(fields)/2) for i := 0; i < len(fields)-1; i += 2 { key, ok := fields[i].(string) if !ok { continue } zapFields = append(zapFields, zap.Any(key, fields[i+1])) } return zapFields }