116 lines
2.9 KiB
Go
116 lines
2.9 KiB
Go
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)
|
|
}
|