allgood 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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
@@ -0,0 +1,5 @@
1
+ ## [Unreleased]
2
+
3
+ ## [0.1.0] - 2024-08-22
4
+
5
+ - Initial release
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
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ task default: %i[]
data/allgood.jpeg ADDED
Binary file
@@ -0,0 +1,5 @@
1
+ module Allgood
2
+ class BaseController < ApplicationController
3
+ layout 'allgood/application'
4
+ end
5
+ end
@@ -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 %>
@@ -0,0 +1,12 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>Health Check</title>
5
+ <%= csrf_meta_tags %>
6
+ <%= csp_meta_tag %>
7
+ <link rel="stylesheet" href="https://cdn.simplecss.org/simple.min.css">
8
+ </head>
9
+ <body>
10
+ <%= yield %>
11
+ </body>
12
+ </html>
data/config/routes.rb ADDED
@@ -0,0 +1,3 @@
1
+ Allgood::Engine.routes.draw do
2
+ root to: "healthcheck#index"
3
+ end
@@ -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
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Allgood
4
+ VERSION = "0.1.0"
5
+ end
data/lib/allgood.rb ADDED
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "allgood/version"
4
+ require_relative "allgood/engine"
5
+ require_relative "allgood/configuration"
6
+
7
+ module Allgood
8
+ class Error < StandardError; end
9
+ # Your code goes here...
10
+ end
data/sig/allgood.rbs ADDED
@@ -0,0 +1,4 @@
1
+ module Allgood
2
+ VERSION: String
3
+ # See the writing guide of rbs: https://github.com/ruby/rbs#guides
4
+ end
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: []