package app import ( "context" "errors" "github.com/gofiber/fiber/v2" "github.com/themakers/hlog" "go.uber.org/zap" "go.uber.org/zap/zapcore" "penahub.gitlab.yandexcloud.net/backend/penahub_common/privilege" "penahub.gitlab.yandexcloud.net/backend/quiz/common.git/model" "penahub.gitlab.yandexcloud.net/backend/quiz/core/internal/brokers" "penahub.gitlab.yandexcloud.net/backend/quiz/core/internal/initialize" "penahub.gitlab.yandexcloud.net/backend/quiz/core/internal/models" server "penahub.gitlab.yandexcloud.net/backend/quiz/core/internal/server/grpc" "penahub.gitlab.yandexcloud.net/backend/quiz/core/internal/server/http" "penahub.gitlab.yandexcloud.net/backend/quiz/core/internal/tools" "penahub.gitlab.yandexcloud.net/backend/quiz/core/internal/workers" "penahub.gitlab.yandexcloud.net/backend/quiz/core/pkg/closer" "penahub.gitlab.yandexcloud.net/external/trashlog/wrappers/zaptrashlog" "time" ) type Build struct { Commit string Version string } var zapOptions = []zap.Option{ zap.AddCaller(), zap.AddCallerSkip(2), zap.AddStacktrace(zap.ErrorLevel), } func Run(ctx context.Context, cfg initialize.Config, logger *zap.Logger, build Build) error { var ( err error zapLogger *zap.Logger ) defer func() { if r := recover(); r != nil { logger.Error("Recovered from a panic", zap.Any("error", r)) } }() ctx, cancel := context.WithCancel(ctx) defer cancel() if cfg.LoggerProdMode { zapLogger, err = zap.NewProduction(zapOptions...) if err != nil { return err } } else { zapLogger, err = zap.NewDevelopment(zapOptions...) if err != nil { return err } } zapLogger = zapLogger.With( zap.String("SvcCommit", build.Commit), zap.String("SvcVersion", build.Version), zap.String("SvcBuildTime", time.Now().String()), ) clickHouseLogger, err := zaptrashlog.NewCore(ctx, zap.InfoLevel, cfg.TrashLogHost, build.Version, build.Commit, time.Now().Unix()) if err != nil { panic(err) } loggerForHlog := zapLogger.WithOptions(zap.WrapCore(func(core zapcore.Core) zapcore.Core { return zapcore.NewTee(core, clickHouseLogger) })) loggerHlog := hlog.New(loggerForHlog).Module(cfg.ModuleLogger) loggerHlog.With(models.AllFields{}) loggerHlog.Emit(InfoSvcStarted{}) shutdownGroup := closer.NewCloserGroup() dalS, err := initialize.NewDALs(ctx, cfg) if err != nil { logger.Error("Error initializing dals", zap.Error(err)) return err } kafkaClient, err := initialize.KafkaInit(ctx, initialize.KafkaDeps{ KafkaGroup: cfg.KafkaGroup, KafkaBrokers: cfg.KafkaBrokers, KafkaTopic: cfg.KafkaTopic, }) if err != nil { logger.Error("Error initializing kafka", zap.Error(err)) return err } producer := brokers.NewProducer(brokers.ProducerDeps{ KafkaClient: kafkaClient, Logger: zapLogger, }) redisClient, err := initialize.Redis(ctx, cfg) if err != nil { logger.Error("Error initializing redis", zap.Error(err)) return err } go tools.PublishPrivilege(privilege.NewPrivilege(privilege.Client{ URL: cfg.HubAdminUrl, ServiceName: cfg.ServiceName, Privileges: model.Privileges, }, &fiber.Client{}), 10, 5*time.Minute) clients, err := initialize.NewClients(ctx, cfg, dalS.PgDAL) if err != nil { logger.Error("Error initializing clients", zap.Error(err)) return err } tgWC := workers.NewTgListenerWC(workers.Deps{ BotID: int64(6712573453), // todo убрать Redis: redisClient, Dal: dalS.PgDAL, TgClient: clients.TgClient, }) go tgWC.Start(ctx) controllers := initialize.NewControllers(initialize.ControllerDeps{ Clients: clients, DALs: dalS, Config: cfg, Producer: producer, RedisClient: redisClient, }) grpc, err := server.NewGRPC(zapLogger) if err != nil { logger.Error("Error initializing grpc", zap.Error(err)) return err } grpc.Register(controllers.GRpcControllers) srv := http.NewServer(http.ServerConfig{ Logger: logger, Controllers: []http.Controller{controllers.HttpControllers.Account, controllers.HttpControllers.Telegram, controllers.HttpControllers.Result, controllers.HttpControllers.Question, controllers.HttpControllers.Quiz, controllers.HttpControllers.Statistic}, Hlogger: loggerHlog, }) go func() { if err := srv.Start(cfg.HttpHost + ":" + cfg.NumberPort); err != nil { logger.Error("HTTP server startup error", zap.Error(err)) cancel() } }() go grpc.Run(server.DepsGrpcRun{ Host: cfg.GrpcHost, Port: cfg.GrpcPort, }) srv.ListRoutes() shutdownGroup.Add(closer.CloserFunc(srv.Shutdown)) shutdownGroup.Add(closer.CloserFunc(grpc.Stop)) shutdownGroup.Add(closer.CloserFunc(dalS.PgDAL.Close)) shutdownGroup.Add(closer.CloserFunc(dalS.ChDAL.Close)) <-ctx.Done() timeoutCtx, timeoutCancel := context.WithTimeout(context.Background(), 10*time.Second) defer timeoutCancel() if err := shutdownGroup.Call(timeoutCtx); err != nil { if errors.Is(err, context.DeadlineExceeded) { logger.Error("Shutdown timed out", zap.Error(err)) } else { logger.Error("Failed to shutdown services gracefully", zap.Error(err)) } return err } logger.Info("Application has stopped") return nil }