health_monitor 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/.gitignore ADDED
@@ -0,0 +1,6 @@
1
+ coverage
2
+ pkg
3
+ doc
4
+ rdoc
5
+ test/tmp
6
+ tmp
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 Blythe Dunham
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,119 @@
1
+ = Health Monitor
2
+ Monitor individual aspects of your rails application's health
3
+
4
+ Most rails applications have many additional moving parts of which the health cannot be assessed with
5
+ simply pinging the (hopefully page cached) homepage
6
+
7
+ For example,
8
+ * Is Email is sent successfully?
9
+ * Is the SMS gateway alive and you bought sufficient credits?
10
+ * All database connections are alive? Backgroundrb down again?
11
+ * The cloud computing setup jacked the imagemagick? Again?
12
+ * You are running out of disk space and there are no more file descriptors for your
13
+ * The git SHA and version is what ?
14
+
15
+ Health Monitor adds a single controller action to generate an html, js, or xml report with details of *all*
16
+ your custom defined health checks. An error response code (500 server error) indicates failure
17
+ when any monitored feature fails or exceeds the custom timeout definition. Health monitor is easier to setup than
18
+ custom server side cron tasks and adds the advantage of exercising more code since everything from your load balancer
19
+ to nginx to mongrels must be happily shoving rubies around to get that 200 oh boy success message.
20
+ So ping away, grab a beer and know that hey, you might be too drunk but at least you will know your application
21
+ is sick before your clients do.
22
+
23
+ === Installation
24
+ Install the plugin or gem
25
+ script/plugin install git://github.com/blythedunham/health_monitor
26
+
27
+ sudo gem install blythedunham-health_monitor --sources=http://gemcutter
28
+
29
+ Run the generator to create HealthMonitorController
30
+ script/generate health_monitor
31
+
32
+ === Examples
33
+
34
+ class HealthMonitorController < ApplicationController
35
+ acts_as_health_monitor
36
+
37
+ # montior the database connection
38
+ monitor_health :database, :description => 'Check database connection'
39
+
40
+ # Monitor email sending. Fail if it exceeds 4 minutes
41
+ monitor_health :email,
42
+ :timeout => 240000 # Fail this test if it exceeds 4 minutes
43
+ :method => lambda{ |controller| ActionMailer::Base.deliver_my_mail('blah') }
44
+
45
+ # Display the results of system df call with the results
46
+ monitor_health :check_disk, :description => 'Check Disk status' do |controller|
47
+ results = `df`
48
+ status = $? == 0 ? :success : :failure
49
+ { :status => status, :message => "DF: #{results}" }
50
+ end
51
+
52
+ protected
53
+ def database; ActiveRecord::Base.connection; end
54
+
55
+ end
56
+
57
+ === +monitor_health+ Options
58
+ <tt>:description</tt> - description of the task
59
+ <tt>:message</tt> - Defaults to SUCCESS or FAILED!
60
+ Additional information that is either a string or a hash with keys <tt>:success</tt> and <tt>:failure</tt>
61
+ Message allows more custom result information, such as the number of servers queried or IP address
62
+ or git version.
63
+
64
+ <tt>:timeout</tt> - Fails the health check if the total time exceeds <tt>:timeout</tt> milliseconds
65
+ <tt>:method</tt> - The name of the method or a Proc to invoke. A block can be given as well.
66
+ Defaults to the method with the feature name
67
+
68
+ === Monitored Methods
69
+ The proc or method defined should return its status as one of the following:
70
+ * true or false indicating success or failure
71
+ monitor_health :mymonitor, :method => { |controller| ...do something ... ; true }
72
+
73
+ monitor_health :myothermonitor
74
+ def myothermonitor; false; end
75
+
76
+ * a status symbol: of <tt>:success</tt>, <tt>:failure</tt>, <tt>:timeout</tt>, <tt>:skipped</tt>
77
+ monitor_health :mymonitor, :method => { |controller| ...do something ... ; :failure }
78
+
79
+ * a hash of attributes including:
80
+ ** <tt>:status</tt> must be a value listed above: defaults to failure
81
+ ** <tt>:message</tt> Custom message with result data
82
+ ** <tt>:description</tt> The task description
83
+
84
+ monitor_health :mymonitor do |controller|
85
+ ...do something ... ;
86
+ { :status => :success, :message => 'My custom results for server abc', :description => 'task description' }
87
+ end
88
+
89
+ === Skip tests or run a single test
90
+ To skip a test, add the skip parameter to your url with a list of comma delimited tests to skip
91
+ http://yayhost/health_monitor.html?skip=testblah
92
+ http://yayhost/health_monitor.js?skip=test1,test2
93
+
94
+ To specify tests to run, use the :only parameter
95
+ http://yayhost/health_monitor?only=test2,test4,test5
96
+
97
+ === Routes
98
+ By default, the route resources are added (assume controller is named +HealthMonitorController+)
99
+ The action defined is named +monitor_health+
100
+
101
+ Base resource if show is not already defined by +health_monitor_url+
102
+ host/health_monitor.js?skip=mysql
103
+
104
+ Member accessor +monitor_health_health_monitor+
105
+ host/health_monitor/monitor_health.js?only=thatone,thisone
106
+
107
+ To disable and write your own routes, use +route+ option with +acts_as_health_monitor+
108
+ acts_as_health_monitor :route => false
109
+
110
+ === Considerations
111
+ If you are reporting sensitive information, consider limiting this controller to specific IP addresses or
112
+ adding authorization logic to your controller with before_filters.
113
+
114
+ === Inspirations and Fun
115
+ Health monitor was inspired by work at http://spongecell.com.
116
+ Health monitor is brought to you by the rule of threes and the letter B.
117
+ The official mascot of health monitor is GIRAFFE! and the official drink is sparklemotion (1 part sparks, 1 part champagne)
118
+
119
+ Copyright (c) 2009 Blythe Dunham. See LICENSE for details.
data/Rakefile ADDED
@@ -0,0 +1,59 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+
4
+ begin
5
+ require 'jeweler'
6
+ Jeweler::Tasks.new do |gem|
7
+ gem.name = "health_monitor"
8
+ gem.summary = %Q{Monitor all aspects of your applications health.}
9
+ gem.email = "blythe@snowgiraffe.com"
10
+ gem.homepage = "http://github.com/blythedunham/health_monitor"
11
+ gem.authors = ["Blythe Dunham"]
12
+
13
+ gem.add_dependency('activesupport', '>= 2.1')
14
+ gem.add_dependency('actionpack', '>=2.1')
15
+
16
+ #gem.add_development_dependency('timecop', '0.3.1')
17
+ gem.add_development_dependency('mocha', '>=0.9.8')
18
+ gem.add_development_dependency "thoughtbot-shoulda", ">= 0"
19
+
20
+ # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
21
+ end
22
+ Jeweler::GemcutterTasks.new
23
+ rescue LoadError
24
+ puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
25
+ end
26
+
27
+ require 'rake/testtask'
28
+ Rake::TestTask.new(:test) do |test|
29
+ test.libs << 'lib' << 'test'
30
+ test.pattern = 'test/**/test_*.rb'
31
+ test.verbose = true
32
+ end
33
+
34
+ begin
35
+ require 'rcov/rcovtask'
36
+ Rcov::RcovTask.new do |test|
37
+ test.libs << 'test'
38
+ test.pattern = 'test/**/test_*.rb'
39
+ test.verbose = true
40
+ end
41
+ rescue LoadError
42
+ task :rcov do
43
+ abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
44
+ end
45
+ end
46
+
47
+ task :test => :check_dependencies
48
+
49
+ task :default => :test
50
+
51
+ require 'rake/rdoctask'
52
+ Rake::RDocTask.new do |rdoc|
53
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
54
+
55
+ rdoc.rdoc_dir = 'rdoc'
56
+ rdoc.title = "health_monitor #{version}"
57
+ rdoc.rdoc_files.include('README*')
58
+ rdoc.rdoc_files.include('lib/**/*.rb')
59
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.0.1
@@ -0,0 +1,26 @@
1
+
2
+ class HealthMonitorGenerator < ControllerGenerator
3
+ def initialize(runtime_args, runtime_options = {})
4
+ runtime_args << 'health_monitor' if runtime_args.empty?
5
+ super runtime_args, runtime_options
6
+ end
7
+
8
+ protected
9
+
10
+ # Override the source path to read the parents templates for everything
11
+ # except the controller
12
+ def source_path(relative_source)
13
+ original_source_root = self.source_root
14
+ if relative_source != 'controller.rb'
15
+ @source_root = File.join(self.class.lookup('Controller').path, 'templates')
16
+ end
17
+ super
18
+ ensure
19
+ @source_root = original_source_root
20
+ end
21
+
22
+ def banner
23
+ "Usage: #{$0} health_monitor [controller_name] [options] \n Default controller_name = HealthMonitorController]"
24
+ end
25
+
26
+ end
@@ -0,0 +1,45 @@
1
+ <h1>Health O Meter</h1>
2
+ <h2>Status: <%= healthy? ? "PASS" : "FAIL" %></h2>
3
+
4
+ <table border="1" width="90%">
5
+ <tr>
6
+ <th></th>
7
+ <th>Name</th>
8
+ <th>Result</th>
9
+ <th>Time (s)</th>
10
+ <th>Description</th>
11
+ </tr>
12
+ <% monitored_features.each do |name, options| %>
13
+ <% result = @results[ name ] %>
14
+ <tr>
15
+ <td style="background-color: <%= case result.try( '[]', :status )
16
+ when nil, :skipped then 'yellow'
17
+ when :success then 'green'
18
+ when :failure then 'red'
19
+ when :timeout then 'blue'
20
+ end
21
+ -%>">
22
+ <%= result ? result[:status].to_s.upcase : "SKIPPED" %>
23
+ </td>
24
+ <td><%= h name.to_s.titleize %></td>
25
+ <td><%= h result ? result[ :message ] : "SKIPPED" %></td>
26
+ <td><%= h "#{result[ :time ]} s" if result %></td>
27
+ <td><%= h result.try( '[]', :description ) || options[ :description ] %></td>
28
+ </tr>
29
+ <% end %>
30
+ </table>
31
+ <br>
32
+ <table>
33
+ <tr>
34
+ <td><b>Total Time: </b></td>
35
+ <td><%= h "#{@results.sum{|n, r| r[ :time ] || 0 }} seconds" %></td>
36
+ </tr>
37
+ <tr>
38
+ <td><b>PID: </b></td>
39
+ <td><%= $$ %></td>
40
+ </tr>
41
+ <tr>
42
+ <td><b>Host: </b></td>
43
+ <td><%= request.host %></td>
44
+ </tr>
45
+ </table>
@@ -0,0 +1,31 @@
1
+ class <%= class_name %>Controller < ApplicationController
2
+ acts_as_health_monitor
3
+
4
+ # Refer to the README at http://github.com/blythedunham/health_monitor
5
+ # for more examples
6
+
7
+ # # montior the database connection
8
+ # monitor_health :database, :description => 'Check database connection'
9
+ #
10
+ # # Monitor email sending. Fail if it exceeds 4 minutes
11
+ # monitor_health :email,
12
+ # :timeout => 240000 # Fail this test if it exceeds 4 minutes
13
+ # :method => lambda{ |controller| ActionMailer::Base.deliver_my_mail('blah') }
14
+ #
15
+ # # Display the results of system df call with the results
16
+ # monitor_health :check_disk, :description => 'Check Disk status' do |controller|
17
+ # results = `df`
18
+ # status = $? == 0 ? :success : :failure # base result on return code
19
+ # { :status => status, :message => "DF: #{results}" }
20
+ # end
21
+ #
22
+ # protected
23
+ # def database; ActiveRecord::Base.connection; end
24
+ #
25
+
26
+ <% for action in actions -%>
27
+ def <%= action %>
28
+ end
29
+
30
+ <% end -%>
31
+ end
@@ -0,0 +1,66 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{health_monitor}
8
+ s.version = "0.0.1"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Blythe Dunham"]
12
+ s.date = %q{2009-12-06}
13
+ s.email = %q{blythe@snowgiraffe.com}
14
+ s.extra_rdoc_files = [
15
+ "LICENSE",
16
+ "README.rdoc"
17
+ ]
18
+ s.files = [
19
+ ".gitignore",
20
+ "LICENSE",
21
+ "README.rdoc",
22
+ "Rakefile",
23
+ "VERSION",
24
+ "generators/health_monitor/health_monitor_generator.rb",
25
+ "generators/health_monitor/templates/_health_monitor.html.erb",
26
+ "generators/health_monitor/templates/controller.rb",
27
+ "health_monitor.gemspec",
28
+ "lib/health_monitor.rb",
29
+ "lib/health_monitoring.rb",
30
+ "templates/_health_monitor.html.erb",
31
+ "test/helper.rb",
32
+ "test/test_health_monitor.rb"
33
+ ]
34
+ s.homepage = %q{http://github.com/blythedunham/health_monitor}
35
+ s.rdoc_options = ["--charset=UTF-8"]
36
+ s.require_paths = ["lib"]
37
+ s.rubygems_version = %q{1.3.5}
38
+ s.summary = %q{Monitor all aspects of your applications health.}
39
+ s.test_files = [
40
+ "test/helper.rb",
41
+ "test/test_health_monitor.rb"
42
+ ]
43
+
44
+ if s.respond_to? :specification_version then
45
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
46
+ s.specification_version = 3
47
+
48
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
49
+ s.add_runtime_dependency(%q<activesupport>, [">= 2.1"])
50
+ s.add_runtime_dependency(%q<actionpack>, [">= 2.1"])
51
+ s.add_development_dependency(%q<mocha>, [">= 0.9.8"])
52
+ s.add_development_dependency(%q<thoughtbot-shoulda>, [">= 0"])
53
+ else
54
+ s.add_dependency(%q<activesupport>, [">= 2.1"])
55
+ s.add_dependency(%q<actionpack>, [">= 2.1"])
56
+ s.add_dependency(%q<mocha>, [">= 0.9.8"])
57
+ s.add_dependency(%q<thoughtbot-shoulda>, [">= 0"])
58
+ end
59
+ else
60
+ s.add_dependency(%q<activesupport>, [">= 2.1"])
61
+ s.add_dependency(%q<actionpack>, [">= 2.1"])
62
+ s.add_dependency(%q<mocha>, [">= 0.9.8"])
63
+ s.add_dependency(%q<thoughtbot-shoulda>, [">= 0"])
64
+ end
65
+ end
66
+
@@ -0,0 +1,2 @@
1
+ require File.dirname(__FILE__) + "/health_monitoring"
2
+ ActionController::Base.extend HealthMonitoring::ActsAsHealthMonitor
@@ -0,0 +1,306 @@
1
+ # = Health Monitor
2
+ # Monitor individual aspects of your rails application's health
3
+ #
4
+ # Most rails applications have many additional moving parts of which the health cannot be assessed with
5
+ # simply pinging the (hopefully page cached) homepage
6
+ #
7
+ # For example,
8
+ # * Is Email is sent successfully?
9
+ # * Is the SMS gateway alive and you bought sufficient credits?
10
+ # * All database connections are alive? Backgroundrb down again?
11
+ # * The cloud computing setup jacked the imagemagick? Again?
12
+ # * You are running out of disk space and there are no more file descriptors for your
13
+ # * The git SHA and version is what ?
14
+ #
15
+ # Health Monitor adds a single controller action to generate an html, js, or xml report with details of *all*
16
+ # your custom defined health checks. An error response code (500 server error) indicates failure
17
+ # when any monitored feature fails or exceeds the custom timeout definition. Health monitor is easier to setup than
18
+ # custom server side cron tasks and adds the advantage of exercising more code since everything from your load balancer
19
+ # to nginx to mongrels must be happily shoving rubies around to get that 200 oh boy success message.
20
+ # So ping away, grab a beer and know that hey, you might be too drunk but at least you will know your application
21
+ # is sick before your clients do.
22
+ #
23
+ # ====Examples
24
+ #
25
+ # class HealthMonitorController < ApplicationController
26
+ # acts_as_health_monitor
27
+ #
28
+ # # montior the database connection
29
+ # monitor_health :database, :description => 'Check database connection'
30
+ #
31
+ # # Monitor email sending. Fail if it exceeds 4 minutes
32
+ # monitor_health :email,
33
+ # :timeout => 240000 # Fail this test if it exceeds 4 minutes
34
+ # :method => lambda{ |controller| ActionMailer::Base.deliver_my_mail('blah') }
35
+ #
36
+ # # Display the results of system df call with the results
37
+ # monitor_health :check_disk, :description => 'Check Disk status' do |controller|
38
+ # results = `df`
39
+ # status = $? == 0 ? :success : :failure
40
+ # { :status => status, :message => "DF: #{results}" }
41
+ # end
42
+ #
43
+ # protected
44
+ # def database; ActiveRecord::Base.connection; end
45
+ #
46
+ # end
47
+ #
48
+ # === +monitor_health+ Options
49
+ # <tt>:description</tt> - description of the task
50
+ # <tt>:message</tt> - Defaults to SUCCESS or FAILED!
51
+ # Additional information that is either a string or a hash with keys <tt>:success</tt> and <tt>:failure</tt>
52
+ # Message allows more custom result information, such as the number of servers queried or IP address
53
+ # or git version.
54
+ #
55
+ # <tt>:timeout</tt> - Fails the health check if the total time exceeds <tt>:timeout</tt> milliseconds
56
+ # <tt>:method</tt> - The name of the method or a Proc to invoke. A block can be given as well.
57
+ # Defaults to the method with the feature name
58
+ #
59
+ # === Monitored Methods
60
+ # The proc or method defined should return its status as one of the following:
61
+ # * true or false indicating success or failure
62
+ # monitor_health :mymonitor, :method => { |controller| ...do something ... ; true }
63
+ #
64
+ # monitor_health :myothermonitor
65
+ # def myothermonitor; false; end
66
+ #
67
+ # * a status symbol: of <tt>:success</tt>, <tt>:failure</tt>, <tt>:timeout</tt>, <tt>:skipped</tt>
68
+ # monitor_health :mymonitor, :method => { |controller| ...do something ... ; :failure }
69
+ #
70
+ # * a hash of attributes including:
71
+ # ** <tt>:status</tt> must be a value listed above: defaults to failure
72
+ # ** <tt>:message</tt> Custom message with result data
73
+ # ** <tt>:description</tt> The task description
74
+ #
75
+ # monitor_health :mymonitor do |controller|
76
+ # ...do something ... ;
77
+ # { :status => :success, :message => 'My custom results for server abc', :description => 'task description' }
78
+ # end
79
+ #
80
+ # === Routes
81
+ # By default, the route resources are added (assume controller is named +HealthMonitorController+)
82
+ # The action defined is named +monitor_health+
83
+ #
84
+ # Base resource if show is not already defined by +health_monitor_url+
85
+ # host/health_monitor.js?skip=mysql
86
+ #
87
+ # Member accessor +monitor_health_health_monitor+
88
+ # host/health_monitor/monitor_health.js?only=thatone,thisone
89
+ #
90
+ # To disable and write your own routes, use +route+ option with +acts_as_health_monitor+
91
+ # acts_as_health_monitor :route => false
92
+ #
93
+ module HealthMonitoring
94
+ def self.included( base )
95
+ base.class_eval do
96
+ extend HealthMonitoring::ClassMethods
97
+ cattr_accessor :monitored_features
98
+ self.monitored_features ||= ActiveSupport::OrderedHash.new
99
+
100
+ helper_method :monitored_features, :healthy?
101
+ hide_action :monitored_features, :monitored_features=
102
+ end
103
+ end
104
+
105
+ # by default, this is a singleton route
106
+ # class HealthMonitor < ApplicationController
107
+ # acts_as_health_monitor
108
+ # end
109
+ #
110
+ # ===Options
111
+ # :route => false. Will disable autogenerating named paths and routes
112
+ #
113
+ # By default, the resouces are defined as (assume controller is named +HealthMonitorController+)
114
+ # Base resource if show is not already defined by +health_monitor_url+
115
+ # host/health_monitor.js?skip=mysql
116
+ #
117
+ # Member accessor +monitor_health_health_monitor+
118
+ # host/health_monitor/monitor_health.js?only=thatone,thisone
119
+ #
120
+ module ActsAsHealthMonitor
121
+ def acts_as_health_monitor( options = {} )
122
+ include HealthMonitoring unless method_defined?( :health_monitor )
123
+
124
+ setup_health_monitor_routes( options.delete( :route ) )
125
+ end
126
+
127
+ def setup_health_monitor_routes( route )
128
+ return if route == false
129
+
130
+ base_methods = if method_defined?( :show )
131
+ :none
132
+ else
133
+ alias_method :show, :monitor_health
134
+ :show
135
+ end
136
+
137
+
138
+ ActionController::Routing::Routes.draw do |map|
139
+ map.resource controller_name, :controller => controller_name, :only => base_methods, :member => { :monitor_health => :get }
140
+ #map.named_route controller_name, controller_name + '.:format', :conditions => {:method => :get}, :controller => controller_name, :action => 'monitor_health'
141
+ #map.named_route "monitor_health_#{controller_name}", "#{controller_name}/monitor_health.:format", :conditions => {:method => :get}, :controller => controller_name, :action => 'monitor_health'
142
+
143
+ #map.resource controller_name, :controller => controller_name, :only => :none, :member => { :monitor_health => :get } do |r|
144
+ # r.named_route '', '.:format', :conditions => {:method => :get}, :controller => controller_name, :action => 'monitor_health'
145
+ #end
146
+ #map.named_route route_name, route_path, :controller => controller_name, :action => 'health', :method => :get
147
+ #map.connect "#{controller_name}", :controller => controller_name, :action => 'health'
148
+ end
149
+ end
150
+ end
151
+
152
+ module ClassMethods
153
+ def monitor_health( *features, &block )
154
+ options = features.extract_options!
155
+ options.symbolize_keys!
156
+ options[:method] = block if block_given?
157
+ features.each {|name| self.monitored_features[ name.to_sym ] = options }
158
+ end
159
+ end
160
+
161
+ # Show a status page showing the health of monitored features
162
+ # Returns a 404 if any features have a success of unsuccessful
163
+ #
164
+ # Skip features: z2live.com/health/status?skip=mongo,mysql
165
+ # Include features: z2live.com/health/status?feature=mongo
166
+ def monitor_health
167
+ find_features
168
+ @results = @features.inject({}) do |results, feature_name|
169
+ results[ feature_name ] = monitor_health_of( feature_name )
170
+ results
171
+ end
172
+ healthy? ? on_healthy : on_unhealthy
173
+ render_health
174
+ end
175
+
176
+ protected
177
+
178
+ def monitor_health_of( feature_name )
179
+ feature_status = { :name => feature_name }
180
+ result = nil
181
+ feature_status[ :time ] = Benchmark.ms { result = invoke_health_check( feature_name ) }
182
+ report_health_status!( feature_status, result )
183
+ end
184
+
185
+ def invoke_health_check( feature_name )
186
+ case method = monitored_features[ feature_name ][ :method ]
187
+ when Proc, Method then method.call( self)
188
+ when NilClass then send( feature_name )
189
+ else send( method )
190
+ end
191
+ rescue => e
192
+ return { :status => :failure, :exception => e }
193
+ end
194
+
195
+ def healthy?
196
+ (@results||{}).all?{ |name, result| result[:status] == :success }
197
+ end
198
+
199
+ #response code
200
+ def healthy_response_code() 200; end
201
+ def unhealthy_response_code() 500; end
202
+
203
+ #callbacks
204
+ def on_healthy() end
205
+ def on_unhealthy() end
206
+
207
+ def health_check_template
208
+ File.join( File.dirname(__FILE__), "/../generators/health_monitor/templates/_health_monitor.html.erb" )
209
+ end
210
+
211
+ def health_response_code
212
+ healthy? ? healthy_response_code : unhealthy_response_code
213
+ end
214
+
215
+ # Render the html file
216
+ def render_health_html
217
+ render :file => health_check_template, :status => health_response_code
218
+ end
219
+
220
+ # Render the json file
221
+ def render_health_json
222
+ render :text => @results.values.to_json, :status => health_response_code
223
+ end
224
+
225
+ # Render the xml file
226
+ def render_health_xml
227
+ render :xml => @results.values, :status => health_response_code
228
+ end
229
+
230
+ # Render the result
231
+ def render_health
232
+ return if performed?
233
+
234
+ respond_to do |format|
235
+ format.html { render_health_html }
236
+ format.js { render_health_json }
237
+ format.xml { render_health_xml }
238
+ end
239
+ end
240
+
241
+
242
+
243
+ # Skip features by appending skip=mongo,fun,etc
244
+ # Include features by appending feature=mongo,urban_airship,etc to filter
245
+ def find_features
246
+ @features = if params[ :only ]
247
+ params[ :only ].to_s.split( "," ).collect( &:to_sym ).uniq
248
+
249
+ elsif skip = params[ :skip ] || params[ :exclude ]
250
+ monitored_features.keys - skip.to_s.split( "," ).collect( &:to_sym )
251
+
252
+ else
253
+ monitored_features.keys
254
+ end
255
+ end
256
+
257
+ def fail_health_timeout!( feature_status )
258
+ timeout = monitored_features[ feature_status[:name] ][:timeout]
259
+ if timeout && feature_status[:time].to_f >= timeout
260
+ feature_status.update(
261
+ :status => :timeout,
262
+ :message => "Timeout: Exceeded #{timeout} ms"
263
+ )
264
+ end
265
+
266
+ #Adjust time to seconds
267
+ feature_status[ :time ] = ("%.4f" % (feature_status[ :time ] / 1000)).to_f
268
+
269
+ feature_status
270
+ end
271
+
272
+ def set_default_health_status!( feature_status )
273
+ monitor_defaults = monitored_features[ feature_status[ :name ] ]
274
+ feature_status[ :description ] ||= monitor_defaults[ :description ]
275
+ health_monitor_message!( feature_status )
276
+ end
277
+
278
+ def health_monitor_message!( feature_status )
279
+ feature_status[ :message ] ||= monitored_features[ feature_status[ :name ] ][ :message ]
280
+ feature_status[ :message ] = feature_status[:message][ feature_status[:status] ] if feature_status[:message].is_a?( Hash )
281
+ feature_status[ :message ] ||= feature_status[:status].to_s.upcase
282
+ feature_status[ :message ] << " Error: #{feature_status[:exception]}" if feature_status[:exception]
283
+ feature_status
284
+ end
285
+
286
+ def report_health_status!( feature_status, result )
287
+
288
+ # update the hash with the result hash
289
+ case result
290
+ when Hash then feature_status.update( result )
291
+ else feature_status[ :status ] = result
292
+ end
293
+
294
+ # symbolize variables if strings
295
+ # convert false to :failure
296
+ # otherwise the existance of any object indicates success
297
+ feature_status[:status] = case feature_status[:status].to_s
298
+ when /(failure|success|timeout|skipped)/ then feature_status[:status].to_sym
299
+ when 'false' then :failure
300
+ else !!feature_status[:status] ? :success : :failure
301
+ end
302
+
303
+ fail_health_timeout!( feature_status )
304
+ set_default_health_status!( feature_status )
305
+ end
306
+ end
@@ -0,0 +1,29 @@
1
+
2
+ <h1>Health O Meter</h1>
3
+ <h2><%= healthy? ? "OK" : "FAIL" %></h2>
4
+
5
+ <table border="1" width="90%">
6
+ <tr>
7
+ <th></th>
8
+ <th>Name</th>
9
+ <th>Result</th>
10
+ <th>Time (s)</th>
11
+ <th>Description</th>
12
+ </tr>
13
+ <% monitored_features.each do |name, options| %>
14
+ <tr>
15
+ <td style="background-color:<%= status_color( name )%>">
16
+ <%= h status_text( name ) %>
17
+ </td>
18
+ <td><%= h name.to_s.titleize %></td>
19
+ <td><%= monitored_message( name ) %></td>
20
+ <td><%= monitored_timing( name ) %></td>
21
+ <td><%= monitored_description( name ) %></td>
22
+ </tr>
23
+ <% end %>
24
+ </table>
25
+
26
+ <br>
27
+ <b>Total: </b><%= total_time %> <br>
28
+ <b>PID: </b><%= $$ %><br>
29
+ <b>host: </b><%= request.host %>
data/test/helper.rb ADDED
@@ -0,0 +1,31 @@
1
+ # Load the environment
2
+ require 'fileutils'
3
+ ENV['RAILS_ENV'] = 'test'
4
+ require 'rubygems'
5
+ require 'active_support'
6
+ require 'action_controller'
7
+ # Load the testing framework
8
+ require 'test_help'
9
+ silence_warnings { RAILS_ENV = ENV['RAILS_ENV'] }
10
+
11
+
12
+ gem 'shoulda'
13
+ gem 'mocha'
14
+ require 'shoulda'
15
+ require 'shoulda/action_view'
16
+ require 'shoulda/action_controller'
17
+ require 'mocha'
18
+
19
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
20
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
21
+
22
+ require 'health_monitor'
23
+
24
+
25
+ test_dir = File.expand_path(File.dirname(__FILE__) + '/../tmp')
26
+ FileUtils.mkdir_p(test_dir) unless File.exist?(test_dir)
27
+ RAILS_LOGGER = Logger.new(test_dir + '/test.log')
28
+ ActionController::Base.logger = nil
29
+
30
+ class Test::Unit::TestCase
31
+ end
@@ -0,0 +1,124 @@
1
+ require 'helper'
2
+
3
+ #require 'shoulda/controller/macros'
4
+ class TestHealthMonitorController < ActionController::Base
5
+ acts_as_health_monitor
6
+
7
+ monitor_health :proc_test,
8
+ :method => lambda{ |c| c.params[:fail] != 'true' },
9
+ :description => 'A proc test',
10
+ :message => {:success => "Successful test.", :failure => 'Failed test'}
11
+
12
+ monitor_health :block_test do |controller|
13
+ @block_controller = controller
14
+ {
15
+ :status => controller.send(:conditional_fail_test),
16
+ :message => "Params are: #{controller.params[:fail]}",
17
+ :description => 'A block test'
18
+ }
19
+ end
20
+
21
+ monitor_health :conditional_fail_test
22
+ monitor_health :exception_test
23
+
24
+ protected
25
+ def conditional_fail_test( key = :fail)
26
+ params[key] != 'true'
27
+ end
28
+
29
+ def exception_test
30
+ raise StandardError.new("Exception test") if params[:exception] == 'true'
31
+ :success
32
+ end
33
+ end
34
+
35
+ class TestHealthMonitorControllerTest < ActionController::TestCase
36
+
37
+ def self.ping_monitor( testname, options = {} )
38
+
39
+ context testname do
40
+ expect_success = ![options[:fail], options[:exception]].include?( 'true' )
41
+ setup do
42
+ get :show, options
43
+ @success = expect_success#![options[:fail], options[:exception]].include?( 'true' )
44
+ @results = assigns(:results)
45
+ end
46
+
47
+ should_respond_with( expect_success ? 200 : 500 )
48
+
49
+ should 'have the description' do
50
+ assert_equal 'A proc test', @results[:proc_test][:description]
51
+ assert_equal 'A block test', @results[:block_test][:description]
52
+ assert_nil @results[:conditional_fail_test][:description]
53
+ end
54
+
55
+ should 'set default message based on test status' do
56
+ result = @success ? 'SUCCESS' : 'FAILURE'
57
+ assert_equal result, @results[:conditional_fail_test][:message], "Expect message #{@success} for #{@results[:conditional_fail_test]} "
58
+ end
59
+
60
+ if expect_success
61
+ should 'set custom message on success to' do
62
+ assert_equal 'Successful test.', @results[:proc_test][:message]
63
+ assert_equal 'Params are: ', @results[:block_test][:message]
64
+ end
65
+ else
66
+ should 'set custom message on failure to' do
67
+ assert_equal 'Failed test', @results[:proc_test][:message]
68
+ assert_equal 'Params are: true', @results[:block_test][:message]
69
+ end
70
+ end
71
+
72
+ should "have #{expect_success ? 'SUCCESS' : 'FAIL'} response" do
73
+ @results.each do |k,v|
74
+ expected_result = (k == :exception_test) || @success ? :success : :failure
75
+ assert_equal expected_result, v[:status], "#{k} results #{v.inspect}. Status should be #{expected_result}"
76
+ end
77
+ end
78
+
79
+ yield if block_given?
80
+ end
81
+ end
82
+
83
+ should_route :get, '/test_health_monitor.js', :action => :show, :format => "js", :controller => 'test_health_monitor'
84
+ should_route :get, '/test_health_monitor/monitor_health', :controller => 'test_health_monitor', :action => :monitor_health
85
+
86
+ ping_monitor 'health monitor web page' do
87
+ should_respond_with_content_type :html
88
+ end
89
+
90
+ ping_monitor( 'monitor unhealthy app', :fail => 'true' ) do
91
+ should_respond_with_content_type :html
92
+ end
93
+
94
+ ping_monitor 'monitor an unhealthy app with js', {:fail => 'true', :format => 'js'} do
95
+ should_respond_with_content_type 'text/javascript'
96
+ end
97
+
98
+ ping_monitor 'monitor a healthy app with js', {:format => 'js'} do
99
+ should_respond_with_content_type 'text/javascript'
100
+ end
101
+
102
+ ping_monitor 'monitor an unhealthy app with js', {:fail => 'true', :format => 'xml'} do
103
+ should_respond_with_content_type :xml
104
+ end
105
+
106
+ ping_monitor 'monitor a healthy app with js', {:format => 'xml'} do
107
+ should_respond_with_content_type :xml
108
+ end
109
+
110
+ context 'render a template if a test exception occurs' do
111
+ setup do
112
+ get :show, :exception => 'true'
113
+ @results = assigns(:results)
114
+ end
115
+ should_respond_with_content_type :html
116
+ should_respond_with 500
117
+
118
+ should 'fail exception test' do
119
+ assert_equal :failure, @results[:exception_test][:status]
120
+ end
121
+ end
122
+
123
+ end
124
+
metadata ADDED
@@ -0,0 +1,109 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: health_monitor
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Blythe Dunham
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-12-06 00:00:00 -08:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: activesupport
17
+ type: :runtime
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: "2.1"
24
+ version:
25
+ - !ruby/object:Gem::Dependency
26
+ name: actionpack
27
+ type: :runtime
28
+ version_requirement:
29
+ version_requirements: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: "2.1"
34
+ version:
35
+ - !ruby/object:Gem::Dependency
36
+ name: mocha
37
+ type: :development
38
+ version_requirement:
39
+ version_requirements: !ruby/object:Gem::Requirement
40
+ requirements:
41
+ - - ">="
42
+ - !ruby/object:Gem::Version
43
+ version: 0.9.8
44
+ version:
45
+ - !ruby/object:Gem::Dependency
46
+ name: thoughtbot-shoulda
47
+ type: :development
48
+ version_requirement:
49
+ version_requirements: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - ">="
52
+ - !ruby/object:Gem::Version
53
+ version: "0"
54
+ version:
55
+ description:
56
+ email: blythe@snowgiraffe.com
57
+ executables: []
58
+
59
+ extensions: []
60
+
61
+ extra_rdoc_files:
62
+ - LICENSE
63
+ - README.rdoc
64
+ files:
65
+ - .gitignore
66
+ - LICENSE
67
+ - README.rdoc
68
+ - Rakefile
69
+ - VERSION
70
+ - generators/health_monitor/health_monitor_generator.rb
71
+ - generators/health_monitor/templates/_health_monitor.html.erb
72
+ - generators/health_monitor/templates/controller.rb
73
+ - health_monitor.gemspec
74
+ - lib/health_monitor.rb
75
+ - lib/health_monitoring.rb
76
+ - templates/_health_monitor.html.erb
77
+ - test/helper.rb
78
+ - test/test_health_monitor.rb
79
+ has_rdoc: true
80
+ homepage: http://github.com/blythedunham/health_monitor
81
+ licenses: []
82
+
83
+ post_install_message:
84
+ rdoc_options:
85
+ - --charset=UTF-8
86
+ require_paths:
87
+ - lib
88
+ required_ruby_version: !ruby/object:Gem::Requirement
89
+ requirements:
90
+ - - ">="
91
+ - !ruby/object:Gem::Version
92
+ version: "0"
93
+ version:
94
+ required_rubygems_version: !ruby/object:Gem::Requirement
95
+ requirements:
96
+ - - ">="
97
+ - !ruby/object:Gem::Version
98
+ version: "0"
99
+ version:
100
+ requirements: []
101
+
102
+ rubyforge_project:
103
+ rubygems_version: 1.3.5
104
+ signing_key:
105
+ specification_version: 3
106
+ summary: Monitor all aspects of your applications health.
107
+ test_files:
108
+ - test/helper.rb
109
+ - test/test_health_monitor.rb