├── .cane ├── Gemfile ├── .gitignore ├── lib ├── sinatra-health-check │ ├── version.rb │ ├── status │ │ ├── strict_aggregator.rb │ │ ├── forgiving_aggregator.rb │ │ ├── overwriting_aggregator.rb │ │ └── aggregated.rb │ ├── status.rb │ └── checker.rb └── sinatra-health-check.rb ├── .simplecov ├── .travis.yml ├── Rakefile ├── spec ├── sinatra-health-check │ ├── status_spec.rb │ ├── overwriting_aggregator_spec.rb │ ├── forgiving_aggregator_spec.rb │ ├── strict_aggregator_spec.rb │ └── checker_spec.rb └── spec_helper.rb ├── LICENSE ├── sinatra-health-check.gemspec └── README.md /.cane: -------------------------------------------------------------------------------- 1 | --style-measure 120 2 | --color 3 | --parallel 4 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'http://rubygems.org' 2 | 3 | gemspec 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .bundle 2 | .DS_Store 3 | *.swp 4 | *.gem 5 | Gemfile.lock 6 | vendor 7 | coverage/ 8 | -------------------------------------------------------------------------------- /lib/sinatra-health-check/version.rb: -------------------------------------------------------------------------------- 1 | module SinatraHealthCheck 2 | VERSION = '0.2.0' 3 | end 4 | -------------------------------------------------------------------------------- /.simplecov: -------------------------------------------------------------------------------- 1 | SimpleCov.start do 2 | add_group 'Library', 'lib' 3 | add_filter '/vendor' 4 | add_filter '/spec' 5 | end 6 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: ruby 2 | rvm: 3 | - 1.9.3 4 | - 2.0.0 5 | - 2.1.5 6 | - 2.2.0 7 | env: 8 | global: 9 | - CODECLIMATE_REPO_TOKEN=8143d01ffb9603990dea3670c8286ab5b7fcad8131d1c3c9dfd714134664e7c6 10 | script: bundle exec rake 11 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), 'lib')) 2 | 3 | require 'rake' 4 | require 'rspec/core/rake_task' 5 | require 'cane/rake_task' 6 | 7 | task :default => [:spec, :quality] 8 | 9 | RSpec::Core::RakeTask.new do |t| 10 | t.pattern = 'spec/**/*_spec.rb' 11 | end 12 | 13 | Cane::RakeTask.new(:quality) do |cane| 14 | cane.canefile = '.cane' 15 | end 16 | -------------------------------------------------------------------------------- /spec/sinatra-health-check/status_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe SinatraHealthCheck::Status do 4 | 5 | describe '#init' do 6 | subject { described_class.new(:ok, 'fooo')} 7 | its(:level) { should == :ok } 8 | its(:message) { should == 'fooo' } 9 | its(:to_i) { should == 0 } 10 | its(:to_h) { should == { :status => 'OK', :message => 'fooo' } } 11 | its(:to_json) { should == { :status => 'OK', :message => 'fooo' }.to_json } 12 | end 13 | 14 | end 15 | -------------------------------------------------------------------------------- /lib/sinatra-health-check/status/strict_aggregator.rb: -------------------------------------------------------------------------------- 1 | # Aggregate sub statuus, worst wins 2 | class SinatraHealthCheck::Status::StrictAggregator 3 | def aggregate(statuus) 4 | status = statuus.values.max_by { |s| s.to_i } || SinatraHealthCheck::Status.new(:ok, 'everything is fine') 5 | message = status.level == :ok ? 'everything is fine' : "at least one status is #{status.level}" 6 | 7 | SinatraHealthCheck::Status::Aggregated.new(status.level, message, statuus) 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /lib/sinatra-health-check/status/forgiving_aggregator.rb: -------------------------------------------------------------------------------- 1 | # Aggregate sub statuus, best wins 2 | class SinatraHealthCheck::Status::ForgivingAggregator 3 | def aggregate(statuus) 4 | status = statuus.values.min_by { |s| s.to_i } || SinatraHealthCheck::Status.new(:ok, 'everything is fine') 5 | message = status.level == :ok ? 'everything is fine' : "all statuus are at least #{status.level}" 6 | 7 | SinatraHealthCheck::Status::Aggregated.new(status.level, message, statuus) 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /lib/sinatra-health-check.rb: -------------------------------------------------------------------------------- 1 | require 'json' 2 | 3 | module SinatraHealthCheck 4 | require_relative 'sinatra-health-check/version' 5 | require_relative 'sinatra-health-check/status' 6 | require_relative 'sinatra-health-check/status/aggregated' 7 | require_relative 'sinatra-health-check/status/overwriting_aggregator' 8 | require_relative 'sinatra-health-check/status/forgiving_aggregator' 9 | require_relative 'sinatra-health-check/status/strict_aggregator' 10 | require_relative 'sinatra-health-check/checker' 11 | end 12 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib')) 2 | 3 | require 'rspec' 4 | require 'rspec/its' 5 | require 'simplecov' 6 | require 'codeclimate-test-reporter' 7 | require 'sinatra-health-check' 8 | 9 | CodeClimate::TestReporter.start 10 | 11 | RSpec.shared_context "local paths" do 12 | def project_dir 13 | File.expand_path(File.join(File.dirname(__FILE__), '..')) 14 | end 15 | end 16 | 17 | Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each { |f| require f } 18 | 19 | RSpec.configure do |c| 20 | c.mock_with :rspec 21 | c.color = true 22 | c.formatter = :documentation 23 | c.tty = true 24 | end 25 | -------------------------------------------------------------------------------- /spec/sinatra-health-check/overwriting_aggregator_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe SinatraHealthCheck::Status::OverwritingAggregator do 4 | 5 | context '#aggregate' do 6 | aggregator = SinatraHealthCheck::Status::ForgivingAggregator.new 7 | subject { described_class.new(aggregator) } 8 | 9 | it 'overwrites the status' do 10 | expect(aggregator).not_to receive(:aggregate) 11 | expect(subject.aggregate({}, SinatraHealthCheck::Status.new(:error, 'foo')).level).to eq(:error) 12 | end 13 | 14 | it 'does not overwrite the status' do 15 | expect(aggregator).to receive(:aggregate) { :foo } 16 | expect(subject.aggregate({})).to eq(:foo) 17 | end 18 | end 19 | 20 | end 21 | -------------------------------------------------------------------------------- /lib/sinatra-health-check/status/overwriting_aggregator.rb: -------------------------------------------------------------------------------- 1 | # Aggregate statuus with an aggregator but allow overwriting :level and :message. 2 | class SinatraHealthCheck::Status::OverwritingAggregator 3 | def initialize(aggregator) 4 | raise ArgumentError, 'aggregator must respond to .aggregate' unless aggregator.respond_to?(:aggregate) 5 | @aggregator = aggregator 6 | end 7 | 8 | # aggregate statuus with given aggregator, but overwrite :status and :message if overwrite is given too 9 | def aggregate(statuus, overwrite = nil) 10 | if overwrite 11 | SinatraHealthCheck::Status::Aggregated.new(overwrite.level, overwrite.message, statuus) 12 | else 13 | @aggregator.aggregate(statuus) 14 | end 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /lib/sinatra-health-check/status/aggregated.rb: -------------------------------------------------------------------------------- 1 | # Application status definition with subsystems 2 | class SinatraHealthCheck::Status::Aggregated < SinatraHealthCheck::Status 3 | 4 | attr_reader :statuus 5 | 6 | def initialize(level, message, statuus, extras = {}) 7 | raise ArgumentError, "statuus must be a hash of SinatraHealthCheck::Status, but is #{statuus.class}" \ 8 | unless statuus.is_a?(Hash) 9 | super(level, message, { :statusDetails => statuus }.merge(extras)) 10 | end 11 | 12 | def to_h 13 | subs = {} 14 | extras[:statusDetails].each { |k,v| subs[k] = v.to_h } 15 | 16 | s = extras.merge({ 17 | :status => level.to_s.upcase, 18 | :message => message, 19 | :statusDetails => subs 20 | }) 21 | s.delete(:statusDetails) if s[:statusDetails].size == 0 22 | s 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /lib/sinatra-health-check/status.rb: -------------------------------------------------------------------------------- 1 | # Application status definition 2 | class SinatraHealthCheck::Status 3 | 4 | SEVERITIES = { 5 | :ok => 0, 6 | :warning => 1, 7 | :error => 2, 8 | } 9 | 10 | attr_reader :level, :message, :extras 11 | 12 | def initialize(level, message, extras = {}) 13 | raise ArgumentError, "level must be one of #{SEVERITIES.keys.join(', ')}, but is #{level}" unless SEVERITIES[level] 14 | raise ArgumentError, "message must not be nil" unless message 15 | raise ArgumentError, "extras must be a hash, but is #{extras.class}" unless extras.is_a?(Hash) 16 | 17 | @level = level 18 | @message = message 19 | @extras = extras 20 | end 21 | 22 | def to_i 23 | SEVERITIES[level] 24 | end 25 | alias :severity :to_i 26 | 27 | def to_h 28 | { 29 | :status => level.to_s.upcase, 30 | :message => message, 31 | }.merge(extras) 32 | end 33 | 34 | def to_json 35 | to_h.to_json 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Otto (GmbH & Co KG) 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. 22 | 23 | -------------------------------------------------------------------------------- /sinatra-health-check.gemspec: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | require File.expand_path('../lib/sinatra-health-check/version', __FILE__) 3 | 4 | Gem::Specification.new do |gem| 5 | gem.name = "sinatra-health-check" 6 | gem.version = SinatraHealthCheck::VERSION 7 | gem.authors = ["Felix Bechstein"] 8 | gem.email = %w{felix.bechstein@otto.de} 9 | gem.description = %q{A simple health check for sinatra applications} 10 | gem.summary = %q{This health check adds graceful stop to your sinatra applications} 11 | gem.homepage = 'https://github.com/otto-de/sinatra-health-check' 12 | gem.license = 'MIT' 13 | 14 | gem.files = `git ls-files`.split($\) 15 | gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) } 16 | gem.test_files = gem.files.grep(%r{^(test|spec|features)/}) 17 | gem.require_paths = %w{lib} 18 | 19 | gem.add_dependency 'json' 20 | 21 | gem.add_development_dependency 'rake' 22 | gem.add_development_dependency 'rspec', '~> 3.0' 23 | gem.add_development_dependency 'rspec-its' 24 | gem.add_development_dependency 'pry' 25 | gem.add_development_dependency 'cane' 26 | gem.add_development_dependency 'simplecov' 27 | gem.add_development_dependency 'codeclimate-test-reporter' 28 | end 29 | -------------------------------------------------------------------------------- /spec/sinatra-health-check/forgiving_aggregator_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe SinatraHealthCheck::Status::ForgivingAggregator do 4 | 5 | context '#aggregate' do 6 | 7 | ONE = SinatraHealthCheck::Status.new(:error, 'foo') 8 | TWO = SinatraHealthCheck::Status.new(:warning, 'bar') 9 | THREE = SinatraHealthCheck::Status.new(:ok, 'foobar') 10 | 11 | it 'aggregates {} to :ok' do 12 | expect(subject.aggregate({}).level).to eq(:ok) 13 | end 14 | 15 | it 'aggregates to :ok when one status is :ok' do 16 | statuus = { 17 | :one => ONE, 18 | :two => TWO, 19 | :three => THREE, 20 | } 21 | expect(subject.aggregate(statuus).level).to eq(:ok) 22 | end 23 | 24 | it 'aggregates to :warning when one status is :warning' do 25 | statuus = { 26 | :one => ONE, 27 | :two => TWO, 28 | } 29 | expect(subject.aggregate(statuus).level).to eq(:warning) 30 | end 31 | 32 | it 'aggregates to :error when all status are :error' do 33 | statuus = { 34 | :one => ONE, 35 | } 36 | expect(subject.aggregate(statuus).level).to eq(:error) 37 | end 38 | 39 | it 'aggregates details' do 40 | statuus = { 41 | :one => ONE, 42 | :two => TWO, 43 | } 44 | expect(subject.aggregate(statuus).to_h[:statusDetails]).to eq({ 45 | :one => { 46 | :status => 'ERROR', 47 | :message => 'foo', 48 | }, 49 | :two => { 50 | :status => 'WARNING', 51 | :message => 'bar', 52 | }, 53 | }) 54 | end 55 | end 56 | 57 | end 58 | -------------------------------------------------------------------------------- /spec/sinatra-health-check/strict_aggregator_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe SinatraHealthCheck::Status::StrictAggregator do 4 | 5 | context '#aggregate' do 6 | 7 | ONE = SinatraHealthCheck::Status.new(:error, 'foo') 8 | TWO = SinatraHealthCheck::Status.new(:warning, 'bar') 9 | THREE = SinatraHealthCheck::Status.new(:ok, 'foobar') 10 | 11 | it 'aggregates {} to :ok' do 12 | expect(subject.aggregate({}).level).to eq(:ok) 13 | end 14 | 15 | it 'aggregates to :ok when one status is :ok' do 16 | statuus = { 17 | :one => ONE, 18 | :two => TWO, 19 | :three => THREE, 20 | } 21 | expect(subject.aggregate(statuus).level).to eq(:error) 22 | end 23 | 24 | it 'aggregates to :warning when one status is :warning' do 25 | statuus = { 26 | :two => TWO, 27 | :three => THREE, 28 | } 29 | expect(subject.aggregate(statuus).level).to eq(:warning) 30 | end 31 | 32 | it 'aggregates to :error when all status are :error' do 33 | statuus = { 34 | :three => THREE, 35 | } 36 | expect(subject.aggregate(statuus).level).to eq(:ok) 37 | end 38 | 39 | it 'aggregates details' do 40 | statuus = { 41 | :one => ONE, 42 | :two => TWO, 43 | } 44 | expect(subject.aggregate(statuus).to_h[:statusDetails]).to eq({ 45 | :one => { 46 | :status => 'ERROR', 47 | :message => 'foo', 48 | }, 49 | :two => { 50 | :status => 'WARNING', 51 | :message => 'bar', 52 | }, 53 | }) 54 | end 55 | end 56 | 57 | end 58 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | sinatra-health-check 2 | ==================== 3 | 4 | [![Gem Version](https://badge.fury.io/rb/sinatra-health-check.svg)](http://badge.fury.io/rb/sinatra-health-check) [![travis-ci](https://travis-ci.org/otto-de/sinatra-health-check.png?branch=master)](https://travis-ci.org/otto-de/sinatra-health-check) [![Code Climate](https://codeclimate.com/github/otto-de/sinatra-health-check/badges/gpa.svg)](https://codeclimate.com/github/otto-de/sinatra-health-check) [![Test Coverage](https://codeclimate.com/github/otto-de/sinatra-health-check/badges/coverage.svg)](https://codeclimate.com/github/otto-de/sinatra-health-check) 5 | 6 | This tiny gem adds graceful stop to your sinatra application. 7 | 8 | Stopping apps gracefully allows your running requests to finish before killing the app. It gives some time to configure load balancers before shutting things down. 9 | 10 | Usage 11 | ----- 12 | 13 | Initialize the health check: 14 | 15 | ```ruby 16 | require 'sinatra-health-check' 17 | @checker = SinatraHealthCheck::Checker.new 18 | ``` 19 | 20 | Optionally add subsystems to the Checker: 21 | 22 | ```ruby 23 | # mysubsystem responds to :status with a SinatraHealthCheck::Status object 24 | @checker.systems[:mysubsystem] = mysubsystem 25 | ``` 26 | 27 | Then use it inside your health check route: 28 | 29 | ```ruby 30 | get "/internal/health" do 31 | if @checker.healthy? 32 | "healthy" 33 | else 34 | status 503 35 | "unhealthy" 36 | end 37 | end 38 | ``` 39 | 40 | Deliver a status document showing overall health/status and status of all subsystems: 41 | 42 | ```ruby 43 | get "/internal/status" do 44 | headers 'content-type' => 'application/json' 45 | @checker.status.to_json 46 | end 47 | ``` 48 | 49 | Contributing 50 | ------------ 51 | 52 | It's fast and simple: fork + PR. 53 | 54 | License 55 | ------- 56 | 57 | This program is licensed under the MIT license. See LICENSE for details. 58 | -------------------------------------------------------------------------------- /spec/sinatra-health-check/checker_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe SinatraHealthCheck::Checker do 4 | 5 | describe '#init, default values' do 6 | its(:health) { should == true } 7 | end 8 | 9 | describe '#init, custom values' do 10 | subject { described_class.new(:timeout => 10, :health => false)} 11 | its(:health) { should == false } 12 | end 13 | 14 | context '#graceful_stop' do 15 | subject { described_class.new(:timeout => 12)} 16 | 17 | it 'gracefully stops the app' do 18 | # it's healthy before stopping 19 | expect(subject.health).to be_truthy 20 | 21 | expect(subject).to receive(:sleep).ordered.with(12) { 22 | if subject.healthy? 23 | puts 'health state should be false here' 24 | raise Error, 'health state should be false here' 25 | end 26 | } 27 | expect(subject).to receive(:exit).ordered 28 | subject.graceful_stop 29 | subject.join 30 | 31 | # it's unhealthy after stopping 32 | expect(subject.health).to be_falsey 33 | end 34 | end 35 | 36 | context '#graceful_stop, w/ :wait' do 37 | subject { described_class.new(:wait => 5)} 38 | 39 | it 'does not call exit' do 40 | expect(subject).to receive(:sleep).ordered.with(5) 41 | expect(subject).to receive(:sleep).ordered.with(10) 42 | expect(subject).to receive(:exit).ordered 43 | subject.graceful_stop 44 | subject.join 45 | end 46 | end 47 | 48 | context '#graceful_stop, w/o exit' do 49 | subject { described_class.new(:exit => false)} 50 | 51 | it 'does not call exit' do 52 | expect(subject).to receive(:sleep).with(10) 53 | expect(subject).not_to receive(:exit) 54 | subject.graceful_stop 55 | subject.join 56 | end 57 | end 58 | 59 | context '#status' do 60 | it 'has a status' do 61 | expect(subject.status.level).to eq(:ok) 62 | expect(subject.healthy?).to be_truthy 63 | expect(subject.status.to_h[:statusDetails]).to be_nil 64 | end 65 | 66 | it 'has substatus' do 67 | substatus = SinatraHealthCheck::Status.new(:ok, 'sub is ok') 68 | system = double("System", :status => substatus) 69 | subject.systems[:sub] = system 70 | expect(subject.status.level).to eq(:ok) 71 | expect(subject.healthy?).to be_truthy 72 | expect(subject.status.to_h[:statusDetails][:sub]).to eq(substatus.to_h) 73 | end 74 | 75 | it 'is unhealthy with unhealthy subsystem' do 76 | substatus = SinatraHealthCheck::Status.new(:error, 'unhealthy') 77 | system = double("System", :status => substatus) 78 | subject.systems[:sub] = system 79 | expect(subject.status.level).to eq(:error) 80 | expect(subject.healthy?).to be_falsey 81 | end 82 | end 83 | 84 | end 85 | -------------------------------------------------------------------------------- /lib/sinatra-health-check/checker.rb: -------------------------------------------------------------------------------- 1 | # The application health check. 2 | # Create an instance and use .health to repond to your health check requests. 3 | class SinatraHealthCheck::Checker 4 | 5 | DEFAULT_OPTS = { 6 | :aggregator => SinatraHealthCheck::Status::StrictAggregator.new, 7 | :exit => true, 8 | :health => true, 9 | :logger => nil, 10 | :signals => %w[TERM INT], 11 | :systems => {}, 12 | :timeout => 10, 13 | :wait => 0, 14 | } 15 | 16 | require 'thread' 17 | 18 | attr_accessor :health 19 | attr_reader :systems 20 | 21 | # Create a health checker. 22 | # Params: 23 | # ++aggrgator++: an aggregator for substatuus, default: StrictAggregator 24 | # ++exit++: call ++exit++ at the end of ++graceful_stop++ 25 | # ++health++: initial health state 26 | # ++logger++: a logger 27 | # ++signals++: array of signals to register a graceful stop handler 28 | # ++systems++: a hash of subsystems responding to .status 29 | # ++timeout++: timeout for graceful stop in seconds 30 | # ++wait++: wait before setting health to unhealthy 31 | def initialize(opts = {}) 32 | @opts = DEFAULT_OPTS.merge(opts) 33 | @aggregator = SinatraHealthCheck::Status::OverwritingAggregator.new(@opts[:aggregator]) 34 | @health = @opts[:health] 35 | @systems = @opts[:systems] 36 | trap(@opts[:signals]) 37 | end 38 | 39 | # Set application to unhealthy state and stop it after wating for ++@timeout++. 40 | def graceful_stop 41 | # set to unhealthy state 42 | unless @stopper 43 | # spawn a thread to stop application after a given time 44 | @stopper = Thread.new do 45 | if @opts[:wait] > 0 46 | logger.info "asked to stop application, waiting for #{@opts[:wait]}s before doing so" if logger 47 | sleep @opts[:wait] 48 | end 49 | logger.info "stopping application, waiting for #{@opts[:timeout]}s" if logger 50 | @health = false 51 | sleep @opts[:timeout] 52 | logger.info "exit application" if logger 53 | exit if @opts[:exit] 54 | end 55 | end 56 | end 57 | 58 | # Waits for the stopping thread to finish 59 | def join 60 | @stopper.join if @stopper 61 | end 62 | 63 | # Returns a Status object 64 | def status 65 | statuus = {} 66 | systems.each { |k,v| statuus[k] = v.status if v.respond_to?(:status) } 67 | @aggregator.aggregate(statuus, health ? nil : SinatraHealthCheck::Status.new(:error, 'app is unhealthy')) 68 | end 69 | 70 | def healthy? 71 | status.level != :error 72 | end 73 | 74 | private 75 | 76 | def logger 77 | @opts[:logger] 78 | end 79 | 80 | # Register signal handler to stop application gracefully. 81 | # Params: 82 | # ++signals++: array of signal names 83 | def trap(signals) 84 | if signals and signals.size > 0 85 | logger.info "register graceful stop handler for signals: #{signals.join(', ')}" if logger 86 | signals.each do |sig| 87 | Signal.trap(sig) { graceful_stop } 88 | end 89 | end 90 | end 91 | end 92 | --------------------------------------------------------------------------------