Files
spsays-bff/cmd/api/main.go
T

100 lines
2.6 KiB
Go

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")
}