health

package module
v0.0.0-...-56713c2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Mar 7, 2026 License: MIT Imports: 12 Imported by: 0

README

lib-health

CI codecov Go Report Card Go version License

Библиотека liveness и readiness проб для HTTP-сервисов в организации selfshop-dev.

Реализует модель проб в стиле Kubernetes: liveness всегда возвращает 200, readiness запускает все зарегистрированные чекеры конкурентно и возвращает 503, если хотя бы один упал. Паники внутри чекеров перехватываются и не роняют эндпоинт.

Возможности

  • Два HTTP-эндпоинта: GET /health/alive — всегда 200, GET /health/ready — результат всех чекеров.
  • Конкурентное выполнение: чекеры запускаются параллельно, максимальный параллелизм задаётся через WithMaxConcurrency (по умолчанию 8).
  • Общий таймаут: один дедлайн на весь CheckAll — чекеры, не успевшие получить слот семафора, отмечаются как "not started: ...".
  • Порядок результатов: результаты всегда возвращаются в порядке регистрации чекеров, независимо от порядка выполнения.
  • Перехват паник: паника внутри чекера конвертируется в ошибку и логируется через lib-logger, не ломая весь эндпоинт.
  • JSON-ответы: тело ответа содержит статус и массив результатов по каждому чекеру.

Установка

go get github.com/selfshop-dev/lib-health

Использование

Регистрация эндпоинтов
import (
   "net/http"
   "time"

   health "github.com/selfshop-dev/lib-health"
)

db := &dbChecker{pool: pool} // реализует health.Checker

coll, err := health.New([]health.Checker{db},
   health.WithTimeout(5 * time.Second),
   health.WithMaxConcurrency(4),
)
if err != nil {
   // один из чекеров nil — ошибка поймана при старте
}

hand := health.NewHandlers(logr, coll)
mux.HandleFunc(health.AlivePath, hand.AliveHandler)
mux.HandleFunc(health.ReadyPath, hand.ReadyHandler)
Реализация чекера

Любая зависимость реализует интерфейс Checker:

type dbChecker struct{ pool *pgxpool.Pool }

func (c *dbChecker) Name() string { return "postgres" }

func (c *dbChecker) Check(ctx context.Context) error {
   return c.pool.Ping(ctx)
}

Метод Check обязан пробрасывать контекст во все блокирующие вызовы — иначе чекер не будет уважать общий таймаут Collector.

Формат HTTP-ответов

Liveness (GET /health/alive) — всегда 200:

{"status":"ok"}

Readiness (GET /health/ready) — 200, если все прошли:

{
  "status": "ok",
  "checkers": [
    {"name": "postgres"},
    {"name": "redis"}
  ]
}

Readiness — 503, если хотя бы один упал:

{
  "status": "unhealthy",
  "checkers": [
    {"name": "postgres"},
    {"name": "redis", "error": "connection refused"}
  ]
}
Опции New
Опция По умолчанию Описание
WithMaxConcurrency(n int) 8 Максимум параллельных чекеров. Значения ≤ 0 игнорируются.
WithTimeout(d time.Duration) 6 s Общий таймаут на весь CheckAll. Значения ≤ 0 игнорируются.
WithLogger(logr *logger.T) nil Логгер для паник внутри чекеров. Без логгера паники перехватываются молча.

Разработка

make lint   # golangci-lint run
make test   # go test -race -coverprofile=coverage.out ./...
Ветки
main ← только из dev через PR
dev  ← рабочая ветка, PR-ы сюда
Соглашение по коммитам

Проект следует Conventional Commits. Формат: <type>: <summary>, где summary — повелительное наклонение, английский язык, без точки в конце.

Тип Когда использовать
feat Новая функциональность
fix Исправление бага
refactor Рефакторинг без изменения поведения
perf Оптимизация производительности
test Добавление или изменение тестов
docs Документация
ci Изменения CI/CD и GitHub Actions
build Сборка, зависимости, инфраструктура проекта
chore Рутинные задачи, не попадающие в другие типы

Тип коммита автоматически определяет метку PR и тип следующего релиза: feat → minor, fix / perf / refactor → patch, feat! / fix! (breaking change) → major.

CI/CD

push / PR → lint → test → codecov
                        → CodeQL (безопасность)
weekly     → Trivy (уязвимости в зависимостях)
merge→main → release-drafter (обновляет черновик релиза)

Покрытие ниже 80% или падение линтера блокируют merge.

Лицензия

