initial commit
This commit is contained in:
9
pkg/health/const.go
Normal file
9
pkg/health/const.go
Normal file
@@ -0,0 +1,9 @@
|
||||
package health
|
||||
|
||||
type Status string
|
||||
|
||||
const (
|
||||
StatusHealthy Status = "healthy"
|
||||
StatusUnhealthy Status = "unhealthy"
|
||||
StatusDegraded Status = "degraded"
|
||||
)
|
||||
82
pkg/health/health.go
Normal file
82
pkg/health/health.go
Normal file
@@ -0,0 +1,82 @@
|
||||
package health
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
type (
|
||||
Checker func(ctx context.Context) HealthCheck
|
||||
|
||||
HealthCheck struct {
|
||||
Name string `json:"name"`
|
||||
Status Status `json:"status"`
|
||||
Message string `json:"message,omitempty"`
|
||||
Timestamp time.Time `json:"timestamp"`
|
||||
Duration time.Duration `json:"duration"`
|
||||
Details map[string]interface{} `json:"details,omitempty"`
|
||||
}
|
||||
|
||||
HealthResponse struct {
|
||||
Status Status `json:"status"`
|
||||
Timestamp time.Time `json:"timestamp"`
|
||||
Version string `json:"version"`
|
||||
Checks map[string]HealthCheck `json:"checks"`
|
||||
Details map[string]interface{} `json:"details,omitempty"`
|
||||
}
|
||||
)
|
||||
|
||||
func Health(ctx context.Context, version string, checkers ...Checker) HealthResponse {
|
||||
start := time.Now()
|
||||
|
||||
results := make(map[string]HealthCheck, len(checkers))
|
||||
wg := sync.WaitGroup{}
|
||||
mu := sync.Mutex{}
|
||||
|
||||
wg.Add(len(checkers))
|
||||
for _, checker := range checkers {
|
||||
go func(c Checker) {
|
||||
defer wg.Done()
|
||||
check := c(ctx)
|
||||
mu.Lock()
|
||||
results[check.Name] = check
|
||||
mu.Unlock()
|
||||
}(checker)
|
||||
}
|
||||
wg.Wait()
|
||||
|
||||
// Determine overall status
|
||||
overallStatus := determineOverallStatus(results)
|
||||
|
||||
return HealthResponse{
|
||||
Status: overallStatus,
|
||||
Timestamp: time.Now(),
|
||||
Version: version,
|
||||
Checks: results,
|
||||
Details: map[string]interface{}{
|
||||
"uptime": time.Since(start).String(),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func determineOverallStatus(checks map[string]HealthCheck) Status {
|
||||
var unhealthyCount, degradedCount int
|
||||
for _, c := range checks {
|
||||
switch c.Status {
|
||||
case StatusUnhealthy:
|
||||
unhealthyCount++
|
||||
case StatusDegraded:
|
||||
degradedCount++
|
||||
}
|
||||
}
|
||||
|
||||
switch {
|
||||
case unhealthyCount > 0:
|
||||
return StatusUnhealthy
|
||||
case degradedCount > 0:
|
||||
return StatusDegraded
|
||||
default:
|
||||
return StatusHealthy
|
||||
}
|
||||
}
|
||||
113
pkg/health/infra_checker.go
Normal file
113
pkg/health/infra_checker.go
Normal file
@@ -0,0 +1,113 @@
|
||||
package health
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/redis/go-redis/v9"
|
||||
"gorm.io/gorm"
|
||||
rabbitmq "base/pkg/rabbit"
|
||||
"time"
|
||||
)
|
||||
|
||||
func DatabaseHealthChecker(db *gorm.DB) Checker {
|
||||
return func(ctx context.Context) HealthCheck {
|
||||
start := time.Now()
|
||||
check := HealthCheck{
|
||||
Name: "database",
|
||||
Timestamp: time.Now(),
|
||||
}
|
||||
|
||||
// Perform health check
|
||||
sqlDB, err := db.DB()
|
||||
if err != nil {
|
||||
check.Status = StatusUnhealthy
|
||||
check.Message = "Failed to get database connection: " + err.Error()
|
||||
check.Duration = time.Since(start)
|
||||
return check
|
||||
}
|
||||
|
||||
err = sqlDB.PingContext(ctx)
|
||||
if err != nil {
|
||||
check.Status = StatusUnhealthy
|
||||
check.Message = "Database ping failed: " + err.Error()
|
||||
check.Duration = time.Since(start)
|
||||
return check
|
||||
}
|
||||
|
||||
check.Status = StatusHealthy
|
||||
check.Message = "Database connection is healthy"
|
||||
check.Duration = time.Since(start)
|
||||
check.Details = map[string]interface{}{
|
||||
"connected": true,
|
||||
}
|
||||
|
||||
return check
|
||||
}
|
||||
}
|
||||
|
||||
func RabbitMQHealthChecker(rabbitmq rabbitmq.Client) Checker {
|
||||
return func(ctx context.Context) HealthCheck {
|
||||
start := time.Now()
|
||||
check := HealthCheck{
|
||||
Name: "rabbitmq",
|
||||
Timestamp: time.Now(),
|
||||
}
|
||||
|
||||
// Perform health check
|
||||
err := rabbitmq.HealthCheck()
|
||||
if err != nil {
|
||||
check.Status = StatusUnhealthy
|
||||
check.Message = "RabbitMQ health check failed: " + err.Error()
|
||||
check.Duration = time.Since(start)
|
||||
return check
|
||||
}
|
||||
|
||||
check.Status = StatusHealthy
|
||||
check.Message = "RabbitMQ connection is healthy"
|
||||
check.Duration = time.Since(start)
|
||||
check.Details = map[string]interface{}{
|
||||
"connected": true,
|
||||
}
|
||||
|
||||
return check
|
||||
}
|
||||
}
|
||||
|
||||
func RedisHealthChecker(redis *redis.Client) Checker {
|
||||
return func(ctx context.Context) HealthCheck {
|
||||
start := time.Now()
|
||||
check := HealthCheck{
|
||||
Name: "redis",
|
||||
Timestamp: time.Now(),
|
||||
}
|
||||
|
||||
// Perform health check
|
||||
ctx, cancel := context.WithTimeout(ctx, 5*time.Second)
|
||||
defer cancel()
|
||||
|
||||
_, err := redis.Ping(ctx).Result()
|
||||
if err != nil {
|
||||
check.Status = StatusUnhealthy
|
||||
check.Message = "Redis ping failed: " + err.Error()
|
||||
check.Duration = time.Since(start)
|
||||
return check
|
||||
}
|
||||
|
||||
// Get Redis info
|
||||
info, err := redis.Info(ctx, "server", "clients", "memory", "stats").Result()
|
||||
if err != nil {
|
||||
check.Status = StatusDegraded
|
||||
check.Message = "Redis is responding but info command failed: " + err.Error()
|
||||
check.Duration = time.Since(start)
|
||||
return check
|
||||
}
|
||||
|
||||
check.Status = StatusHealthy
|
||||
check.Message = "Redis connection is healthy"
|
||||
check.Duration = time.Since(start)
|
||||
check.Details = map[string]interface{}{
|
||||
"info": info,
|
||||
}
|
||||
|
||||
return check
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user