thoughtbot-hoptoad_notifier 1.1
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/INSTALL +60 -0
- data/README +165 -0
- data/Rakefile +22 -0
- data/lib/hoptoad_notifier.rb +322 -0
- data/tasks/hoptoad_notifier_tasks.rake +55 -0
- data/test/hoptoad_notifier_test.rb +527 -0
- metadata +57 -0
data/INSTALL
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
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
|
+
Then, to enable hoptoad in your appication, include this code...
|
|
43
|
+
|
|
44
|
+
include HoptoadNotifier::Catcher
|
|
45
|
+
|
|
46
|
+
...at the top of your ApplicationController, and all exceptions will be logged
|
|
47
|
+
to Hoptoad where they can be aggregated, filtered, sorted, analyzed, massaged,
|
|
48
|
+
and searched.
|
|
49
|
+
|
|
50
|
+
** NOTE FOR RAILS 1.2.* USERS: **
|
|
51
|
+
You will need to copy the hoptoad_notifier_tasks.rake file into your
|
|
52
|
+
RAILS_ROOT/lib/tasks directory in order for the following to work:
|
|
53
|
+
|
|
54
|
+
You can test that hoptoad is working in your production environment by using
|
|
55
|
+
this rake task (from RAILS_ROOT):
|
|
56
|
+
|
|
57
|
+
rake hoptoad:test
|
|
58
|
+
|
|
59
|
+
If everything is configured properly, that task will send a notice to hoptoad
|
|
60
|
+
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,55 @@
|
|
|
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'
|
|
6
|
+
|
|
7
|
+
request = ActionController::TestRequest.new({
|
|
8
|
+
'action' => 'verify',
|
|
9
|
+
'controller' => 'hoptoad_verification',
|
|
10
|
+
'_method' => 'GET'
|
|
11
|
+
})
|
|
12
|
+
|
|
13
|
+
response = ActionController::TestResponse.new
|
|
14
|
+
|
|
15
|
+
class HoptoadTestingException < RuntimeError; end
|
|
16
|
+
|
|
17
|
+
unless ApplicationController.ancestors.include? HoptoadNotifier::Catcher
|
|
18
|
+
puts "You have not included HoptoadNotifier::Catcher in your ApplicationController!"
|
|
19
|
+
exit
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
puts 'Setting up the Controller.'
|
|
23
|
+
class ApplicationController
|
|
24
|
+
# This is to bypass any filters that may prevent access to the action.
|
|
25
|
+
prepend_before_filter :test_hoptoad
|
|
26
|
+
def test_hoptoad
|
|
27
|
+
puts "Raising '#{exception_class.name}' to simulate application failure."
|
|
28
|
+
raise exception_class.new, 'Testing hoptoad via "rake hoptoad:test". If you can see this, it works.'
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def rescue_action exception
|
|
32
|
+
rescue_action_in_public exception
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def public_environment?
|
|
36
|
+
true
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Ensure we actually have an action to go to.
|
|
40
|
+
def verify; end
|
|
41
|
+
|
|
42
|
+
def exception_class
|
|
43
|
+
exception_name = ENV['EXCEPTION'] || "HoptoadTestingException"
|
|
44
|
+
Object.const_get(exception_name)
|
|
45
|
+
rescue
|
|
46
|
+
Object.const_set(exception_name, Class.new(Exception))
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
puts 'Processing request.'
|
|
51
|
+
class HoptoadVerificationController < ApplicationController; end
|
|
52
|
+
HoptoadVerificationController.new.process(request, response)
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
@@ -0,0 +1,527 @@
|
|
|
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 File.join(File.dirname(__FILE__), "..", "lib", "hoptoad_notifier")
|
|
10
|
+
|
|
11
|
+
RAILS_ROOT = File.join( File.dirname(__FILE__), "rails_root" )
|
|
12
|
+
RAILS_ENV = "test"
|
|
13
|
+
|
|
14
|
+
class HoptoadController < ActionController::Base
|
|
15
|
+
def rescue_action e
|
|
16
|
+
raise e
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def do_raise
|
|
20
|
+
raise "Hoptoad"
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def do_not_raise
|
|
24
|
+
render :text => "Success"
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def do_raise_ignored
|
|
28
|
+
raise ActiveRecord::RecordNotFound.new("404")
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def do_raise_not_ignored
|
|
32
|
+
raise ActiveRecord::StatementInvalid.new("Statement invalid")
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def manual_notify
|
|
36
|
+
notify_hoptoad(Exception.new)
|
|
37
|
+
render :text => "Success"
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def manual_notify_ignored
|
|
41
|
+
notify_hoptoad(ActiveRecord::RecordNotFound.new("404"))
|
|
42
|
+
render :text => "Success"
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
class HoptoadNotifierTest < Test::Unit::TestCase
|
|
47
|
+
def request(action = nil, method = :get)
|
|
48
|
+
@request = ActionController::TestRequest.new({
|
|
49
|
+
"controller" => "hoptoad",
|
|
50
|
+
"action" => action ? action.to_s : "",
|
|
51
|
+
"_method" => method.to_s
|
|
52
|
+
})
|
|
53
|
+
@response = ActionController::TestResponse.new
|
|
54
|
+
@controller.process(@request, @response)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
context "Hoptoad inclusion" do
|
|
58
|
+
should "be able to occur even outside Rails controllers" do
|
|
59
|
+
assert_nothing_raised do
|
|
60
|
+
class MyHoptoad
|
|
61
|
+
include HoptoadNotifier::Catcher
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
my = MyHoptoad.new
|
|
65
|
+
assert my.respond_to?(:notify_hoptoad)
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
context "HoptoadNotifier configuration" do
|
|
70
|
+
setup do
|
|
71
|
+
@controller = HoptoadController.new
|
|
72
|
+
class ::HoptoadController
|
|
73
|
+
include HoptoadNotifier::Catcher
|
|
74
|
+
def rescue_action e
|
|
75
|
+
rescue_action_in_public e
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
assert @controller.methods.include?("notify_hoptoad")
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
should "be done with a block" do
|
|
82
|
+
HoptoadNotifier.configure do |config|
|
|
83
|
+
config.host = "host"
|
|
84
|
+
config.port = 3333
|
|
85
|
+
config.secure = true
|
|
86
|
+
config.api_key = "1234567890abcdef"
|
|
87
|
+
config.ignore << [ RuntimeError ]
|
|
88
|
+
config.proxy_host = 'proxyhost1'
|
|
89
|
+
config.proxy_port = '80'
|
|
90
|
+
config.proxy_user = 'user'
|
|
91
|
+
config.proxy_pass = 'secret'
|
|
92
|
+
config.http_open_timeout = 2
|
|
93
|
+
config.http_read_timeout = 5
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
assert_equal "host", HoptoadNotifier.host
|
|
97
|
+
assert_equal 3333, HoptoadNotifier.port
|
|
98
|
+
assert_equal true, HoptoadNotifier.secure
|
|
99
|
+
assert_equal "1234567890abcdef", HoptoadNotifier.api_key
|
|
100
|
+
assert_equal 'proxyhost1', HoptoadNotifier.proxy_host
|
|
101
|
+
assert_equal '80', HoptoadNotifier.proxy_port
|
|
102
|
+
assert_equal 'user', HoptoadNotifier.proxy_user
|
|
103
|
+
assert_equal 'secret', HoptoadNotifier.proxy_pass
|
|
104
|
+
assert_equal 2, HoptoadNotifier.http_open_timeout
|
|
105
|
+
assert_equal 5, HoptoadNotifier.http_read_timeout
|
|
106
|
+
assert_equal (HoptoadNotifier::IGNORE_DEFAULT + [RuntimeError]), HoptoadNotifier.ignore
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
should "set a default host" do
|
|
110
|
+
HoptoadNotifier.instance_variable_set("@host",nil)
|
|
111
|
+
assert_equal "hoptoadapp.com", HoptoadNotifier.host
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
should "add filters to the backtrace_filters" do
|
|
115
|
+
assert_difference "HoptoadNotifier.backtrace_filters.length" do
|
|
116
|
+
HoptoadNotifier.configure do |config|
|
|
117
|
+
config.filter_backtrace do |line|
|
|
118
|
+
line = "1234"
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
assert_equal %w( 1234 1234 ), @controller.send(:clean_hoptoad_backtrace, %w( foo bar ))
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
should "use standard rails logging filters on params and env" do
|
|
127
|
+
::HoptoadController.class_eval do
|
|
128
|
+
filter_parameter_logging :ghi
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
expected = {"notice" => {"request" => {"params" => {"abc" => "123", "def" => "456", "ghi" => "[FILTERED]"}},
|
|
132
|
+
"environment" => {"abc" => "123", "ghi" => "[FILTERED]"}}}
|
|
133
|
+
notice = {"notice" => {"request" => {"params" => {"abc" => "123", "def" => "456", "ghi" => "789"}},
|
|
134
|
+
"environment" => {"abc" => "123", "ghi" => "789"}}}
|
|
135
|
+
assert @controller.respond_to?(:filter_parameters)
|
|
136
|
+
assert_equal( expected[:notice], @controller.send(:clean_notice, notice)[:notice] )
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
should "add filters to the params filters" do
|
|
140
|
+
assert_difference "HoptoadNotifier.params_filters.length", 2 do
|
|
141
|
+
HoptoadNotifier.configure do |config|
|
|
142
|
+
config.params_filters << "abc"
|
|
143
|
+
config.params_filters << "def"
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
assert HoptoadNotifier.params_filters.include?( "abc" )
|
|
148
|
+
assert HoptoadNotifier.params_filters.include?( "def" )
|
|
149
|
+
|
|
150
|
+
assert_equal( {:abc => "[FILTERED]", :def => "[FILTERED]", :ghi => "789"},
|
|
151
|
+
@controller.send(:clean_hoptoad_params, :abc => "123", :def => "456", :ghi => "789" ) )
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
should "add filters to the environment filters" do
|
|
155
|
+
assert_difference "HoptoadNotifier.environment_filters.length", 2 do
|
|
156
|
+
HoptoadNotifier.configure do |config|
|
|
157
|
+
config.environment_filters << "secret"
|
|
158
|
+
config.environment_filters << "supersecret"
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
assert HoptoadNotifier.environment_filters.include?( "secret" )
|
|
163
|
+
assert HoptoadNotifier.environment_filters.include?( "supersecret" )
|
|
164
|
+
|
|
165
|
+
assert_equal( {:secret => "[FILTERED]", :supersecret => "[FILTERED]", :ghi => "789"},
|
|
166
|
+
@controller.send(:clean_hoptoad_environment, :secret => "123", :supersecret => "456", :ghi => "789" ) )
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
should "have at default ignored exceptions" do
|
|
170
|
+
assert HoptoadNotifier::IGNORE_DEFAULT.any?
|
|
171
|
+
end
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
context "The hoptoad test controller" do
|
|
175
|
+
setup do
|
|
176
|
+
@controller = ::HoptoadController.new
|
|
177
|
+
class ::HoptoadController
|
|
178
|
+
def rescue_action e
|
|
179
|
+
raise e
|
|
180
|
+
end
|
|
181
|
+
end
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
context "with no notifier catcher" do
|
|
185
|
+
should "not prevent raises" do
|
|
186
|
+
assert_raises RuntimeError do
|
|
187
|
+
request("do_raise")
|
|
188
|
+
end
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
should "allow a non-raising action to complete" do
|
|
192
|
+
assert_nothing_raised do
|
|
193
|
+
request("do_not_raise")
|
|
194
|
+
end
|
|
195
|
+
end
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
context "with the notifier installed" do
|
|
199
|
+
setup do
|
|
200
|
+
class ::HoptoadController
|
|
201
|
+
include HoptoadNotifier::Catcher
|
|
202
|
+
def rescue_action e
|
|
203
|
+
rescue_action_in_public e
|
|
204
|
+
end
|
|
205
|
+
end
|
|
206
|
+
HoptoadNotifier.ignore_only = HoptoadNotifier::IGNORE_DEFAULT
|
|
207
|
+
@controller.stubs(:public_environment?).returns(true)
|
|
208
|
+
@controller.stubs(:send_to_hoptoad)
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
should "have inserted its methods into the controller" do
|
|
212
|
+
assert @controller.methods.include?("inform_hoptoad")
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
should "prevent raises and send the error to hoptoad" do
|
|
216
|
+
@controller.expects(:notify_hoptoad)
|
|
217
|
+
@controller.expects(:rescue_action_in_public_without_hoptoad)
|
|
218
|
+
assert_nothing_raised do
|
|
219
|
+
request("do_raise")
|
|
220
|
+
end
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
should "allow a non-raising action to complete" do
|
|
224
|
+
assert_nothing_raised do
|
|
225
|
+
request("do_not_raise")
|
|
226
|
+
end
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
should "allow manual sending of exceptions" do
|
|
230
|
+
@controller.expects(:notify_hoptoad)
|
|
231
|
+
@controller.expects(:rescue_action_in_public_without_hoptoad).never
|
|
232
|
+
assert_nothing_raised do
|
|
233
|
+
request("manual_notify")
|
|
234
|
+
end
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
should "disable manual sending of exceptions in a non-public (development or test) environment" do
|
|
238
|
+
@controller.stubs(:public_environment?).returns(false)
|
|
239
|
+
@controller.expects(:send_to_hoptoad).never
|
|
240
|
+
@controller.expects(:rescue_action_in_public_without_hoptoad).never
|
|
241
|
+
assert_nothing_raised do
|
|
242
|
+
request("manual_notify")
|
|
243
|
+
end
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
should "send even ignored exceptions if told manually" do
|
|
247
|
+
@controller.expects(:notify_hoptoad)
|
|
248
|
+
@controller.expects(:rescue_action_in_public_without_hoptoad).never
|
|
249
|
+
assert_nothing_raised do
|
|
250
|
+
request("manual_notify_ignored")
|
|
251
|
+
end
|
|
252
|
+
end
|
|
253
|
+
|
|
254
|
+
should "ignore default exceptions" do
|
|
255
|
+
@controller.expects(:notify_hoptoad).never
|
|
256
|
+
@controller.expects(:rescue_action_in_public_without_hoptoad)
|
|
257
|
+
assert_nothing_raised do
|
|
258
|
+
request("do_raise_ignored")
|
|
259
|
+
end
|
|
260
|
+
end
|
|
261
|
+
|
|
262
|
+
should "filter non-serializable data" do
|
|
263
|
+
File.open(__FILE__) do |file|
|
|
264
|
+
assert_equal( {:ghi => "789"},
|
|
265
|
+
@controller.send(:clean_non_serializable_data, :ghi => "789", :class => Class.new, :file => file) )
|
|
266
|
+
end
|
|
267
|
+
end
|
|
268
|
+
|
|
269
|
+
should "apply all params, environment and technical filters" do
|
|
270
|
+
params_hash = {:abc => 123}
|
|
271
|
+
environment_hash = {:def => 456}
|
|
272
|
+
backtrace_data = :backtrace_data
|
|
273
|
+
|
|
274
|
+
raw_notice = {:request => {:params => params_hash},
|
|
275
|
+
:environment => environment_hash,
|
|
276
|
+
:backtrace => backtrace_data}
|
|
277
|
+
|
|
278
|
+
processed_notice = {:backtrace => :backtrace_data,
|
|
279
|
+
:request => {:params => :params_data},
|
|
280
|
+
:environment => :environment_data}
|
|
281
|
+
|
|
282
|
+
@controller.expects(:clean_hoptoad_backtrace).with(backtrace_data).returns(:backtrace_data)
|
|
283
|
+
@controller.expects(:clean_hoptoad_params).with(params_hash).returns(:params_data)
|
|
284
|
+
@controller.expects(:clean_hoptoad_environment).with(environment_hash).returns(:environment_data)
|
|
285
|
+
@controller.expects(:clean_non_serializable_data).with(processed_notice).returns(:serializable_data)
|
|
286
|
+
|
|
287
|
+
assert_equal(:serializable_data, @controller.send(:clean_notice, raw_notice))
|
|
288
|
+
end
|
|
289
|
+
|
|
290
|
+
context "and configured to ignore additional exceptions" do
|
|
291
|
+
setup do
|
|
292
|
+
HoptoadNotifier.ignore << ActiveRecord::StatementInvalid
|
|
293
|
+
end
|
|
294
|
+
|
|
295
|
+
should "still ignore default exceptions" do
|
|
296
|
+
@controller.expects(:notify_hoptoad).never
|
|
297
|
+
@controller.expects(:rescue_action_in_public_without_hoptoad)
|
|
298
|
+
assert_nothing_raised do
|
|
299
|
+
request("do_raise_ignored")
|
|
300
|
+
end
|
|
301
|
+
end
|
|
302
|
+
|
|
303
|
+
should "ignore specified exceptions" do
|
|
304
|
+
@controller.expects(:notify_hoptoad).never
|
|
305
|
+
@controller.expects(:rescue_action_in_public_without_hoptoad)
|
|
306
|
+
assert_nothing_raised do
|
|
307
|
+
request("do_raise_not_ignored")
|
|
308
|
+
end
|
|
309
|
+
end
|
|
310
|
+
|
|
311
|
+
should "not ignore unspecified, non-default exceptions" do
|
|
312
|
+
@controller.expects(:notify_hoptoad)
|
|
313
|
+
@controller.expects(:rescue_action_in_public_without_hoptoad)
|
|
314
|
+
assert_nothing_raised do
|
|
315
|
+
request("do_raise")
|
|
316
|
+
end
|
|
317
|
+
end
|
|
318
|
+
end
|
|
319
|
+
|
|
320
|
+
context "and configured to ignore only certain exceptions" do
|
|
321
|
+
setup do
|
|
322
|
+
HoptoadNotifier.ignore_only = [ActiveRecord::StatementInvalid]
|
|
323
|
+
end
|
|
324
|
+
|
|
325
|
+
should "no longer ignore default exceptions" do
|
|
326
|
+
@controller.expects(:notify_hoptoad)
|
|
327
|
+
@controller.expects(:rescue_action_in_public_without_hoptoad)
|
|
328
|
+
assert_nothing_raised do
|
|
329
|
+
request("do_raise_ignored")
|
|
330
|
+
end
|
|
331
|
+
end
|
|
332
|
+
|
|
333
|
+
should "ignore specified exceptions" do
|
|
334
|
+
@controller.expects(:notify_hoptoad).never
|
|
335
|
+
@controller.expects(:rescue_action_in_public_without_hoptoad)
|
|
336
|
+
assert_nothing_raised do
|
|
337
|
+
request("do_raise_not_ignored")
|
|
338
|
+
end
|
|
339
|
+
end
|
|
340
|
+
|
|
341
|
+
should "not ignore unspecified, non-default exceptions" do
|
|
342
|
+
@controller.expects(:notify_hoptoad)
|
|
343
|
+
@controller.expects(:rescue_action_in_public_without_hoptoad)
|
|
344
|
+
assert_nothing_raised do
|
|
345
|
+
request("do_raise")
|
|
346
|
+
end
|
|
347
|
+
end
|
|
348
|
+
end
|
|
349
|
+
end
|
|
350
|
+
end
|
|
351
|
+
|
|
352
|
+
context "Sending a notice" do
|
|
353
|
+
context "with an exception" do
|
|
354
|
+
setup do
|
|
355
|
+
@sender = HoptoadNotifier::Sender.new
|
|
356
|
+
@backtrace = caller
|
|
357
|
+
@exception = begin
|
|
358
|
+
raise
|
|
359
|
+
rescue => caught_exception
|
|
360
|
+
caught_exception
|
|
361
|
+
end
|
|
362
|
+
@options = {:error_message => "123",
|
|
363
|
+
:backtrace => @backtrace}
|
|
364
|
+
HoptoadNotifier.instance_variable_set("@backtrace_filters", [])
|
|
365
|
+
HoptoadNotifier::Sender.expects(:new).returns(@sender)
|
|
366
|
+
@sender.stubs(:public_environment?).returns(true)
|
|
367
|
+
end
|
|
368
|
+
|
|
369
|
+
context "when using an HTTP Proxy" do
|
|
370
|
+
setup do
|
|
371
|
+
@body = 'body'
|
|
372
|
+
@response = stub(:body => @body)
|
|
373
|
+
@http = stub(:post => @response, :read_timeout= => nil, :open_timeout= => nil, :use_ssl= => nil)
|
|
374
|
+
@sender.stubs(:logger).returns(stub(:error => nil, :info => nil))
|
|
375
|
+
@proxy = stub
|
|
376
|
+
@proxy.stubs(:new).returns(@http)
|
|
377
|
+
|
|
378
|
+
HoptoadNotifier.port = nil
|
|
379
|
+
HoptoadNotifier.host = nil
|
|
380
|
+
HoptoadNotifier.secure = false
|
|
381
|
+
|
|
382
|
+
Net::HTTP.expects(:Proxy).with(
|
|
383
|
+
HoptoadNotifier.proxy_host,
|
|
384
|
+
HoptoadNotifier.proxy_port,
|
|
385
|
+
HoptoadNotifier.proxy_user,
|
|
386
|
+
HoptoadNotifier.proxy_pass
|
|
387
|
+
).returns(@proxy)
|
|
388
|
+
end
|
|
389
|
+
|
|
390
|
+
context "on notify" do
|
|
391
|
+
setup { HoptoadNotifier.notify(@exception) }
|
|
392
|
+
|
|
393
|
+
before_should "post to Hoptoad" do
|
|
394
|
+
url = "http://hoptoadapp.com:80/notices/"
|
|
395
|
+
uri = URI.parse(url)
|
|
396
|
+
URI.expects(:parse).with(url).returns(uri)
|
|
397
|
+
@http.expects(:post).with(uri.path, anything, anything).returns(@response)
|
|
398
|
+
end
|
|
399
|
+
end
|
|
400
|
+
end
|
|
401
|
+
|
|
402
|
+
context "when stubbing out Net::HTTP" do
|
|
403
|
+
setup do
|
|
404
|
+
@body = 'body'
|
|
405
|
+
@response = stub(:body => @body)
|
|
406
|
+
@http = stub(:post => @response, :read_timeout= => nil, :open_timeout= => nil, :use_ssl= => nil)
|
|
407
|
+
@sender.stubs(:logger).returns(stub(:error => nil, :info => nil))
|
|
408
|
+
Net::HTTP.stubs(:new).returns(@http)
|
|
409
|
+
HoptoadNotifier.port = nil
|
|
410
|
+
HoptoadNotifier.host = nil
|
|
411
|
+
HoptoadNotifier.proxy_host = nil
|
|
412
|
+
end
|
|
413
|
+
|
|
414
|
+
context "on notify" do
|
|
415
|
+
setup { HoptoadNotifier.notify(@exception) }
|
|
416
|
+
|
|
417
|
+
before_should "post to the right url for non-ssl" do
|
|
418
|
+
HoptoadNotifier.secure = false
|
|
419
|
+
url = "http://hoptoadapp.com:80/notices/"
|
|
420
|
+
uri = URI.parse(url)
|
|
421
|
+
URI.expects(:parse).with(url).returns(uri)
|
|
422
|
+
@http.expects(:post).with(uri.path, anything, anything).returns(@response)
|
|
423
|
+
end
|
|
424
|
+
|
|
425
|
+
before_should "post to the right path" do
|
|
426
|
+
@http.expects(:post).with("/notices/", anything, anything).returns(@response)
|
|
427
|
+
end
|
|
428
|
+
|
|
429
|
+
before_should "call send_to_hoptoad" do
|
|
430
|
+
@sender.expects(:send_to_hoptoad)
|
|
431
|
+
end
|
|
432
|
+
|
|
433
|
+
before_should "default the open timeout to 2 seconds" do
|
|
434
|
+
HoptoadNotifier.http_open_timeout = nil
|
|
435
|
+
@http.expects(:open_timeout=).with(2)
|
|
436
|
+
end
|
|
437
|
+
|
|
438
|
+
before_should "default the read timeout to 5 seconds" do
|
|
439
|
+
HoptoadNotifier.http_read_timeout = nil
|
|
440
|
+
@http.expects(:read_timeout=).with(5)
|
|
441
|
+
end
|
|
442
|
+
|
|
443
|
+
before_should "allow override of the open timeout" do
|
|
444
|
+
HoptoadNotifier.http_open_timeout = 4
|
|
445
|
+
@http.expects(:open_timeout=).with(4)
|
|
446
|
+
end
|
|
447
|
+
|
|
448
|
+
before_should "allow override of the read timeout" do
|
|
449
|
+
HoptoadNotifier.http_read_timeout = 10
|
|
450
|
+
@http.expects(:read_timeout=).with(10)
|
|
451
|
+
end
|
|
452
|
+
|
|
453
|
+
before_should "connect to the right port for ssl" do
|
|
454
|
+
HoptoadNotifier.secure = true
|
|
455
|
+
Net::HTTP.expects(:new).with("hoptoadapp.com", 443).returns(@http)
|
|
456
|
+
end
|
|
457
|
+
|
|
458
|
+
before_should "connect to the right port for non-ssl" do
|
|
459
|
+
HoptoadNotifier.secure = false
|
|
460
|
+
Net::HTTP.expects(:new).with("hoptoadapp.com", 80).returns(@http)
|
|
461
|
+
end
|
|
462
|
+
|
|
463
|
+
before_should "use ssl if secure" do
|
|
464
|
+
HoptoadNotifier.secure = true
|
|
465
|
+
HoptoadNotifier.host = 'example.org'
|
|
466
|
+
Net::HTTP.expects(:new).with('example.org', 443).returns(@http)
|
|
467
|
+
end
|
|
468
|
+
|
|
469
|
+
before_should "not use ssl if not secure" do
|
|
470
|
+
HoptoadNotifier.secure = nil
|
|
471
|
+
HoptoadNotifier.host = 'example.org'
|
|
472
|
+
Net::HTTP.expects(:new).with('example.org', 80).returns(@http)
|
|
473
|
+
end
|
|
474
|
+
end
|
|
475
|
+
end
|
|
476
|
+
|
|
477
|
+
should "send as if it were a normally caught exception" do
|
|
478
|
+
@sender.expects(:notify_hoptoad).with(@exception)
|
|
479
|
+
HoptoadNotifier.notify(@exception)
|
|
480
|
+
end
|
|
481
|
+
|
|
482
|
+
should "make sure the exception is munged into a hash" do
|
|
483
|
+
options = HoptoadNotifier.default_notice_options.merge({
|
|
484
|
+
:backtrace => @exception.backtrace,
|
|
485
|
+
:environment => ENV.to_hash,
|
|
486
|
+
:error_class => @exception.class.name,
|
|
487
|
+
:error_message => "#{@exception.class.name}: #{@exception.message}",
|
|
488
|
+
:api_key => HoptoadNotifier.api_key,
|
|
489
|
+
})
|
|
490
|
+
@sender.expects(:send_to_hoptoad).with(:notice => options)
|
|
491
|
+
HoptoadNotifier.notify(@exception)
|
|
492
|
+
end
|
|
493
|
+
|
|
494
|
+
should "parse massive one-line exceptions into multiple lines" do
|
|
495
|
+
@original_backtrace = "one big line\n separated\n by new lines\nand some spaces"
|
|
496
|
+
@expected_backtrace = ["one big line", "separated", "by new lines", "and some spaces"]
|
|
497
|
+
@exception.set_backtrace [@original_backtrace]
|
|
498
|
+
|
|
499
|
+
options = HoptoadNotifier.default_notice_options.merge({
|
|
500
|
+
:backtrace => @expected_backtrace,
|
|
501
|
+
:environment => ENV.to_hash,
|
|
502
|
+
:error_class => @exception.class.name,
|
|
503
|
+
:error_message => "#{@exception.class.name}: #{@exception.message}",
|
|
504
|
+
:api_key => HoptoadNotifier.api_key,
|
|
505
|
+
})
|
|
506
|
+
|
|
507
|
+
@sender.expects(:send_to_hoptoad).with(:notice => options)
|
|
508
|
+
HoptoadNotifier.notify(@exception)
|
|
509
|
+
end
|
|
510
|
+
end
|
|
511
|
+
|
|
512
|
+
context "without an exception" do
|
|
513
|
+
setup do
|
|
514
|
+
@sender = HoptoadNotifier::Sender.new
|
|
515
|
+
@backtrace = caller
|
|
516
|
+
@options = {:error_message => "123",
|
|
517
|
+
:backtrace => @backtrace}
|
|
518
|
+
HoptoadNotifier::Sender.expects(:new).returns(@sender)
|
|
519
|
+
end
|
|
520
|
+
|
|
521
|
+
should "send sensible defaults" do
|
|
522
|
+
@sender.expects(:notify_hoptoad).with(@options)
|
|
523
|
+
HoptoadNotifier.notify(:error_message => "123", :backtrace => @backtrace)
|
|
524
|
+
end
|
|
525
|
+
end
|
|
526
|
+
end
|
|
527
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: thoughtbot-hoptoad_notifier
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: "1.1"
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Thoughtbot
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: bin
|
|
10
|
+
cert_chain: []
|
|
11
|
+
|
|
12
|
+
date: 2008-12-31 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
|