app_status 1.0.0 → 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +33 -0
- data/app/models/app_status/check_collection.rb +77 -46
- data/app/models/app_status/check_item.rb +58 -0
- data/app/views/app_status/status/index.html.erb +32 -0
- data/bin/kramdown +16 -0
- data/lib/app_status/version.rb +1 -1
- data/lib/app_status.rb +0 -1
- data/spec/dummy/config/initializers/app_status.rb +28 -0
- data/spec/dummy/config/routes.rb +0 -1
- data/spec/dummy/log/development.log +817 -0
- data/spec/dummy/log/test.log +113 -0
- data/spec/dummy/tmp/cache/assets/CA5/160/sprockets%2Fc4b6dd696758a9c186897a8c25615778 +0 -0
- data/spec/dummy/tmp/cache/assets/CB2/070/sprockets%2Fe1b30e44248fc0395505c899c0f2357e +0 -0
- data/spec/dummy/tmp/cache/assets/CB2/5B0/sprockets%2Fc8e423e85d0a1cb22332859990909a3f +0 -0
- data/spec/dummy/tmp/cache/assets/CF2/9A0/sprockets%2Fb3eee5a6a87662f428f47c908157f606 +0 -0
- data/spec/dummy/tmp/cache/assets/D15/DA0/sprockets%2Fc7e74644f623ec92a443b53b8c1d19c8 +0 -0
- data/spec/dummy/tmp/cache/assets/D3F/E00/sprockets%2Fa7629a6ce27d07753dbb05bd48098ce0 +0 -0
- data/spec/dummy/tmp/cache/assets/D51/F30/sprockets%2Fbe466f3302fef7895aad76d05d93963d +0 -0
- data/spec/dummy/tmp/cache/assets/DB8/430/sprockets%2Ff731c6599577d8f741f3afd2d8ed2efb +0 -0
- data/spec/dummy/tmp/cache/assets/DDD/460/sprockets%2Fe5b8947c2a5caf5460fecdfd48c818d5 +0 -0
- data/spec/dummy/tmp/cache/assets/E07/FD0/sprockets%2Fb3d7aafd7ce063e022cd29e7ccb898f9 +0 -0
- metadata +31 -23
- data/app/views/app_status/status/index.html.haml +0 -16
data/README.md
CHANGED
@@ -63,6 +63,8 @@ This is where you set up the checks which you want to be run when
|
|
63
63
|
someone hits the URL above. Set up some calls which evaluate the health
|
64
64
|
of your application and call `add_check` for each one.
|
65
65
|
|
66
|
+
#### add_check
|
67
|
+
|
66
68
|
`add_check` expects a service name, plus a block to be evaluated to determine
|
67
69
|
the health of that service. The block should return either a status value, or
|
68
70
|
a 2-element array with status and some details.
|
@@ -94,6 +96,37 @@ Valid status values (in ascending order of seriousness) are:
|
|
94
96
|
|
95
97
|
These are set up to be compatible with Nagios.
|
96
98
|
|
99
|
+
#### add_description
|
100
|
+
|
101
|
+
`add_description` allows you to specify extended description and troubleshooting
|
102
|
+
information for any check which has been added via `add_check`.
|
103
|
+
|
104
|
+
Currently these are not returned via the JSON endpoint, but are available
|
105
|
+
via the HTML status page.
|
106
|
+
|
107
|
+
Description information is parsed by kramdown for display. Refer to the
|
108
|
+
[kramdown style guide](http://kramdown.rubyforge.org/quickref.html) for
|
109
|
+
usage information.
|
110
|
+
|
111
|
+
```ruby
|
112
|
+
AppStatus::CheckCollection.configure do |c|
|
113
|
+
|
114
|
+
c.add_check('some_service') do
|
115
|
+
[:critical, 'what is going on']
|
116
|
+
end
|
117
|
+
c.add_description 'some_service', <<-EOF
|
118
|
+
some_service failures indicate that some_service is going wrong.
|
119
|
+
|
120
|
+
this is handy since nagios really requires brief output, but sometimes you need
|
121
|
+
more space to explain what a check is.
|
122
|
+
|
123
|
+
think of it as the answer to the problem of "That guy is on vaction, but his
|
124
|
+
app is raising alarms. WTF do I do?"
|
125
|
+
EOF
|
126
|
+
|
127
|
+
end
|
128
|
+
```
|
129
|
+
|
97
130
|
Keep in mind that anyone who hits your status URL can cause your checks to run,
|
98
131
|
so if they expose sensitive data or are a potential DOS vector you should
|
99
132
|
probably protect them with some kind of authentication.
|
@@ -4,6 +4,9 @@ module AppStatus
|
|
4
4
|
|
5
5
|
class CheckCollection
|
6
6
|
|
7
|
+
include Enumerable
|
8
|
+
|
9
|
+
@@check_descriptions = HashWithIndifferentAccess.new
|
7
10
|
@@checks = HashWithIndifferentAccess.new
|
8
11
|
|
9
12
|
# Add checks here.
|
@@ -28,89 +31,117 @@ module AppStatus
|
|
28
31
|
end
|
29
32
|
|
30
33
|
def self.clear_checks!
|
34
|
+
@@check_descriptions = HashWithIndifferentAccess.new
|
31
35
|
@@checks = HashWithIndifferentAccess.new
|
32
36
|
end
|
33
37
|
|
34
|
-
def
|
35
|
-
|
38
|
+
def self.valid_status_map
|
39
|
+
{
|
36
40
|
ok: 0,
|
37
41
|
warning: 1,
|
38
42
|
critical: 2,
|
39
43
|
unknown: 3
|
40
|
-
}
|
41
|
-
|
42
|
-
@check_results = HashWithIndifferentAccess.new
|
43
|
-
@eval_finished = nil
|
44
|
-
@eval_time = 0
|
44
|
+
}
|
45
45
|
end
|
46
46
|
|
47
47
|
# add the results of a check to the collection.
|
48
48
|
# this should describe the health of some portion of your application
|
49
|
+
# The check block you supply should return a status value, or a
|
50
|
+
# [:status, 'details'] array.
|
49
51
|
#
|
50
52
|
# example:
|
51
|
-
#
|
52
|
-
#
|
53
|
+
# AppStatus::CheckCollection.add_check('some_service') do
|
54
|
+
# [:ok, 'these are optional details']
|
55
|
+
# end
|
53
56
|
def self.add_check(name, &block)
|
54
57
|
raise ArgumentError, ":name option is required." if ! name
|
55
|
-
# raise ArgumentError, ":status option is required." if ! options[:status]
|
56
58
|
|
57
59
|
name = name.to_sym
|
58
60
|
raise ArgumentError, "Check name '#{name}' has already been added." if @@checks.keys.include?(name.to_s)
|
59
61
|
raise ArgumentError, "No check defined for '#{name}'." if ! block_given?
|
60
62
|
|
61
|
-
|
63
|
+
item = CheckItem.new(name)
|
64
|
+
item.proc = block
|
65
|
+
@@checks[name] = item
|
62
66
|
end
|
63
67
|
|
64
|
-
|
65
|
-
|
68
|
+
# add a long-form description of a check
|
69
|
+
# service must have already been added via add_check.
|
70
|
+
#
|
71
|
+
# example:
|
72
|
+
# AppStatus::CheckCollection.configure do |c|
|
73
|
+
# c.add_check('some_service') do
|
74
|
+
# [:ok, 'these are optional details']
|
75
|
+
# end
|
76
|
+
# c.add_decription 'some_service', <<-EOF
|
77
|
+
# some_service is pretty easy to understand.
|
78
|
+
# it always works, no matter what.
|
79
|
+
# but if it were **harder to comprehend** you
|
80
|
+
# could add markdown here to explain what it is
|
81
|
+
# and what to do if it starts failing.
|
82
|
+
# EOF
|
83
|
+
#
|
84
|
+
def self.add_description(name, markdown)
|
85
|
+
raise ArgumentError, "Check '#{name}' is not defined." if ! @@checks[name]
|
86
|
+
@@checks[name].description = markdown
|
87
|
+
end
|
88
|
+
|
89
|
+
|
90
|
+
|
91
|
+
attr_reader :finished, :ms, :status, :status_code
|
92
|
+
|
93
|
+
|
94
|
+
def initialize
|
95
|
+
reset
|
96
|
+
end
|
97
|
+
|
98
|
+
def reset
|
99
|
+
@finished = nil
|
100
|
+
@ms = 0
|
101
|
+
@status = :ok
|
102
|
+
@status_code = self.class.valid_status_map[@status]
|
103
|
+
@@checks.each {|key,check| check.reset }
|
104
|
+
end
|
105
|
+
|
106
|
+
def each
|
107
|
+
@@checks.each {|name,check| yield check }
|
66
108
|
end
|
67
109
|
|
68
110
|
# run the checks added via configure
|
69
111
|
# results of the checks are available via as_json
|
70
112
|
def evaluate!
|
71
113
|
eval_start = Time.now
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
status = status.to_sym if status
|
79
|
-
details = details.to_s if details
|
80
|
-
|
81
|
-
if ! valid_status?(status)
|
82
|
-
details = "Check returned invalid status '#{status}'. #{details}".strip
|
83
|
-
status = :unknown
|
84
|
-
end
|
85
|
-
@check_results[name] = {
|
86
|
-
status: status,
|
87
|
-
status_code: @valid_status[status],
|
88
|
-
details: details,
|
89
|
-
ms: check_time.to_i
|
90
|
-
}
|
114
|
+
|
115
|
+
reset
|
116
|
+
|
117
|
+
@@checks.each do |name,check|
|
118
|
+
@status_code = [check.evaluate!, @status_code].max
|
91
119
|
end
|
92
120
|
|
93
|
-
@
|
94
|
-
@
|
95
|
-
end
|
121
|
+
@finished = Time.now.utc
|
122
|
+
@ms = (Time.now - eval_start) * 1000
|
96
123
|
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
max_int = @valid_status[max_status]
|
124
|
+
if @@checks.size == 0
|
125
|
+
@status = :unknown
|
126
|
+
@status_code = self.class.valid_status_map[@status]
|
101
127
|
else
|
102
|
-
|
103
|
-
max_status = @valid_status.invert[max_int]
|
128
|
+
@status = self.class.valid_status_map.invert[@status_code]
|
104
129
|
end
|
130
|
+
end
|
105
131
|
|
132
|
+
def as_hash
|
106
133
|
HashWithIndifferentAccess.new({
|
107
|
-
status:
|
108
|
-
status_code:
|
109
|
-
ms: @
|
110
|
-
finished: @
|
111
|
-
checks:
|
134
|
+
status: @status,
|
135
|
+
status_code: @status_code,
|
136
|
+
ms: @ms.to_i,
|
137
|
+
finished: @finished.iso8601,
|
138
|
+
checks: @@checks.inject({}) {|memo,(name,check)| memo[name] = check.as_hash; memo}
|
112
139
|
})
|
113
140
|
end
|
141
|
+
|
142
|
+
def as_json
|
143
|
+
as_hash
|
144
|
+
end
|
114
145
|
end
|
115
146
|
|
116
147
|
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
require 'kramdown'
|
2
|
+
|
3
|
+
module AppStatus
|
4
|
+
class CheckItem
|
5
|
+
attr_reader :name, :status, :status_code, :details, :ms
|
6
|
+
attr_accessor :proc, :description
|
7
|
+
|
8
|
+
def initialize(name)
|
9
|
+
@name = name
|
10
|
+
@proc = nil
|
11
|
+
@description = ''
|
12
|
+
|
13
|
+
reset
|
14
|
+
end
|
15
|
+
|
16
|
+
def reset
|
17
|
+
@result = {}
|
18
|
+
end
|
19
|
+
|
20
|
+
def valid_status?(status)
|
21
|
+
CheckCollection.valid_status_map.keys.include?(status)
|
22
|
+
end
|
23
|
+
|
24
|
+
def evaluate!
|
25
|
+
check_start = Time.now
|
26
|
+
status, details = @proc ? @proc.call : [:unknown, "Check is not configured."]
|
27
|
+
check_time = (Time.now - check_start) * 1000
|
28
|
+
|
29
|
+
status = status.to_sym if status
|
30
|
+
details = details.to_s if details
|
31
|
+
|
32
|
+
if ! valid_status?(status)
|
33
|
+
details = "Check returned invalid status '#{status}'. #{details}".strip
|
34
|
+
status = :unknown
|
35
|
+
end
|
36
|
+
|
37
|
+
@status = status
|
38
|
+
@status_code = CheckCollection.valid_status_map[@status]
|
39
|
+
@details = details
|
40
|
+
@ms = check_time.to_i
|
41
|
+
|
42
|
+
return @status_code
|
43
|
+
end
|
44
|
+
|
45
|
+
def as_hash
|
46
|
+
{
|
47
|
+
status: @status,
|
48
|
+
status_code: @status_code,
|
49
|
+
details: @details,
|
50
|
+
ms: @ms
|
51
|
+
}
|
52
|
+
end
|
53
|
+
|
54
|
+
def html_description
|
55
|
+
Kramdown::Document.new(@description).to_html
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
<div class="app_status">
|
2
|
+
|
3
|
+
<h1 class="<%= @checks.status %>">Current Status : <%= @checks.status %></h1>
|
4
|
+
|
5
|
+
<table>
|
6
|
+
<% @checks.each do |check| %>
|
7
|
+
<tr class="check <%= check.status %> <%= check.name %>" data-check-name="<%= check.name %>">
|
8
|
+
<td class="status"><%= check.status %></td>
|
9
|
+
<td class="name"><%= check.name %></td>
|
10
|
+
<td class="details"><%= check.details %></td>
|
11
|
+
<td class="ms"><%= check.ms %>ms</td>
|
12
|
+
</tr>
|
13
|
+
<% if check.description.present? -%>
|
14
|
+
<tr class="<%= check.name %> description" style="display: none;">
|
15
|
+
<td colspan="4">
|
16
|
+
<%= check.html_description.html_safe %>
|
17
|
+
</td>
|
18
|
+
</tr>
|
19
|
+
<% end %>
|
20
|
+
<% end %>
|
21
|
+
</table>
|
22
|
+
|
23
|
+
</div>
|
24
|
+
|
25
|
+
<script>
|
26
|
+
$(function() {
|
27
|
+
$('.app_status .check').click(function() {
|
28
|
+
var checkName = $(this).data('check-name');
|
29
|
+
$('.'+checkName+'.description').fadeToggle();
|
30
|
+
})
|
31
|
+
})
|
32
|
+
</script>
|
data/bin/kramdown
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
#
|
3
|
+
# This file was generated by Bundler.
|
4
|
+
#
|
5
|
+
# The application 'kramdown' is installed as part of a gem, and
|
6
|
+
# this file is here to facilitate running it.
|
7
|
+
#
|
8
|
+
|
9
|
+
require 'pathname'
|
10
|
+
ENV['BUNDLE_GEMFILE'] ||= File.expand_path("../../Gemfile",
|
11
|
+
Pathname.new(__FILE__).realpath)
|
12
|
+
|
13
|
+
require 'rubygems'
|
14
|
+
require 'bundler/setup'
|
15
|
+
|
16
|
+
load Gem.bin_path('kramdown', 'kramdown')
|
data/lib/app_status/version.rb
CHANGED
data/lib/app_status.rb
CHANGED
@@ -0,0 +1,28 @@
|
|
1
|
+
AppStatus::CheckCollection.configure do |c|
|
2
|
+
c.add_check('ok') do
|
3
|
+
[:ok, "looks good"]
|
4
|
+
end
|
5
|
+
c.add_description 'ok', <<-EOF
|
6
|
+
### OK Check
|
7
|
+
|
8
|
+
This check is always ok.
|
9
|
+
EOF
|
10
|
+
|
11
|
+
c.add_check('crit') do
|
12
|
+
[:critical, "fail"]
|
13
|
+
end
|
14
|
+
c.add_description 'crit', <<-EOF
|
15
|
+
### Critical Check
|
16
|
+
|
17
|
+
This check is always critical.
|
18
|
+
EOF
|
19
|
+
|
20
|
+
c.add_check('no_description') do
|
21
|
+
[:ok, 'check has no longer description']
|
22
|
+
end
|
23
|
+
|
24
|
+
c.add_check('no_details') do
|
25
|
+
:ok
|
26
|
+
end
|
27
|
+
c.add_description('no_details', 'check only returns a status.')
|
28
|
+
end
|
data/spec/dummy/config/routes.rb
CHANGED