Files
vm/api/health/health.go

137 lines
3.9 KiB
Go

// Copyright (C) 2019-2025, Lux Industries, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package health
import (
"context"
"time"
"github.com/luxfi/log"
"github.com/luxfi/metric"
)
const (
// CheckLabel is the label used to differentiate between health checks.
CheckLabel = "check"
// TagLabel is the label used to differentiate between health check tags.
TagLabel = "tag"
// AllTag is automatically added to every registered check.
AllTag = "all"
// ApplicationTag checks will act as if they specified every tag that has
// been registered.
// Registering a health check with this tag will ensure that it is always
// included in all health query results.
ApplicationTag = "application"
)
var _ Health = (*health)(nil)
// Health defines the full health service interface for registering, reporting
// and refreshing health checks.
type Health interface {
Registerer
Reporter
// Start running periodic health checks at the specified frequency.
// Repeated calls to Start will be no-ops.
Start(ctx context.Context, freq time.Duration)
// Stop running periodic health checks. Stop should only be called after
// Start. Once Stop returns, no more health checks will be executed.
Stop()
}
// Registerer defines how to register new components to check the health of.
type Registerer interface {
RegisterReadinessCheck(name string, checker Checker, tags ...string) error
RegisterHealthCheck(name string, checker Checker, tags ...string) error
RegisterLivenessCheck(name string, checker Checker, tags ...string) error
}
// Reporter returns the current health status.
type Reporter interface {
Readiness(tags ...string) (map[string]Result, bool)
Health(tags ...string) (map[string]Result, bool)
Liveness(tags ...string) (map[string]Result, bool)
}
type health struct {
log log.Logger
readiness *worker
health *worker
liveness *worker
}
func New(log log.Logger, registerer metric.Registerer) (Health, error) {
metricsInstance := metric.NewWithRegistry("health", registerer.(metric.Registry))
failingChecks := metricsInstance.NewGaugeVec(
"checks_failing",
"number of currently failing health checks",
[]string{CheckLabel, TagLabel},
)
return &health{
log: log,
readiness: newWorker(log, "readiness", failingChecks),
health: newWorker(log, "health", failingChecks),
liveness: newWorker(log, "liveness", failingChecks),
}, nil
}
func (h *health) RegisterReadinessCheck(name string, checker Checker, tags ...string) error {
return h.readiness.RegisterMonotonicCheck(name, checker, tags...)
}
func (h *health) RegisterHealthCheck(name string, checker Checker, tags ...string) error {
return h.health.RegisterCheck(name, checker, tags...)
}
func (h *health) RegisterLivenessCheck(name string, checker Checker, tags ...string) error {
return h.liveness.RegisterCheck(name, checker, tags...)
}
func (h *health) Readiness(tags ...string) (map[string]Result, bool) {
results, healthy := h.readiness.Results(tags...)
if !healthy {
h.log.Warn("failing check",
log.UserString("namespace", "readiness"),
log.Reflect("reason", results),
)
}
return results, healthy
}
func (h *health) Health(tags ...string) (map[string]Result, bool) {
results, healthy := h.health.Results(tags...)
if !healthy {
h.log.Warn("failing check",
log.UserString("namespace", "health"),
log.Reflect("reason", results),
)
}
return results, healthy
}
func (h *health) Liveness(tags ...string) (map[string]Result, bool) {
results, healthy := h.liveness.Results(tags...)
if !healthy {
h.log.Warn("failing check",
log.UserString("namespace", "liveness"),
log.Reflect("reason", results),
)
}
return results, healthy
}
func (h *health) Start(ctx context.Context, freq time.Duration) {
h.readiness.Start(ctx, freq)
h.health.Start(ctx, freq)
h.liveness.Start(ctx, freq)
}
func (h *health) Stop() {
h.readiness.Stop()
h.health.Stop()
h.liveness.Stop()
}