package main import ( "context" "fmt" "net/http" "os" "os/signal" "syscall" "time" "github.com/prabhupadasays/spsays-bff/internal/config" "github.com/prabhupadasays/spsays-bff/internal/server" "go.uber.org/zap" "go.uber.org/zap/zapcore" ) // newLogger builds the app logger, routing logs by level: Debug/Info/Warn go // to stdout and only Error/Fatal/Panic go to stderr. Hosting platforms tag // every stderr line as "error", so without this split a routine Debug/Info // line shows up with a misleading error badge. Production uses JSON at Info+; // development uses colored console at Debug+. func newLogger(production bool) *zap.Logger { var encoder zapcore.Encoder minLevel := zapcore.DebugLevel if production { encoder = zapcore.NewJSONEncoder(zap.NewProductionEncoderConfig()) minLevel = zapcore.InfoLevel } else { encCfg := zap.NewDevelopmentEncoderConfig() encCfg.EncodeLevel = zapcore.CapitalColorLevelEncoder encoder = zapcore.NewConsoleEncoder(encCfg) } low := zap.LevelEnablerFunc(func(l zapcore.Level) bool { return l >= minLevel && l < zapcore.ErrorLevel }) high := zap.LevelEnablerFunc(func(l zapcore.Level) bool { return l >= zapcore.ErrorLevel }) core := zapcore.NewTee( zapcore.NewCore(encoder, zapcore.Lock(os.Stdout), low), zapcore.NewCore(encoder, zapcore.Lock(os.Stderr), high), ) return zap.New(core, zap.AddCaller(), zap.AddStacktrace(zapcore.ErrorLevel)) } func main() { // Load config cfg, err := config.Load() if err != nil { fmt.Fprintf(os.Stderr, "failed to load config: %v\n", err) os.Exit(1) } // Initialize logger — routes normal logs to stdout, errors to stderr. logger := newLogger(cfg.Env == "production") defer logger.Sync() sugar := logger.Sugar() // Create server srv := server.New(cfg, logger) // Start HTTP server httpServer := &http.Server{ Addr: fmt.Sprintf(":%d", cfg.Port), Handler: srv.Router(), ReadTimeout: 15 * time.Second, WriteTimeout: 15 * time.Second, IdleTimeout: 60 * time.Second, } // Graceful shutdown go func() { sugar.Infof("🚀 Server starting on port %d (%s)", cfg.Port, cfg.Env) if err := httpServer.ListenAndServe(); err != nil && err != http.ErrServerClosed { sugar.Fatalf("server error: %v", err) } }() quit := make(chan os.Signal, 1) signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM) <-quit sugar.Info("🛑 Shutting down server...") ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) defer cancel() if err := httpServer.Shutdown(ctx); err != nil { sugar.Fatalf("forced shutdown: %v", err) } // Close connections srv.Close() sugar.Info("✅ Server stopped gracefully") }