// main.go с graceful shutdown package main import ( "context" "log" "net/http" "os" "os/signal" "syscall" "time" "go.uber.org/zap" "gorm.io/driver/postgres" "gorm.io/gorm" "api_bb/internal/config" "api_bb/internal/models" "api_bb/internal/routes" "api_bb/pkg/logger" ) func main() { // Загрузка конфигурации cfg := config.Load() // Инициализация логгера if err := logger.Init( os.Getenv("LOG_LEVEL"), os.Getenv("ENVIRONMENT"), ); err != nil { log.Printf("Failed to initialize logger: %v", err) os.Exit(1) } defer logger.Sync() zapLogger := logger.Get() // Логируем начало работы logger.LogApplicationStart(os.Getenv("REST_API_VERSION"), os.Getenv("ENVIRONMENT"), "") // Логирование попытки подключения к БД zapLogger.Info("attempting to connect to database", zap.String("host", extractHostFromDSN(cfg.DatabaseURL)), // функция для извлечения хоста из DSN zap.String("database", extractDBNameFromDSN(cfg.DatabaseURL)), // функция для извлечения имени БД ) // Подключение к базе данных db, err := gorm.Open(postgres.Open(cfg.DatabaseURL), &gorm.Config{}) if err != nil { zapLogger.Fatal("failed to connect to database", zap.Error(err), zap.String("database_url", maskPassword(cfg.DatabaseURL)), // маскируем пароль в логах ) } // Логирование успешного подключения к БД zapLogger.Info("successfully connected to database", zap.String("host", extractHostFromDSN(cfg.DatabaseURL)), zap.String("database", extractDBNameFromDSN(cfg.DatabaseURL)), ) // Проверка соединения с БД sqlDB, err := db.DB() if err != nil { zapLogger.Fatal("failed to get database instance", zap.Error(err)) } if err := sqlDB.Ping(); err != nil { zapLogger.Fatal("database ping failed", zap.Error(err)) } zapLogger.Info("database ping successful") // Автомиграция zapLogger.Info("starting database migration") if err := db.AutoMigrate(&models.User{}); err != nil { zapLogger.Fatal("database migration failed", zap.Error(err)) } zapLogger.Info("database migration completed successfully") // Настройка роутера router := routes.SetupRouter(db, cfg) // Настройка HTTP сервера server := &http.Server{ Addr: ":" + cfg.Port, Handler: router, } // Канал для graceful shutdown done := make(chan bool, 1) quit := make(chan os.Signal, 1) signal.Notify(quit, os.Interrupt, syscall.SIGTERM) // Запуск сервера в горутине go func() { zapLogger.Info("starting HTTP server", zap.String("port", cfg.Port)) if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed { zapLogger.Fatal("failed to start server", zap.Error(err)) } }() // Ожидание сигнала shutdown <-quit zapLogger.Info("shutdown signal received") // Логирование закрытия соединения с БД zapLogger.Info("closing database connection") if err := sqlDB.Close(); err != nil { zapLogger.Error("failed to close database connection", zap.Error(err)) } else { zapLogger.Info("database connection closed successfully") } // Graceful shutdown ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) defer cancel() server.SetKeepAlivesEnabled(false) if err := server.Shutdown(ctx); err != nil { zapLogger.Fatal("could not gracefully shutdown the server", zap.Error(err)) } logger.LogApplicationShutdown("graceful shutdown") close(done) } // Вспомогательные функции для работы с DSN // extractHostFromDSN извлекает хост из DSN строки func extractHostFromDSN(dsn string) string { // Простая реализация - в продакшене лучше использовать парсер DSN // Для postgres DSN формата: "host=localhost user=gorm password=gorm dbname=gorm port=9920 sslmode=disable TimeZone=Asia/Shanghai" // Можно использовать более сложный парсер или регулярные выражения return dsn // Заглушка - нужно реализовать парсинг DSN } // extractDBNameFromDSN извлекает имя базы данных из DSN строки func extractDBNameFromDSN(dsn string) string { // Аналогично extractHostFromDSN - нужно реализовать парсинг return dsn // Заглушка - нужно реализовать парсинг DSN } // maskPassword маскирует пароль в DSN строке для безопасного логирования func maskPassword(dsn string) string { // Простая реализация - заменяет пароль на *** // В продакшене нужно использовать более надежный метод return dsn // Заглушка - нужно реализовать маскирование пароля }