lib/heartcheck/checks/base.rb
# frozen_string_literal: true
module Heartcheck
module Checks
# Base check that contains the common functionality for chechs
class Base
# When it is true the check will work just for `/functional` route.
# It should be used to add warning messages that need to verify,
# but doesn't break your application.
# For example when your async process has more than 3 jobs.
#
# @return [Boolean] (default: false)
attr_accessor :functional
alias functional? functional
# When it is true the check will work just for `/dev` route.
# you can use it to create some check is not essential/functional for you app.
# For example you can execute some performance check.
#
# @return [Boolean] (default: false)
attr_accessor :dev
alias dev? dev
# The name for identify the check in the result page
#
# @return [String]
attr_accessor :name
# Time that the check has to execute.
# If it is 0 the timeout will be disable
#
# @return [Integer] (default: 0)
attr_accessor :timeout
# An url to provide more info about the check.
# It is important to provide details about the failure, for example:
# - what is the impact
# - how to fix it
# - how to reproduce the failure
# @return [String] (default: nil)
attr_accessor :doc_url
# Create a new instance and set the default values.
#
# @return [Check]
def initialize
@dynamic_services = nil
@error_proc = nil
@errors = []
@functional = false
@dev = false
@name = self.class.name.split('::').last.downcase
@services = []
@timeout = 0
@validate_proc = nil
end
# Seter to add a proc to execute when some check fail.
#
# @return [void]
def on_error(&block)
@error_proc = block if block_given?
end
# Seter to add a proc to execute to check.
#
# @yieldparam services [Array<Hash>] services to check
# @yieldparam errors [Array<String>] to add error message when check fails
#
# @example
# c.to_validate do |services, errors|
# services.each do |service|
# errors << "erro message" unless if service[:connection].connected?
# end
# end
#
# @return [void]
def to_validate(&block)
@validate_proc = block if block_given?
end
# It is used to register dynamic services thats need to be evaluate when the checker is running.
# This proc need to return an Array.
#
# @example
# register_dynamic_services do
# [
# { name: 'service name', connection: MyConnection }
# ]
# end
#
# @return [void]
def register_dynamic_services(&block)
@dynamic_services = block if block_given?
end
# It is used to add a service that will use when check run.
#
# @param service [Hash]
#
# @example
# add_service(name: 'service name', connection: MyConnection)
#
# @return [void]
def add_service(service)
@services << service
end
# It is used to add a service that will use when check run.
#
# @return [Array<Hash>]
def services
if @dynamic_services
@services + @dynamic_services.call
else
@services
end
end
# run the check and return formated erros
#
# @return [Hash]
def check
validation
{ name => { 'status' => (@errors.empty? ? 'ok' : 'error') } }.tap do |response|
response[name]['message'] = error_message unless @errors.empty?
end
end
# Returns a human-readable representation of the check
#
# @return [String]
def inspect
"#<#{self.class.name} name: #{name}, functional: #{functional?}, dev: #{dev?}>"
end
# Returns a structure comprised of host, port and
# schema (protocol) for the check
#
# @return [Hash]
def uri_info
{ error: "#{self.class.name} #url_info not implemented." }
end
def informations
info
rescue StandardError => e
{ 'error' => e.message }
end
private
# Used to add an error to @erros.
#
# @return [void]
def append_error(*args)
if @error_proc
@error_proc.call(@errors, *args)
else
custom_error(*args)
end
end
# run the check
#
# @return [Array<String>]
def validation
@errors = []
begin
Timeout.timeout(timeout, Heartcheck::Errors::Warning) do
if @validate_proc
@validate_proc.call(services, @errors)
else
validate
end
end
rescue Heartcheck::Errors::Warning => e
@errors = [{ type: 'warning', message: e.message }]
rescue StandardError => e
@errors = [e.message]
end
@errors
end
# Get formated error messages for each service after run the check
#
# @example
# @erros = ['some string']
# error_message
# # => [{type: 'error', message: 'some string'}]
#
# @return [Array<Hash>]
def error_message
@errors.map(&format_error)
end
# format the an error message with a format the check need.
#
# @example
# "some string".format_error
# # => {type: 'error', message: 'some string'}
#
# @return [Hash]
def format_error
lambda do |error|
if error.is_a?(Hash)
error
else
{ type: 'error', message: error, doc_url: doc_url }
end
end
end
end
end
end