// Package logging configures the backend structured logger. package logging import ( "strings" "galaxy/backend/internal/config" "go.uber.org/zap" "go.uber.org/zap/zapcore" ) // New constructs the process-wide JSON logger from cfg. func New(cfg config.LoggingConfig) (*zap.Logger, error) { level := zap.NewAtomicLevel() if err := level.UnmarshalText([]byte(strings.TrimSpace(cfg.Level))); err != nil { return nil, err } zapCfg := zap.NewProductionConfig() zapCfg.Level = level zapCfg.Sampling = nil zapCfg.Encoding = "json" zapCfg.EncoderConfig.TimeKey = "timestamp" zapCfg.EncoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder zapCfg.OutputPaths = []string{"stdout"} zapCfg.ErrorOutputPaths = []string{"stderr"} return zapCfg.Build() } // Sync flushes logger and ignores the benign stdout or stderr sync errors // commonly returned by containerized or redirected process outputs. func Sync(logger *zap.Logger) error { if logger == nil { return nil } err := logger.Sync() if err == nil || isIgnorableSyncError(err) { return nil } return err } func isIgnorableSyncError(err error) bool { if err == nil { return false } message := strings.ToLower(err.Error()) switch { case strings.Contains(message, "invalid argument"): return true case strings.Contains(message, "bad file descriptor"): return true case strings.Contains(message, "inappropriate ioctl for device"): return true default: return false } }