package logger import ( "fmt" "codeberg.org/mapleopentech/monorepo/cloud/maplepress-backend/config" "go.uber.org/zap" "go.uber.org/zap/zapcore" ) // emojiCore wraps a zapcore.Core to add emoji icon field type emojiCore struct { zapcore.Core } func (c *emojiCore) With(fields []zapcore.Field) zapcore.Core { return &emojiCore{c.Core.With(fields)} } func (c *emojiCore) Check(entry zapcore.Entry, ce *zapcore.CheckedEntry) *zapcore.CheckedEntry { if c.Enabled(entry.Level) { return ce.AddCore(entry, c) } return ce } func (c *emojiCore) Write(entry zapcore.Entry, fields []zapcore.Field) error { // Only add emoji icon field for warnings and errors // Skip for info and debug to keep output clean var emoji string var addEmoji bool switch entry.Level { case zapcore.WarnLevel: emoji = "🟡" // Yellow circle for warnings addEmoji = true case zapcore.ErrorLevel: emoji = "🔴" // Red circle for errors addEmoji = true case zapcore.DPanicLevel: emoji = "🔴" // Red circle for panic addEmoji = true case zapcore.PanicLevel: emoji = "🔴" // Red circle for panic addEmoji = true case zapcore.FatalLevel: emoji = "🔴" // Red circle for fatal addEmoji = true default: // No emoji for debug and info levels addEmoji = false } // Only prepend emoji field if we're adding one if addEmoji { fieldsWithEmoji := make([]zapcore.Field, 0, len(fields)+1) fieldsWithEmoji = append(fieldsWithEmoji, zap.String("ico", emoji)) fieldsWithEmoji = append(fieldsWithEmoji, fields...) return c.Core.Write(entry, fieldsWithEmoji) } // For debug/info, write as-is without emoji return c.Core.Write(entry, fields) } // ProvideLogger creates a new zap logger based on configuration func ProvideLogger(cfg *config.Config) (*zap.Logger, error) { var zapConfig zap.Config // Set config based on environment if cfg.App.Environment == "production" { zapConfig = zap.NewProductionConfig() } else { zapConfig = zap.NewDevelopmentConfig() } // Set log level level, err := zapcore.ParseLevel(cfg.Logger.Level) if err != nil { return nil, fmt.Errorf("invalid log level %s: %w", cfg.Logger.Level, err) } zapConfig.Level = zap.NewAtomicLevelAt(level) // Set encoding format if cfg.Logger.Format == "console" { zapConfig.Encoding = "console" zapConfig.EncoderConfig.EncodeLevel = zapcore.CapitalColorLevelEncoder } else { zapConfig.Encoding = "json" } // Build logger with environment-specific options var loggerOptions []zap.Option // Enable caller information in development for easier debugging if cfg.App.Environment != "production" { loggerOptions = append(loggerOptions, zap.AddCaller()) loggerOptions = append(loggerOptions, zap.AddCallerSkip(0)) } // Add stack traces for error level and above loggerOptions = append(loggerOptions, zap.AddStacktrace(zapcore.ErrorLevel)) // Wrap core with emoji core to add icon field loggerOptions = append(loggerOptions, zap.WrapCore(func(core zapcore.Core) zapcore.Core { return &emojiCore{core} })) logger, err := zapConfig.Build(loggerOptions...) if err != nil { return nil, fmt.Errorf("failed to build logger: %w", err) } logger.Info("✓ Logger initialized", zap.String("level", cfg.Logger.Level), zap.String("format", cfg.Logger.Format), zap.String("environment", cfg.App.Environment)) return logger, nil }