100 lines
2.6 KiB
Go
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")
|
|
}
|