app_status 1.0.0 → 1.1.0
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/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