feat(go): add platform principles and health probes
This commit is contained in:
132
go/vehicle-gateway/internal/health/health.go
Normal file
132
go/vehicle-gateway/internal/health/health.go
Normal file
@@ -0,0 +1,132 @@
|
||||
package health
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Check struct {
|
||||
Name string
|
||||
Check func(context.Context) error
|
||||
}
|
||||
|
||||
type Handler struct {
|
||||
service string
|
||||
checks []Check
|
||||
}
|
||||
|
||||
type Logger interface {
|
||||
Info(string, ...any)
|
||||
Error(string, ...any)
|
||||
}
|
||||
|
||||
func NewHandler(service string, checks []Check) *Handler {
|
||||
service = strings.TrimSpace(service)
|
||||
if service == "" {
|
||||
service = "vehicle-service"
|
||||
}
|
||||
return &Handler{service: service, checks: checks}
|
||||
}
|
||||
|
||||
func NewMux(service string, checks []Check) *http.ServeMux {
|
||||
mux := http.NewServeMux()
|
||||
handler := NewHandler(service, checks)
|
||||
mux.Handle("/healthz", handler)
|
||||
mux.Handle("/readyz", handler)
|
||||
return mux
|
||||
}
|
||||
|
||||
func NewServer(addr string, service string, checks []Check) *http.Server {
|
||||
addr = strings.TrimSpace(addr)
|
||||
if addr == "" {
|
||||
return nil
|
||||
}
|
||||
return &http.Server{
|
||||
Addr: addr,
|
||||
Handler: NewMux(service, checks),
|
||||
ReadHeaderTimeout: 5 * time.Second,
|
||||
}
|
||||
}
|
||||
|
||||
func Start(ctx context.Context, logger Logger, server *http.Server) {
|
||||
if server == nil {
|
||||
return
|
||||
}
|
||||
go func() {
|
||||
<-ctx.Done()
|
||||
shutdownCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
_ = server.Shutdown(shutdownCtx)
|
||||
}()
|
||||
go func() {
|
||||
if logger != nil {
|
||||
logger.Info("health server started", "addr", server.Addr)
|
||||
}
|
||||
if err := server.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) && logger != nil {
|
||||
logger.Error("health server failed", "error", err)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodGet {
|
||||
writeHealth(w, http.StatusMethodNotAllowed, map[string]any{"error": "method not allowed"})
|
||||
return
|
||||
}
|
||||
path := strings.Trim(r.URL.Path, "/")
|
||||
if path != "healthz" && path != "readyz" {
|
||||
writeHealth(w, http.StatusNotFound, map[string]any{"error": "route not found"})
|
||||
return
|
||||
}
|
||||
if path == "healthz" {
|
||||
writeHealth(w, http.StatusOK, map[string]any{
|
||||
"service": h.service,
|
||||
"status": "ok",
|
||||
"time": time.Now().UTC().Format(time.RFC3339),
|
||||
})
|
||||
return
|
||||
}
|
||||
h.writeReadiness(w, r)
|
||||
}
|
||||
|
||||
func (h *Handler) writeReadiness(w http.ResponseWriter, r *http.Request) {
|
||||
ctx, cancel := context.WithTimeout(r.Context(), 2*time.Second)
|
||||
defer cancel()
|
||||
|
||||
status := "ok"
|
||||
code := http.StatusOK
|
||||
checks := make(map[string]any, len(h.checks))
|
||||
for _, check := range h.checks {
|
||||
name := strings.TrimSpace(check.Name)
|
||||
if name == "" {
|
||||
continue
|
||||
}
|
||||
if check.Check == nil {
|
||||
checks[name] = map[string]any{"status": "ok"}
|
||||
continue
|
||||
}
|
||||
if err := check.Check(ctx); err != nil {
|
||||
status = "degraded"
|
||||
code = http.StatusServiceUnavailable
|
||||
checks[name] = map[string]any{"status": "error", "error": err.Error()}
|
||||
continue
|
||||
}
|
||||
checks[name] = map[string]any{"status": "ok"}
|
||||
}
|
||||
writeHealth(w, code, map[string]any{
|
||||
"service": h.service,
|
||||
"status": status,
|
||||
"time": time.Now().UTC().Format(time.RFC3339),
|
||||
"checks": checks,
|
||||
})
|
||||
}
|
||||
|
||||
func writeHealth(w http.ResponseWriter, status int, payload map[string]any) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(status)
|
||||
_ = json.NewEncoder(w).Encode(payload)
|
||||
}
|
||||
Reference in New Issue
Block a user