Files
tokens-reef/backend/internal/server/routes/common.go
User c4007afe6b
Some checks failed
CI / test (push) Has been cancelled
CI / golangci-lint (push) Has been cancelled
Security Scan / backend-security (push) Has been cancelled
Security Scan / frontend-security (push) Has been cancelled
feat: add Sora admin page and integrate DB/Redis Prometheus metrics
- Create SoraAdminView with overview, user stats, and generations tabs
- Add /admin/sora route for Sora management
- Add i18n support (zh/en) for Sora admin page
- Extract Prometheus metrics to prommetrics package to avoid import cycles
- Integrate SetDBConnections/SetRedisConnections in OpsMetricsCollector
2026-04-16 12:01:12 +08:00

211 lines
5.1 KiB
Go

package routes
import (
"net/http"
"sync"
"time"
"github.com/Wei-Shaw/sub2api/internal/prommetrics"
"github.com/gin-gonic/gin"
"github.com/prometheus/client_golang/prometheus/promhttp"
)
// HealthChecker defines the interface for health check dependencies
type HealthChecker interface {
CheckDatabase() bool
CheckRedis() bool
}
var (
healthChecker HealthChecker
healthCheckerOnce sync.Once
)
// SetHealthChecker sets the health checker instance (called during app initialization)
func SetHealthChecker(checker HealthChecker) {
healthCheckerOnce.Do(func() {
healthChecker = checker
})
}
// RegisterCommonRoutes registers common routes (health check, metrics, etc.)
func RegisterCommonRoutes(r *gin.Engine) {
// Health check - enhanced with dependency checks
r.GET("/health", healthHandler)
// Readiness check - for Kubernetes readiness probe
r.GET("/ready", readinessHandler)
// Liveness check - for Kubernetes liveness probe
r.GET("/live", livenessHandler)
// Prometheus metrics endpoint - use the shared registry from prommetrics package
r.GET("/metrics", gin.WrapH(promhttp.HandlerFor(prommetrics.Registry, promhttp.HandlerOpts{})))
// Claude Code telemetry logs (ignore, return 200 directly)
r.POST("/api/event_logging/batch", func(c *gin.Context) {
c.Status(http.StatusOK)
})
// Setup status endpoint (always returns needs_setup: false in normal mode)
// This is used by the frontend to detect when the service has restarted after setup
r.GET("/setup/status", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"code": 0,
"data": gin.H{
"needs_setup": false,
"step": "completed",
},
})
})
}
// healthHandler returns the health status of the service and its dependencies
func healthHandler(c *gin.Context) {
status := "ok"
statusCode := http.StatusOK
components := make(map[string]interface{})
allHealthy := true
// Check database
dbStatus := "unknown"
dbHealthy := false
if healthChecker != nil {
dbHealthy = healthChecker.CheckDatabase()
if dbHealthy {
dbStatus = "healthy"
} else {
dbStatus = "unhealthy"
allHealthy = false
}
}
components["database"] = gin.H{
"status": dbStatus,
"healthy": dbHealthy,
}
// Check Redis
redisStatus := "unknown"
redisHealthy := false
if healthChecker != nil {
redisHealthy = healthChecker.CheckRedis()
if redisHealthy {
redisStatus = "healthy"
} else {
redisStatus = "unhealthy"
allHealthy = false
}
}
components["redis"] = gin.H{
"status": redisStatus,
"healthy": redisHealthy,
}
// Overall status
if !allHealthy {
status = "degraded"
statusCode = http.StatusServiceUnavailable
}
response := gin.H{
"status": status,
"timestamp": time.Now().UTC().Format(time.RFC3339),
"components": components,
}
c.JSON(statusCode, response)
}
// readinessHandler checks if the service is ready to accept traffic
func readinessHandler(c *gin.Context) {
// For readiness, we require all critical dependencies to be healthy
if healthChecker == nil {
c.JSON(http.StatusOK, gin.H{
"status": "ready",
})
return
}
dbHealthy := healthChecker.CheckDatabase()
redisHealthy := healthChecker.CheckRedis()
if dbHealthy && redisHealthy {
c.JSON(http.StatusOK, gin.H{
"status": "ready",
"timestamp": time.Now().UTC().Format(time.RFC3339),
})
return
}
components := make(map[string]bool)
components["database"] = dbHealthy
components["redis"] = redisHealthy
c.JSON(http.StatusServiceUnavailable, gin.H{
"status": "not_ready",
"timestamp": time.Now().UTC().Format(time.RFC3339),
"components": components,
})
}
// livenessHandler checks if the service is alive (for Kubernetes liveness probe)
func livenessHandler(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"status": "alive",
"timestamp": time.Now().UTC().Format(time.RFC3339),
})
}
// Prometheus metric update functions - delegate to prommetrics package
// RecordHTTPRequest records an HTTP request for Prometheus metrics
func RecordHTTPRequest(method, path string, statusCode int, duration time.Duration) {
prommetrics.RecordHTTPRequest(method, path, statusCode, duration)
}
// SetDBConnections sets database connection metrics
func SetDBConnections(active, idle int) {
prommetrics.SetDBConnections(active, idle)
}
// SetRedisConnections sets Redis connection metrics
func SetRedisConnections(total, idle int) {
prommetrics.SetRedisConnections(total, idle)
}
// SetActiveAccounts sets the active accounts count
func SetActiveAccounts(count int) {
prommetrics.SetActiveAccounts(count)
}
// SetRequestQueueDepth sets the request queue depth
func SetRequestQueueDepth(depth int) {
prommetrics.SetRequestQueueDepth(depth)
}
// SetOpsHealthScore sets the overall health score
func SetOpsHealthScore(score int) {
prommetrics.SetOpsHealthScore(score)
}
// SetErrorRate sets the error rate
func SetErrorRate(rate float64) {
prommetrics.SetErrorRate(rate)
}
// SetSuccessRate sets the success rate
func SetSuccessRate(rate float64) {
prommetrics.SetSuccessRate(rate)
}
// SetQPS sets queries per second
func SetQPS(value float64) {
prommetrics.SetQPS(value)
}
// SetTPS sets tokens per second
func SetTPS(value float64) {
prommetrics.SetTPS(value)
}