package main import ( "context" "errors" "fmt" "log" "net/http" "os" "os/signal" "sync" "syscall" "time" "git.apinb.com/bsm-sdk/core/infra" "git.apinb.com/bsm-sdk/core/middleware" "git.apinb.com/ops/logs/internal/config" "git.apinb.com/ops/logs/internal/impl" "git.apinb.com/ops/logs/internal/ingest" "git.apinb.com/ops/logs/internal/models" "git.apinb.com/ops/logs/internal/routers" settingssync "git.apinb.com/ops/logs/internal/systemsettings" "github.com/gin-gonic/gin" ) var ServiceKey = "Logs" func main() { ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM) defer stop() var backgroundJobs sync.WaitGroup config.New(ServiceKey) settingsClient, err := settingssync.LoadInitial(ctx) if err != nil { panic(fmt.Errorf("logs: 初始化系统参数失败: %w", err)) } impl.NewImpl() if err := models.RequireSchemaVersion(impl.DBService, "logs"); err != nil { panic(err) } if err := ingest.StartRefresher(ctx); err != nil { shutdownAfterStartupFailure(stop, err) } ingest.StartAlertDispatcher(ctx) if err := ingest.StartSyslogUDP(ctx); err != nil { shutdownAfterStartupFailure(stop, err) } if err := ingest.StartTrapUDP(ctx); err != nil { shutdownAfterStartupFailure(stop, err) } backgroundJobs.Add(1) go func() { defer backgroundJobs.Done() settingssync.Start(ctx, settingsClient) }() app := gin.Default() middleware.Mode(app) app.Use(middleware.Cors()) app.Use(gin.Recovery()) app.HEAD("/", infra.Health) routers.Register(ServiceKey, app) server := &http.Server{Addr: config.Spec.Addr, Handler: app, ReadHeaderTimeout: 10 * time.Second} errCh := make(chan error, 1) go func() { errCh <- server.ListenAndServe() }() var serveErr error select { case err := <-errCh: if err != nil && !errors.Is(err, http.ErrServerClosed) { serveErr = err } case <-ctx.Done(): shutdownCtx, cancel := context.WithTimeout(context.Background(), 30*time.Second) defer cancel() if err := server.Shutdown(shutdownCtx); err != nil { serveErr = err } } stop() if !ingest.Wait(20 * time.Second) { serveErr = errors.Join(serveErr, fmt.Errorf("后台 worker 未在退出超时内结束")) } if !waitForBackgroundJobs(&backgroundJobs, 20*time.Second) { serveErr = errors.Join(serveErr, fmt.Errorf("系统参数任务未在退出超时内结束")) } if err := errors.Join(serveErr, impl.Close()); err != nil { log.Printf("logs: 服务退出失败: %v", err) panic(err) } } func waitForBackgroundJobs(wg *sync.WaitGroup, timeout time.Duration) bool { done := make(chan struct{}) go func() { wg.Wait() close(done) }() select { case <-done: return true case <-time.After(timeout): return false } } func shutdownAfterStartupFailure(stop context.CancelFunc, startErr error) { stop() if !ingest.Wait(20 * time.Second) { startErr = errors.Join(startErr, fmt.Errorf("后台 worker 未在退出超时内结束")) } panic(errors.Join(startErr, impl.Close())) }