allgood 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: fe79d04db962fcfc50cb094c0b122506d09f903b7ee02e2ee7ae0c3930c78557
4
- data.tar.gz: fb9a69be909db1c10d5019dfadbdbee5b7da514913d8a907933cd2873e73f2f0
3
+ metadata.gz: 28b060b89f8c61aa677718b5fb317179709bc8fb41e72e393f4df54c779ad927
4
+ data.tar.gz: f2c53147cd88aa374d261f5922a33188f6fca0ceed0648c44de320c15ac91ade
5
5
  SHA512:
6
- metadata.gz: fc26bbc3685f38fbfa49e05047987537c64e06c55356c28d0c8a0a50b73b6981c10d66c96996ca44057720402f4f5ed1319309a33402b25a39d0bdf472518496
7
- data.tar.gz: ea88c1027193b356560b2361e65a28c42d3b10683df7e777796ff5de10c0c6e3aa93f7bb42fa70ef6ce9699d288e4efa74ceaa82667a5208ec006aa41506e6ea
6
+ metadata.gz: 2135e908374e1621ad8cddc12605fad50f2b84ac7253c67e32d6ceae02c0d0a805a0a2c682fefe9800a7b49c1d9ae7276059f7473499510de8e59b87f9ba2c9e
7
+ data.tar.gz: 482317b8650757f64e8a37a15b4f0f9e3789fd61d74dc05e51fcd58eff2710c701f59e7bd42594d724fe107bb39552565179c77d300b0d131a8857955d172f8d
data/CHANGELOG.md CHANGED
@@ -1,4 +1,12 @@
1
- ## [Unreleased]
1
+ ## [0.2.0] - 2024-10-26
2
+
3
+ - Improved the `allgood` DSL by adding optional conditionals on when individual checks are run
4
+ - Allow for environment-specific checks with `only` and `except` options (`check "Test Check", only: [:development, :test]`)
5
+ - Allow for conditional checks with `if` and `unless` options, which can be procs or any other condition (`check "Test Check", if: -> { condition }`)
6
+ - Added visual indication of skipped checks in the healthcheck page
7
+ - Improved developer experience by showing why checks were skipped (didn't meet conditions, environment-specific, etc.)
8
+ - New DSL changes are fully backward compatible with the previous version (new options are optional, and checks will run normally if they are not specified), so the new version won't break existing configurations
9
+ - Changed configuration loading to happen after Rails initialization so we fix the segfault that could occur when requiring gems in the `allgood.rb` configuration file before Rails was initialized
2
10
 
3
11
  ## [0.1.0] - 2024-08-22
4
12
 
data/README.md CHANGED
@@ -1,12 +1,20 @@
1
1
  # ✅ Allgood - Rails gem for health checks
2
2
 
3
- Add quick, simple, and beautiful health checks to your Rails application.
3
+ [![Gem Version](https://badge.fury.io/rb/allgood.svg)](https://badge.fury.io/rb/allgood)
4
4
 
5
- `allgood` allows you to define custom, business-oriented health checks (as in: are there any new users in the past 24 hours, are they actually using the app, does the last record have all the attributes we expect, etc.) in a very intuitive way that reads just like English – and provides a `/healthcheck` endpoint that displays the results in a beautiful page.
5
+ Add quick, simple, and beautiful health checks to your Rails application via a `/healthcheck` page.
6
6
 
7
- You can then use that endpoint to monitor the health of your application via UptimeRobot, Pingdom, etc. These services will load your `/healthcheck` page every few minutes, so all checks will be run when UptimeRobot fetches the page.
7
+ ![Example dashboard of the Allgood health check page](allgood.jpeg)
8
8
 
9
- ![alt text](allgood.jpeg)
9
+ ## How it works
10
+
11
+ `allgood` allows you to define custom health checks (as in: can the Rails app connect to the DB, are there any new users in the past 24 hours, are they actually using the app, etc.) in a very intuitive way that reads just like English.
12
+
13
+ It provides a `/healthcheck` endpoint that displays the results in a beautiful page.
14
+
15
+ You can then [use that endpoint to monitor the health of your application via UptimeRobot](https://uptimerobot.com/?rid=854006b5fe82e4), Pingdom, etc. These services will load your `/healthcheck` page every few minutes, so all checks will be run when UptimeRobot fetches the page.
16
+
17
+ `allgood` aims to provide developers with peace of mind by answering the question "is production okay?" at a glance.
10
18
 
11
19
  ## Installation
12
20
 
@@ -17,9 +25,10 @@ gem 'allgood'
17
25
 
18
26
  Then run `bundle install`.
19
27
 
20
- ## Usage
28
+ After installing the gem, you need to mount the `/healthcheck` route and define your health checks in a `config/allgood.rb` file.
29
+
21
30
 
22
- ### Mounting the Engine
31
+ ## Mount the `/healthcheck` route
23
32
 
24
33
  In your `config/routes.rb` file, mount the Allgood engine:
25
34
  ```ruby
@@ -28,50 +37,39 @@ mount Allgood::Engine => '/healthcheck'
28
37
 
29
38
  You can now navigate to `/healthcheck` to see the health check results.
30
39
 
31
- The `/healthcheck` page returns a `200` HTTP code if all checks are successful – and error `503 Service Unavailable` otherwise.
40
+ The `/healthcheck` page returns HTTP codes:
41
+ - `200 OK` if all checks are successful
42
+ - `503 Service Unavailable` error otherwise
32
43
 
33
- `allgood` is also a nice replacement for the default `/up` Rails action, so Kamal to also checks things like if the database connection is good. Just change the mounting route to `/up` instead of `/healthcheck`
44
+ Services like UptimeRobot pick up these HTTP codes, which makes monitoring easy.
34
45
 
46
+ **Kamal**: `allgood` can also be used as a replacement for the default `/up` Rails action, to make [Kamal](https://github.com/basecamp/kamal) check things like if the database connection is healthy when deploying your app's containers. Just change `allgood`'s mounting route to `/up` instead of `/healthcheck`, or configure Kamal to use the `allgood` route.
35
47
 
36
- ### Configuring Health Checks
48
+ > [!TIP]
49
+ > If you're using Kamal with `allgood`, container deployment will fail if any defined checks fail, [without feedback from Kamal](https://github.com/rameerez/allgood/issues/1) on what went wrong. Your containers will just not start, and you'll get a generic error message. To avoid this, you can either keep the `allgood.rb` file very minimal (e.g., only check for active DB connection, migrations up to date, etc.) so the app deployment is likely to succeed, or you can use the default `/up` route for Kamal, and then mount `allgood` on another route for more advanced business-oriented checks. What you want to avoid is your app deployment failing because of usage-dependent or business-oriented checks, like your app not deploying because it didn't get any users in the past hour, or something like that.
37
50
 
38
- Create a file `config/allgood.rb` in your Rails application. This is where you'll define your health checks:
51
+ ## Configure your health checks
52
+
53
+ Create a file `config/allgood.rb` in your Rails application. This is where you'll define your health checks. Here's a simple example:
39
54
  ```ruby
40
55
  # config/allgood.rb
41
56
 
42
57
  check "We have an active database connection" do
43
- make_sure ActiveRecord::Base.connection.active?
44
- end
45
- ```
46
-
47
- This will run the check upon page load, and will show "Check passed" or "Check failed" next to it. You can also specify a custom human-readable success / error message for each check, so you don't go crazy when things fail and you can't figure out what the check expected output was:
48
- ```ruby
49
- check "Cache is accessible and functioning" do
50
- Rails.cache.write('health_check_test', 'ok')
51
- make_sure Rails.cache.read('health_check_test') == 'ok', "The `health_check_test` key in the cache should contain `'ok'`"
58
+ make_sure ActiveRecord::Base.connection.connect!.active?
52
59
  end
53
60
  ```
54
61
 
55
- As you can see, there's a very simple DSL (Domain-Specific Language) you can use to define health checks. It reads almost like natural English, and allows you to define powerful yet simple checks to make sure your app is healthy.
62
+ `allgood` will run all checks upon page load, and will show "Check passed" or "Check failed" next to it. That's it add as many health checks as you want!
56
63
 
57
- Other than checking for an active database connection, it's useful to check for business-oriented metrics, such as whether your app has gotten any new users in the past 24 hours (to make sure your signup flow is not broken), check whether there have been any new posts / records created recently (to make sure your users are performing the actions you'd expect them to do in your app), check for recent purchases, check for external API connections, check whether new records contain values within expected range, etc.
64
+ Here's my default `config/allgood.rb` file that should work for most Rails applications, feel free to use it as a starting point:
58
65
 
59
- Some business health check examples that you'd need to adapt to the specifics of your particular app:
60
66
  ```ruby
61
- check "There's been new signups in the past 24 hours" do
62
- count = User.where(created_at: 24.hours.ago..Time.now).count
63
- expect(count).to_be_greater_than(0)
64
- end
67
+ # config/allgood.rb
65
68
 
66
- check "The last created Purchase has a valid total" do
67
- last_purchase = Purchase.order(created_at: :desc).limit(1).first
68
- make_sure last_purchase.total.is_a?(Numeric), "Purchase total should be a number"
69
- expect(last_purchase.total).to_be_greater_than(0)
69
+ check "We have an active database connection" do
70
+ make_sure ActiveRecord::Base.connection.connect!.active?
70
71
  end
71
- ```
72
72
 
73
- Other nice checks to have:
74
- ```ruby
75
73
  check "Database can perform a simple query" do
76
74
  make_sure ActiveRecord::Base.connection.execute("SELECT 1").any?
77
75
  end
@@ -80,11 +78,6 @@ check "Database migrations are up to date" do
80
78
  make_sure ActiveRecord::Migration.check_all_pending! == nil
81
79
  end
82
80
 
83
- check "Cache is accessible and functioning" do
84
- Rails.cache.write('health_check_test', 'ok')
85
- make_sure Rails.cache.read('health_check_test') == 'ok', "The `health_check_test` key in the cache should contain `'ok'`"
86
- end
87
-
88
81
  check "Disk space usage is below 90%" do
89
82
  usage = `df -h / | tail -1 | awk '{print $5}' | sed 's/%//'`.to_i
90
83
  expect(usage).to_be_less_than(90)
@@ -96,12 +89,44 @@ check "Memory usage is below 90%" do
96
89
  end
97
90
  ```
98
91
 
99
- If you have other nice default checks, please open a PR! I'd love to provide a good default `config/allgood.rb` file.
92
+ I've also added an example [`config/allgood.rb`](examples/allgood.rb) file in the `examples` folder, with very comprehensive checks for a Rails 8+ app, that you can use as a starting point.
93
+
94
+ > [!IMPORTANT]
95
+ > Make sure you restart the Rails server (`bin/rails s`) every time you modify the `config/allgood.rb` file for the changes to apply – the `allgood` config is only loaded once when the Rails server starts.
100
96
 
101
- > ⚠️ Make sure to restart the Rails server every time you modify the `config/allgood.rb` file for the config to reload and the changes to apply.
97
+ ### The `allgood` DSL
102
98
 
99
+ As you can see, there's a very simple DSL (Domain-Specific Language) you can use to define health checks.
103
100
 
104
- ### Available Check Methods
101
+ It reads almost like natural English, and allows you to define powerful yet simple checks to make sure your app is healthy.
102
+
103
+ For example, you can specify a custom human-readable success / error message for each check, so you don't go crazy when things fail and you can't figure out what the check expected output was:
104
+ ```ruby
105
+ check "Cache is accessible and functioning" do
106
+ Rails.cache.write('allgood_test', 'ok')
107
+ make_sure Rails.cache.read('allgood_test') == 'ok', "The `allgood_test` key in the cache should contain `'ok'`"
108
+ end
109
+ ```
110
+
111
+ Other than checking for an active database connection, it's useful to check for business-oriented metrics, such as whether your app has gotten any new users in the past 24 hours (to make sure your signup flow is not broken), check whether there have been any new posts / records created recently (to make sure your users are performing the actions you'd expect them to do in your app), check for recent purchases, check for external API connections, check whether new records contain values within expected range, etc.
112
+
113
+ Some business health check examples that you'd need to adapt to the specifics of your particular app:
114
+ ```ruby
115
+ # Adapt these to your app specifics
116
+
117
+ check "There's been new signups in the past 24 hours" do
118
+ count = User.where(created_at: 24.hours.ago..Time.now).count
119
+ expect(count).to_be_greater_than(0)
120
+ end
121
+
122
+ check "The last created Purchase has a valid total" do
123
+ last_purchase = Purchase.order(created_at: :desc).limit(1).first
124
+ make_sure last_purchase.total.is_a?(Numeric), "Purchase total should be a number"
125
+ expect(last_purchase.total).to_be_greater_than(0)
126
+ end
127
+ ```
128
+
129
+ ### Available check methods
105
130
 
106
131
  - `make_sure(condition, message = nil)`: Ensures that the given condition is true.
107
132
  - `expect(actual).to_eq(expected)`: Checks if the actual value equals the expected value.
@@ -110,23 +135,67 @@ If you have other nice default checks, please open a PR! I'd love to provide a g
110
135
 
111
136
  Please help us develop by adding more expectation methods in the `Expectation` class!
112
137
 
113
- ## Customization
138
+ ### Run checks only in specific environments or under certain conditions
139
+
140
+ You can also make certain checks run only in specific environments or under certain conditions. Some examples:
141
+
142
+ ```ruby
143
+ # Only run in production
144
+ check "There have been new user signups in the past hour", only: :production do
145
+ make_sure User.where(created_at: 1.hour.ago..Time.now).count.positive?
146
+ end
147
+
148
+ # Run in both staging and production
149
+ check "External API is responsive", only: [:staging, :production] do
150
+ # ...
151
+ end
152
+
153
+ # Run everywhere except development
154
+ check "A SolidCable connection is active and healthy", except: :development do
155
+ # ...
156
+ end
157
+
158
+ # Using if with a direct boolean
159
+ check "Feature flag is enabled", if: ENV['FEATURE_ENABLED'] == 'true' do
160
+ # ...
161
+ end
162
+
163
+ # Using if with a Proc for more complex conditions
164
+ check "Complex condition", if: -> { User.count > 1000 && User.last.created_at < 10.minutes.ago } do
165
+ # ...
166
+ end
167
+
168
+ # Override default timeout (in seconds) for specific checks
169
+ # By default, each check has a timeout of 10 seconds
170
+ check "Slow external API", timeout: 30 do
171
+ # ...
172
+ end
173
+
174
+ # Combine multiple conditions
175
+ check "Complex check",
176
+ only: :production,
177
+ if: -> { User.count > 1000 },
178
+ timeout: 15 do
179
+ # ...
180
+ end
181
+ ```
114
182
 
115
- ### Timeout
183
+ When a check is skipped due to its conditions not being met, it will appear in the healthcheck page with a skip emoji (⏭️) and a clear explanation of why it was skipped.
116
184
 
117
- By default, each check has a timeout of 10 seconds.
185
+ ![Example dashboard of the Allgood health check page with skipped checks](allgood_skipped.webp)
118
186
 
187
+ _Note: the `allgood` health check dashboard has an automatic dark mode, based on the system's appearance settings._
119
188
 
120
189
  ## Development
121
190
 
122
- After checking out the repo, run `bin/setup` to install dependencies. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
191
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
123
192
 
124
- To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
193
+ To install this gem onto your local machine, run `bundle exec rake install`.
125
194
 
126
195
  ## Contributing
127
196
 
128
- Bug reports and pull requests are welcome on GitHub at https://github.com/rameerez/allgood Our code of conduct is: just be nice and make your mom proud of what you do and post online.
197
+ Bug reports and pull requests are welcome on GitHub at https://github.com/rameerez/allgood. Our code of conduct is: just be nice and make your mom proud of what you do and post online.
129
198
 
130
199
  ## License
131
200
 
132
- The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
201
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/allgood.jpeg CHANGED
Binary file
Binary file
@@ -29,7 +29,17 @@ module Allgood
29
29
 
30
30
  def run_checks
31
31
  Allgood.configuration.checks.map do |check|
32
- run_single_check(check)
32
+ if check[:status] == :skipped
33
+ {
34
+ name: check[:name],
35
+ success: true,
36
+ skipped: true,
37
+ message: check[:skip_reason],
38
+ duration: 0
39
+ }
40
+ else
41
+ run_single_check(check)
42
+ end
33
43
  end
34
44
  end
35
45
 
@@ -14,6 +14,10 @@
14
14
  .check {
15
15
  margin: 0.5em 0;
16
16
  }
17
+
18
+ .skipped {
19
+ opacity: 0.6;
20
+ }
17
21
  </style>
18
22
 
19
23
  <header>
@@ -22,11 +26,18 @@
22
26
 
23
27
  <% if @results.any? %>
24
28
  <% @results.each do |result| %>
25
- <div class="check">
26
- <%= result[:success] ? "✅" : "❌" %>
27
- <b><%= result[:name] %></b>: <i><%= result[:message] %></i> <code>[<%= result[:duration] %>ms]</code>
29
+ <div class="check <%= 'skipped' if result[:skipped] %>">
30
+ <% if result[:skipped] %>
31
+ ⏭️
32
+ <% else %>
33
+ <%= result[:success] ? "✅" : "❌" %>
34
+ <% end %>
35
+ <b><%= result[:name] %></b>: <i><%= result[:message] %></i>
36
+ <% unless result[:skipped] %>
37
+ <code>[<%= result[:duration] %>ms]</code>
38
+ <% end %>
28
39
  </div>
29
40
  <% end %>
30
41
  <% else %>
31
42
  <p>No health checks were run. Please check your configuration.</p>
32
- <% end %>
43
+ <% end %>
@@ -0,0 +1,166 @@
1
+ require 'open-uri'
2
+ TEST_IMAGE = URI.open("https://picsum.photos/id/237/536/354").read
3
+
4
+ # --- ACTIVE RECORD ---
5
+
6
+ check "We have an active database connection" do
7
+ make_sure ActiveRecord::Base.connection.connect!.active?
8
+ end
9
+
10
+ check "The database can perform a simple query" do
11
+ make_sure ActiveRecord::Base.connection.execute("SELECT 1 LIMIT 1").any?
12
+ end
13
+
14
+ check "The database can perform writes" do
15
+ table_name = "allgood_health_check_#{Time.now.to_i}"
16
+ random_id = rand(1..999999)
17
+
18
+ result = ActiveRecord::Base.connection.execute(<<~SQL)
19
+ DROP TABLE IF EXISTS #{table_name};
20
+ CREATE TEMPORARY TABLE #{table_name} (id integer);
21
+ INSERT INTO #{table_name} (id) VALUES (#{random_id});
22
+ SELECT id FROM #{table_name} LIMIT 1;
23
+ SQL
24
+
25
+ ActiveRecord::Base.connection.execute("DROP TABLE #{table_name}")
26
+
27
+ make_sure result.present? && result.first["id"] == random_id, "Able to write to temporary table"
28
+ end
29
+
30
+ check "The database connection pool is healthy" do
31
+ pool = ActiveRecord::Base.connection_pool
32
+
33
+ used_connections = pool.connections.count
34
+ max_connections = pool.size
35
+ usage_percentage = (used_connections.to_f / max_connections * 100).round
36
+
37
+ make_sure usage_percentage < 90, "Pool usage at #{usage_percentage}% (#{used_connections}/#{max_connections})"
38
+ end
39
+
40
+ check "Database migrations are up to date" do
41
+ make_sure ActiveRecord::Migration.check_all_pending! == nil
42
+ end
43
+
44
+ # --- IMAGE PROCESSING ---
45
+
46
+ check "Vips (libvips) is installed on Linux", except: :development do
47
+ output = `ldconfig -p | grep libvips`
48
+ make_sure output.present? && output.include?("libvips.so") && output.include?("libvips-cpp.so"), "libvips is found in the Linux system's library cache"
49
+ end
50
+
51
+ check "Vips is available to Rails" do
52
+ throw "ImageProcessing::Vips is not available" if !ImageProcessing::Vips.present? # Need this line to load `Vips`
53
+
54
+ make_sure Vips::VERSION.present?, "Vips available with version #{Vips::VERSION}"
55
+ end
56
+
57
+ check "Vips can perform operations on images" do
58
+ throw "ImageProcessing::Vips is not available" if !ImageProcessing::Vips.present? # Need this line to load `Vips`
59
+
60
+ image = Vips::Image.new_from_buffer(TEST_IMAGE, "")
61
+ processed_image = image
62
+ .gaussblur(10) # Apply Gaussian blur with sigma 10
63
+ .linear([1.2], [0]) # Increase brightness
64
+ .invert # Invert colors for a wild effect
65
+ .sharpen # Apply sharpening
66
+ .resize(0.5)
67
+
68
+ make_sure processed_image.present? && processed_image.width == 268 && processed_image.height == 177, "If we input an image of 536x354px, and we apply filters and a 0.5 resize, we should get an image of 268x177px"
69
+ end
70
+
71
+ check "ImageProcessing::Vips is available to Rails" do
72
+ make_sure ImageProcessing::Vips.present?
73
+ end
74
+
75
+ check "ImageProcessing can perform operations on images" do
76
+ image_processing_image = ImageProcessing::Vips
77
+ .source(Vips::Image.new_from_buffer(TEST_IMAGE, ""))
78
+ .resize_to_limit(123, 123) # Resize to fit within 500x500
79
+ .convert("webp") # Convert to webp format
80
+ .call
81
+ processed_image = Vips::Image.new_from_file(image_processing_image.path)
82
+
83
+ make_sure processed_image.present? && processed_image.width == 123 && processed_image.get("vips-loader") == "webpload", "ImageProcessing can resize and convert to webp"
84
+ end
85
+
86
+ # --- ACTIVE STORAGE ---
87
+
88
+ check "Active Storage is available to Rails" do
89
+ make_sure ActiveStorage.present?
90
+ end
91
+
92
+ check "Active Storage tables are present in the database" do
93
+ make_sure ActiveRecord::Base.connection.table_exists?("active_storage_attachments") && ActiveRecord::Base.connection.table_exists?("active_storage_blobs")
94
+ end
95
+
96
+ check "Active Storage has a valid client configured" do
97
+ service = ActiveStorage::Blob.service
98
+ service_name = service&.class&.name&.split("::")&.last&.split("Service")&.first
99
+
100
+ if !service_name.downcase.include?("disk")
101
+ make_sure service.present? && service.respond_to?(:client) && service.client.present?, "Active Storage service has a valid #{service_name} client configured"
102
+ else
103
+ make_sure !Rails.env.production? && service.present?, "Active Storage using #{service_name} service in #{Rails.env.to_s}"
104
+ end
105
+ end
106
+
107
+ check "ActiveStorage can store images, retrieve them, and purge them" do
108
+ blob = ActiveStorage::Blob.create_and_upload!(io: StringIO.new(TEST_IMAGE), filename: "allgood-test-image-#{Time.now.to_i}.jpg", content_type: "image/jpeg")
109
+ make_sure blob.persisted? && blob.service.exist?(blob.key) && blob.purge, "Image was successfully stored, retrieved, and purged from #{ActiveStorage::Blob.service.class.name}"
110
+ end
111
+
112
+ # --- CACHE ---
113
+
114
+ check "Cache is accessible and functioning" do
115
+ cache_value = "allgood_#{Time.now.to_i}"
116
+ Rails.cache.write("allgood_health_check_test", cache_value, expires_in: 1.minute)
117
+ make_sure Rails.cache.read("allgood_health_check_test") == cache_value, "The `allgood_health_check_test` key in the cache should return the string `#{cache_value}`"
118
+ end
119
+
120
+ # --- SOLID QUEUE ---
121
+
122
+ check "SolidQueue is available to Rails" do
123
+ make_sure SolidQueue.present?
124
+ end
125
+
126
+ check "We have an active SolidQueue connection to the database" do
127
+ make_sure SolidQueue::Job.connection.connect!.active?
128
+ end
129
+
130
+ check "SolidQueue tables are present in the database" do
131
+ make_sure SolidQueue::Job.connection.table_exists?("solid_queue_jobs") && SolidQueue::Job.connection.table_exists?("solid_queue_failed_executions") && SolidQueue::Job.connection.table_exists?("solid_queue_semaphores")
132
+ end
133
+
134
+ check "The percentage of failed jobs in the last 24 hours is less than 1%", only: :production do
135
+ failed_jobs = SolidQueue::FailedExecution.where(created_at: 24.hours.ago..Time.now).count
136
+ all_jobs = SolidQueue::Job.where(created_at: 24.hours.ago..Time.now).count
137
+
138
+ if all_jobs > 10
139
+ percentage = all_jobs > 0 ? (failed_jobs.to_f / all_jobs.to_f * 100) : 0
140
+ make_sure percentage < 1, "#{percentage.round(2)}% of jobs are failing"
141
+ else
142
+ make_sure true, "Not enough jobs to calculate meaningful failure rate (only #{all_jobs} jobs in last 24h)"
143
+ end
144
+ end
145
+
146
+ # --- SYSTEM ---
147
+
148
+ check "Disk space usage is below 90%" do
149
+ usage = `df -h / | tail -1 | awk '{print $5}' | sed 's/%//'`.to_i
150
+ expect(usage).to_be_less_than(90)
151
+ end
152
+
153
+ check "Memory usage is below 90%" do
154
+ usage = `free | grep Mem | awk '{print $3/$2 * 100.0}' | cut -d. -f1`.to_i
155
+ expect(usage).to_be_less_than(90)
156
+ end
157
+
158
+ # --- USAGE-DEPENDENT CHECKS ---
159
+
160
+ check "SolidQueue has processed jobs in the last 24 hours", only: :production do
161
+ make_sure SolidQueue::Job.where(created_at: 24.hours.ago..Time.now).order(created_at: :desc).limit(1).any?
162
+ end
163
+
164
+ # --- PAY / STRIPE ---
165
+
166
+ # TODO: no error webhooks in the past 24 hours, new sales in the past few hours, etc.
@@ -8,8 +8,59 @@ module Allgood
8
8
  @default_timeout = 10 # Default timeout of 10 seconds
9
9
  end
10
10
 
11
- def check(name, &block)
12
- @checks << { name: name, block: block, timeout: @default_timeout }
11
+ def check(name, **options, &block)
12
+ check_info = {
13
+ name: name,
14
+ block: block,
15
+ timeout: options[:timeout] || @default_timeout,
16
+ options: options,
17
+ status: :pending
18
+ }
19
+
20
+ # Handle environment-specific options
21
+ if options[:only]
22
+ environments = Array(options[:only])
23
+ unless environments.include?(Rails.env.to_sym)
24
+ check_info[:status] = :skipped
25
+ check_info[:skip_reason] = "Only runs in #{environments.join(', ')}"
26
+ @checks << check_info
27
+ return
28
+ end
29
+ end
30
+
31
+ if options[:except]
32
+ environments = Array(options[:except])
33
+ if environments.include?(Rails.env.to_sym)
34
+ check_info[:status] = :skipped
35
+ check_info[:skip_reason] = "This check doesn't run in #{environments.join(', ')}"
36
+ @checks << check_info
37
+ return
38
+ end
39
+ end
40
+
41
+ # Handle conditional checks
42
+ if options[:if]
43
+ condition = options[:if]
44
+ unless condition.is_a?(Proc) ? condition.call : condition
45
+ check_info[:status] = :skipped
46
+ check_info[:skip_reason] = "Check condition not met"
47
+ @checks << check_info
48
+ return
49
+ end
50
+ end
51
+
52
+ if options[:unless]
53
+ condition = options[:unless]
54
+ if condition.is_a?(Proc) ? condition.call : condition
55
+ check_info[:status] = :skipped
56
+ check_info[:skip_reason] = "Check `unless` condition met"
57
+ @checks << check_info
58
+ return
59
+ end
60
+ end
61
+
62
+ check_info[:status] = :active
63
+ @checks << check_info
13
64
  end
14
65
 
15
66
  def run_check(&block)
@@ -2,7 +2,7 @@ module Allgood
2
2
  class Engine < ::Rails::Engine
3
3
  isolate_namespace Allgood
4
4
 
5
- initializer "allgood.load_configuration" do
5
+ config.after_initialize do
6
6
  config_file = Rails.root.join("config", "allgood.rb")
7
7
  if File.exist?(config_file)
8
8
  Allgood.configure do |config|
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Allgood
4
- VERSION = "0.1.0"
4
+ VERSION = "0.2.0"
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: allgood
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - rameerez
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2024-08-23 00:00:00.000000000 Z
11
+ date: 2024-10-26 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -24,12 +24,13 @@ dependencies:
24
24
  - - ">="
25
25
  - !ruby/object:Gem::Version
26
26
  version: 6.0.0
27
- description: 'Define custom, business-oriented health checks for your app (as in:
28
- are there any new users in the past 24 hours) and see the results in a simple /healthcheck
29
- page that you can use to monitor your app with UptimeRobot, Pingdom, or other monitoring
30
- services.'
27
+ description: 'Define custom health checks for your app (as in: are there any new users
28
+ in the past 24 hours) and see the results in a simple /healthcheck page that you
29
+ can use to monitor your production app with UptimeRobot, Pingdom, or other monitoring
30
+ services. It''s also useful as a drop-in replacement for the default `/up` health
31
+ check endpoint for Kamal deployments.'
31
32
  email:
32
- - allgood@rameerez.com
33
+ - rubygems@rameerez.com
33
34
  executables: []
34
35
  extensions: []
35
36
  extra_rdoc_files: []
@@ -39,11 +40,13 @@ files:
39
40
  - README.md
40
41
  - Rakefile
41
42
  - allgood.jpeg
43
+ - allgood_skipped.webp
42
44
  - app/controllers/allgood/base_controller.rb
43
45
  - app/controllers/allgood/healthcheck_controller.rb
44
46
  - app/views/allgood/healthcheck/index.html.erb
45
47
  - app/views/layouts/allgood/application.html.erb
46
48
  - config/routes.rb
49
+ - examples/allgood.rb
47
50
  - lib/allgood.rb
48
51
  - lib/allgood/configuration.rb
49
52
  - lib/allgood/engine.rb
@@ -56,7 +59,7 @@ metadata:
56
59
  allowed_push_host: https://rubygems.org
57
60
  homepage_uri: https://github.com/rameerez/allgood
58
61
  source_code_uri: https://github.com/rameerez/allgood
59
- changelog_uri: https://github.com/rameerez/allgood
62
+ changelog_uri: https://github.com/rameerez/allgood/blob/main/CHANGELOG.md
60
63
  post_install_message:
61
64
  rdoc_options: []
62
65
  require_paths:
@@ -72,7 +75,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
72
75
  - !ruby/object:Gem::Version
73
76
  version: '0'
74
77
  requirements: []
75
- rubygems_version: 3.5.17
78
+ rubygems_version: 3.5.16
76
79
  signing_key:
77
80
  specification_version: 4
78
81
  summary: Add quick, simple, and beautiful health checks to your Rails application.