resque-exceptional 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/HISTORY.md +6 -0
- data/README.md +20 -4
- data/Rakefile +2 -2
- data/lib/resque/failure/exceptional.rb +31 -14
- data/test/exceptional_test.rb +149 -15
- data/test/test_helper.rb +18 -8
- metadata +21 -7
data/HISTORY.md
CHANGED
data/README.md
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
resque-exceptional
|
2
2
|
==================
|
3
3
|
|
4
|
-
resque-exceptional provides a Resque failure backend that sends exceptions
|
5
|
-
raised by jobs to
|
4
|
+
resque-exceptional provides a [Resque][re] failure backend that sends exceptions
|
5
|
+
raised by jobs to [getexceptional.com][ge]
|
6
6
|
|
7
7
|
Install & Quick Start
|
8
8
|
---------------------
|
9
9
|
|
10
|
-
Before you jump into code, you'll need a
|
10
|
+
Before you jump into code, you'll need a getexceptional.com account.
|
11
11
|
|
12
12
|
To install:
|
13
13
|
|
@@ -64,6 +64,18 @@ Configuration Options
|
|
64
64
|
* `http_open_timeout` - timeout in seconds to establish the connection. (default: `2`)
|
65
65
|
* `http_read_timeout` - timeout in seconds to wait for a reply. (default: `5`)
|
66
66
|
|
67
|
+
Screenshots
|
68
|
+
-----------
|
69
|
+
|
70
|
+
Below are some screenshots of the getexceptional.com web interface, showing
|
71
|
+
Resque exceptions.
|
72
|
+
|
73
|
+
**App Overview**
|
74
|
+

|
75
|
+
|
76
|
+
**Detailed Information**
|
77
|
+

