package main import ( "context" "net" "net/http" "os" "os/signal" "syscall" "time" "DanceLessonsCoach/pkg/config" "DanceLessonsCoach/pkg/server" "github.com/rs/zerolog" "github.com/rs/zerolog/log" ) func main() { // Initialize Zerolog with default console format first zerolog.SetGlobalLevel(zerolog.TraceLevel) zerolog.TimeFieldFormat = zerolog.TimeFormatUnix consoleWriter := zerolog.ConsoleWriter{Out: os.Stderr} // Check if JSON logging is requested via environment variable // This allows JSON logging even during config loading jsonLogging := os.Getenv("DLC_LOGGING_JSON") == "true" if jsonLogging { // JSON output for structured logging log.Logger = log.Output(os.Stderr) } else { // Console output for initial logging log.Logger = log.Output(consoleWriter) } // Load configuration cfg, err := config.LoadConfig() if err != nil { log.Fatal().Err(err).Msg("Failed to load configuration") } // Reconfigure logging based on loaded configuration (overrides env var) if cfg.Logging.JSON { // JSON output for structured logging log.Logger = log.Output(os.Stderr) } else { // Keep console output log.Logger = log.Output(consoleWriter) } log.Info().Bool("json_logging", cfg.Logging.JSON).Msg("Logging configured") // Setup signal context for graceful shutdown rootCtx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM) defer stop() // Create root context with cancellation ctx, cancel := context.WithCancel(context.Background()) defer cancel() // Create ongoing context for active requests ongoingCtx, stopOngoingGracefully := context.WithCancel(context.Background()) // Create readiness context to control readiness state readyCtx, readyCancel := context.WithCancel(context.Background()) defer readyCancel() // Start server in goroutine server := server.NewServer(cfg, readyCtx) serverCtx, serverStop := context.WithCancel(ctx) go func() { log.Info().Str("address", cfg.GetServerAddress()).Msg("Server running") srv := &http.Server{ Addr: cfg.GetServerAddress(), Handler: server.Router(), BaseContext: func(_ net.Listener) context.Context { return ongoingCtx }, } // Start the HTTP server in a separate goroutine go func() { if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed { log.Error().Err(err).Msg("Server error") } }() // Wait for signal <-rootCtx.Done() stop() log.Info().Msg("Shutdown signal received") // Cancel readiness context to stop accepting new requests readyCancel() log.Info().Msg("Readiness set to false, no longer accepting new requests") // Give time for readiness check to propagate (simplified for our case) time.Sleep(1 * time.Second) log.Info().Msg("Readiness check propagated, now waiting for ongoing requests to finish.") // Create shutdown context with timeout from config shutdownCtx, shutdownCancel := context.WithTimeout(context.Background(), cfg.Shutdown.Timeout) defer shutdownCancel() if err := srv.Shutdown(shutdownCtx); err != nil { log.Error().Err(err).Msg("Server shutdown failed") } else { log.Info().Msg("Server shutdown complete") } // Stop ongoing requests context stopOngoingGracefully() cancel() serverStop() log.Info().Msg("Server exited") // Force log flush by writing to stderr directly // This ensures logs are written before process exits time.Sleep(100 * time.Millisecond) }() // Wait for shutdown <-serverCtx.Done() }