pzingg-hoptoad_notifier 1.1.1
Sign up to get free protection for your applications and to get access to all the features.
- data/INSTALL +64 -0
- data/README +165 -0
- data/Rakefile +22 -0
- data/lib/hoptoad_notifier.rb +322 -0
- data/tasks/hoptoad_notifier_tasks.rake +57 -0
- data/test/hoptoad_notifier_test.rb +526 -0
- metadata +57 -0
data/INSTALL
ADDED
@@ -0,0 +1,64 @@
|
|
1
|
+
HoptoadNotifier
|
2
|
+
===============
|
3
|
+
|
4
|
+
This is the notifier plugin for integrating apps with Hoptoad.
|
5
|
+
|
6
|
+
When an uncaught exception occurs, HoptoadNotifier will POST the relevant data
|
7
|
+
to the Hoptoad server specified in your environment.
|
8
|
+
|
9
|
+
|
10
|
+
INSTALLATION
|
11
|
+
------------
|
12
|
+
|
13
|
+
REMOVE EXCEPTION_NOTIFIER
|
14
|
+
|
15
|
+
In your ApplicationController, REMOVE this line:
|
16
|
+
|
17
|
+
include ExceptionNotifiable
|
18
|
+
|
19
|
+
In your config/environment* files, remove all references to ExceptionNotifier
|
20
|
+
|
21
|
+
Remove the vendor/plugins/exception_notifier directory.
|
22
|
+
|
23
|
+
INSTALL HOPTOAD_NOTIFIER
|
24
|
+
|
25
|
+
From your project's RAILS_ROOT, run:
|
26
|
+
|
27
|
+
script/plugin install git://github.com/thoughtbot/hoptoad_notifier.git
|
28
|
+
|
29
|
+
CONFIGURATION
|
30
|
+
|
31
|
+
You should have something like this in config/initializers/hoptoad.rb.
|
32
|
+
|
33
|
+
HoptoadNotifier.configure do |config|
|
34
|
+
config.api_key = '1234567890abcdef'
|
35
|
+
end
|
36
|
+
|
37
|
+
(Please note that this configuration should be in a global configuration, and
|
38
|
+
is *not* enrivonment-specific. Hoptoad is smart enough to know what errors are
|
39
|
+
caused by what environments, so your staging errors don't get mixed in with
|
40
|
+
your production errors.)
|
41
|
+
|
42
|
+
To enable hoptoad in your application, you must modify your application.rb to
|
43
|
+
include the module HoptoadNotifier::Catcher into your application controller.
|
44
|
+
|
45
|
+
Example...
|
46
|
+
|
47
|
+
class ApplicationController < ActionController::Base
|
48
|
+
include HoptoadNotifier::Catcher
|
49
|
+
end
|
50
|
+
|
51
|
+
After this is in place, all exceptions will be logged to Hoptoad where they can
|
52
|
+
be aggregated, filtered, sorted, analyzed, massaged, and searched.
|
53
|
+
|
54
|
+
** NOTE FOR RAILS 1.2.* USERS: **
|
55
|
+
You will need to copy the hoptoad_notifier_tasks.rake file into your
|
56
|
+
RAILS_ROOT/lib/tasks directory in order for the following to work:
|
57
|
+
|
58
|
+
You can test that hoptoad is working in your production environment by using
|
59
|
+
this rake task (from RAILS_ROOT):
|
60
|
+
|
61
|
+
rake hoptoad:test
|
62
|
+
|
63
|
+
If everything is configured properly, that task will send a notice to hoptoad
|
64
|
+
which will be visible immediately.
|
data/README
ADDED
@@ -0,0 +1,165 @@
|
|
1
|
+
HoptoadNotifier
|
2
|
+
===============
|
3
|
+
|
4
|
+
This is the notifier plugin for integrating apps with Hoptoad.
|
5
|
+
|
6
|
+
When an uncaught exception occurs, HoptoadNotifier will POST the relevant data
|
7
|
+
to the Hoptoad server specified in your environment.
|
8
|
+
|
9
|
+
INSTALLATION
|
10
|
+
------------
|
11
|
+
|
12
|
+
REMOVE EXCEPTION_NOTIFIER
|
13
|
+
|
14
|
+
In your ApplicationController, REMOVE this line:
|
15
|
+
|
16
|
+
include ExceptionNotifiable
|
17
|
+
|
18
|
+
In your config/environment* files, remove all references to ExceptionNotifier
|
19
|
+
|
20
|
+
Remove the vendor/plugins/exception_notifier directory.
|
21
|
+
|
22
|
+
INSTALL HOPTOAD_NOTIFIER
|
23
|
+
|
24
|
+
From your project's RAILS_ROOT, run:
|
25
|
+
|
26
|
+
script/plugin install git://github.com/thoughtbot/hoptoad_notifier.git
|
27
|
+
|
28
|
+
CONFIGURATION
|
29
|
+
|
30
|
+
You should have something like this in config/initializers/hoptoad.rb.
|
31
|
+
|
32
|
+
HoptoadNotifier.configure do |config|
|
33
|
+
config.api_key = '1234567890abcdef'
|
34
|
+
end
|
35
|
+
|
36
|
+
(Please note that this configuration should be in a global configuration, and
|
37
|
+
is *not* enrivonment-specific. Hoptoad is smart enough to know what errors are
|
38
|
+
caused by what environments, so your staging errors don't get mixed in with
|
39
|
+
your production errors.)
|
40
|
+
|
41
|
+
Then, to enable hoptoad in your application, include this code...
|
42
|
+
|
43
|
+
include HoptoadNotifier::Catcher
|
44
|
+
|
45
|
+
...in your ApplicationController, and all exceptions will be logged to Hoptoad
|
46
|
+
where they can be aggregated, filtered, sorted, analyzed, massaged, and
|
47
|
+
searched.
|
48
|
+
|
49
|
+
** NOTE FOR RAILS 1.2.* USERS: **
|
50
|
+
You will need to copy the hoptoad_notifier_tasks.rake file into your
|
51
|
+
RAILS_ROOT/lib/tasks directory in order for the following to work:
|
52
|
+
|
53
|
+
You can test that hoptoad is working in your production environment by using
|
54
|
+
this rake task (from RAILS_ROOT):
|
55
|
+
|
56
|
+
rake hoptoad:test
|
57
|
+
|
58
|
+
If everything is configured properly, that task will send a notice to hoptoad
|
59
|
+
which will be visible immediately.
|
60
|
+
|
61
|
+
USAGE
|
62
|
+
|
63
|
+
For the most part, hoptoad works for itself. Once you've included the notifier
|
64
|
+
in your ApplicationController, all errors will be rescued by the
|
65
|
+
#rescue_action_in_public provided by the plugin.
|
66
|
+
|
67
|
+
If you want to log arbitrary things which you've rescued yourself from a
|
68
|
+
controller, you can do something like this:
|
69
|
+
|
70
|
+
...
|
71
|
+
rescue => ex
|
72
|
+
notify_hoptoad(ex)
|
73
|
+
flash[:failure] = 'Encryptions could not be rerouted, try again.'
|
74
|
+
end
|
75
|
+
...
|
76
|
+
|
77
|
+
The #notify_hoptoad call will send the notice over to hoptoad for later
|
78
|
+
analysis.
|
79
|
+
|
80
|
+
GOING BEYOND EXCEPTIONS
|
81
|
+
|
82
|
+
You can also pass a hash to notify_hoptoad method and store whatever you want, not just an exception. And you can also use it anywhere, not just in controllers:
|
83
|
+
|
84
|
+
begin
|
85
|
+
params = {
|
86
|
+
# params that you pass to a method that can throw an exception
|
87
|
+
}
|
88
|
+
my_unpredicable_method(params)
|
89
|
+
rescue => e
|
90
|
+
HoptoadNotifier.notify(
|
91
|
+
:error_class => "Special Error",
|
92
|
+
:error_message => "Special Error: #{e.message}",
|
93
|
+
:request => { :params => params }
|
94
|
+
)
|
95
|
+
end
|
96
|
+
|
97
|
+
While in your controllers you use the notify_hoptoad method, anywhere else in your code, use HoptoadNotifier.notify. Hoptoad will get all the information about the error itself. As for a hash, these are the keys you should pass:
|
98
|
+
|
99
|
+
* :error_class – Use this to group similar errors together. When Hoptoad catches an exception it sends the class name of that exception object.
|
100
|
+
* :error_message – This is the title of the error you see in the errors list. For exceptions it is "#{exception.class.name}: #{exception.message}"
|
101
|
+
* :request – While there are several ways to send additional data to Hoptoad, passing a Hash with :params key as :request as in the example above is the most common use case. When Hoptoad catches an exception in a controller, the actual HTTP client request is being sent using this key.
|
102
|
+
|
103
|
+
Hoptoad merges the hash you pass with these default options:
|
104
|
+
|
105
|
+
def default_notice_options
|
106
|
+
{
|
107
|
+
:api_key => HoptoadNotifier.api_key,
|
108
|
+
:error_message => 'Notification',
|
109
|
+
:backtrace => caller,
|
110
|
+
:request => {},
|
111
|
+
:session => {},
|
112
|
+
:environment => ENV.to_hash
|
113
|
+
}
|
114
|
+
end
|
115
|
+
|
116
|
+
You can override any of those parameters.
|
117
|
+
|
118
|
+
FILTERING
|
119
|
+
|
120
|
+
You can specify a whitelist of errors, that Hoptoad will not report on. Use
|
121
|
+
this feature when you are so apathetic to certain errors that you don't want
|
122
|
+
them even logged.
|
123
|
+
|
124
|
+
This filter will only be applied to automatic notifications, not manual
|
125
|
+
notifications (when #notify is called directly).
|
126
|
+
|
127
|
+
Hoptoad ignores the following exceptions by default:
|
128
|
+
ActiveRecord::RecordNotFound
|
129
|
+
ActionController::RoutingError
|
130
|
+
ActionController::InvalidAuthenticityToken
|
131
|
+
CGI::Session::CookieStore::TamperedWithCookie
|
132
|
+
|
133
|
+
To ignore errors in addition to those, specify their names in your Hoptoad
|
134
|
+
configuration block.
|
135
|
+
|
136
|
+
HoptoadNotifier.configure do |config|
|
137
|
+
config.api_key = '1234567890abcdef'
|
138
|
+
config.ignore << ActiveRecord::IgnoreThisError
|
139
|
+
end
|
140
|
+
|
141
|
+
To ignore *only* certain errors (and override the defaults), use the
|
142
|
+
#ignore_only attribute.
|
143
|
+
|
144
|
+
HoptoadNotifier.configure do |config|
|
145
|
+
config.api_key = '1234567890abcdef'
|
146
|
+
config.ignore_only = [ActiveRecord::IgnoreThisError]
|
147
|
+
end
|
148
|
+
|
149
|
+
TESTING
|
150
|
+
|
151
|
+
When you run your tests, you might notice that the hoptoad service is recording
|
152
|
+
notices generated using #notify when you don't expect it to. You can
|
153
|
+
use code like this in your test_helper.rb to redefine that method so those
|
154
|
+
errors are not reported while running tests.
|
155
|
+
|
156
|
+
module HoptoadNotifier::Catcher
|
157
|
+
def notify(thing)
|
158
|
+
# do nothing.
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
THANKS
|
163
|
+
|
164
|
+
Thanks to Eugene Bolshakov for the excellent write-up on GOING BEYOND EXCEPTIONS, which we have included above.
|
165
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'rake'
|
2
|
+
require 'rake/testtask'
|
3
|
+
require 'rake/rdoctask'
|
4
|
+
|
5
|
+
desc 'Default: run unit tests.'
|
6
|
+
task :default => :test
|
7
|
+
|
8
|
+
desc 'Test the hoptoad_notifier plugin.'
|
9
|
+
Rake::TestTask.new(:test) do |t|
|
10
|
+
t.libs << 'lib'
|
11
|
+
t.pattern = 'test/**/*_test.rb'
|
12
|
+
t.verbose = true
|
13
|
+
end
|
14
|
+
|
15
|
+
desc 'Generate documentation for the hoptoad_notifier plugin.'
|
16
|
+
Rake::RDocTask.new(:rdoc) do |rdoc|
|
17
|
+
rdoc.rdoc_dir = 'rdoc'
|
18
|
+
rdoc.title = 'HoptoadNotifier'
|
19
|
+
rdoc.options << '--line-numbers' << '--inline-source'
|
20
|
+
rdoc.rdoc_files.include('README')
|
21
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
22
|
+
end
|
@@ -0,0 +1,322 @@
|
|
1
|
+
require 'net/http'
|
2
|
+
require 'net/https'
|
3
|
+
require 'rubygems'
|
4
|
+
require 'active_support'
|
5
|
+
|
6
|
+
# Plugin for applications to automatically post errors to the Hoptoad of their choice.
|
7
|
+
module HoptoadNotifier
|
8
|
+
|
9
|
+
IGNORE_DEFAULT = ['ActiveRecord::RecordNotFound',
|
10
|
+
'ActionController::RoutingError',
|
11
|
+
'ActionController::InvalidAuthenticityToken',
|
12
|
+
'CGI::Session::CookieStore::TamperedWithCookie']
|
13
|
+
|
14
|
+
# Some of these don't exist for Rails 1.2.*, so we have to consider that.
|
15
|
+
IGNORE_DEFAULT.map!{|e| eval(e) rescue nil }.compact!
|
16
|
+
IGNORE_DEFAULT.freeze
|
17
|
+
|
18
|
+
class << self
|
19
|
+
attr_accessor :host, :port, :secure, :api_key, :http_open_timeout, :http_read_timeout,
|
20
|
+
:proxy_host, :proxy_port, :proxy_user, :proxy_pass
|
21
|
+
attr_reader :backtrace_filters
|
22
|
+
|
23
|
+
# Takes a block and adds it to the list of backtrace filters. When the filters
|
24
|
+
# run, the block will be handed each line of the backtrace and can modify
|
25
|
+
# it as necessary. For example, by default a path matching the RAILS_ROOT
|
26
|
+
# constant will be transformed into "[RAILS_ROOT]"
|
27
|
+
def filter_backtrace &block
|
28
|
+
(@backtrace_filters ||= []) << block
|
29
|
+
end
|
30
|
+
|
31
|
+
# The port on which your Hoptoad server runs.
|
32
|
+
def port
|
33
|
+
@port || (secure ? 443 : 80)
|
34
|
+
end
|
35
|
+
|
36
|
+
# The host to connect to.
|
37
|
+
def host
|
38
|
+
@host ||= 'hoptoadapp.com'
|
39
|
+
end
|
40
|
+
|
41
|
+
# The HTTP open timeout (defaults to 2 seconds).
|
42
|
+
def http_open_timeout
|
43
|
+
@http_open_timeout ||= 2
|
44
|
+
end
|
45
|
+
|
46
|
+
# The HTTP read timeout (defaults to 5 seconds).
|
47
|
+
def http_read_timeout
|
48
|
+
@http_read_timeout ||= 5
|
49
|
+
end
|
50
|
+
|
51
|
+
# Returns the list of errors that are being ignored. The array can be appended to.
|
52
|
+
def ignore
|
53
|
+
@ignore ||= (HoptoadNotifier::IGNORE_DEFAULT.dup)
|
54
|
+
@ignore.flatten!
|
55
|
+
@ignore
|
56
|
+
end
|
57
|
+
|
58
|
+
# Sets the list of ignored errors to only what is passed in here. This method
|
59
|
+
# can be passed a single error or a list of errors.
|
60
|
+
def ignore_only=(names)
|
61
|
+
@ignore = [names].flatten
|
62
|
+
end
|
63
|
+
|
64
|
+
# Returns a list of parameters that should be filtered out of what is sent to Hoptoad.
|
65
|
+
# By default, all "password" attributes will have their contents replaced.
|
66
|
+
def params_filters
|
67
|
+
@params_filters ||= %w(password)
|
68
|
+
end
|
69
|
+
|
70
|
+
def environment_filters
|
71
|
+
@environment_filters ||= %w()
|
72
|
+
end
|
73
|
+
|
74
|
+
# Call this method to modify defaults in your initializers.
|
75
|
+
#
|
76
|
+
# HoptoadNotifier.configure do |config|
|
77
|
+
# config.api_key = '1234567890abcdef'
|
78
|
+
# config.secure = false
|
79
|
+
# end
|
80
|
+
#
|
81
|
+
# NOTE: secure connections are not yet supported.
|
82
|
+
def configure
|
83
|
+
yield self
|
84
|
+
end
|
85
|
+
|
86
|
+
def protocol #:nodoc:
|
87
|
+
secure ? "https" : "http"
|
88
|
+
end
|
89
|
+
|
90
|
+
def url #:nodoc:
|
91
|
+
URI.parse("#{protocol}://#{host}:#{port}/notices/")
|
92
|
+
end
|
93
|
+
|
94
|
+
def default_notice_options #:nodoc:
|
95
|
+
{
|
96
|
+
:api_key => HoptoadNotifier.api_key,
|
97
|
+
:error_message => 'Notification',
|
98
|
+
:backtrace => caller,
|
99
|
+
:request => {},
|
100
|
+
:session => {},
|
101
|
+
:environment => ENV.to_hash
|
102
|
+
}
|
103
|
+
end
|
104
|
+
|
105
|
+
# You can send an exception manually using this method, even when you are not in a
|
106
|
+
# controller. You can pass an exception or a hash that contains the attributes that
|
107
|
+
# would be sent to Hoptoad:
|
108
|
+
# * api_key: The API key for this project. The API key is a unique identifier that Hoptoad
|
109
|
+
# uses for identification.
|
110
|
+
# * error_message: The error returned by the exception (or the message you want to log).
|
111
|
+
# * backtrace: A backtrace, usually obtained with +caller+.
|
112
|
+
# * request: The controller's request object.
|
113
|
+
# * session: The contents of the user's session.
|
114
|
+
# * environment: ENV merged with the contents of the request's environment.
|
115
|
+
def notify notice = {}
|
116
|
+
Sender.new.notify_hoptoad( notice )
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
filter_backtrace do |line|
|
121
|
+
line.gsub(/#{RAILS_ROOT}/, "[RAILS_ROOT]")
|
122
|
+
end
|
123
|
+
|
124
|
+
filter_backtrace do |line|
|
125
|
+
line.gsub(/^\.\//, "")
|
126
|
+
end
|
127
|
+
|
128
|
+
filter_backtrace do |line|
|
129
|
+
if defined?(Gem)
|
130
|
+
Gem.path.inject(line) do |line, path|
|
131
|
+
line.gsub(/#{path}/, "[GEM_ROOT]")
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
# Include this module in Controllers in which you want to be notified of errors.
|
137
|
+
module Catcher
|
138
|
+
|
139
|
+
def self.included(base) #:nodoc:
|
140
|
+
if base.instance_methods.include? 'rescue_action_in_public' and !base.instance_methods.include? 'rescue_action_in_public_without_hoptoad'
|
141
|
+
base.send(:alias_method, :rescue_action_in_public_without_hoptoad, :rescue_action_in_public)
|
142
|
+
base.send(:alias_method, :rescue_action_in_public, :rescue_action_in_public_with_hoptoad)
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
# Overrides the rescue_action method in ActionController::Base, but does not inhibit
|
147
|
+
# any custom processing that is defined with Rails 2's exception helpers.
|
148
|
+
def rescue_action_in_public_with_hoptoad exception
|
149
|
+
notify_hoptoad(exception) unless ignore?(exception)
|
150
|
+
rescue_action_in_public_without_hoptoad(exception)
|
151
|
+
end
|
152
|
+
|
153
|
+
# This method should be used for sending manual notifications while you are still
|
154
|
+
# inside the controller. Otherwise it works like HoptoadNotifier.notify.
|
155
|
+
def notify_hoptoad hash_or_exception
|
156
|
+
if public_environment?
|
157
|
+
notice = normalize_notice(hash_or_exception)
|
158
|
+
notice = clean_notice(notice)
|
159
|
+
send_to_hoptoad(:notice => notice)
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
alias_method :inform_hoptoad, :notify_hoptoad
|
164
|
+
|
165
|
+
# Returns the default logger or a logger that prints to STDOUT. Necessary for manual
|
166
|
+
# notifications outside of controllers.
|
167
|
+
def logger
|
168
|
+
ActiveRecord::Base.logger
|
169
|
+
rescue
|
170
|
+
@logger ||= Logger.new(STDERR)
|
171
|
+
end
|
172
|
+
|
173
|
+
private
|
174
|
+
|
175
|
+
def public_environment? #nodoc:
|
176
|
+
defined?(RAILS_ENV) and !['development', 'test'].include?(RAILS_ENV)
|
177
|
+
end
|
178
|
+
|
179
|
+
def ignore?(exception) #:nodoc:
|
180
|
+
ignore_these = HoptoadNotifier.ignore.flatten
|
181
|
+
ignore_these.include?(exception.class) || ignore_these.include?(exception.class.name)
|
182
|
+
end
|
183
|
+
|
184
|
+
def exception_to_data exception #:nodoc:
|
185
|
+
data = {
|
186
|
+
:api_key => HoptoadNotifier.api_key,
|
187
|
+
:error_class => exception.class.name,
|
188
|
+
:error_message => "#{exception.class.name}: #{exception.message}",
|
189
|
+
:backtrace => exception.backtrace,
|
190
|
+
:environment => ENV.to_hash
|
191
|
+
}
|
192
|
+
|
193
|
+
if self.respond_to? :request
|
194
|
+
data[:request] = {
|
195
|
+
:params => request.parameters.to_hash,
|
196
|
+
:rails_root => File.expand_path(RAILS_ROOT),
|
197
|
+
:url => "#{request.protocol}#{request.host}#{request.request_uri}"
|
198
|
+
}
|
199
|
+
data[:environment].merge!(request.env.to_hash)
|
200
|
+
end
|
201
|
+
|
202
|
+
if self.respond_to? :session
|
203
|
+
data[:session] = {
|
204
|
+
:key => session.instance_variable_get("@session_id"),
|
205
|
+
:data => session.instance_variable_get("@data")
|
206
|
+
}
|
207
|
+
end
|
208
|
+
|
209
|
+
data
|
210
|
+
end
|
211
|
+
|
212
|
+
def normalize_notice(notice) #:nodoc:
|
213
|
+
case notice
|
214
|
+
when Hash
|
215
|
+
HoptoadNotifier.default_notice_options.merge(notice)
|
216
|
+
when Exception
|
217
|
+
HoptoadNotifier.default_notice_options.merge(exception_to_data(notice))
|
218
|
+
end
|
219
|
+
end
|
220
|
+
|
221
|
+
def clean_notice(notice) #:nodoc:
|
222
|
+
notice[:backtrace] = clean_hoptoad_backtrace(notice[:backtrace])
|
223
|
+
if notice[:request].is_a?(Hash) && notice[:request][:params].is_a?(Hash)
|
224
|
+
notice[:request][:params] = filter_parameters(notice[:request][:params]) if respond_to?(:filter_parameters)
|
225
|
+
notice[:request][:params] = clean_hoptoad_params(notice[:request][:params])
|
226
|
+
end
|
227
|
+
if notice[:environment].is_a?(Hash)
|
228
|
+
notice[:environment] = filter_parameters(notice[:environment]) if respond_to?(:filter_parameters)
|
229
|
+
notice[:environment] = clean_hoptoad_environment(notice[:environment])
|
230
|
+
end
|
231
|
+
clean_non_serializable_data(notice)
|
232
|
+
end
|
233
|
+
|
234
|
+
def send_to_hoptoad data #:nodoc:
|
235
|
+
headers = {
|
236
|
+
'Content-type' => 'application/x-yaml',
|
237
|
+
'Accept' => 'text/xml, application/xml'
|
238
|
+
}
|
239
|
+
|
240
|
+
url = HoptoadNotifier.url
|
241
|
+
|
242
|
+
http = Net::HTTP::Proxy(HoptoadNotifier.proxy_host,
|
243
|
+
HoptoadNotifier.proxy_port,
|
244
|
+
HoptoadNotifier.proxy_user,
|
245
|
+
HoptoadNotifier.proxy_pass).new(url.host, url.port)
|
246
|
+
|
247
|
+
http.use_ssl = true
|
248
|
+
http.read_timeout = HoptoadNotifier.http_read_timeout
|
249
|
+
http.open_timeout = HoptoadNotifier.http_open_timeout
|
250
|
+
http.use_ssl = !!HoptoadNotifier.secure
|
251
|
+
|
252
|
+
response = begin
|
253
|
+
http.post(url.path, stringify_keys(data).to_yaml, headers)
|
254
|
+
rescue TimeoutError => e
|
255
|
+
logger.error "Timeout while contacting the Hoptoad server."
|
256
|
+
nil
|
257
|
+
end
|
258
|
+
|
259
|
+
case response
|
260
|
+
when Net::HTTPSuccess then
|
261
|
+
logger.info "Hoptoad Success: #{response.class}"
|
262
|
+
else
|
263
|
+
logger.error "Hoptoad Failure: #{response.class}\n#{response.body if response.respond_to? :body}"
|
264
|
+
end
|
265
|
+
end
|
266
|
+
|
267
|
+
def clean_hoptoad_backtrace backtrace #:nodoc:
|
268
|
+
if backtrace.to_a.size == 1
|
269
|
+
backtrace = backtrace.to_a.first.split(/\n\s*/)
|
270
|
+
end
|
271
|
+
|
272
|
+
backtrace.to_a.map do |line|
|
273
|
+
HoptoadNotifier.backtrace_filters.inject(line) do |line, proc|
|
274
|
+
proc.call(line)
|
275
|
+
end
|
276
|
+
end
|
277
|
+
end
|
278
|
+
|
279
|
+
def clean_hoptoad_params params #:nodoc:
|
280
|
+
params.each do |k, v|
|
281
|
+
params[k] = "[FILTERED]" if HoptoadNotifier.params_filters.any? do |filter|
|
282
|
+
k.to_s.match(/#{filter}/)
|
283
|
+
end
|
284
|
+
end
|
285
|
+
end
|
286
|
+
|
287
|
+
def clean_hoptoad_environment env #:nodoc:
|
288
|
+
env.each do |k, v|
|
289
|
+
env[k] = "[FILTERED]" if HoptoadNotifier.environment_filters.any? do |filter|
|
290
|
+
k.to_s.match(/#{filter}/)
|
291
|
+
end
|
292
|
+
end
|
293
|
+
end
|
294
|
+
|
295
|
+
def clean_non_serializable_data(notice) #:nodoc:
|
296
|
+
notice.select{|k,v| serializable?(v) }.inject({}) do |h, pair|
|
297
|
+
h[pair.first] = pair.last.is_a?(Hash) ? clean_non_serializable_data(pair.last) : pair.last
|
298
|
+
h
|
299
|
+
end
|
300
|
+
end
|
301
|
+
|
302
|
+
def serializable?(value) #:nodoc:
|
303
|
+
!(value.is_a?(Module) || value.kind_of?(IO))
|
304
|
+
end
|
305
|
+
|
306
|
+
def stringify_keys(hash) #:nodoc:
|
307
|
+
hash.inject({}) do |h, pair|
|
308
|
+
h[pair.first.to_s] = pair.last.is_a?(Hash) ? stringify_keys(pair.last) : pair.last
|
309
|
+
h
|
310
|
+
end
|
311
|
+
end
|
312
|
+
|
313
|
+
end
|
314
|
+
|
315
|
+
# A dummy class for sending notifications manually outside of a controller.
|
316
|
+
class Sender
|
317
|
+
def rescue_action_in_public(exception)
|
318
|
+
end
|
319
|
+
|
320
|
+
include HoptoadNotifier::Catcher
|
321
|
+
end
|
322
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
namespace :hoptoad do
|
2
|
+
desc "Verify your plugin installation by sending a test exception to the hoptoad service"
|
3
|
+
task :test => :environment do
|
4
|
+
require 'action_controller/test_process'
|
5
|
+
require 'application_controller'
|
6
|
+
|
7
|
+
request = ActionController::TestRequest.new
|
8
|
+
request.query_parameters = {
|
9
|
+
'action' => 'verify',
|
10
|
+
'controller' => 'hoptoad_verification'
|
11
|
+
}
|
12
|
+
|
13
|
+
response = ActionController::TestResponse.new
|
14
|
+
|
15
|
+
class HoptoadTestingException < RuntimeError; end
|
16
|
+
|
17
|
+
in_controller = ApplicationController.included_modules.include? HoptoadNotifier::Catcher
|
18
|
+
in_base = ActionController::Base.included_modules.include? HoptoadNotifier::Catcher
|
19
|
+
unless in_controller and not in_base
|
20
|
+
puts "HoptoadNotifier::Catcher must be included inside your ApplicationController class."
|
21
|
+
exit
|
22
|
+
end
|
23
|
+
|
24
|
+
puts 'Setting up the Controller.'
|
25
|
+
class ApplicationController
|
26
|
+
# This is to bypass any filters that may prevent access to the action.
|
27
|
+
prepend_before_filter :test_hoptoad
|
28
|
+
def test_hoptoad
|
29
|
+
puts "Raising '#{exception_class.name}' to simulate application failure."
|
30
|
+
raise exception_class.new, 'Testing hoptoad via "rake hoptoad:test". If you can see this, it works.'
|
31
|
+
end
|
32
|
+
|
33
|
+
def rescue_action exception
|
34
|
+
rescue_action_in_public exception
|
35
|
+
end
|
36
|
+
|
37
|
+
def public_environment?
|
38
|
+
true
|
39
|
+
end
|
40
|
+
|
41
|
+
# Ensure we actually have an action to go to.
|
42
|
+
def verify; end
|
43
|
+
|
44
|
+
def exception_class
|
45
|
+
exception_name = ENV['EXCEPTION'] || "HoptoadTestingException"
|
46
|
+
Object.const_get(exception_name)
|
47
|
+
rescue
|
48
|
+
Object.const_set(exception_name, Class.new(Exception))
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
puts 'Processing request.'
|
53
|
+
class HoptoadVerificationController < ApplicationController; end
|
54
|
+
HoptoadVerificationController.new.process(request, response)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
@@ -0,0 +1,526 @@
|
|
1
|
+
require 'test/unit'
|
2
|
+
require 'rubygems'
|
3
|
+
require 'mocha'
|
4
|
+
gem 'thoughtbot-shoulda', ">= 2.0.0"
|
5
|
+
require 'shoulda'
|
6
|
+
require 'action_controller'
|
7
|
+
require 'action_controller/test_process'
|
8
|
+
require 'active_record'
|
9
|
+
require 'active_record/base'
|
10
|
+
require 'active_support/testing/core_ext/test/unit/assertions'
|
11
|
+
require File.join(File.dirname(__FILE__), "..", "lib", "hoptoad_notifier")
|
12
|
+
|
13
|
+
RAILS_ROOT = File.join( File.dirname(__FILE__), "rails_root" )
|
14
|
+
RAILS_ENV = "test"
|
15
|
+
|
16
|
+
class HoptoadController < ActionController::Base
|
17
|
+
def rescue_action e
|
18
|
+
raise e
|
19
|
+
end
|
20
|
+
|
21
|
+
def do_raise
|
22
|
+
raise "Hoptoad"
|
23
|
+
end
|
24
|
+
|
25
|
+
def do_not_raise
|
26
|
+
render :text => "Success"
|
27
|
+
end
|
28
|
+
|
29
|
+
def do_raise_ignored
|
30
|
+
raise ActiveRecord::RecordNotFound.new("404")
|
31
|
+
end
|
32
|
+
|
33
|
+
def do_raise_not_ignored
|
34
|
+
raise ActiveRecord::StatementInvalid.new("Statement invalid")
|
35
|
+
end
|
36
|
+
|
37
|
+
def manual_notify
|
38
|
+
notify_hoptoad(Exception.new)
|
39
|
+
render :text => "Success"
|
40
|
+
end
|
41
|
+
|
42
|
+
def manual_notify_ignored
|
43
|
+
notify_hoptoad(ActiveRecord::RecordNotFound.new("404"))
|
44
|
+
render :text => "Success"
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
class HoptoadNotifierTest < Test::Unit::TestCase
|
49
|
+
def request(action = nil, method = :get)
|
50
|
+
@request = ActionController::TestRequest.new
|
51
|
+
@request.action = action ? action.to_s : ""
|
52
|
+
@response = ActionController::TestResponse.new
|
53
|
+
@controller.process(@request, @response)
|
54
|
+
end
|
55
|
+
|
56
|
+
context "Hoptoad inclusion" do
|
57
|
+
should "be able to occur even outside Rails controllers" do
|
58
|
+
assert_nothing_raised do
|
59
|
+
class MyHoptoad
|
60
|
+
include HoptoadNotifier::Catcher
|
61
|
+
end
|
62
|
+
end
|
63
|
+
my = MyHoptoad.new
|
64
|
+
assert my.respond_to?(:notify_hoptoad)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
context "HoptoadNotifier configuration" do
|
69
|
+
setup do
|
70
|
+
@controller = HoptoadController.new
|
71
|
+
class ::HoptoadController
|
72
|
+
include HoptoadNotifier::Catcher
|
73
|
+
def rescue_action e
|
74
|
+
rescue_action_in_public e
|
75
|
+
end
|
76
|
+
end
|
77
|
+
assert @controller.methods.include?("notify_hoptoad")
|
78
|
+
end
|
79
|
+
|
80
|
+
should "be done with a block" do
|
81
|
+
HoptoadNotifier.configure do |config|
|
82
|
+
config.host = "host"
|
83
|
+
config.port = 3333
|
84
|
+
config.secure = true
|
85
|
+
config.api_key = "1234567890abcdef"
|
86
|
+
config.ignore << [ RuntimeError ]
|
87
|
+
config.proxy_host = 'proxyhost1'
|
88
|
+
config.proxy_port = '80'
|
89
|
+
config.proxy_user = 'user'
|
90
|
+
config.proxy_pass = 'secret'
|
91
|
+
config.http_open_timeout = 2
|
92
|
+
config.http_read_timeout = 5
|
93
|
+
end
|
94
|
+
|
95
|
+
assert_equal "host", HoptoadNotifier.host
|
96
|
+
assert_equal 3333, HoptoadNotifier.port
|
97
|
+
assert_equal true, HoptoadNotifier.secure
|
98
|
+
assert_equal "1234567890abcdef", HoptoadNotifier.api_key
|
99
|
+
assert_equal 'proxyhost1', HoptoadNotifier.proxy_host
|
100
|
+
assert_equal '80', HoptoadNotifier.proxy_port
|
101
|
+
assert_equal 'user', HoptoadNotifier.proxy_user
|
102
|
+
assert_equal 'secret', HoptoadNotifier.proxy_pass
|
103
|
+
assert_equal 2, HoptoadNotifier.http_open_timeout
|
104
|
+
assert_equal 5, HoptoadNotifier.http_read_timeout
|
105
|
+
assert_equal (HoptoadNotifier::IGNORE_DEFAULT + [RuntimeError]), HoptoadNotifier.ignore
|
106
|
+
end
|
107
|
+
|
108
|
+
should "set a default host" do
|
109
|
+
HoptoadNotifier.instance_variable_set("@host",nil)
|
110
|
+
assert_equal "hoptoadapp.com", HoptoadNotifier.host
|
111
|
+
end
|
112
|
+
|
113
|
+
should "add filters to the backtrace_filters" do
|
114
|
+
assert_difference "HoptoadNotifier.backtrace_filters.length" do
|
115
|
+
HoptoadNotifier.configure do |config|
|
116
|
+
config.filter_backtrace do |line|
|
117
|
+
line = "1234"
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
assert_equal %w( 1234 1234 ), @controller.send(:clean_hoptoad_backtrace, %w( foo bar ))
|
123
|
+
end
|
124
|
+
|
125
|
+
should "use standard rails logging filters on params and env" do
|
126
|
+
::HoptoadController.class_eval do
|
127
|
+
filter_parameter_logging :ghi
|
128
|
+
end
|
129
|
+
|
130
|
+
expected = {"notice" => {"request" => {"params" => {"abc" => "123", "def" => "456", "ghi" => "[FILTERED]"}},
|
131
|
+
"environment" => {"abc" => "123", "ghi" => "[FILTERED]"}}}
|
132
|
+
notice = {"notice" => {"request" => {"params" => {"abc" => "123", "def" => "456", "ghi" => "789"}},
|
133
|
+
"environment" => {"abc" => "123", "ghi" => "789"}}}
|
134
|
+
assert @controller.respond_to?(:filter_parameters)
|
135
|
+
assert_equal( expected[:notice], @controller.send(:clean_notice, notice)[:notice] )
|
136
|
+
end
|
137
|
+
|
138
|
+
should "add filters to the params filters" do
|
139
|
+
assert_difference "HoptoadNotifier.params_filters.length", 2 do
|
140
|
+
HoptoadNotifier.configure do |config|
|
141
|
+
config.params_filters << "abc"
|
142
|
+
config.params_filters << "def"
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
assert HoptoadNotifier.params_filters.include?( "abc" )
|
147
|
+
assert HoptoadNotifier.params_filters.include?( "def" )
|
148
|
+
|
149
|
+
assert_equal( {:abc => "[FILTERED]", :def => "[FILTERED]", :ghi => "789"},
|
150
|
+
@controller.send(:clean_hoptoad_params, :abc => "123", :def => "456", :ghi => "789" ) )
|
151
|
+
end
|
152
|
+
|
153
|
+
should "add filters to the environment filters" do
|
154
|
+
assert_difference "HoptoadNotifier.environment_filters.length", 2 do
|
155
|
+
HoptoadNotifier.configure do |config|
|
156
|
+
config.environment_filters << "secret"
|
157
|
+
config.environment_filters << "supersecret"
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
assert HoptoadNotifier.environment_filters.include?( "secret" )
|
162
|
+
assert HoptoadNotifier.environment_filters.include?( "supersecret" )
|
163
|
+
|
164
|
+
assert_equal( {:secret => "[FILTERED]", :supersecret => "[FILTERED]", :ghi => "789"},
|
165
|
+
@controller.send(:clean_hoptoad_environment, :secret => "123", :supersecret => "456", :ghi => "789" ) )
|
166
|
+
end
|
167
|
+
|
168
|
+
should "have at default ignored exceptions" do
|
169
|
+
assert HoptoadNotifier::IGNORE_DEFAULT.any?
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
context "The hoptoad test controller" do
|
174
|
+
setup do
|
175
|
+
@controller = ::HoptoadController.new
|
176
|
+
class ::HoptoadController
|
177
|
+
def rescue_action e
|
178
|
+
raise e
|
179
|
+
end
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
context "with no notifier catcher" do
|
184
|
+
should "not prevent raises" do
|
185
|
+
assert_raises RuntimeError do
|
186
|
+
request("do_raise")
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
should "allow a non-raising action to complete" do
|
191
|
+
assert_nothing_raised do
|
192
|
+
request("do_not_raise")
|
193
|
+
end
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
197
|
+
context "with the notifier installed" do
|
198
|
+
setup do
|
199
|
+
class ::HoptoadController
|
200
|
+
include HoptoadNotifier::Catcher
|
201
|
+
def rescue_action e
|
202
|
+
rescue_action_in_public e
|
203
|
+
end
|
204
|
+
end
|
205
|
+
HoptoadNotifier.ignore_only = HoptoadNotifier::IGNORE_DEFAULT
|
206
|
+
@controller.stubs(:public_environment?).returns(true)
|
207
|
+
@controller.stubs(:send_to_hoptoad)
|
208
|
+
end
|
209
|
+
|
210
|
+
should "have inserted its methods into the controller" do
|
211
|
+
assert @controller.methods.include?("inform_hoptoad")
|
212
|
+
end
|
213
|
+
|
214
|
+
should "prevent raises and send the error to hoptoad" do
|
215
|
+
@controller.expects(:notify_hoptoad)
|
216
|
+
@controller.expects(:rescue_action_in_public_without_hoptoad)
|
217
|
+
assert_nothing_raised do
|
218
|
+
request("do_raise")
|
219
|
+
end
|
220
|
+
end
|
221
|
+
|
222
|
+
should "allow a non-raising action to complete" do
|
223
|
+
assert_nothing_raised do
|
224
|
+
request("do_not_raise")
|
225
|
+
end
|
226
|
+
end
|
227
|
+
|
228
|
+
should "allow manual sending of exceptions" do
|
229
|
+
@controller.expects(:notify_hoptoad)
|
230
|
+
@controller.expects(:rescue_action_in_public_without_hoptoad).never
|
231
|
+
assert_nothing_raised do
|
232
|
+
request("manual_notify")
|
233
|
+
end
|
234
|
+
end
|
235
|
+
|
236
|
+
should "disable manual sending of exceptions in a non-public (development or test) environment" do
|
237
|
+
@controller.stubs(:public_environment?).returns(false)
|
238
|
+
@controller.expects(:send_to_hoptoad).never
|
239
|
+
@controller.expects(:rescue_action_in_public_without_hoptoad).never
|
240
|
+
assert_nothing_raised do
|
241
|
+
request("manual_notify")
|
242
|
+
end
|
243
|
+
end
|
244
|
+
|
245
|
+
should "send even ignored exceptions if told manually" do
|
246
|
+
@controller.expects(:notify_hoptoad)
|
247
|
+
@controller.expects(:rescue_action_in_public_without_hoptoad).never
|
248
|
+
assert_nothing_raised do
|
249
|
+
request("manual_notify_ignored")
|
250
|
+
end
|
251
|
+
end
|
252
|
+
|
253
|
+
should "ignore default exceptions" do
|
254
|
+
@controller.expects(:notify_hoptoad).never
|
255
|
+
@controller.expects(:rescue_action_in_public_without_hoptoad)
|
256
|
+
assert_nothing_raised do
|
257
|
+
request("do_raise_ignored")
|
258
|
+
end
|
259
|
+
end
|
260
|
+
|
261
|
+
should "filter non-serializable data" do
|
262
|
+
File.open(__FILE__) do |file|
|
263
|
+
assert_equal( {:ghi => "789"},
|
264
|
+
@controller.send(:clean_non_serializable_data, :ghi => "789", :class => Class.new, :file => file) )
|
265
|
+
end
|
266
|
+
end
|
267
|
+
|
268
|
+
should "apply all params, environment and technical filters" do
|
269
|
+
params_hash = {:abc => 123}
|
270
|
+
environment_hash = {:def => 456}
|
271
|
+
backtrace_data = :backtrace_data
|
272
|
+
|
273
|
+
raw_notice = {:request => {:params => params_hash},
|
274
|
+
:environment => environment_hash,
|
275
|
+
:backtrace => backtrace_data}
|
276
|
+
|
277
|
+
processed_notice = {:backtrace => :backtrace_data,
|
278
|
+
:request => {:params => :params_data},
|
279
|
+
:environment => :environment_data}
|
280
|
+
|
281
|
+
@controller.expects(:clean_hoptoad_backtrace).with(backtrace_data).returns(:backtrace_data)
|
282
|
+
@controller.expects(:clean_hoptoad_params).with(params_hash).returns(:params_data)
|
283
|
+
@controller.expects(:clean_hoptoad_environment).with(environment_hash).returns(:environment_data)
|
284
|
+
@controller.expects(:clean_non_serializable_data).with(processed_notice).returns(:serializable_data)
|
285
|
+
|
286
|
+
assert_equal(:serializable_data, @controller.send(:clean_notice, raw_notice))
|
287
|
+
end
|
288
|
+
|
289
|
+
context "and configured to ignore additional exceptions" do
|
290
|
+
setup do
|
291
|
+
HoptoadNotifier.ignore << ActiveRecord::StatementInvalid
|
292
|
+
end
|
293
|
+
|
294
|
+
should "still ignore default exceptions" do
|
295
|
+
@controller.expects(:notify_hoptoad).never
|
296
|
+
@controller.expects(:rescue_action_in_public_without_hoptoad)
|
297
|
+
assert_nothing_raised do
|
298
|
+
request("do_raise_ignored")
|
299
|
+
end
|
300
|
+
end
|
301
|
+
|
302
|
+
should "ignore specified exceptions" do
|
303
|
+
@controller.expects(:notify_hoptoad).never
|
304
|
+
@controller.expects(:rescue_action_in_public_without_hoptoad)
|
305
|
+
assert_nothing_raised do
|
306
|
+
request("do_raise_not_ignored")
|
307
|
+
end
|
308
|
+
end
|
309
|
+
|
310
|
+
should "not ignore unspecified, non-default exceptions" do
|
311
|
+
@controller.expects(:notify_hoptoad)
|
312
|
+
@controller.expects(:rescue_action_in_public_without_hoptoad)
|
313
|
+
assert_nothing_raised do
|
314
|
+
request("do_raise")
|
315
|
+
end
|
316
|
+
end
|
317
|
+
end
|
318
|
+
|
319
|
+
context "and configured to ignore only certain exceptions" do
|
320
|
+
setup do
|
321
|
+
HoptoadNotifier.ignore_only = [ActiveRecord::StatementInvalid]
|
322
|
+
end
|
323
|
+
|
324
|
+
should "no longer ignore default exceptions" do
|
325
|
+
@controller.expects(:notify_hoptoad)
|
326
|
+
@controller.expects(:rescue_action_in_public_without_hoptoad)
|
327
|
+
assert_nothing_raised do
|
328
|
+
request("do_raise_ignored")
|
329
|
+
end
|
330
|
+
end
|
331
|
+
|
332
|
+
should "ignore specified exceptions" do
|
333
|
+
@controller.expects(:notify_hoptoad).never
|
334
|
+
@controller.expects(:rescue_action_in_public_without_hoptoad)
|
335
|
+
assert_nothing_raised do
|
336
|
+
request("do_raise_not_ignored")
|
337
|
+
end
|
338
|
+
end
|
339
|
+
|
340
|
+
should "not ignore unspecified, non-default exceptions" do
|
341
|
+
@controller.expects(:notify_hoptoad)
|
342
|
+
@controller.expects(:rescue_action_in_public_without_hoptoad)
|
343
|
+
assert_nothing_raised do
|
344
|
+
request("do_raise")
|
345
|
+
end
|
346
|
+
end
|
347
|
+
end
|
348
|
+
end
|
349
|
+
end
|
350
|
+
|
351
|
+
context "Sending a notice" do
|
352
|
+
context "with an exception" do
|
353
|
+
setup do
|
354
|
+
@sender = HoptoadNotifier::Sender.new
|
355
|
+
@backtrace = caller
|
356
|
+
@exception = begin
|
357
|
+
raise
|
358
|
+
rescue => caught_exception
|
359
|
+
caught_exception
|
360
|
+
end
|
361
|
+
@options = {:error_message => "123",
|
362
|
+
:backtrace => @backtrace}
|
363
|
+
HoptoadNotifier.instance_variable_set("@backtrace_filters", [])
|
364
|
+
HoptoadNotifier::Sender.expects(:new).returns(@sender)
|
365
|
+
@sender.stubs(:public_environment?).returns(true)
|
366
|
+
end
|
367
|
+
|
368
|
+
context "when using an HTTP Proxy" do
|
369
|
+
setup do
|
370
|
+
@body = 'body'
|
371
|
+
@response = stub(:body => @body)
|
372
|
+
@http = stub(:post => @response, :read_timeout= => nil, :open_timeout= => nil, :use_ssl= => nil)
|
373
|
+
@sender.stubs(:logger).returns(stub(:error => nil, :info => nil))
|
374
|
+
@proxy = stub
|
375
|
+
@proxy.stubs(:new).returns(@http)
|
376
|
+
|
377
|
+
HoptoadNotifier.port = nil
|
378
|
+
HoptoadNotifier.host = nil
|
379
|
+
HoptoadNotifier.secure = false
|
380
|
+
|
381
|
+
Net::HTTP.expects(:Proxy).with(
|
382
|
+
HoptoadNotifier.proxy_host,
|
383
|
+
HoptoadNotifier.proxy_port,
|
384
|
+
HoptoadNotifier.proxy_user,
|
385
|
+
HoptoadNotifier.proxy_pass
|
386
|
+
).returns(@proxy)
|
387
|
+
end
|
388
|
+
|
389
|
+
context "on notify" do
|
390
|
+
setup { HoptoadNotifier.notify(@exception) }
|
391
|
+
|
392
|
+
before_should "post to Hoptoad" do
|
393
|
+
url = "http://hoptoadapp.com:80/notices/"
|
394
|
+
uri = URI.parse(url)
|
395
|
+
URI.expects(:parse).with(url).returns(uri)
|
396
|
+
@http.expects(:post).with(uri.path, anything, anything).returns(@response)
|
397
|
+
end
|
398
|
+
end
|
399
|
+
end
|
400
|
+
|
401
|
+
context "when stubbing out Net::HTTP" do
|
402
|
+
setup do
|
403
|
+
@body = 'body'
|
404
|
+
@response = stub(:body => @body)
|
405
|
+
@http = stub(:post => @response, :read_timeout= => nil, :open_timeout= => nil, :use_ssl= => nil)
|
406
|
+
@sender.stubs(:logger).returns(stub(:error => nil, :info => nil))
|
407
|
+
Net::HTTP.stubs(:new).returns(@http)
|
408
|
+
HoptoadNotifier.port = nil
|
409
|
+
HoptoadNotifier.host = nil
|
410
|
+
HoptoadNotifier.proxy_host = nil
|
411
|
+
end
|
412
|
+
|
413
|
+
context "on notify" do
|
414
|
+
setup { HoptoadNotifier.notify(@exception) }
|
415
|
+
|
416
|
+
before_should "post to the right url for non-ssl" do
|
417
|
+
HoptoadNotifier.secure = false
|
418
|
+
url = "http://hoptoadapp.com:80/notices/"
|
419
|
+
uri = URI.parse(url)
|
420
|
+
URI.expects(:parse).with(url).returns(uri)
|
421
|
+
@http.expects(:post).with(uri.path, anything, anything).returns(@response)
|
422
|
+
end
|
423
|
+
|
424
|
+
before_should "post to the right path" do
|
425
|
+
@http.expects(:post).with("/notices/", anything, anything).returns(@response)
|
426
|
+
end
|
427
|
+
|
428
|
+
before_should "call send_to_hoptoad" do
|
429
|
+
@sender.expects(:send_to_hoptoad)
|
430
|
+
end
|
431
|
+
|
432
|
+
before_should "default the open timeout to 2 seconds" do
|
433
|
+
HoptoadNotifier.http_open_timeout = nil
|
434
|
+
@http.expects(:open_timeout=).with(2)
|
435
|
+
end
|
436
|
+
|
437
|
+
before_should "default the read timeout to 5 seconds" do
|
438
|
+
HoptoadNotifier.http_read_timeout = nil
|
439
|
+
@http.expects(:read_timeout=).with(5)
|
440
|
+
end
|
441
|
+
|
442
|
+
before_should "allow override of the open timeout" do
|
443
|
+
HoptoadNotifier.http_open_timeout = 4
|
444
|
+
@http.expects(:open_timeout=).with(4)
|
445
|
+
end
|
446
|
+
|
447
|
+
before_should "allow override of the read timeout" do
|
448
|
+
HoptoadNotifier.http_read_timeout = 10
|
449
|
+
@http.expects(:read_timeout=).with(10)
|
450
|
+
end
|
451
|
+
|
452
|
+
before_should "connect to the right port for ssl" do
|
453
|
+
HoptoadNotifier.secure = true
|
454
|
+
Net::HTTP.expects(:new).with("hoptoadapp.com", 443).returns(@http)
|
455
|
+
end
|
456
|
+
|
457
|
+
before_should "connect to the right port for non-ssl" do
|
458
|
+
HoptoadNotifier.secure = false
|
459
|
+
Net::HTTP.expects(:new).with("hoptoadapp.com", 80).returns(@http)
|
460
|
+
end
|
461
|
+
|
462
|
+
before_should "use ssl if secure" do
|
463
|
+
HoptoadNotifier.secure = true
|
464
|
+
HoptoadNotifier.host = 'example.org'
|
465
|
+
Net::HTTP.expects(:new).with('example.org', 443).returns(@http)
|
466
|
+
end
|
467
|
+
|
468
|
+
before_should "not use ssl if not secure" do
|
469
|
+
HoptoadNotifier.secure = nil
|
470
|
+
HoptoadNotifier.host = 'example.org'
|
471
|
+
Net::HTTP.expects(:new).with('example.org', 80).returns(@http)
|
472
|
+
end
|
473
|
+
end
|
474
|
+
end
|
475
|
+
|
476
|
+
should "send as if it were a normally caught exception" do
|
477
|
+
@sender.expects(:notify_hoptoad).with(@exception)
|
478
|
+
HoptoadNotifier.notify(@exception)
|
479
|
+
end
|
480
|
+
|
481
|
+
should "make sure the exception is munged into a hash" do
|
482
|
+
options = HoptoadNotifier.default_notice_options.merge({
|
483
|
+
:backtrace => @exception.backtrace,
|
484
|
+
:environment => ENV.to_hash,
|
485
|
+
:error_class => @exception.class.name,
|
486
|
+
:error_message => "#{@exception.class.name}: #{@exception.message}",
|
487
|
+
:api_key => HoptoadNotifier.api_key,
|
488
|
+
})
|
489
|
+
@sender.expects(:send_to_hoptoad).with(:notice => options)
|
490
|
+
HoptoadNotifier.notify(@exception)
|
491
|
+
end
|
492
|
+
|
493
|
+
should "parse massive one-line exceptions into multiple lines" do
|
494
|
+
@original_backtrace = "one big line\n separated\n by new lines\nand some spaces"
|
495
|
+
@expected_backtrace = ["one big line", "separated", "by new lines", "and some spaces"]
|
496
|
+
@exception.set_backtrace [@original_backtrace]
|
497
|
+
|
498
|
+
options = HoptoadNotifier.default_notice_options.merge({
|
499
|
+
:backtrace => @expected_backtrace,
|
500
|
+
:environment => ENV.to_hash,
|
501
|
+
:error_class => @exception.class.name,
|
502
|
+
:error_message => "#{@exception.class.name}: #{@exception.message}",
|
503
|
+
:api_key => HoptoadNotifier.api_key,
|
504
|
+
})
|
505
|
+
|
506
|
+
@sender.expects(:send_to_hoptoad).with(:notice => options)
|
507
|
+
HoptoadNotifier.notify(@exception)
|
508
|
+
end
|
509
|
+
end
|
510
|
+
|
511
|
+
context "without an exception" do
|
512
|
+
setup do
|
513
|
+
@sender = HoptoadNotifier::Sender.new
|
514
|
+
@backtrace = caller
|
515
|
+
@options = {:error_message => "123",
|
516
|
+
:backtrace => @backtrace}
|
517
|
+
HoptoadNotifier::Sender.expects(:new).returns(@sender)
|
518
|
+
end
|
519
|
+
|
520
|
+
should "send sensible defaults" do
|
521
|
+
@sender.expects(:notify_hoptoad).with(@options)
|
522
|
+
HoptoadNotifier.notify(:error_message => "123", :backtrace => @backtrace)
|
523
|
+
end
|
524
|
+
end
|
525
|
+
end
|
526
|
+
end
|
metadata
ADDED
@@ -0,0 +1,57 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: pzingg-hoptoad_notifier
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.1.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Thoughtbot
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2009-02-19 00:00:00 -08:00
|
13
|
+
default_executable:
|
14
|
+
dependencies: []
|
15
|
+
|
16
|
+
description: Rails plugin that reports exceptions to Hoptoad.
|
17
|
+
email: info@thoughtbot.com
|
18
|
+
executables: []
|
19
|
+
|
20
|
+
extensions: []
|
21
|
+
|
22
|
+
extra_rdoc_files: []
|
23
|
+
|
24
|
+
files:
|
25
|
+
- INSTALL
|
26
|
+
- lib/hoptoad_notifier.rb
|
27
|
+
- Rakefile
|
28
|
+
- README
|
29
|
+
- tasks/hoptoad_notifier_tasks.rake
|
30
|
+
has_rdoc: true
|
31
|
+
homepage: http://github.com/thoughtbot/hoptoad_notifier
|
32
|
+
post_install_message:
|
33
|
+
rdoc_options: []
|
34
|
+
|
35
|
+
require_paths:
|
36
|
+
- lib
|
37
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
38
|
+
requirements:
|
39
|
+
- - ">="
|
40
|
+
- !ruby/object:Gem::Version
|
41
|
+
version: "0"
|
42
|
+
version:
|
43
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: "0"
|
48
|
+
version:
|
49
|
+
requirements: []
|
50
|
+
|
51
|
+
rubyforge_project:
|
52
|
+
rubygems_version: 1.2.0
|
53
|
+
signing_key:
|
54
|
+
specification_version: 2
|
55
|
+
summary: Rails plugin that reports exceptions to Hoptoad.
|
56
|
+
test_files:
|
57
|
+
- test/hoptoad_notifier_test.rb
|