pinglish 0.0.1 → 0.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/.gitignore +1 -0
- data/README.md +6 -12
- data/lib/pinglish.rb +56 -68
- data/lib/pinglish/check.rb +21 -0
- data/pinglish.gemspec +1 -1
- data/script/bootstrap +1 -1
- data/script/{tests → test} +0 -0
- data/test/pinglish_test.rb +66 -51
- metadata +4 -4
- data/Gemfile.lock +0 -21
data/.gitignore
CHANGED
data/README.md
CHANGED
@@ -29,16 +29,16 @@ conforms to the spec below.
|
|
29
29
|
serialization or other fundamental code fails.
|
30
30
|
|
31
31
|
0. The response __must__ contain a `"status"` key set either to `"ok"`
|
32
|
-
or `"
|
32
|
+
or `"failures"`.
|
33
33
|
|
34
34
|
0. The response __must__ contain a `"now"` key set to the current
|
35
35
|
server's time in seconds since epoch as a string.
|
36
36
|
|
37
|
-
0. If the `"status"` key is set to `"
|
37
|
+
0. If the `"status"` key is set to `"failures"`, the response __may__
|
38
38
|
contain a `"failures"` key set to an Array of string names
|
39
39
|
representing failed checks.
|
40
40
|
|
41
|
-
0. If the `"status"` key is set to `"
|
41
|
+
0. If the `"status"` key is set to `"failures"`, the response __may__
|
42
42
|
contain a `"timeouts"` key set to an Array of string names
|
43
43
|
representing checks that exceeded an implementation-specific
|
44
44
|
individual timeout.
|
@@ -56,7 +56,7 @@ conforms to the spec below.
|
|
56
56
|
// These two keys will always exist.
|
57
57
|
|
58
58
|
"now": "1359055102",
|
59
|
-
"status": "
|
59
|
+
"status": "failures",
|
60
60
|
|
61
61
|
// This key may only exist when a named check has failed.
|
62
62
|
|
@@ -75,8 +75,6 @@ conforms to the spec below.
|
|
75
75
|
|
76
76
|
## The Middleware
|
77
77
|
|
78
|
-
FIX: exegesis
|
79
|
-
|
80
78
|
```ruby
|
81
79
|
require "pinglish"
|
82
80
|
|
@@ -108,14 +106,10 @@ use Pinglish do |ping|
|
|
108
106
|
App.dawdle
|
109
107
|
end
|
110
108
|
|
111
|
-
# Signal check failure by raising an exception.
|
109
|
+
# Signal check failure by raising an exception. Any exception will do.
|
112
110
|
|
113
111
|
ping.check :fails do
|
114
|
-
|
112
|
+
raise "Everything's ruined."
|
115
113
|
end
|
116
114
|
end
|
117
115
|
```
|
118
|
-
|
119
|
-
## Contributing
|
120
|
-
|
121
|
-
FIX
|
data/lib/pinglish.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
require "json"
|
2
|
-
require "
|
2
|
+
require "pinglish/check"
|
3
3
|
require "rack/request"
|
4
|
+
require "timeout"
|
4
5
|
|
5
6
|
# This Rack middleware provides a "/_ping" endpoint for configurable
|
6
7
|
# system health checks. It's intended to be consumed by machines.
|
@@ -13,42 +14,21 @@ class Pinglish
|
|
13
14
|
"Content-Type" => "application/json; charset=UTF-8"
|
14
15
|
}
|
15
16
|
|
16
|
-
# The maximum amount of time for all checks. 29 seconds, to come in
|
17
|
-
# just under the 30 second limit of many monitoring services.
|
18
|
-
|
19
|
-
MAX_TOTAL_TIME = 29
|
20
|
-
|
21
|
-
# Represents a check, which is a behavior block with a name and
|
22
|
-
# timeout in seconds.
|
23
|
-
|
24
|
-
class Check
|
25
|
-
attr_reader :name
|
26
|
-
attr_reader :timeout
|
27
|
-
|
28
|
-
def initialize(name, options = nil, &block)
|
29
|
-
@name = name
|
30
|
-
@timeout = options && options[:timeout] || 1
|
31
|
-
@block = block
|
32
|
-
end
|
33
|
-
|
34
|
-
# Call this check's behavior, returning the result of the block.
|
35
|
-
|
36
|
-
def call(*args, &block)
|
37
|
-
@block.call *args, &block
|
38
|
-
end
|
39
|
-
end
|
40
|
-
|
41
17
|
# Raised when a check exceeds its timeout.
|
42
18
|
|
43
19
|
class TooLong < RuntimeError; end
|
44
20
|
|
45
21
|
# Create a new instance of the middleware wrapping `app`, with an
|
46
|
-
# optional
|
22
|
+
# optional `:path` (default: `"/_ping"`), `:max` timeout in seconds
|
23
|
+
# (default: `29`), and behavior `block`.
|
24
|
+
|
25
|
+
def initialize(app, options = nil, &block)
|
26
|
+
options ||= {}
|
47
27
|
|
48
|
-
def initialize(app, path = nil, &block)
|
49
28
|
@app = app
|
50
29
|
@checks = {}
|
51
|
-
@
|
30
|
+
@max = options[:max] || 29 # seconds
|
31
|
+
@path = options[:path] || "/_ping"
|
52
32
|
|
53
33
|
yield self if block_given?
|
54
34
|
end
|
@@ -61,63 +41,71 @@ class Pinglish
|
|
61
41
|
|
62
42
|
return @app.call env unless request.path == @path
|
63
43
|
|
64
|
-
|
65
|
-
|
44
|
+
groups = [].map(&:to_s) # FIX
|
45
|
+
|
46
|
+
begin
|
47
|
+
timeout @timeout do
|
48
|
+
results = {}
|
49
|
+
filtered = @checks.values
|
66
50
|
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
51
|
+
unless groups.empty?
|
52
|
+
filtered = filtered.select { |c| groups.include? c.group.to_s }
|
53
|
+
end
|
54
|
+
|
55
|
+
filtered.each do |check|
|
56
|
+
begin
|
57
|
+
timeout check.timeout do
|
58
|
+
results[check.name] = check.call
|
59
|
+
end
|
60
|
+
rescue StandardError => e
|
61
|
+
results[check.name] = e
|
71
62
|
end
|
72
|
-
rescue StandardError => e
|
73
|
-
results[check.name] = e
|
74
63
|
end
|
75
|
-
end
|
76
64
|
|
77
|
-
|
78
|
-
|
79
|
-
|
65
|
+
failed = results.values.any? { |v| failure? v }
|
66
|
+
http_status = failed ? 503 : 200
|
67
|
+
text_status = failed ? "failures" : "ok"
|
80
68
|
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
69
|
+
data = {
|
70
|
+
:now => Time.now.to_i.to_s,
|
71
|
+
:status => text_status
|
72
|
+
}
|
85
73
|
|
86
|
-
|
74
|
+
results.each do |name, value|
|
87
75
|
|
88
|
-
|
89
|
-
|
76
|
+
# The unnnamed/default check doesn't contribute data.
|
77
|
+
next if name.nil?
|
90
78
|
|
91
|
-
|
79
|
+
if failure? value
|
92
80
|
|
93
|
-
|
94
|
-
|
95
|
-
|
81
|
+
# If a check fails its name is added to a `failures` array.
|
82
|
+
# If the check failed because it timed out, its name is
|
83
|
+
# added to a `timeouts` array instead.
|
96
84
|
|
97
|
-
|
98
|
-
|
85
|
+
key = timeout?(value) ? :timeouts : :failures
|
86
|
+
(data[key] ||= []) << name
|
99
87
|
|
100
|
-
|
88
|
+
elsif value
|
101
89
|
|
102
|
-
|
103
|
-
|
90
|
+
# If the check passed and returned a value, the stringified
|
91
|
+
# version of the value is returned under the `name` key.
|
104
92
|
|
105
|
-
|
93
|
+
data[name] = value.to_s
|
94
|
+
end
|
106
95
|
end
|
107
|
-
end
|
108
96
|
|
109
|
-
|
110
|
-
|
97
|
+
[http_status, HEADERS, [JSON.generate(data)]]
|
98
|
+
end
|
111
99
|
|
112
|
-
|
100
|
+
rescue Exception => ex
|
113
101
|
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
# and interpolate the current epoch time.
|
102
|
+
# Something catastrophic happened. We can't even run the checks
|
103
|
+
# and render a JSON response. Fall back on a pre-rendered string
|
104
|
+
# and interpolate the current epoch time.
|
118
105
|
|
119
|
-
|
120
|
-
|
106
|
+
now = Time.now.to_i.to_s
|
107
|
+
[500, HEADERS, ['{"status":"failures","now":"' + now + '"}']]
|
108
|
+
end
|
121
109
|
end
|
122
110
|
|
123
111
|
# Add a new check with optional `name`. A `:timeout` option can be
|
@@ -0,0 +1,21 @@
|
|
1
|
+
class Pinglish
|
2
|
+
class Check
|
3
|
+
attr_reader :group
|
4
|
+
attr_reader :name
|
5
|
+
attr_reader :timeout
|
6
|
+
|
7
|
+
def initialize(name, options = nil, &block)
|
8
|
+
options ||= {}
|
9
|
+
@group = options[:group]
|
10
|
+
@name = name
|
11
|
+
@timeout = options[:timeout] || 1
|
12
|
+
@block = block
|
13
|
+
end
|
14
|
+
|
15
|
+
# Call this check's behavior, returning the result of the block.
|
16
|
+
|
17
|
+
def call(*args, &block)
|
18
|
+
@block.call *args, &block
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
data/pinglish.gemspec
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
Gem::Specification.new do |gem|
|
4
4
|
gem.name = "pinglish"
|
5
|
-
gem.version = "0.0
|
5
|
+
gem.version = "0.1.0"
|
6
6
|
gem.authors = ["John Barnette", "Will Farrington"]
|
7
7
|
gem.email = ["jbarnette@github.com", "wfarr@github.com"]
|
8
8
|
gem.description = "A simple Rack middleware for checking app health."
|
data/script/bootstrap
CHANGED
data/script/{tests → test}
RENAMED
File without changes
|
data/test/pinglish_test.rb
CHANGED
@@ -2,7 +2,7 @@ require "helper"
|
|
2
2
|
require "rack/test"
|
3
3
|
|
4
4
|
class PinglishTest < MiniTest::Unit::TestCase
|
5
|
-
FakeApp = lambda { |env| [
|
5
|
+
FakeApp = lambda { |env| [200, {}, ["fake"]] }
|
6
6
|
|
7
7
|
def build_app(*args, &block)
|
8
8
|
Rack::Builder.new do |builder|
|
@@ -11,18 +11,40 @@ class PinglishTest < MiniTest::Unit::TestCase
|
|
11
11
|
end
|
12
12
|
end
|
13
13
|
|
14
|
+
def test_with_non_matching_request_path
|
15
|
+
app = build_app
|
16
|
+
|
17
|
+
session = Rack::Test::Session.new(app)
|
18
|
+
session.get "/something"
|
19
|
+
assert_equal 200, session.last_response.status
|
20
|
+
assert_equal "fake", session.last_response.body
|
21
|
+
end
|
22
|
+
|
23
|
+
def test_with_non_matching_request_path_and_exception
|
24
|
+
app = Rack::Builder.new do |builder|
|
25
|
+
builder.use Pinglish
|
26
|
+
builder.run lambda { |env| raise "boom" }
|
27
|
+
end
|
28
|
+
|
29
|
+
session = Rack::Test::Session.new(app)
|
30
|
+
|
31
|
+
assert_raises RuntimeError do
|
32
|
+
session.get "/something"
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
14
36
|
def test_with_defaults
|
15
37
|
app = build_app
|
16
38
|
|
17
39
|
session = Rack::Test::Session.new(app)
|
18
|
-
session.get
|
40
|
+
session.get "/_ping"
|
19
41
|
assert_equal 200, session.last_response.status
|
20
|
-
assert_equal
|
42
|
+
assert_equal "application/json; charset=UTF-8",
|
21
43
|
session.last_response.content_type
|
22
44
|
|
23
45
|
json = JSON.load(session.last_response.body)
|
24
|
-
assert json.key?(
|
25
|
-
assert_equal
|
46
|
+
assert json.key?("now")
|
47
|
+
assert_equal "ok", json["status"]
|
26
48
|
end
|
27
49
|
|
28
50
|
def test_with_good_check
|
@@ -32,16 +54,16 @@ class PinglishTest < MiniTest::Unit::TestCase
|
|
32
54
|
end
|
33
55
|
|
34
56
|
session = Rack::Test::Session.new(app)
|
35
|
-
session.get
|
57
|
+
session.get "/_ping"
|
36
58
|
|
37
|
-
assert_equal
|
59
|
+
assert_equal "application/json; charset=UTF-8",
|
38
60
|
session.last_response.content_type
|
39
61
|
|
40
62
|
json = JSON.load(session.last_response.body)
|
41
|
-
assert json.key?(
|
42
|
-
assert_equal
|
43
|
-
assert_equal
|
44
|
-
assert_equal
|
63
|
+
assert json.key?("now")
|
64
|
+
assert_equal "ok", json["status"]
|
65
|
+
assert_equal "up_and_at_em", json["db"]
|
66
|
+
assert_equal "pushin_and_poppin", json["queue"]
|
45
67
|
end
|
46
68
|
|
47
69
|
def test_with_unnamed_check
|
@@ -50,32 +72,32 @@ class PinglishTest < MiniTest::Unit::TestCase
|
|
50
72
|
end
|
51
73
|
|
52
74
|
session = Rack::Test::Session.new(app)
|
53
|
-
session.get
|
75
|
+
session.get "/_ping"
|
54
76
|
|
55
|
-
assert_equal
|
77
|
+
assert_equal "application/json; charset=UTF-8",
|
56
78
|
session.last_response.content_type
|
57
79
|
|
58
80
|
json = JSON.load(session.last_response.body)
|
59
|
-
assert json.key?(
|
60
|
-
assert_equal
|
81
|
+
assert json.key?("now")
|
82
|
+
assert_equal "ok", json["status"]
|
61
83
|
end
|
62
84
|
|
63
85
|
def test_with_check_that_raises
|
64
86
|
app = build_app do |ping|
|
65
87
|
ping.check(:db) { :ok }
|
66
|
-
ping.check(:raise) { raise
|
88
|
+
ping.check(:raise) { raise "nooooope" }
|
67
89
|
end
|
68
90
|
|
69
91
|
session = Rack::Test::Session.new(app)
|
70
|
-
session.get
|
92
|
+
session.get "/_ping"
|
71
93
|
|
72
94
|
assert_equal 503, session.last_response.status
|
73
|
-
assert_equal
|
95
|
+
assert_equal "application/json; charset=UTF-8",
|
74
96
|
session.last_response.content_type
|
75
97
|
|
76
98
|
json = JSON.load(session.last_response.body)
|
77
|
-
assert json.key?(
|
78
|
-
assert_equal
|
99
|
+
assert json.key?("now")
|
100
|
+
assert_equal "failures", json["status"]
|
79
101
|
end
|
80
102
|
|
81
103
|
def test_with_check_that_returns_false
|
@@ -85,16 +107,16 @@ class PinglishTest < MiniTest::Unit::TestCase
|
|
85
107
|
end
|
86
108
|
|
87
109
|
session = Rack::Test::Session.new(app)
|
88
|
-
session.get
|
110
|
+
session.get "/_ping"
|
89
111
|
|
90
112
|
assert_equal 503, session.last_response.status
|
91
|
-
assert_equal
|
113
|
+
assert_equal "application/json; charset=UTF-8",
|
92
114
|
session.last_response.content_type
|
93
115
|
|
94
116
|
json = JSON.load(session.last_response.body)
|
95
|
-
assert json.key?(
|
96
|
-
assert_equal
|
97
|
-
assert_equal [
|
117
|
+
assert json.key?("now")
|
118
|
+
assert_equal "failures", json["status"]
|
119
|
+
assert_equal ["fail"], json["failures"]
|
98
120
|
end
|
99
121
|
|
100
122
|
def test_with_check_that_times_out
|
@@ -104,30 +126,30 @@ class PinglishTest < MiniTest::Unit::TestCase
|
|
104
126
|
end
|
105
127
|
|
106
128
|
session = Rack::Test::Session.new(app)
|
107
|
-
session.get
|
129
|
+
session.get "/_ping"
|
108
130
|
|
109
131
|
assert_equal 503, session.last_response.status
|
110
|
-
assert_equal
|
132
|
+
assert_equal "application/json; charset=UTF-8",
|
111
133
|
session.last_response.content_type
|
112
134
|
|
113
135
|
json = JSON.load(session.last_response.body)
|
114
|
-
assert json.key?(
|
115
|
-
assert_equal
|
116
|
-
assert_equal [
|
136
|
+
assert json.key?("now")
|
137
|
+
assert_equal "failures", json["status"]
|
138
|
+
assert_equal ["long"], json["timeouts"]
|
117
139
|
end
|
118
140
|
|
119
141
|
def test_with_custom_path
|
120
|
-
app = build_app("/_piiiiing")
|
142
|
+
app = build_app(:path => "/_piiiiing")
|
121
143
|
|
122
144
|
session = Rack::Test::Session.new(app)
|
123
|
-
session.get
|
145
|
+
session.get "/_piiiiing"
|
124
146
|
assert_equal 200, session.last_response.status
|
125
|
-
assert_equal
|
147
|
+
assert_equal "application/json; charset=UTF-8",
|
126
148
|
session.last_response.content_type
|
127
149
|
|
128
150
|
json = JSON.load(session.last_response.body)
|
129
|
-
assert json.key?(
|
130
|
-
assert_equal
|
151
|
+
assert json.key?("now")
|
152
|
+
assert_equal "ok", json["status"]
|
131
153
|
end
|
132
154
|
|
133
155
|
def test_check_without_name
|
@@ -146,32 +168,25 @@ class PinglishTest < MiniTest::Unit::TestCase
|
|
146
168
|
def test_failure_boolean
|
147
169
|
pinglish = Pinglish.new(FakeApp)
|
148
170
|
|
149
|
-
assert pinglish.failure?(Exception.new)
|
150
|
-
|
171
|
+
assert pinglish.failure?(Exception.new)
|
172
|
+
assert pinglish.failure?(false)
|
151
173
|
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
assert !pinglish.failure?(true),
|
156
|
-
"Expected failure with true value to be false"
|
157
|
-
|
158
|
-
assert !pinglish.failure?(:ok),
|
159
|
-
"Expected failure with non-false and non-exception to be false"
|
174
|
+
refute pinglish.failure?(true)
|
175
|
+
refute pinglish.failure?(:ok)
|
160
176
|
end
|
161
177
|
|
162
178
|
def test_timeout
|
163
179
|
pinglish = Pinglish.new(FakeApp)
|
164
|
-
|
180
|
+
|
181
|
+
assert_raises Pinglish::TooLong do
|
165
182
|
pinglish.timeout(0.001) { sleep 0.003 }
|
166
|
-
assert false, "Timeout did not happen, but should have."
|
167
|
-
rescue Pinglish::TooLong => e
|
168
|
-
# all good
|
169
183
|
end
|
170
184
|
end
|
171
185
|
|
172
186
|
def test_timeout_boolean
|
173
187
|
pinglish = Pinglish.new(FakeApp)
|
174
|
-
|
175
|
-
|
188
|
+
|
189
|
+
assert pinglish.timeout?(Pinglish::TooLong.new)
|
190
|
+
refute pinglish.timeout?(Exception.new)
|
176
191
|
end
|
177
192
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: pinglish
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
4
|
+
version: 0.1.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -10,7 +10,7 @@ authors:
|
|
10
10
|
autorequire:
|
11
11
|
bindir: bin
|
12
12
|
cert_chain: []
|
13
|
-
date: 2013-
|
13
|
+
date: 2013-04-18 00:00:00.000000000 Z
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
16
|
name: rack
|
@@ -70,14 +70,14 @@ extra_rdoc_files: []
|
|
70
70
|
files:
|
71
71
|
- .gitignore
|
72
72
|
- Gemfile
|
73
|
-
- Gemfile.lock
|
74
73
|
- LICENSE
|
75
74
|
- README.md
|
76
75
|
- lib/pinglish.rb
|
76
|
+
- lib/pinglish/check.rb
|
77
77
|
- pinglish.gemspec
|
78
78
|
- script/bootstrap
|
79
79
|
- script/release
|
80
|
-
- script/
|
80
|
+
- script/test
|
81
81
|
- test/check_test.rb
|
82
82
|
- test/helper.rb
|
83
83
|
- test/pinglish_test.rb
|
data/Gemfile.lock
DELETED
@@ -1,21 +0,0 @@
|
|
1
|
-
PATH
|
2
|
-
remote: .
|
3
|
-
specs:
|
4
|
-
pinglish (0.0.0)
|
5
|
-
rack
|
6
|
-
|
7
|
-
GEM
|
8
|
-
remote: https://rubygems.org/
|
9
|
-
specs:
|
10
|
-
minitest (4.5.0)
|
11
|
-
rack (1.5.0)
|
12
|
-
rack-test (0.6.2)
|
13
|
-
rack (>= 1.0)
|
14
|
-
|
15
|
-
PLATFORMS
|
16
|
-
ruby
|
17
|
-
|
18
|
-
DEPENDENCIES
|
19
|
-
minitest (~> 4.5)
|
20
|
-
pinglish!
|
21
|
-
rack-test (~> 0.6)
|