rails-health-checker 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/CHANGELOG.md +37 -0
- data/COMMANDS.md +118 -0
- data/Gemfile +6 -0
- data/Gemfile.lock +222 -0
- data/LICENSE +21 -0
- data/README.md +174 -0
- data/Rakefile +6 -0
- data/SECURITY.md +41 -0
- data/TESTING.md +64 -0
- data/TEST_RESULTS.md +51 -0
- data/example_usage.rb +23 -0
- data/lib/rails_health_checker/checker.rb +88 -0
- data/lib/rails_health_checker/dashboard_middleware.rb +503 -0
- data/lib/rails_health_checker/gem_analyzer.rb +39 -0
- data/lib/rails_health_checker/health_middleware.rb +53 -0
- data/lib/rails_health_checker/job_analyzer.rb +108 -0
- data/lib/rails_health_checker/railtie.rb +11 -0
- data/lib/rails_health_checker/report_generator.rb +499 -0
- data/lib/rails_health_checker/system_analyzer.rb +182 -0
- data/lib/rails_health_checker/tasks.rb +63 -0
- data/lib/rails_health_checker/version.rb +3 -0
- data/lib/rails_health_checker.rb +17 -0
- data/rails_health_checker.gemspec +33 -0
- data/simple_test.rb +52 -0
- data/test_gem.rb +100 -0
- metadata +117 -0
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
module RailsHealthChecker
|
|
2
|
+
class GemAnalyzer
|
|
3
|
+
def analyze
|
|
4
|
+
gems = Bundler.load.specs
|
|
5
|
+
outdated = check_outdated_gems
|
|
6
|
+
|
|
7
|
+
{
|
|
8
|
+
total: gems.count,
|
|
9
|
+
outdated: outdated.count,
|
|
10
|
+
vulnerable: check_vulnerable_gems.count,
|
|
11
|
+
details: gem_details(gems, outdated)
|
|
12
|
+
}
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
private
|
|
16
|
+
|
|
17
|
+
def check_outdated_gems
|
|
18
|
+
`bundle outdated --parseable`.split("\n").map do |line|
|
|
19
|
+
line.split(" ")[0] if line.include?("(")
|
|
20
|
+
end.compact
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def check_vulnerable_gems
|
|
24
|
+
# Simple check - in production, integrate with bundler-audit
|
|
25
|
+
[]
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def gem_details(gems, outdated)
|
|
29
|
+
gems.map do |gem|
|
|
30
|
+
{
|
|
31
|
+
name: gem.name,
|
|
32
|
+
version: gem.version.to_s,
|
|
33
|
+
outdated: outdated.include?(gem.name),
|
|
34
|
+
path: gem.gem_dir
|
|
35
|
+
}
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
module RailsHealthChecker
|
|
2
|
+
class HealthMiddleware
|
|
3
|
+
def initialize(app)
|
|
4
|
+
@app = app
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
def call(env)
|
|
8
|
+
if env['PATH_INFO'] == '/health'
|
|
9
|
+
health_check_response
|
|
10
|
+
else
|
|
11
|
+
@app.call(env)
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
private
|
|
16
|
+
|
|
17
|
+
def health_check_response
|
|
18
|
+
status = perform_quick_health_check
|
|
19
|
+
|
|
20
|
+
[
|
|
21
|
+
status[:code],
|
|
22
|
+
{ 'Content-Type' => 'application/json' },
|
|
23
|
+
[status[:body].to_json]
|
|
24
|
+
]
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def perform_quick_health_check
|
|
28
|
+
begin
|
|
29
|
+
db_healthy = ActiveRecord::Base.connection.active?
|
|
30
|
+
|
|
31
|
+
{
|
|
32
|
+
code: 200,
|
|
33
|
+
body: {
|
|
34
|
+
status: 'healthy',
|
|
35
|
+
timestamp: Time.current.iso8601,
|
|
36
|
+
database: db_healthy ? 'connected' : 'disconnected',
|
|
37
|
+
rails_version: Rails.version,
|
|
38
|
+
ruby_version: RUBY_VERSION
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
rescue => e
|
|
42
|
+
{
|
|
43
|
+
code: 503,
|
|
44
|
+
body: {
|
|
45
|
+
status: 'unhealthy',
|
|
46
|
+
timestamp: Time.current.iso8601,
|
|
47
|
+
error: e.message
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
module RailsHealthChecker
|
|
2
|
+
class JobAnalyzer
|
|
3
|
+
def analyze
|
|
4
|
+
{
|
|
5
|
+
sidekiq: analyze_sidekiq,
|
|
6
|
+
resque: analyze_resque,
|
|
7
|
+
active_job: analyze_active_job,
|
|
8
|
+
status: overall_job_status
|
|
9
|
+
}
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
private
|
|
13
|
+
|
|
14
|
+
def analyze_sidekiq
|
|
15
|
+
return { available: false } unless sidekiq_available?
|
|
16
|
+
|
|
17
|
+
stats = Sidekiq::Stats.new
|
|
18
|
+
{
|
|
19
|
+
available: true,
|
|
20
|
+
processed: stats.processed,
|
|
21
|
+
failed: stats.failed,
|
|
22
|
+
enqueued: stats.enqueued,
|
|
23
|
+
retry_size: stats.retry_size,
|
|
24
|
+
dead_size: stats.dead_size,
|
|
25
|
+
workers: stats.workers_size,
|
|
26
|
+
queues: queue_stats,
|
|
27
|
+
status: sidekiq_health_status(stats)
|
|
28
|
+
}
|
|
29
|
+
rescue => e
|
|
30
|
+
{ available: true, error: "Sidekiq connection failed: #{e.message}", status: 'error' }
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def analyze_resque
|
|
34
|
+
return { available: false } unless resque_available?
|
|
35
|
+
|
|
36
|
+
{
|
|
37
|
+
available: true,
|
|
38
|
+
pending: Resque.info[:pending],
|
|
39
|
+
processed: Resque.info[:processed],
|
|
40
|
+
failed: Resque.info[:failed],
|
|
41
|
+
workers: Resque.info[:workers],
|
|
42
|
+
working: Resque.info[:working],
|
|
43
|
+
queues: Resque.queues.size,
|
|
44
|
+
status: resque_health_status
|
|
45
|
+
}
|
|
46
|
+
rescue => e
|
|
47
|
+
{ available: true, error: "Resque connection failed: #{e.message}", status: 'error' }
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def analyze_active_job
|
|
51
|
+
return { available: false } unless active_job_available?
|
|
52
|
+
|
|
53
|
+
{
|
|
54
|
+
available: true,
|
|
55
|
+
adapter: ActiveJob::Base.queue_adapter.class.name,
|
|
56
|
+
status: 'healthy'
|
|
57
|
+
}
|
|
58
|
+
rescue => e
|
|
59
|
+
{ available: true, error: e.message, status: 'error' }
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def sidekiq_available?
|
|
63
|
+
defined?(Sidekiq)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def resque_available?
|
|
67
|
+
defined?(Resque)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def active_job_available?
|
|
71
|
+
defined?(ActiveJob)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def queue_stats
|
|
75
|
+
return {} unless sidekiq_available?
|
|
76
|
+
|
|
77
|
+
Sidekiq::Queue.all.map do |queue|
|
|
78
|
+
{
|
|
79
|
+
name: queue.name,
|
|
80
|
+
size: queue.size,
|
|
81
|
+
latency: queue.latency.round(2)
|
|
82
|
+
}
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def sidekiq_health_status(stats)
|
|
87
|
+
return 'critical' if stats.failed > 100
|
|
88
|
+
return 'warning' if stats.enqueued > 1000 || stats.retry_size > 50
|
|
89
|
+
'healthy'
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def resque_health_status
|
|
93
|
+
return 'critical' if Resque.info[:failed] > 100
|
|
94
|
+
return 'warning' if Resque.info[:pending] > 1000
|
|
95
|
+
'healthy'
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def overall_job_status
|
|
99
|
+
statuses = []
|
|
100
|
+
statuses << analyze_sidekiq[:status] if sidekiq_available?
|
|
101
|
+
statuses << analyze_resque[:status] if resque_available?
|
|
102
|
+
|
|
103
|
+
return 'critical' if statuses.include?('critical') || statuses.include?('error')
|
|
104
|
+
return 'warning' if statuses.include?('warning')
|
|
105
|
+
statuses.empty? ? 'not_configured' : 'healthy'
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
end
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
module RailsHealthChecker
|
|
2
|
+
class Railtie < Rails::Railtie
|
|
3
|
+
rake_tasks do
|
|
4
|
+
load "rails_health_checker/tasks.rb"
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
initializer "rails_health_checker.add_middleware" do |app|
|
|
8
|
+
app.middleware.use RailsHealthChecker::DashboardMiddleware
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
end
|