Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
8d6930b087
|
16
CHANGELOG.md
16
CHANGELOG.md
@@ -5,6 +5,22 @@ All notable changes to this module will be documented in this file.
|
|||||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||||
and this module adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
and this module adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
|
## [1.0.0] — 2026-05-12
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- `Config` struct — `CheckTimeout time.Duration`; zero value defaults to 5 seconds.
|
||||||
|
- `NewHandlerWithConfig(logger Logger, cfg Config, checks ...Checkable) http.Handler` —
|
||||||
|
constructor with explicit configuration. `NewHandler` is now a backward-compatible
|
||||||
|
wrapper that calls `NewHandlerWithConfig` with zero `Config`.
|
||||||
|
|
||||||
|
### Unchanged
|
||||||
|
|
||||||
|
All existing API (`Level`, `LevelCritical`, `LevelDegraded`, `Checkable`, `Logger`,
|
||||||
|
`ComponentStatus`, `Response`, `NewHandler`) is API-compatible with v0.9.0.
|
||||||
|
|
||||||
|
[1.0.0]: https://code.nochebuena.dev/go/health/releases/tag/v1.0.0
|
||||||
|
|
||||||
## [0.9.0] - 2026-03-18
|
## [0.9.0] - 2026-03-18
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|||||||
27
health.go
27
health.go
@@ -48,23 +48,44 @@ type Response struct {
|
|||||||
Components map[string]ComponentStatus `json:"components"`
|
Components map[string]ComponentStatus `json:"components"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Config configures a health handler.
|
||||||
|
// The zero value is valid: 5-second check timeout.
|
||||||
|
type Config struct {
|
||||||
|
// CheckTimeout is the per-request deadline for all health checks.
|
||||||
|
// Defaults to 5 seconds when zero.
|
||||||
|
CheckTimeout time.Duration
|
||||||
|
}
|
||||||
|
|
||||||
|
const defaultCheckTimeout = 5 * time.Second
|
||||||
|
|
||||||
type handler struct {
|
type handler struct {
|
||||||
logger Logger
|
logger Logger
|
||||||
checks []Checkable
|
checks []Checkable
|
||||||
|
timeout time.Duration
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewHandler returns an http.Handler for the health endpoint.
|
// NewHandler returns an http.Handler for the health endpoint with default configuration.
|
||||||
// Runs all checks concurrently with a 5-second timeout.
|
// Runs all checks concurrently with a 5-second timeout.
|
||||||
// Returns 200 (UP/DEGRADED) or 503 (DOWN).
|
// Returns 200 (UP/DEGRADED) or 503 (DOWN).
|
||||||
func NewHandler(logger Logger, checks ...Checkable) http.Handler {
|
func NewHandler(logger Logger, checks ...Checkable) http.Handler {
|
||||||
return &handler{logger: logger, checks: checks}
|
return NewHandlerWithConfig(logger, Config{}, checks...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewHandlerWithConfig returns an http.Handler configured by cfg.
|
||||||
|
// If cfg.CheckTimeout is zero, a 5-second default is used.
|
||||||
|
func NewHandlerWithConfig(logger Logger, cfg Config, checks ...Checkable) http.Handler {
|
||||||
|
timeout := cfg.CheckTimeout
|
||||||
|
if timeout == 0 {
|
||||||
|
timeout = defaultCheckTimeout
|
||||||
|
}
|
||||||
|
return &handler{logger: logger, checks: checks, timeout: timeout}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
func (h *handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
logger := h.logger.WithContext(r.Context())
|
logger := h.logger.WithContext(r.Context())
|
||||||
logger.Debug("health: running checks")
|
logger.Debug("health: running checks")
|
||||||
|
|
||||||
ctx, cancel := context.WithTimeout(r.Context(), 5*time.Second)
|
ctx, cancel := context.WithTimeout(r.Context(), h.timeout)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
type result struct {
|
type result struct {
|
||||||
|
|||||||
@@ -167,6 +167,30 @@ func TestHandler_JSON_Shape(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestHandlerWithConfig_CustomTimeout(t *testing.T) {
|
||||||
|
// 200ms delay check with a 50ms timeout — should be reported as DOWN.
|
||||||
|
h := NewHandlerWithConfig(&noopLogger{}, Config{CheckTimeout: 50 * time.Millisecond},
|
||||||
|
&mockCheck{name: "slow", priority: LevelCritical, delay: 200 * time.Millisecond},
|
||||||
|
)
|
||||||
|
code, resp := doRequest(t, h)
|
||||||
|
if code != http.StatusServiceUnavailable {
|
||||||
|
t.Errorf("want 503, got %d", code)
|
||||||
|
}
|
||||||
|
if resp.Status != "DOWN" {
|
||||||
|
t.Errorf("want DOWN, got %s", resp.Status)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHandlerWithConfig_ZeroTimeout_UsesDefault(t *testing.T) {
|
||||||
|
h := NewHandlerWithConfig(&noopLogger{}, Config{},
|
||||||
|
&mockCheck{name: "db", priority: LevelCritical},
|
||||||
|
)
|
||||||
|
code, resp := doRequest(t, h)
|
||||||
|
if code != http.StatusOK || resp.Status != "UP" {
|
||||||
|
t.Errorf("want 200 UP, got %d %s", code, resp.Status)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestHandler_ContextTimeout(t *testing.T) {
|
func TestHandler_ContextTimeout(t *testing.T) {
|
||||||
// Check that times out faster than the 5s global timeout when client cancels.
|
// Check that times out faster than the 5s global timeout when client cancels.
|
||||||
h := NewHandler(&noopLogger{},
|
h := NewHandler(&noopLogger{},
|
||||||
|
|||||||
Reference in New Issue
Block a user