allgood 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/CHANGELOG.md +5 -0
- data/LICENSE.txt +21 -0
- data/README.md +132 -0
- data/Rakefile +4 -0
- data/allgood.jpeg +0 -0
- data/app/controllers/allgood/base_controller.rb +5 -0
- data/app/controllers/allgood/healthcheck_controller.rb +61 -0
- data/app/views/allgood/healthcheck/index.html.erb +32 -0
- data/app/views/layouts/allgood/application.html.erb +12 -0
- data/config/routes.rb +3 -0
- data/lib/allgood/configuration.rb +75 -0
- data/lib/allgood/engine.rb +14 -0
- data/lib/allgood/version.rb +5 -0
- data/lib/allgood.rb +10 -0
- data/sig/allgood.rbs +4 -0
- metadata +79 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: fe79d04db962fcfc50cb094c0b122506d09f903b7ee02e2ee7ae0c3930c78557
|
4
|
+
data.tar.gz: fb9a69be909db1c10d5019dfadbdbee5b7da514913d8a907933cd2873e73f2f0
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: fc26bbc3685f38fbfa49e05047987537c64e06c55356c28d0c8a0a50b73b6981c10d66c96996ca44057720402f4f5ed1319309a33402b25a39d0bdf472518496
|
7
|
+
data.tar.gz: ea88c1027193b356560b2361e65a28c42d3b10683df7e777796ff5de10c0c6e3aa93f7bb42fa70ef6ce9699d288e4efa74ceaa82667a5208ec006aa41506e6ea
|
data/CHANGELOG.md
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2024 Javi R
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,132 @@
|
|
1
|
+
# ✅ Allgood - Rails gem for health checks
|
2
|
+
|
3
|
+
Add quick, simple, and beautiful health checks to your Rails application.
|
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.
|
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.
|
8
|
+
|
9
|
+
![alt text](allgood.jpeg)
|
10
|
+
|
11
|
+
## Installation
|
12
|
+
|
13
|
+
Add this line to your application's Gemfile:
|
14
|
+
```ruby
|
15
|
+
gem 'allgood'
|
16
|
+
```
|
17
|
+
|
18
|
+
Then run `bundle install`.
|
19
|
+
|
20
|
+
## Usage
|
21
|
+
|
22
|
+
### Mounting the Engine
|
23
|
+
|
24
|
+
In your `config/routes.rb` file, mount the Allgood engine:
|
25
|
+
```ruby
|
26
|
+
mount Allgood::Engine => '/healthcheck'
|
27
|
+
```
|
28
|
+
|
29
|
+
You can now navigate to `/healthcheck` to see the health check results.
|
30
|
+
|
31
|
+
The `/healthcheck` page returns a `200` HTTP code if all checks are successful – and error `503 Service Unavailable` otherwise.
|
32
|
+
|
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`
|
34
|
+
|
35
|
+
|
36
|
+
### Configuring Health Checks
|
37
|
+
|
38
|
+
Create a file `config/allgood.rb` in your Rails application. This is where you'll define your health checks:
|
39
|
+
```ruby
|
40
|
+
# config/allgood.rb
|
41
|
+
|
42
|
+
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'`"
|
52
|
+
end
|
53
|
+
```
|
54
|
+
|
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.
|
56
|
+
|
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.
|
58
|
+
|
59
|
+
Some business health check examples that you'd need to adapt to the specifics of your particular app:
|
60
|
+
```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
|
65
|
+
|
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)
|
70
|
+
end
|
71
|
+
```
|
72
|
+
|
73
|
+
Other nice checks to have:
|
74
|
+
```ruby
|
75
|
+
check "Database can perform a simple query" do
|
76
|
+
make_sure ActiveRecord::Base.connection.execute("SELECT 1").any?
|
77
|
+
end
|
78
|
+
|
79
|
+
check "Database migrations are up to date" do
|
80
|
+
make_sure ActiveRecord::Migration.check_all_pending! == nil
|
81
|
+
end
|
82
|
+
|
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
|
+
check "Disk space usage is below 90%" do
|
89
|
+
usage = `df -h / | tail -1 | awk '{print $5}' | sed 's/%//'`.to_i
|
90
|
+
expect(usage).to_be_less_than(90)
|
91
|
+
end
|
92
|
+
|
93
|
+
check "Memory usage is below 90%" do
|
94
|
+
usage = `free | grep Mem | awk '{print $3/$2 * 100.0}' | cut -d. -f1`.to_i
|
95
|
+
expect(usage).to_be_less_than(90)
|
96
|
+
end
|
97
|
+
```
|
98
|
+
|
99
|
+
If you have other nice default checks, please open a PR! I'd love to provide a good default `config/allgood.rb` file.
|
100
|
+
|
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.
|
102
|
+
|
103
|
+
|
104
|
+
### Available Check Methods
|
105
|
+
|
106
|
+
- `make_sure(condition, message = nil)`: Ensures that the given condition is true.
|
107
|
+
- `expect(actual).to_eq(expected)`: Checks if the actual value equals the expected value.
|
108
|
+
- `expect(actual).to_be_greater_than(expected)`: Checks if the actual value is greater than the expected value.
|
109
|
+
- `expect(actual).to_be_less_than(expected)`: Checks if the actual value is less than the expected value.
|
110
|
+
|
111
|
+
Please help us develop by adding more expectation methods in the `Expectation` class!
|
112
|
+
|
113
|
+
## Customization
|
114
|
+
|
115
|
+
### Timeout
|
116
|
+
|
117
|
+
By default, each check has a timeout of 10 seconds.
|
118
|
+
|
119
|
+
|
120
|
+
## Development
|
121
|
+
|
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.
|
123
|
+
|
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).
|
125
|
+
|
126
|
+
## Contributing
|
127
|
+
|
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.
|
129
|
+
|
130
|
+
## License
|
131
|
+
|
132
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
data/Rakefile
ADDED
data/allgood.jpeg
ADDED
Binary file
|
@@ -0,0 +1,61 @@
|
|
1
|
+
require 'timeout'
|
2
|
+
|
3
|
+
module Allgood
|
4
|
+
class HealthcheckController < BaseController
|
5
|
+
def index
|
6
|
+
@results = run_checks
|
7
|
+
@status = @results.all? { |r| r[:success] } ? "ok" : "error"
|
8
|
+
status_code = @status == "ok" ? :ok : :service_unavailable
|
9
|
+
|
10
|
+
respond_to do |format|
|
11
|
+
format.html { render :index, status: status_code }
|
12
|
+
format.json { render json: { status: @status, checks: @results }, status: status_code }
|
13
|
+
end
|
14
|
+
rescue StandardError => e
|
15
|
+
# Log the error
|
16
|
+
Rails.logger.error "Allgood Healthcheck Error: #{e.message}\n#{e.backtrace.join("\n")}"
|
17
|
+
|
18
|
+
# Return a minimal response
|
19
|
+
@results = [{ name: "Healthcheck Error", success: false, message: "Internal error occurred", duration: 0 }]
|
20
|
+
@status = "error"
|
21
|
+
|
22
|
+
respond_to do |format|
|
23
|
+
format.html { render :index, status: :internal_server_error }
|
24
|
+
format.json { render json: { status: @status, checks: @results }, status: :internal_server_error }
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def run_checks
|
31
|
+
Allgood.configuration.checks.map do |check|
|
32
|
+
run_single_check(check)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def run_single_check(check)
|
37
|
+
start_time = Time.now
|
38
|
+
result = { success: false, message: "Check timed out after #{check[:timeout]} seconds" }
|
39
|
+
|
40
|
+
begin
|
41
|
+
Timeout.timeout(check[:timeout]) do
|
42
|
+
check_result = Allgood.configuration.run_check(&check[:block])
|
43
|
+
result = { success: check_result[:success], message: check_result[:message] }
|
44
|
+
end
|
45
|
+
rescue Timeout::Error
|
46
|
+
# The result is already set to a timeout message
|
47
|
+
rescue Allgood::CheckFailedError => e
|
48
|
+
result = { success: false, message: e.message }
|
49
|
+
rescue StandardError => e
|
50
|
+
result = { success: false, message: "Error: #{e.message}" }
|
51
|
+
end
|
52
|
+
|
53
|
+
{
|
54
|
+
name: check[:name],
|
55
|
+
success: result[:success],
|
56
|
+
message: result[:message],
|
57
|
+
duration: ((Time.now - start_time) * 1000).round(1)
|
58
|
+
}
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
<style>
|
2
|
+
header {
|
3
|
+
min-height: 20vh;
|
4
|
+
display: flex;
|
5
|
+
justify-content: center;
|
6
|
+
align-items: center;
|
7
|
+
color: white;
|
8
|
+
background-color: <%= @status == "ok" ? "MediumSeaGreen" : "FireBrick" %> !important;
|
9
|
+
font-weight: bold;
|
10
|
+
font-size: 2.7rem;
|
11
|
+
margin-bottom: 1em;
|
12
|
+
}
|
13
|
+
|
14
|
+
.check {
|
15
|
+
margin: 0.5em 0;
|
16
|
+
}
|
17
|
+
</style>
|
18
|
+
|
19
|
+
<header>
|
20
|
+
<h1><%= @status == "ok" ? "🤙 It's all good" : "❌ Something's wrong" %></h1>
|
21
|
+
</header>
|
22
|
+
|
23
|
+
<% if @results.any? %>
|
24
|
+
<% @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>
|
28
|
+
</div>
|
29
|
+
<% end %>
|
30
|
+
<% else %>
|
31
|
+
<p>No health checks were run. Please check your configuration.</p>
|
32
|
+
<% end %>
|
data/config/routes.rb
ADDED
@@ -0,0 +1,75 @@
|
|
1
|
+
module Allgood
|
2
|
+
class Configuration
|
3
|
+
attr_reader :checks
|
4
|
+
attr_accessor :default_timeout
|
5
|
+
|
6
|
+
def initialize
|
7
|
+
@checks = []
|
8
|
+
@default_timeout = 10 # Default timeout of 10 seconds
|
9
|
+
end
|
10
|
+
|
11
|
+
def check(name, &block)
|
12
|
+
@checks << { name: name, block: block, timeout: @default_timeout }
|
13
|
+
end
|
14
|
+
|
15
|
+
def run_check(&block)
|
16
|
+
CheckRunner.new.instance_eval(&block)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
class CheckRunner
|
21
|
+
def make_sure(condition, message = nil)
|
22
|
+
if condition
|
23
|
+
{ success: true, message: message || "Check passed" }
|
24
|
+
else
|
25
|
+
raise CheckFailedError.new(message || "Check failed")
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def expect(actual)
|
30
|
+
Expectation.new(actual)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
class Expectation
|
35
|
+
def initialize(actual)
|
36
|
+
@actual = actual
|
37
|
+
end
|
38
|
+
|
39
|
+
def to_eq(expected)
|
40
|
+
if @actual == expected
|
41
|
+
{ success: true, message: "Got: #{@actual || 'nil'}" }
|
42
|
+
else
|
43
|
+
raise CheckFailedError.new("Expected #{expected} to equal #{@actual || 'nil'} but it doesn't")
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def to_be_greater_than(expected)
|
48
|
+
if @actual > expected
|
49
|
+
{ success: true, message: "Got: #{@actual || 'nil'} (> #{expected})" }
|
50
|
+
else
|
51
|
+
raise CheckFailedError.new("We were expecting #{@actual || 'nil'} to be greater than #{expected} but it's not")
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def to_be_less_than(expected)
|
56
|
+
if @actual < expected
|
57
|
+
{ success: true, message: "Got: #{@actual || 'nil'} (< #{expected})" }
|
58
|
+
else
|
59
|
+
raise CheckFailedError.new("We were expecting #{@actual || 'nil'} to be less than #{expected} but it's not")
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
# Add more expectations as needed
|
64
|
+
end
|
65
|
+
|
66
|
+
class CheckFailedError < StandardError; end
|
67
|
+
|
68
|
+
def self.configuration
|
69
|
+
@configuration ||= Configuration.new
|
70
|
+
end
|
71
|
+
|
72
|
+
def self.configure
|
73
|
+
yield(configuration)
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module Allgood
|
2
|
+
class Engine < ::Rails::Engine
|
3
|
+
isolate_namespace Allgood
|
4
|
+
|
5
|
+
initializer "allgood.load_configuration" do
|
6
|
+
config_file = Rails.root.join("config", "allgood.rb")
|
7
|
+
if File.exist?(config_file)
|
8
|
+
Allgood.configure do |config|
|
9
|
+
config.instance_eval(File.read(config_file))
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
data/lib/allgood.rb
ADDED
data/sig/allgood.rbs
ADDED
metadata
ADDED
@@ -0,0 +1,79 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: allgood
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- rameerez
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2024-08-23 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rails
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 6.0.0
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
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.'
|
31
|
+
email:
|
32
|
+
- allgood@rameerez.com
|
33
|
+
executables: []
|
34
|
+
extensions: []
|
35
|
+
extra_rdoc_files: []
|
36
|
+
files:
|
37
|
+
- CHANGELOG.md
|
38
|
+
- LICENSE.txt
|
39
|
+
- README.md
|
40
|
+
- Rakefile
|
41
|
+
- allgood.jpeg
|
42
|
+
- app/controllers/allgood/base_controller.rb
|
43
|
+
- app/controllers/allgood/healthcheck_controller.rb
|
44
|
+
- app/views/allgood/healthcheck/index.html.erb
|
45
|
+
- app/views/layouts/allgood/application.html.erb
|
46
|
+
- config/routes.rb
|
47
|
+
- lib/allgood.rb
|
48
|
+
- lib/allgood/configuration.rb
|
49
|
+
- lib/allgood/engine.rb
|
50
|
+
- lib/allgood/version.rb
|
51
|
+
- sig/allgood.rbs
|
52
|
+
homepage: https://github.com/rameerez/allgood
|
53
|
+
licenses:
|
54
|
+
- MIT
|
55
|
+
metadata:
|
56
|
+
allowed_push_host: https://rubygems.org
|
57
|
+
homepage_uri: https://github.com/rameerez/allgood
|
58
|
+
source_code_uri: https://github.com/rameerez/allgood
|
59
|
+
changelog_uri: https://github.com/rameerez/allgood
|
60
|
+
post_install_message:
|
61
|
+
rdoc_options: []
|
62
|
+
require_paths:
|
63
|
+
- lib
|
64
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: 3.0.0
|
69
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
70
|
+
requirements:
|
71
|
+
- - ">="
|
72
|
+
- !ruby/object:Gem::Version
|
73
|
+
version: '0'
|
74
|
+
requirements: []
|
75
|
+
rubygems_version: 3.5.17
|
76
|
+
signing_key:
|
77
|
+
specification_version: 4
|
78
|
+
summary: Add quick, simple, and beautiful health checks to your Rails application.
|
79
|
+
test_files: []
|