MIT © 2026 selfshop-dev

Documentation

Overview

Package health is a generated GoMock package.

Package health implements liveness and readiness probes for HTTP services.

Probe model

Two probes are provided, following the Kubernetes convention:

  • Liveness (GET AlivePath) — indicates the process is running and the HTTP server is accepting connections. Always returns 200 OK. Use this as the kubelet liveness probe; a failure restarts the pod.

  • Readiness (GET ReadyPath) — indicates the service can handle traffic. Runs all registered [Checker]s concurrently and returns 200 OK when all pass, or 503 Service Unavailable when any fail. Use this as the kubelet readiness probe; a failure removes the pod from load-balancer rotation.

Readiness is evaluated live on every request; results are not cached.

Usage

db := &dbChecker{pool: pool}   // implements health.Checker

coll := health.New([]health.Checker{db},
	health.WithTimeout(5 * time.Second),
	health.WithMaxConcurrency(4),
)

handlers := health.NewHandlers(logr, coll)
mux.HandleFunc(health.AlivePath, handlers.AliveHandler)
mux.HandleFunc(health.ReadyPath, handlers.ReadyHandler)

Implementing a Checker

type dbChecker struct{ pool *pgxpool.Pool }

func (c *dbChecker) Name() string { return "postgres" }

func (c *dbChecker) Check(ctx context.Context) error {
	return c.pool.Ping(ctx)
}

Concurrency and timeouts

Collector.CheckAll runs checkers concurrently up to WithMaxConcurrency goroutines at a time (default 8). A shared timeout (default 6 s) is applied to the entire CheckAll call, not to individual checkers. Checkers must respect the context they receive to honour this deadline.

Panics inside a checker are recovered and reported as errors so that a single misbehaving checker cannot bring down the health endpoint. The panic value and stack trace are logged via the Collector logger (see WithLogger) but are not included in the HTTP response to avoid leaking internal details.

Index

Constants

View Source
const (
	// AlivePath is the HTTP path for the liveness probe.
	// Register it with [Handlers.AliveHandler].
	AlivePath = "/health/alive"
	// ReadyPath is the HTTP path for the readiness probe.
	// Register it with [Handlers.ReadyHandler].
	ReadyPath = "/health/ready"
)

Variables

This section is empty.

Functions

This section is empty.

Types

type CheckAllResult

type CheckAllResult struct {
	// Results contains one entry per registered checker, in registration order.
	// An entry with a non-empty Error field indicates a failed check.
	// Checkers that were not started because the shared context expired before
	// the semaphore could be acquired are reported with
	// Error = "not started: context deadline exceeded", so every entry always
	// carries a meaningful Name.
	Results []CheckResult
	// HasErrors is true if any checker failed or if the shared context expired
	// before all checkers could be started.
	HasErrors bool
}

CheckAllResult is the aggregated output of Collector.CheckAll.

type CheckResult

type CheckResult struct {
	// Name is the value returned by [Checker.Name].
	Name string `json:"name"`
	// Error is the error message returned by [Checker.Check], or empty if the
	// check passed. Omitted from JSON when empty.
	Error string `json:"error,omitempty"`
}

CheckResult holds the outcome of a single Checker run. It is included in the JSON body of the readiness response.

type Checker

type Checker interface {
	// Name returns a short human-readable identifier for this checker
	// (e.g. "postgres", "redis"). Used as the key in readiness responses
	// and log output. Must be stable across calls.
	//
	// Names do not need to be unique, but duplicates make readiness responses
	// and log output ambiguous. Prefer distinct names across all checkers.
	Name() string

	// Check performs the health probe. It should return a non-nil error if the
	// dependency is unavailable or degraded. The context carries the deadline
	// set by [Collector.CheckAll]; implementations must propagate it to any
	// blocking calls (e.g. DB ping, HTTP request).
	Check(ctx context.Context) error
}

Checker is the interface that a dependency check must implement. Implementations must be safe for concurrent use and must respect context cancellation to honour the Collector timeout.

type Collector

type Collector struct {
	// contains filtered or unexported fields
}

Collector runs a set of [Checker]s concurrently and aggregates their results. Construct one with New; the zero value is not usable.

func New

func New(checkers []Checker, options ...Option) (*Collector, error)

New creates a Collector with the provided checkers and applies any options. Defaults: maxConcurrency = 8, timeout = 6 s. Passing an empty or nil checkers slice is valid; [CheckAll] will return an empty result immediately.

