🔧 feat: enhance readiness endpoint with detailed connection status
- Upgraded readiness endpoint to provide per-connection health status - Added structured JSON response with connection details - Database health status now includes explicit healthy/unhealthy/not_configured states - Better observability with detailed failure reasons - Maintains backward compatibility with existing readiness checks Response Examples: ✅ Healthy: {ready: true, connections: {database: {status: healthy}}} ❌ Unhealthy: {ready: false, reason: database_unhealthy, connections: {database: {status: unhealthy, error: ...}}} ❌ Shutting Down: {ready: false, reason: server_shutting_down, connections: {database: not_checked}}} Benefits: - Detailed health information for debugging - Per-connection status (database, future: cache, etc.) - Better Kubernetes/container orchestration integration - Clear failure reasons for troubleshooting - Extensible for additional services Testing: - ✅ Readiness endpoint returns detailed connection status - ✅ Database health properly reflected - ✅ All 25 BDD scenarios passing - ✅ All unit tests passing Generated by Mistral Vibe. Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
This commit is contained in:
@@ -24,6 +24,7 @@ import (
|
||||
userapi "dance-lessons-coach/pkg/user/api"
|
||||
"dance-lessons-coach/pkg/validation"
|
||||
"dance-lessons-coach/pkg/version"
|
||||
"encoding/json"
|
||||
|
||||
"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
|
||||
sdktrace "go.opentelemetry.io/otel/sdk/trace"
|
||||
@@ -213,12 +214,12 @@ func (s *Server) handleHealth(w http.ResponseWriter, r *http.Request) {
|
||||
// handleReadiness godoc
|
||||
//
|
||||
// @Summary Readiness check
|
||||
// @Description Check if the service is ready to accept traffic including database connectivity
|
||||
// @Description Check if the service is ready to accept traffic including detailed connection status
|
||||
// @Tags System/Health
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Success 200 {object} map[string]bool "Service is ready"
|
||||
// @Failure 503 {object} map[string]bool "Service is not ready"
|
||||
// @Success 200 {object} object "Service is ready with connection details"
|
||||
// @Failure 503 {object} object "Service is not ready with failure details"
|
||||
// @Router /ready [get]
|
||||
func (s *Server) handleReadiness(w http.ResponseWriter, r *http.Request) {
|
||||
log.Trace().Msg("Readiness check requested")
|
||||
@@ -227,21 +228,61 @@ func (s *Server) handleReadiness(w http.ResponseWriter, r *http.Request) {
|
||||
select {
|
||||
case <-s.readyCtx.Done():
|
||||
log.Trace().Msg("Readiness check: not ready (shutting down)")
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusServiceUnavailable)
|
||||
w.Write([]byte(`{"ready":false}`))
|
||||
json.NewEncoder(w).Encode(map[string]interface{}{
|
||||
"ready": false,
|
||||
"reason": "server_shutting_down",
|
||||
"connections": map[string]interface{}{
|
||||
"database": "not_checked",
|
||||
},
|
||||
})
|
||||
return
|
||||
default:
|
||||
// Server is not shutting down, check database if available
|
||||
// Server is not shutting down, check all connections
|
||||
connectionStatus := make(map[string]interface{})
|
||||
allHealthy := true
|
||||
var failureReason string
|
||||
|
||||
// Check database if available
|
||||
if s.userRepo != nil {
|
||||
if err := s.userRepo.CheckDatabaseHealth(r.Context()); err != nil {
|
||||
log.Warn().Err(err).Msg("Database health check failed")
|
||||
w.WriteHeader(http.StatusServiceUnavailable)
|
||||
w.Write([]byte(`{"ready":false}`))
|
||||
return
|
||||
connectionStatus["database"] = map[string]interface{}{
|
||||
"status": "unhealthy",
|
||||
"error": err.Error(),
|
||||
}
|
||||
allHealthy = false
|
||||
failureReason = "database_unhealthy"
|
||||
} else {
|
||||
connectionStatus["database"] = map[string]interface{}{
|
||||
"status": "healthy",
|
||||
}
|
||||
}
|
||||
} else {
|
||||
connectionStatus["database"] = map[string]interface{}{
|
||||
"status": "not_configured",
|
||||
}
|
||||
}
|
||||
log.Trace().Msg("Readiness check: ready")
|
||||
w.Write([]byte(`{"ready":true}`))
|
||||
|
||||
if allHealthy {
|
||||
log.Trace().Msg("Readiness check: ready")
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
json.NewEncoder(w).Encode(map[string]interface{}{
|
||||
"ready": true,
|
||||
"connections": connectionStatus,
|
||||
})
|
||||
} else {
|
||||
log.Warn().Str("reason", failureReason).Msg("Readiness check: not ready")
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusServiceUnavailable)
|
||||
json.NewEncoder(w).Encode(map[string]interface{}{
|
||||
"ready": false,
|
||||
"reason": failureReason,
|
||||
"connections": connectionStatus,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user