@@ -7,10 +7,18 @@ module PlaceOS::Source::Api
77 class Root < Application
88 base " /api/source/v1/"
99
10- # healthcheck, returns OK if all connections are good
10+ # healthcheck, returns JSON with status of all services
1111 @[AC ::Route ::GET (" /" )]
12- def index : Nil
13- raise " health check failed" unless self .class.healthcheck?
12+ def index : NamedTuple (healthy: Bool , services: Hash (String , NamedTuple (status: String , error: String ?)))
13+ result = self .class.healthcheck
14+ unless result[:healthy ]
15+ failed_services = result[:services ].select { |_ , service_info | service_info[:status ] == " unhealthy" }
16+ error_details = failed_services.map { |service , info | " #{ service } : #{ info[:error ] } " }.join(" , " )
17+ Log .error { " HEALTH CHECK FAILED - #{ error_details } " }
18+ end
19+
20+ # Return 200 if all healthy, 503 (Service Unavailable) if any service is unhealthy
21+ render status: (result[:healthy ] ? 200 : 503 ), json: result
1422 end
1523
1624 @[AC ::Route ::GET (" /version" )]
@@ -23,26 +31,47 @@ module PlaceOS::Source::Api
2331 )
2432 end
2533
26- def self.healthcheck ? : Bool
27- Promise .all(
34+ def self.healthcheck : NamedTuple ( healthy: Bool , services: Hash ( String , NamedTuple ( status: String , error: String ?)))
35+ results = Promise .all(
2836 Promise .defer {
29- check_resource? (" redis" ) { redis.ping }
37+ check_resource(" redis" ) { redis.ping }
3038 },
3139 Promise .defer {
32- check_resource? (" postgres" ) { pg_healthcheck }
40+ check_resource(" postgres" ) { pg_healthcheck }
3341 },
3442 Promise .defer {
35- check_resource? (" influx" ) { influx_healthcheck }
43+ check_resource(" influx" ) { influx_healthcheck }
3644 },
37- ).then(& .all?).get
45+ ).get
46+
47+ services = Hash (String , NamedTuple (status: String , error: String ?)).new
48+ overall_healthy = true
49+
50+ results.each do |result |
51+ if result[:success ]
52+ services[result[:service ]] = {status: " healthy" , error: nil }
53+ else
54+ services[result[:service ]] = {status: " unhealthy" , error: result[:error ]}
55+ overall_healthy = false
56+ end
57+ end
58+
59+ {
60+ healthy: overall_healthy,
61+ services: services,
62+ }
3863 end
3964
40- private def self.check_resource? (resource , & )
41- Log .trace { " healthchecking #{ resource } " }
42- !! yield
65+ private def self.check_resource (service_name : String , & ) : NamedTuple (service: String , success: Bool , error: String ?)
66+ Log .trace { " healthchecking #{ service_name } " }
67+ yield
68+ {service: service_name, success: true , error: nil }
4369 rescue exception
44- Log .error(exception: exception) { {" connection check to #{ resource } failed" } }
45- false
70+ error_msg = exception.message || exception.class.name
71+ Log .error(exception: exception) { {" connection check to #{ service_name } failed" } }
72+ # Also log to console for Docker/K8s visibility
73+ Log .error { " Health check failed for #{ service_name } : #{ error_msg } " }
74+ {service: service_name, success: false , error: error_msg}
4675 end
4776
4877 private def self.pg_healthcheck
@@ -74,15 +103,11 @@ module PlaceOS::Source::Api
74103 influx_host = INFLUX_HOST
75104 return false if influx_host.nil?
76105
77- begin
78- HTTP ::Client .new(URI .parse(influx_host)) do |client |
79- client.connect_timeout = 5 .seconds
80- client.read_timeout = 5 .seconds
81- response = client.get(" /health" )
82- response.status_code == 200
83- end
84- rescue
85- false
106+ HTTP ::Client .new(URI .parse(influx_host)) do |client |
107+ client.connect_timeout = 5 .seconds
108+ client.read_timeout = 5 .seconds
109+ response = client.get(" /health" )
110+ response.status_code == 200
86111 end
87112 end
88113
0 commit comments