|
78
|
+
|
67
79
|
Note on Patches/Pull Requests
|
68
80
|
-----------------------------
|
69
81
|
|
@@ -79,4 +91,8 @@ Note on Patches/Pull Requests
|
|
79
91
|
Author
|
80
92
|
------
|
81
93
|
|
82
|
-
Luke Antins :: http://lividpenguin.com :: @lantins
|
94
|
+
Luke Antins :: [http://lividpenguin.com][lp] :: @lantins
|
95
|
+
|
96
|
+
[re]: http://github.com/defunkt/resque
|
97
|
+
[lp]: http://lividpenguin.com
|
98
|
+
[ge]: http://getexceptional.com
|
data/Rakefile
CHANGED
@@ -18,8 +18,8 @@ end
|
|
18
18
|
# docs task.
|
19
19
|
YARD::Rake::YardocTask.new :yardoc do |t|
|
20
20
|
t.files = ['lib/**/*.rb']
|
21
|
-
t.options = ['--output-dir',
|
22
|
-
'--files', 'LICENSE
|
21
|
+
t.options = ['--output-dir', 'doc/',
|
22
|
+
'--files', 'LICENSE,HISTORY.md',
|
23
23
|
'--readme', 'README.md',
|
24
24
|
'--title', 'resque-exceptional documentation']
|
25
25
|
end
|
@@ -1,26 +1,30 @@
|
|
1
1
|
module Resque
|
2
2
|
module Failure
|
3
|
-
|
4
|
-
# A resque failure backend that sends exception data to getexceptional.com
|
3
|
+
# A Resque failure backend that sends exception data to getexceptional.com
|
5
4
|
class Exceptional < Base
|
5
|
+
Version = '0.1.0' # Failure backend version number.
|
6
|
+
|
6
7
|
# Raised if the api_key is not set.
|
7
8
|
class APIKeyError < StandardError
|
8
9
|
end
|
9
10
|
|
10
|
-
# Our version number =)
|
11
|
-
Version = '0.0.1'
|
12
|
-
|
13
11
|
class << self
|
14
|
-
#
|
15
|
-
attr_accessor :
|
16
|
-
# HTTP
|
12
|
+
attr_accessor :api_key # your getexceptional api key.
|
13
|
+
attr_accessor :use_ssl # enable/disable SSL.
|
14
|
+
# HTTP proxy option
|
17
15
|
attr_accessor :proxy_host, :proxy_port, :proxy_user, :proxy_pass
|
18
|
-
# HTTP
|
19
|
-
attr_accessor :
|
16
|
+
# HTTP client option
|
17
|
+
attr_accessor :http_open_timeout, :http_read_timeout
|
20
18
|
end
|
21
19
|
|
22
20
|
# Configures the failure backend. At a minimum you will need to set
|
23
21
|
# an api_key.
|
22
|
+
#
|
23
|
+
# @example Setting your API Key and enabling SSL:
|
24
|
+
# Resque::Failure::Exceptional.configure do |config|
|
25
|
+
# config.api_key = '505f2518c41866bb0be7ba434bb2b079'
|
26
|
+
# config.use_ssl = true
|
27
|
+
# end
|
24
28
|
def self.configure
|
25
29
|
yield self
|
26
30
|
end
|
@@ -60,7 +64,11 @@ module Resque
|
|
60
64
|
#
|
61
65
|
# @return [Hash] http headers.
|
62
66
|
def http_headers
|
63
|
-
{
|
67
|
+
{
|
68
|
+
'Content-Type' => 'application/json',
|
69
|
+
'Accept' => 'application/json',
|
70
|
+
'User-Agent' => "resque-exceptional/#{Version}"
|
71
|
+
}
|
64
72
|
end
|
65
73
|
|
66
74
|
# Returns the compressed request data.
|
@@ -82,6 +90,9 @@ module Resque
|
|
82
90
|
#
|
83
91
|
# nb. this isn't documented in the public api... not sure if we should
|
84
92
|
# use it or not...
|
93
|
+
#
|
94
|
+
# @return [String] md5sum of the backtrace.
|
95
|
+
# @return [nil] if we don't have a backtrace available.
|
85
96
|
def uniqueness_hash
|
86
97
|
return nil if (exception.backtrace.nil? || exception.backtrace.empty?)
|
87
98
|
Digest::MD5.hexdigest(exception.backtrace.join)
|
@@ -104,17 +115,24 @@ module Resque
|
|
104
115
|
http
|
105
116
|
end
|
106
117
|
|
107
|
-
# Helper method to return the correct HTTP port number
|
118
|
+
# Helper method to return the correct HTTP port number, depending on
|
119
|
+
# if were using SSL or not.
|
120
|
+
#
|
121
|
+
# @return [Fixnum] HTTP port number.
|
108
122
|
def http_port
|
109
123
|
use_ssl? ? 443 : 80
|
110
124
|
end
|
111
125
|
|
112
126
|
# Helper method to check if were using SSL or not.
|
127
|
+
#
|
128
|
+
# @return [Boolean] true if ssl is enabled.
|
113
129
|
def use_ssl?
|
114
130
|
self.class.use_ssl || false
|
115
131
|
end
|
116
132
|
|
117
133
|
# Adds a prefix to log messages.
|
134
|
+
#
|
135
|
+
# @param [String] msg your log message.
|
118
136
|
def log(msg)
|
119
137
|
super("resque-exception - #{msg}")
|
120
138
|
end
|
@@ -154,6 +172,5 @@ module Resque
|
|
154
172
|
end
|
155
173
|
|
156
174
|
end
|
157
|
-
|
158
175
|
end
|
159
|
-
end
|
176
|
+
end
|
data/test/exceptional_test.rb
CHANGED
@@ -8,6 +8,8 @@ class ExceptionalTest < Test::Unit::TestCase
|
|
8
8
|
@queue = 'test_queue'
|
9
9
|
@payload = { 'class' => 'TestJob', 'args' => ['foo', 'bar'] }
|
10
10
|
@failure = Resque::Failure::Exceptional.new(@exception, @worker, @queue, @payload)
|
11
|
+
WebMock.disable_net_connect!
|
12
|
+
WebMock.reset_webmock
|
11
13
|
end
|
12
14
|
|
13
15
|
# test we can build a hash to send to the api.
|
@@ -90,29 +92,161 @@ class ExceptionalTest < Test::Unit::TestCase
|
|
90
92
|
assert_equal true, @failure.respond_to?(:save)
|
91
93
|
end
|
92
94
|
|
93
|
-
#
|
94
|
-
def
|
95
|
-
|
95
|
+
# test save success.
|
96
|
+
def test_save_success
|
97
|
+
with_api_key '27810b263f0e11eef2f1d29be75d2f39' do
|
98
|
+
stub_request(:post, /.*api.getexceptional.com.*/)
|
99
|
+
@failure.save
|
100
|
+
assert_requested(:post, /.*api.getexceptional.com.*/)
|
101
|
+
assert_match /^(resque-exception).*(success).*$/, @worker.log_history.first
|
102
|
+
end
|
96
103
|
end
|
97
104
|
|
98
|
-
#
|
99
|
-
def
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
105
|
+
# test save fail.
|
106
|
+
def test_save_fail
|
107
|
+
with_api_key '27810b263f0e11eef2f1d29be75d2f39' do
|
108
|
+
stub_request(:post, /.*api.getexceptional.com.*/).to_return(:status => [500, 'Internal Server Error'])
|
109
|
+
@failure.save
|
110
|
+
assert_requested(:post, /.*api.getexceptional.com.*/)
|
111
|
+
assert_match /^(resque-exception).*(fail).*(500).*$/, @worker.log_history.first
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
# we need a Net::HTTP client setup to send the data.
|
116
|
+
def test_http_client
|
117
|
+
assert_kind_of Net::HTTP, @failure.http_client
|
106
118
|
end
|
107
119
|
|
108
120
|
# we should fail if the api_key is not set.
|
109
121
|
def test_fail_if_api_key_nil
|
110
122
|
# should already be nil, but lets just be sure...
|
111
|
-
|
112
|
-
|
113
|
-
|
123
|
+
with_api_key nil do
|
124
|
+
@failure.save
|
125
|
+
assert_match /^(resque-exception).*(error).*(api_key).*$/, @worker.log_history.first
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
# test we prefix our log messages.
|
130
|
+
def test_log_adds_prefix
|
131
|
+
@failure.log('test message')
|
132
|
+
@failure.log('123 another msg bud!')
|
133
|
+
assert_match /^resque-exception - .*/, @worker.log_history.first
|
134
|
+
assert_match /^resque-exception - .*/, @worker.log_history.last
|
135
|
+
end
|
136
|
+
|
137
|
+
# test our `#use_ssl?` and `#http_port` helper methods.
|
138
|
+
def test_helper_methods
|
139
|
+
# check defaults
|
140
|
+
assert_equal false, @failure.use_ssl?, 'use_ssl? should default to false.'
|
141
|
+
assert_equal 80, @failure.http_port, 'http_port should default to 80.'
|
142
|
+
|
143
|
+
# enable ssl.
|
144
|
+
Resque::Failure::Exceptional.configure { |c| c.use_ssl = true }
|
145
|
+
assert_equal true, @failure.use_ssl?, 'use_ssl? should now be true'
|
146
|
+
assert_equal 443, @failure.http_port, 'http_port should now be 443.'
|
147
|
+
|
148
|
+
# put the config back.
|
149
|
+
Resque::Failure::Exceptional.configure { |c| c.use_ssl = false }
|
150
|
+
end
|
151
|
+
|
152
|
+
# returns nil if the backtrace is empty.
|
153
|
+
def test_uniqueness_hash_returns_nil_when_empty_backtrace
|
154
|
+
mock(@failure.exception).backtrace.times(any_times) { Array.new }
|
155
|
+
assert_equal nil, @failure.uniqueness_hash
|
156
|
+
end
|
157
|
+
|
158
|
+
# returns nil if the backtrace is empty.
|
159
|
+
def test_uniqueness_hash_returns_nil_when_nil_backtrace
|
160
|
+
mock(@failure.exception).backtrace.times(any_times) { nil }
|
161
|
+
assert_equal nil, @failure.uniqueness_hash
|
162
|
+
end
|
163
|
+
|
164
|
+
# uniqueness_hash builds a md5sum.
|
165
|
+
def test_uniqueness_hash_returns_a_md5_of_the_backtrace
|
166
|
+
# fake backtrace.
|
167
|
+
fake_backtrace = ['fake', 'backtrace', 'that_wont_change']
|
168
|
+
mock(@failure.exception).backtrace.times(any_times) { fake_backtrace }
|
169
|
+
|
170
|
+
assert_equal '27810b263f0e11eef2f1d29be75d2f39', @failure.uniqueness_hash
|
171
|
+
end
|
172
|
+
|
173
|
+
# return the HTTP path and query string with uniqueness hash.
|
174
|
+
def test_http_path_query
|
175
|
+
# fake backtrace.
|
176
|
+
fake_backtrace = ['fake', 'backtrace', 'that_wont_change']
|
177
|
+
mock(@failure.exception).backtrace.times(any_times) { fake_backtrace }
|
178
|
+
|
179
|
+
with_api_key '27810b263f0e11eef2f1d29be75d2f39' do
|
180
|
+
path, query = *@failure.http_path_query.split('?', 2)
|
181
|
+
assert_match /^api_key=27810b263f0e11eef2f1d29be75d2f39/, query, 'query should have api_key.'
|
182
|
+
assert_match /protocol_version=\d{1}/, query, 'query should have protocol_version.'
|
183
|
+
assert_match /hash=27810b263f0e11eef2f1d29be75d2f39$/, query, 'query should have a uniqueness hash.'
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
# build a path & query without a uniqueness hash.
|
188
|
+
def test_http_path_query_without_uniqueness_hash
|
189
|
+
# fake empty backtrace.
|
190
|
+
mock(@failure.exception).backtrace.times(any_times) { Array.new }
|
191
|
+
|
192
|
+
with_api_key '27810b263f0e11eef2f1d29be75d2f39' do
|
193
|
+
path, query = *@failure.http_path_query.split('?', 2)
|
194
|
+
assert_match /^api_key=27810b263f0e11eef2f1d29be75d2f39/, query, 'query should have api_key.'
|
195
|
+
assert_match /protocol_version=\d{1}$/, query, 'query should have protocol_version.'
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
199
|
+
# raise exception if api key is not set.
|
200
|
+
def test_http_path_query_without_api_key_raises_exception
|
201
|
+
assert_raise Resque::Failure::Exceptional::APIKeyError, 'should raise APIKeyError if api key is not set' do
|
202
|
+
@failure.http_path_query
|
203
|
+
end
|
204
|
+
end
|
205
|
+
|
206
|
+
# should return http response if successful.
|
207
|
+
def test_http_post_request
|
208
|
+
with_api_key '27810b263f0e11eef2f1d29be75d2f39' do
|
209
|
+
stub_request(:post, /.*api.getexceptional.com.*/)
|
210
|
+
|
211
|
+
response = @failure.http_post_request
|
212
|
+
assert_requested(:post, /.*api.getexceptional.com.*/)
|
213
|
+
assert_equal '200', response.code, 'should be a successful http request'
|
214
|
+
end
|
114
215
|
end
|
115
216
|
|
116
217
|
# should handle exceptions raised during the HTTP Post.
|
117
|
-
|
218
|
+
def test_http_post_request_handles_exceptions_and_returns_nil
|
219
|
+
response = @failure.http_post_request
|
220
|
+
assert_equal nil, @failure.http_post_request, 'should be nil, APIKeyError should have been caught.'
|
221
|
+
|
222
|
+
with_api_key '27810b263f0e11eef2f1d29be75d2f39' do
|
223
|
+
WebMock.reset_webmock
|
224
|
+
stub_request(:post, /.*api.getexceptional.com.*/).to_raise(StandardError)
|
225
|
+
assert_equal nil, @failure.http_post_request, 'should be nil, StandardError should have been caught.'
|
226
|
+
assert_requested(:post, /.*api.getexceptional.com.*/)
|
227
|
+
end
|
228
|
+
end
|
229
|
+
|
230
|
+
# make sure we catch timeout errors.
|
231
|
+
def test_http_post_request_timeout
|
232
|
+
with_api_key '27810b263f0e11eef2f1d29be75d2f39' do
|
233
|
+
stub_request(:post, /.*api.getexceptional.com.*/).to_timeout
|
234
|
+
assert_equal nil, @failure.http_post_request, 'should be nil, TimeoutError should have been caught.'
|
235
|
+
assert_requested(:post, /.*api.getexceptional.com.*/)
|
236
|
+
end
|
237
|
+
end
|
238
|
+
|
239
|
+
# perform a test with the real api.
|
240
|
+
def test_live_fire_with_real_api!
|
241
|
+
unless ENV['EXCEPTIONAL_API_KEY']
|
242
|
+
omit 'Test with the REAL API. Example: `EXCEPTIONAL_API_KEY=27810b263f0e11eef2f1d29be75d2f39 rake test`'
|
243
|
+
end
|
244
|
+
|
245
|
+
with_api_key ENV['EXCEPTIONAL_API_KEY'] do
|
246
|
+
WebMock.allow_net_connect!
|
247
|
+
@failure.save
|
248
|
+
assert_match /^(resque-exception).*(success).*$/, @worker.log_history.first
|
249
|
+
end
|
250
|
+
end
|
251
|
+
|
118
252
|
end
|
data/test/test_helper.rb
CHANGED
@@ -1,22 +1,32 @@
|
|
1
|
+
require 'simplecov'
|
2
|
+
SimpleCov.start do
|
3
|
+
add_filter '/test/'
|
4
|
+
end
|
5
|
+
|
1
6
|
dir = File.dirname(File.expand_path(__FILE__))
|
2
7
|
$LOAD_PATH.unshift dir + '/../lib'
|
3
8
|
$TESTING = true
|
4
9
|
|
5
10
|
require 'test/unit'
|
6
11
|
require 'rubygems'
|
7
|
-
require 'simplecov'
|
8
12
|
require 'rr'
|
13
|
+
require 'webmock'
|
14
|
+
require 'webmock/test_unit'
|
9
15
|
|
10
|
-
|
11
|
-
|
12
|
-
end
|
16
|
+
# require our failure backend to test.
|
17
|
+
require 'resque-exceptional'
|
13
18
|
|
14
19
|
class Test::Unit::TestCase
|
15
20
|
include RR::Adapters::TestUnit
|
16
|
-
|
21
|
+
include WebMock::API
|
17
22
|
|
18
|
-
#
|
19
|
-
|
23
|
+
# periodicly set the api key.
|
24
|
+
def with_api_key(key, &block)
|
25
|
+
Resque::Failure::Exceptional.api_key = key
|
26
|
+
yield
|
27
|
+
Resque::Failure::Exceptional.api_key = nil
|
28
|
+
end
|
29
|
+
end
|
20
30
|
|
21
31
|
# fake worker.
|
22
32
|
class FakeWorker
|
@@ -28,7 +38,7 @@ class FakeWorker
|
|
28
38
|
|
29
39
|
def log(msg)
|
30
40
|
@log_history << msg
|
31
|
-
p msg
|
41
|
+
p msg if ENV['VERBOSE']
|
32
42
|
end
|
33
43
|
|
34
44
|
def to_s
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: resque-exceptional
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 27
|
5
5
|
prerelease: false
|
6
6
|
segments:
|
7
7
|
- 0
|
8
|
-
- 0
|
9
8
|
- 1
|
10
|
-
|
9
|
+
- 0
|
10
|
+
version: 0.1.0
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- Luke Antins
|
@@ -15,7 +15,7 @@ autorequire:
|
|
15
15
|
bindir: bin
|
16
16
|
cert_chain: []
|
17
17
|
|
18
|
-
date: 2010-10-
|
18
|
+
date: 2010-10-16 00:00:00 +01:00
|
19
19
|
default_executable:
|
20
20
|
dependencies:
|
21
21
|
- !ruby/object:Gem::Dependency
|
@@ -65,7 +65,7 @@ dependencies:
|
|
65
65
|
type: :development
|
66
66
|
version_requirements: *id003
|
67
67
|
- !ruby/object:Gem::Dependency
|
68
|
-
name:
|
68
|
+
name: webmock
|
69
69
|
prerelease: false
|
70
70
|
requirement: &id004 !ruby/object:Gem::Requirement
|
71
71
|
none: false
|
@@ -79,9 +79,23 @@ dependencies:
|
|
79
79
|
type: :development
|
80
80
|
version_requirements: *id004
|
81
81
|
- !ruby/object:Gem::Dependency
|
82
|
-
name:
|
82
|
+
name: yard
|
83
83
|
prerelease: false
|
84
84
|
requirement: &id005 !ruby/object:Gem::Requirement
|
85
|
+
none: false
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
hash: 3
|
90
|
+
segments:
|
91
|
+
- 0
|
92
|
+
version: "0"
|
93
|
+
type: :development
|
94
|
+
version_requirements: *id005
|
95
|
+
- !ruby/object:Gem::Dependency
|
96
|
+
name: simplecov
|
97
|
+
prerelease: false
|
98
|
+
requirement: &id006 !ruby/object:Gem::Requirement
|
85
99
|
none: false
|
86
100
|
requirements:
|
87
101
|
- - ">="
|
@@ -93,7 +107,7 @@ dependencies:
|
|
93
107
|
- 0
|
94
108
|
version: 0.3.0
|
95
109
|
type: :development
|
96
|
-
version_requirements: *
|
110
|
+
version_requirements: *id006
|
97
111
|
description: " resque-exceptional provides a Resque failure backend that sends exceptions\n raised by jobs to getexceptional.com.\n"
|
98
112
|
email: luke@lividpenguin.com
|
99
113
|
executables: []
|