resque-exceptional 0.0.1 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
![Get Exceptional - Overview](http://img.skitch.com/20101013-k7hgurmaqew6sn8cik5gywbt2.png)
|
75
|
+
|
76
|
+
**Detailed Information**
|
77
|
+
![Get Exceptional - Details](http://img.skitch.com/20101013-ftjrjhh3fegcqr9mig9kttmwi4.png)
|
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: []
|