// 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" "go.uber.org/fx" ) // Params defines the dependencies for Healthcheck. type Params struct { fx.In 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. func (s *Healthcheck) Healthcheck() *HealthcheckResponse { return &HealthcheckResponse{ Appname: s.params.Globals.Appname, Now: time.Now().UTC().Format(time.RFC3339Nano), Status: "ok", 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) }