check / check (push) Canceled after 0s
The health check answered 200 "status":"ok" whatever happened, so a container whose report files could not be written stayed healthy while every report was refused with 500. The report buffer now says whether its last write of a report file failed. While it has, the health check's status is "error" and the handler answers 503 with the same fields, which fails the image's HEALTHCHECK. At start no write has failed, and the first write that succeeds brings back 200. A test removes DATA_DIR to make a write fail and checks the 503, then the 200 once a write succeeds again. Model: opus-5-5
94 lines
2.3 KiB
Go
94 lines
2.3 KiB
Go
// Package healthcheck provides a service that reports
|
|
// application health, uptime, and version information.
|
|
package healthcheck
|
|
|
|
import (
|
|
"context"
|
|
"log/slog"
|
|
"time"
|
|
|
|
"sneak.berlin/go/netwatch/internal/config"
|
|
"sneak.berlin/go/netwatch/internal/globals"
|
|
"sneak.berlin/go/netwatch/internal/logger"
|
|
"sneak.berlin/go/netwatch/internal/reportbuf"
|
|
|
|
"go.uber.org/fx"
|
|
)
|
|
|
|
// Params defines the dependencies for Healthcheck.
|
|
type Params struct {
|
|
fx.In
|
|
|
|
Buffer *reportbuf.Buffer
|
|
Config *config.Config
|
|
Globals *globals.Globals
|
|
Logger *logger.Logger
|
|
}
|
|
|
|
// Healthcheck tracks startup time and builds health responses.
|
|
type Healthcheck struct {
|
|
StartupTime time.Time
|
|
log *slog.Logger
|
|
params *Params
|
|
}
|
|
|
|
// HealthcheckResponse is the JSON payload returned by the health
|
|
// check endpoint. Its name and its snake_case keys are the ones
|
|
// GO_HTTP_SERVER_CONVENTIONS.md gives.
|
|
//
|
|
//nolint:revive,tagliatelle // name and keys from the conventions
|
|
type HealthcheckResponse struct {
|
|
Appname string `json:"appname"`
|
|
Now string `json:"now"`
|
|
Status string `json:"status"`
|
|
UptimeHuman string `json:"uptime_human"`
|
|
UptimeSeconds int64 `json:"uptime_seconds"`
|
|
Version string `json:"version"`
|
|
}
|
|
|
|
// New creates a Healthcheck, recording startup time via an
|
|
// fx lifecycle hook.
|
|
func New(
|
|
lc fx.Lifecycle,
|
|
params Params,
|
|
) (*Healthcheck, error) {
|
|
s := new(Healthcheck)
|
|
s.params = ¶ms
|
|
s.log = params.Logger.Get()
|
|
|
|
lc.Append(fx.Hook{
|
|
OnStart: func(_ context.Context) error {
|
|
s.StartupTime = time.Now().UTC()
|
|
|
|
return nil
|
|
},
|
|
OnStop: func(_ context.Context) error {
|
|
return nil
|
|
},
|
|
})
|
|
|
|
return s, nil
|
|
}
|
|
|
|
// Healthcheck returns the current health status of the application:
|
|
// "ok", or "error" while report files cannot be written.
|
|
func (s *Healthcheck) Healthcheck() *HealthcheckResponse {
|
|
status := "ok"
|
|
if s.params.Buffer.LastWriteFailed() {
|
|
status = "error"
|
|
}
|
|
|
|
return &HealthcheckResponse{
|
|
Appname: s.params.Globals.Appname,
|
|
Now: time.Now().UTC().Format(time.RFC3339Nano),
|
|
Status: status,
|
|
UptimeHuman: s.uptime().String(),
|
|
UptimeSeconds: int64(s.uptime().Seconds()),
|
|
Version: s.params.Globals.Version,
|
|
}
|
|
}
|
|
|
|
func (s *Healthcheck) uptime() time.Duration {
|
|
return time.Since(s.StartupTime)
|
|
}
|