diff --git a/cmd/server/main.go b/cmd/server/main.go index ebf8368..ac9a6f9 100644 --- a/cmd/server/main.go +++ b/cmd/server/main.go @@ -2,9 +2,12 @@ package main import ( "context" - "log" + "log/slog" "net/http" "os" + "os/signal" + "syscall" + "time" "gitea.mrixs.me/minecraft-platform/backend/internal/api" "gitea.mrixs.me/minecraft-platform/backend/internal/core" @@ -15,6 +18,12 @@ import ( ) func main() { + // --- Инициализация логгера (slog) --- + logger := slog.New(slog.NewJSONHandler(os.Stdout, nil)) + slog.SetDefault(logger) + + slog.Info("Starting backend server initialization...") + dbPool := database.Connect() defer dbPool.Close() @@ -30,25 +39,32 @@ func main() { keyPath := os.Getenv("RSA_PRIVATE_KEY_PATH") if keyPath == "" { - log.Fatal("RSA_PRIVATE_KEY_PATH environment variable is not set") + slog.Error("RSA_PRIVATE_KEY_PATH environment variable is not set") + os.Exit(1) } domain := os.Getenv("APP_DOMAIN") if domain == "" { - log.Fatal("APP_DOMAIN environment variable is not set") + slog.Error("APP_DOMAIN environment variable is not set") + os.Exit(1) } profileService, err := core.NewProfileService(userRepo, keyPath, domain) if err != nil { - log.Fatalf("Failed to create profile service: %v", err) + slog.Error("Failed to create profile service", "error", err) + os.Exit(1) } modpacksStoragePath := os.Getenv("MODPACKS_STORAGE_PATH") if modpacksStoragePath == "" { - log.Fatal("MODPACKS_STORAGE_PATH environment variable is not set") + slog.Error("MODPACKS_STORAGE_PATH environment variable is not set") + os.Exit(1) } janitorService := core.NewFileJanitorService(modpackRepo, modpacksStoragePath) // --- Запуск фоновых задач --- - go serverPoller.Start(context.Background()) + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + go serverPoller.Start(ctx) // Передаем контекст для отмены // --- Инициализация хендлеров --- userHandler := &api.UserHandler{Service: userService} @@ -60,13 +76,19 @@ func main() { ModpackRepo: modpackRepo, JanitorService: janitorService, } - adminUserHandler := &api.AdminUserHandler{UserRepo: userRepo} // Этот хендлер мы создали для админских функций + adminUserHandler := &api.AdminUserHandler{UserRepo: userRepo} // --- Настройка роутера --- r := chi.NewRouter() - r.Use(middleware.Logger) + r.Use(middleware.Logger) // Можно заменить на slog middleware, но пока оставим standard r.Use(middleware.Recoverer) + // Health Check + r.Get("/health", func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + w.Write([]byte("OK")) + }) + // --- Публичные роуты --- r.Route("/api", func(r chi.Router) { r.Post("/register", userHandler.Register) @@ -103,15 +125,40 @@ func main() { }) r.Route("/users", func(r chi.Router) { - // ИСПРАВЛЕНО: Используем adminUserHandler r.Get("/", adminUserHandler.GetAllUsers) r.Patch("/{id}/role", adminUserHandler.UpdateUserRole) }) }) }) - log.Println("Starting backend server on :8080") - if err := http.ListenAndServe(":8080", r); err != nil { - log.Fatalf("Failed to start server: %v", err) + // --- Graceful Shutdown --- + srv := &http.Server{ + Addr: ":8080", + Handler: r, } + + go func() { + slog.Info("Starting backend server on :8080") + if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed { + slog.Error("Failed to start server", "error", err) + os.Exit(1) + } + }() + + // Wait for interrupt signal to gracefully shutdown the server with a timeout. + quit := make(chan os.Signal, 1) + signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM) + <-quit + slog.Info("Shutting down server...") + + // The context is used to inform the server it has 5 seconds to finish + // the request it is currently handling + shutdownCtx, shutdownCancel := context.WithTimeout(context.Background(), 5*time.Second) + defer shutdownCancel() + + if err := srv.Shutdown(shutdownCtx); err != nil { + slog.Error("Server forced to shutdown", "error", err) + } + + slog.Info("Server exiting") } diff --git a/internal/api/auth_handler.go b/internal/api/auth_handler.go index 566cf0b..9cb8d10 100644 --- a/internal/api/auth_handler.go +++ b/internal/api/auth_handler.go @@ -3,7 +3,7 @@ package api import ( "encoding/json" "errors" - "log" + "log/slog" "net/http" "gitea.mrixs.me/minecraft-platform/backend/internal/core" @@ -40,7 +40,7 @@ func (h *AuthHandler) Authenticate(w http.ResponseWriter, r *http.Request) { } // Другие ошибки - внутренние - log.Printf("internal server error during authentication: %v", err) + slog.Error("internal server error during authentication", "error", err) http.Error(w, "Internal server error", http.StatusInternalServerError) return } @@ -69,7 +69,7 @@ func (h *AuthHandler) Join(w http.ResponseWriter, r *http.Request) { return } - log.Printf("internal server error during join: %v", err) + slog.Error("internal server error during join", "error", err) http.Error(w, "Internal server error", http.StatusInternalServerError) return } @@ -90,7 +90,7 @@ func (h *AuthHandler) Login(w http.ResponseWriter, r *http.Request) { http.Error(w, "Invalid username or password", http.StatusUnauthorized) return } - log.Printf("internal server error during login: %v", err) + slog.Error("internal server error during login", "error", err) http.Error(w, "Internal server error", http.StatusInternalServerError) return } diff --git a/internal/core/server_poller.go b/internal/core/server_poller.go index f838bd3..0410aa6 100644 --- a/internal/core/server_poller.go +++ b/internal/core/server_poller.go @@ -3,7 +3,7 @@ package core import ( "context" "encoding/json" - "log" + "log/slog" "time" "gitea.mrixs.me/minecraft-platform/backend/internal/database" @@ -29,7 +29,7 @@ type ServerPoller struct { } func (p *ServerPoller) Start(ctx context.Context) { - log.Println("Starting server poller...") + slog.Info("Starting server poller...") ticker := time.NewTicker(60 * time.Second) defer ticker.Stop() @@ -40,7 +40,7 @@ func (p *ServerPoller) Start(ctx context.Context) { case <-ticker.C: p.pollAllServers(ctx) case <-ctx.Done(): - log.Println("Stopping server poller...") + slog.Info("Stopping server poller...") return } } @@ -49,7 +49,7 @@ func (p *ServerPoller) Start(ctx context.Context) { func (p *ServerPoller) pollAllServers(ctx context.Context) { servers, err := p.Repo.GetAllEnabledServers(ctx) if err != nil { - log.Printf("Poller: failed to get servers: %v", err) + slog.Error("Poller: failed to get servers", "error", err) return } @@ -61,13 +61,13 @@ func (p *ServerPoller) pollAllServers(ctx context.Context) { func (p *ServerPoller) pollServer(ctx context.Context, server *models.GameServer) { resp, delay, err := bot.PingAndList(server.Address) if err != nil { - log.Printf("Poller: failed to ping %s (%s): %v", server.Name, server.Address, err) + slog.Warn("Poller: failed to ping server", "server", server.Name, "address", server.Address, "error", err) return } var status pingResponse if err := json.Unmarshal(resp, &status); err != nil { - log.Printf("Poller: failed to unmarshal status for %s: %v", server.Name, err) + slog.Error("Poller: failed to unmarshal status", "server", server.Name, "error", err) return } @@ -83,8 +83,8 @@ func (p *ServerPoller) pollServer(ctx context.Context, server *models.GameServer } if err := p.Repo.UpdateServerStatus(ctx, server.ID, updateData); err != nil { - log.Printf("Poller: failed to update status for %s: %v", server.Name, err) + slog.Error("Poller: failed to update status", "server", server.Name, "error", err) } else { - log.Printf("Poller: successfully polled %s", server.Name) + slog.Info("Poller: successfully polled server", "server", server.Name) } }