Returns an error if any element of checkers is nil: a nil Checker cannot provide a name and would panic inside Collector.CheckAll rather than producing a recoverable error result.

Checker names do not have to be unique, but duplicates make readiness responses and log output ambiguous. Prefer distinct names across all checkers.

func (*Collector) CheckAll

func (c *Collector) CheckAll(ctx context.Context) CheckAllResult

CheckAll runs all registered checkers concurrently and returns aggregated results. It is safe to call from multiple goroutines simultaneously.

Execution model:

  • A context with [Collector.timeout] is derived from ctx and shared by all checker goroutines. Callers should still pass a meaningful parent context (e.g. the request context) so that an upstream cancellation propagates.
  • Up to [Collector.maxConcurrency] checkers run at a time; the rest wait for the semaphore. If the context expires while waiting, the remaining checkers are marked as "not started" and HasErrors is set to true.
  • Results are written to pre-allocated index positions so no mutex is needed for the slice itself. HasErrors is updated atomically.
  • Panics inside checkers are recovered by [runCheck] and reported as errors.

type Handlers

type Handlers struct {
	// contains filtered or unexported fields
}

Handlers exposes the liveness and readiness probes as http.HandlerFunc values. Construct one with NewHandlers.

func NewHandlers

func NewHandlers(logr *logger.T, coll *Collector) *Handlers

NewHandlers creates a Handlers that delegates readiness checks to coll and logs warnings via logr when any checker fails.

Panics if logr or coll is nil: both are required for correct operation and a nil dereference inside a request handler is harder to diagnose than an early panic at construction time.

func (*Handlers) AliveHandler

func (h *Handlers) AliveHandler(w http.ResponseWriter, _ *http.Request)

AliveHandler is the liveness probe handler. It always returns 200 OK with a static JSON body. No checks are performed: if the HTTP server can route this request, the process is alive.

Response body: {"status":"ok"}.

func (*Handlers) ReadyHandler

func (h *Handlers) ReadyHandler(w http.ResponseWriter, r *http.Request)

ReadyHandler is the readiness probe handler. It runs all registered checkers via Collector.CheckAll and responds based on the outcome:

  • 200 OK — all checkers passed; body: {"status":"ok", "checkers":[...]}
  • 503 Service Unavail. — one or more checkers failed; body: {"status":"unhealthy", "checkers":[...]}

Only failed checkers are logged at Warn level to keep log volume bounded.

type MockChecker

type MockChecker struct {
	// contains filtered or unexported fields
}

MockChecker is a mock of Checker interface.

func NewMockChecker

func NewMockChecker(ctrl *gomock.Controller) *MockChecker

NewMockChecker creates a new mock instance.

func (*MockChecker) Check

func (m *MockChecker) Check(ctx context.Context) error

Check mocks base method.

func (*MockChecker) EXPECT

func (m *MockChecker) EXPECT() *MockCheckerMockRecorder

EXPECT returns an object that allows the caller to indicate expected use.

func (*MockChecker) Name

func (m *MockChecker) Name() string

Name mocks base method.

type MockCheckerMockRecorder

type MockCheckerMockRecorder struct {
	// contains filtered or unexported fields
}

MockCheckerMockRecorder is the mock recorder for MockChecker.

func (*MockCheckerMockRecorder) Check

func (mr *MockCheckerMockRecorder) Check(ctx any) *gomock.Call

Check indicates an expected call of Check.

func (*MockCheckerMockRecorder) Name

func (mr *MockCheckerMockRecorder) Name() *gomock.Call

Name indicates an expected call of Name.

type Option

type Option func(*Collector)

Option is a functional option for New.

func WithLogger

func WithLogger(logr *logger.T) Option

WithLogger attaches a logger to the Collector. When set, checker panics are logged at Error level with the panic value and full stack trace before being suppressed in the HTTP response. Without a logger, panics are silently recovered and only reported as "name: panic" in the response body.

func WithMaxConcurrency

func WithMaxConcurrency(n int) Option

WithMaxConcurrency sets the maximum number of [Checker]s that run in parallel during Collector.CheckAll. Values <= 0 are ignored. Default: 8.

func WithTimeout

func WithTimeout(d time.Duration) Option

WithTimeout sets the maximum wall-clock time for a complete Collector.CheckAll call, shared across all checkers. Values <= 0 are ignored. Default: 6 s.

Individual checkers are not given their own deadline; they share this budget. Set the timeout conservatively: it directly affects the latency of the readiness probe endpoint.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL