health_monitor 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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