package health import ( "context" "fmt" "net/http" "runtime/debug" "strings" "time" "git.apinb.com/ops/logs/internal/config" "git.apinb.com/ops/logs/internal/impl" "git.apinb.com/ops/logs/internal/ingest" "github.com/gin-gonic/gin" "gorm.io/gorm" ) type CheckResult struct { Name string `json:"name"` Required bool `json:"required"` Ready bool `json:"ready"` Message string `json:"message,omitempty"` } type Readiness struct { Ready bool `json:"ready"` CheckedAt time.Time `json:"checked_at"` Checks []CheckResult `json:"checks,omitempty"` Workers []ingest.WorkerStatus `json:"workers,omitempty"` Version string `json:"version"` } func Evaluate(ctx context.Context) Readiness { result := Readiness{Ready: true, CheckedAt: time.Now().UTC(), Workers: ingest.WorkerStatuses(), Version: runtimeVersion()} result.add("config", true, config.Validate()) result.add("database", true, pingDatabase(ctx, impl.DBService)) workerMap := make(map[string]ingest.WorkerStatus, len(result.Workers)) for _, worker := range result.Workers { workerMap[worker.Name] = worker } for _, name := range requiredWorkers() { worker, exists := workerMap[name] if !exists || !worker.Running { message := "worker 未启动" if exists && worker.LastError != "" { message = worker.LastError } result.add(name, true, fmt.Errorf("%s", message)) continue } result.add(name, true, nil) } return result } func runtimeVersion() string { info, ok := debug.ReadBuildInfo() if !ok || strings.TrimSpace(info.Main.Version) == "" { return "unknown" } return info.Main.Version } func Ready(c *gin.Context) { result := Evaluate(c.Request.Context()) status := http.StatusOK if !result.Ready { status = http.StatusServiceUnavailable } c.JSON(status, gin.H{"ready": result.Ready}) } func Status(c *gin.Context) { result := Evaluate(c.Request.Context()) status := http.StatusOK if !result.Ready { status = http.StatusServiceUnavailable } c.JSON(status, result) } func requiredWorkers() []string { workers := []string{"rule_refresher", "alert_dispatcher"} if strings.TrimSpace(config.Spec.Ingest.SyslogListenAddr) != "" { workers = append(workers, "syslog_udp") } if strings.TrimSpace(config.Spec.Ingest.TrapListenAddr) != "" { workers = append(workers, "trap_udp") } return workers } func (r *Readiness) add(name string, required bool, err error) { check := CheckResult{Name: name, Required: required, Ready: err == nil} if err != nil { check.Message = strings.TrimSpace(err.Error()) if required { r.Ready = false } } r.Checks = append(r.Checks, check) } func pingDatabase(ctx context.Context, db *gorm.DB) error { if db == nil { return fmt.Errorf("数据库未初始化") } sqlDB, err := db.DB() if err != nil { return err } checkCtx, cancel := context.WithTimeout(ctx, 2*time.Second) defer cancel() return sqlDB.PingContext(checkCtx) }