Implement health check for deploy. Make version endpoint to return revision if not set

This commit is contained in:
2026-03-25 23:48:48 +03:00
parent ee6b510e41
commit fc0c84f7f7
9 changed files with 100 additions and 15 deletions

View File

@@ -7,11 +7,6 @@ Server.Tls.Enabled = false
Server.Tls.CertFile = Server.Tls.CertFile =
Server.Tls.KeyFile = Server.Tls.KeyFile =
Socket.MaxHttpBufferSize = 2097152
Socket.PingInterval = 25s
Socket.PingTimeout = 20s
Socket.Debug = false
# Prometheus settings # Prometheus settings
Metrics.Endpoint = /metrics Metrics.Endpoint = /metrics
Metrics.HistogramBuckets = 0.001,0.002,0.005,0.01,0.025,0.05,0.1,0.25,0.5,1,2.5,5,10 Metrics.HistogramBuckets = 0.001,0.002,0.005,0.01,0.025,0.05,0.1,0.25,0.5,1,2.5,5,10

View File

@@ -0,0 +1,46 @@
package health
import (
"encoding/json"
"fmt"
"log/slog"
"net/http"
"payouts/internal/service/database"
)
// Route health route
const Route = "/health"
// New constructs a new health Handler.
func New(dbService database.Service) (Handler, error) {
return &handler{
dbService: dbService,
}, nil
}
type handler struct {
dbService database.Service
}
// HealthHandler handles the health check requests
func (h *handler) Health(w http.ResponseWriter, r *http.Request) {
defer r.Body.Close()
w.Header().Set("Content-Type", "application/json")
status := map[string]any{}
// Check database connection
err := h.dbService.HealthCheck()
if err != nil {
w.WriteHeader(http.StatusServiceUnavailable)
status["Error"] = fmt.Sprintf("%v", err)
slog.Error("Health check failed", slog.String("error", status["Error"].(string)))
} else {
w.WriteHeader(http.StatusOK)
}
status["OK"] = (err == nil)
encoder := json.NewEncoder(w)
encoder.Encode(status)
}

View File

@@ -0,0 +1,16 @@
package health
import (
"net/http"
"go.uber.org/fx"
)
var Module = fx.Options(
fx.Provide(New),
)
// Handler health handler interface
type Handler interface {
Health(http.ResponseWriter, *http.Request)
}

View File

@@ -11,6 +11,7 @@ import (
"github.com/prometheus/client_golang/prometheus/promhttp" "github.com/prometheus/client_golang/prometheus/promhttp"
"go.uber.org/fx" "go.uber.org/fx"
"payouts/internal/api/health"
"payouts/internal/api/payout" "payouts/internal/api/payout"
"payouts/internal/api/user" "payouts/internal/api/user"
"payouts/internal/api/version" "payouts/internal/api/version"
@@ -21,6 +22,7 @@ import (
// Module is a fx module // Module is a fx module
var Module = fx.Options( var Module = fx.Options(
user.Module, user.Module,
health.Module,
payout.Module, payout.Module,
version.Module, version.Module,
monitoring.Module, monitoring.Module,
@@ -40,6 +42,7 @@ type Params struct {
PayoutHandler payout.Handler PayoutHandler payout.Handler
UserHandler user.Handler UserHandler user.Handler
Version version.Handler Version version.Handler
HealthHandler health.Handler
Metrics monitoring.Metrics Metrics monitoring.Metrics
} }
@@ -50,7 +53,10 @@ func RegisterRoutes(p Params, lc fx.Lifecycle) {
router := mux.NewRouter() router := mux.NewRouter()
router.StrictSlash(true) router.StrictSlash(true)
// Version endpoint
router.HandleFunc(version.Route, p.Version.VersionHandler).Methods(http.MethodGet) router.HandleFunc(version.Route, p.Version.VersionHandler).Methods(http.MethodGet)
// Health check endpoint
router.HandleFunc(health.Route, p.HealthHandler.Health).Methods(http.MethodGet)
if p.AppConfig.Server.EnablePProfEndpoints { if p.AppConfig.Server.EnablePProfEndpoints {
router.HandleFunc("/debug/pprof/cmdline", pprof.Cmdline) router.HandleFunc("/debug/pprof/cmdline", pprof.Cmdline)

View File

@@ -3,6 +3,7 @@ package version
import ( import (
"io" "io"
"net/http" "net/http"
"runtime/debug"
"go.uber.org/fx" "go.uber.org/fx"
@@ -39,7 +40,20 @@ type handler struct {
func (h *handler) VersionHandler(w http.ResponseWriter, r *http.Request) { func (h *handler) VersionHandler(w http.ResponseWriter, r *http.Request) {
defer r.Body.Close() defer r.Body.Close()
ver := h.version
if ver == "unknown" {
buildInfo, ok := debug.ReadBuildInfo()
if ok {
for _, setting := range buildInfo.Settings {
if setting.Key == "vcs.revision" {
ver = ver + "-" + setting.Value
break
}
}
}
}
w.Header().Set("Content-Type", "text/plain") w.Header().Set("Content-Type", "text/plain")
w.WriteHeader(http.StatusOK) w.WriteHeader(http.StatusOK)
io.WriteString(w, h.version) io.WriteString(w, ver+"\n")
} }

View File

@@ -109,3 +109,17 @@ func (d *dbService) UpdatePayoutByPayoutID(payoutId string, updateModel orm.Payo
p := d.getParams(opts...) p := d.getParams(opts...)
return gorm.G[orm.Payout](d.db).Where("payout_id = ?", payoutId).Updates(p.ctx, updateModel) return gorm.G[orm.Payout](d.db).Where("payout_id = ?", payoutId).Updates(p.ctx, updateModel)
} }
// HealthCheck implements [Service].
func (d *dbService) HealthCheck() error {
if d.db == nil {
return errors.New("database connection is nil")
}
db, err := d.db.DB()
if err != nil {
return err
}
return db.Ping()
}

View File

@@ -31,6 +31,7 @@ type Service interface {
CreatePayout(payoutModel *orm.Payout, opts ...Optional) error CreatePayout(payoutModel *orm.Payout, opts ...Optional) error
UpdatePayoutById(id uint, updateModel orm.Payout, opts ...Optional) (int, error) UpdatePayoutById(id uint, updateModel orm.Payout, opts ...Optional) (int, error)
UpdatePayoutByPayoutID(payoutId string, updateModel orm.Payout, opts ...Optional) (int, error) UpdatePayoutByPayoutID(payoutId string, updateModel orm.Payout, opts ...Optional) (int, error)
HealthCheck() error
} }
// Params represents the module input params // Params represents the module input params

View File

@@ -1,10 +1,3 @@
/*
* Copyright (c) New Cloud Technologies, Ltd., 2013-2026
*
* You can not use the contents of the file in any way without New Cloud Technologies Ltd. written permission.
* To obtain such a permit, you should contact New Cloud Technologies, Ltd. at https://myoffice.ru/contacts/
*/
package config package config
import ( import (