shlima / health_bit

Tiny health check of Rack apps like Rails, Sinatra for use with uptime checking systems like Kubernetes, Docker or Uptimerobot
MIT License
114 stars 8 forks source link
health health-check healthcheck rack ruby ruby-on-rails

CI gem version

HealthBit

This gem was inspired by the health_check, but is simpler and more extensible and contains up to 95% less code.

Key differences:

Toc

Check Examples

Installation

Add this line to your application's Gemfile:

gem 'health_bit'

Configuration

# config/initializers/health_bit.rb

HealthBit.configure do |c|
  # DEFAULT SETTINGS ARE SHOWN BELOW
  c.success_text = '%<count>d checks passed 🎉'
  c.headers = {
    'Content-Type' => 'text/plain;charset=utf-8',
    'Cache-Control' => 'private,max-age=0,must-revalidate,no-store'
  }
  c.success_code = 200
  c.fail_code = 500
  c.show_backtrace = false
  c.formatter = HealthBit::Formatter.new
  # DEFAULT SETTINGS ARE SHOWN ABOVE

  c.add('Check name') do
    # Body check, should returns `true`
    true
  end
end

Add Checks

By default, the gem does not contain any checks, you should add the necessary checks by yourself. The check should return false or nil to be considered unsuccessful or throw an exception, any other values are considered satisfactory.

Example checks:

## Successful checks
HealthBit.add('PostgreSQL') do |env|
  ApplicationRecord.connection.select_value('SELECT 1') == 1
end

HealthBit.add('Custom') do |env|
  next(false) if 1 != 0 

  true
end

## Failed checks
HealthBit.add('Database') do |env|
  false
end

HealthBit.add('Docker service') do |env|
  raise 'not responding'
end

# The Check can be added as an object responding to a call
# (to be able to test your check)
class Covid19Check
  def self.call(env)
    false
  end
end

HealthBit.add('COVID-19 Checker', Covid19Check)

Add a Route

Since the gem is a rack application, you must mount it to app's routes. Below is an example for the Rails.

# config/routes.rb

Rails.application.routes.draw do
  mount HealthBit.rack => '/health'
end
curl --verbose http://localhost:3000/health

< HTTP/1.1 200 OK
< Content-Type: text/plain;charset=utf-8
< Cache-Control: private,max-age=0,must-revalidate,no-store
< X-Request-Id: 59a796b9-29f7-4302-b1ff-5d0b06dd6637
< X-Runtime: 0.006007
< Vary: Origin
< Transfer-Encoding: chunked

4 checks passed 🎉

Password Protection

Since the gem is a common rack application, you can add any rack middleware to it. Below is an example with HTTP-auth for the Rails.

# config/routes.rb

Rails.application.routes.draw do
  HealthBit.rack.use Rack::Auth::Basic do |username, password|
    ActiveSupport::SecurityUtils.secure_compare(Digest::SHA256.hexdigest(username), Digest::SHA256.hexdigest('user')) & ActiveSupport::SecurityUtils.secure_compare(Digest::SHA256.hexdigest(password), Digest::SHA256.hexdigest('password'))
  end

  mount HealthBit.rack => '/health'
end

Database check

HealthBit.add('Database') do |env|
  ApplicationRecord.connection.select_value('SELECT 1') == 1
end

Redis check

HealthBit.add('Redis') do |env|
  Redis.current.ping == 'PONG'
end

Sidekiq check

HealthBit.add('Sidekiq') do |env|
  Sidekiq.redis(&:ping) == 'PONG'
end

Rails cache check

HealthBit.add('Rails cache') do |env|
  Rails.cache.write('__health_bit__', '1', expires_in: 1.second)
end

Elasticsearch check

HealthBit.add('Elasticsearch') do |env|
  Elasticsearch::Client.new.ping
end

RabbitMQ check

HealthBit.add('RabbitMQ') do |env|
  Bunny::Connection.connect(&:connection)
end

HTTP check

HealthBit.add('HTTP check') do |env|
  Net::HTTP.new('www.example.com', 80).request_get('/').kind_of?(Net::HTTPSuccess)
end

ClickHouse check

HealthBit.add('ClickHouse') do |env|
  ClickHouse.connection.ping
end

Multiple endpoints

Sometimes you have to add several health check endpoints. Let's say you have to check the docker container health and the health of your application as a whole. Below is an example for the Rails.

# config/initializers/health_bit.rb

DockerCheck = HealthBit.clone
AppCheck = HealthBit.clone

DockerCheck.add('Docker Health') do |env|
  true
end

AppCheck.add('App Health') do |env|
  ApplicationRecord.connection.select_value("SELECT 't'::boolean")
end
# config/routes.rb

Rails.application.routes.draw do
  mount DockerCheck.rack => '/docker'
  mount AppCheck.rack => '/app'
end

Custom formatter

You can easily configure custom format of response body, headers and http statuses.

class JsonFormatter < HealthBit::Formatter
  # @param error HealthBit::CheckError
  # @param env Hash
  # @param health_bit HealthBit
  def format_failure(error, env, health_bit)
    { 'status': 'error' }.to_json
  end

  # @param error HealthBit::CheckError
  # @param env Hash
  # @param health_bit HealthBit
  def headers_failure(error, env, health_bit)
    {
      'Content-Type' => 'application/json'
    }
  end
end

HealthBit.configure do |c|
  c.formatter = JsonFormatter.new
end