package health import ( "context" "encoding/json" "net/http" "time" ) // Level represents the criticality of a component to the overall application health. type Level int const ( // LevelCritical indicates a component essential for the application. // If any critical component is DOWN, the overall status is DOWN (503). LevelCritical Level = iota // LevelDegraded indicates a component that is important but not essential. // If a degraded component is DOWN, the overall status is DEGRADED (200). LevelDegraded ) // Checkable is the interface that infrastructure components implement. type Checkable interface { HealthCheck(ctx context.Context) error Name() string Priority() Level } // Logger is the minimal interface health needs — satisfied by logz.Logger via duck typing. type Logger interface { Debug(msg string, args ...any) Info(msg string, args ...any) Warn(msg string, args ...any) Error(msg string, err error, args ...any) WithContext(ctx context.Context) Logger } // ComponentStatus represents the health state of an individual component. type ComponentStatus struct { Status string `json:"status"` Latency string `json:"latency,omitempty"` Error string `json:"error,omitempty"` } // Response is the JSON body returned by the health handler. type Response struct { Status string `json:"status"` Components map[string]ComponentStatus `json:"components"` } type handler struct { logger Logger checks []Checkable } // NewHandler returns an http.Handler for the health endpoint. // Runs all checks concurrently with a 5-second timeout. // Returns 200 (UP/DEGRADED) or 503 (DOWN). func NewHandler(logger Logger, checks ...Checkable) http.Handler { return &handler{logger: logger, checks: checks} } func (h *handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { logger := h.logger.WithContext(r.Context()) logger.Debug("health: running checks") ctx, cancel := context.WithTimeout(r.Context(), 5*time.Second) defer cancel() type result struct { name string status ComponentStatus priority Level } resChan := make(chan result, len(h.checks)) for _, check := range h.checks { go func(chk Checkable) { start := time.Now() err := chk.HealthCheck(ctx) latency := time.Since(start).String() status := "UP" errMsg := "" if err != nil { errMsg = err.Error() if chk.Priority() == LevelDegraded { status = "DEGRADED" } else { status = "DOWN" } } resChan <- result{ name: chk.Name(), priority: chk.Priority(), status: ComponentStatus{ Status: status, Latency: latency, Error: errMsg, }, } }(check) } overallStatus := "UP" httpStatus := http.StatusOK components := make(map[string]ComponentStatus) for range h.checks { res := <-resChan components[res.name] = res.status switch res.status.Status { case "DOWN": overallStatus = "DOWN" httpStatus = http.StatusServiceUnavailable case "DEGRADED": if overallStatus == "UP" { overallStatus = "DEGRADED" } } } resp := Response{Status: overallStatus, Components: components} w.Header().Set("Content-Type", "application/json") w.WriteHeader(httpStatus) if err := json.NewEncoder(w).Encode(resp); err != nil { logger.Error("health: failed to encode response", err) } }