errornot_notifier 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (41) hide show
  1. data/INSTALL +25 -0
  2. data/MIT-LICENSE +22 -0
  3. data/README.rdoc +289 -0
  4. data/Rakefile +124 -0
  5. data/SUPPORTED_RAILS_VERSIONS +8 -0
  6. data/TESTING.rdoc +8 -0
  7. data/generators/hoptoad/hoptoad_generator.rb +55 -0
  8. data/generators/hoptoad/lib/insert_commands.rb +34 -0
  9. data/generators/hoptoad/lib/rake_commands.rb +24 -0
  10. data/generators/hoptoad/templates/capistrano_hook.rb +6 -0
  11. data/generators/hoptoad/templates/hoptoad_notifier_tasks.rake +5 -0
  12. data/generators/hoptoad/templates/initializer.rb +7 -0
  13. data/lib/hoptoad_notifier/backtrace.rb +99 -0
  14. data/lib/hoptoad_notifier/capistrano.rb +20 -0
  15. data/lib/hoptoad_notifier/configuration.rb +232 -0
  16. data/lib/hoptoad_notifier/notice.rb +287 -0
  17. data/lib/hoptoad_notifier/rack.rb +40 -0
  18. data/lib/hoptoad_notifier/rails/action_controller_catcher.rb +29 -0
  19. data/lib/hoptoad_notifier/rails/controller_methods.rb +59 -0
  20. data/lib/hoptoad_notifier/rails/error_lookup.rb +33 -0
  21. data/lib/hoptoad_notifier/rails.rb +37 -0
  22. data/lib/hoptoad_notifier/sender.rb +85 -0
  23. data/lib/hoptoad_notifier/tasks.rb +97 -0
  24. data/lib/hoptoad_notifier/version.rb +3 -0
  25. data/lib/hoptoad_notifier.rb +146 -0
  26. data/lib/hoptoad_tasks.rb +37 -0
  27. data/lib/templates/rescue.erb +91 -0
  28. data/rails/init.rb +1 -0
  29. data/script/integration_test.rb +38 -0
  30. data/test/backtrace_test.rb +118 -0
  31. data/test/catcher_test.rb +300 -0
  32. data/test/configuration_test.rb +208 -0
  33. data/test/helper.rb +232 -0
  34. data/test/hoptoad_tasks_test.rb +138 -0
  35. data/test/logger_test.rb +85 -0
  36. data/test/notice_test.rb +395 -0
  37. data/test/notifier_test.rb +222 -0
  38. data/test/rack_test.rb +58 -0
  39. data/test/rails_initializer_test.rb +36 -0
  40. data/test/sender_test.rb +123 -0
  41. metadata +164 -0
@@ -0,0 +1,99 @@
1
+ module HoptoadNotifier
2
+ # Front end to parsing the backtrace for each notice
3
+ class Backtrace
4
+
5
+ # Handles backtrace parsing line by line
6
+ class Line
7
+
8
+ INPUT_FORMAT = %r{^([^:]+):(\d+)(?::in `([^']+)')?$}.freeze
9
+
10
+ # The file portion of the line (such as app/models/user.rb)
11
+ attr_reader :file
12
+
13
+ # The line number portion of the line
14
+ attr_reader :number
15
+
16
+ # The method of the line (such as index)
17
+ attr_reader :method
18
+
19
+ # Parses a single line of a given backtrace
20
+ # @param [String] unparsed_line The raw line from +caller+ or some backtrace
21
+ # @return [Line] The parsed backtrace line
22
+ def self.parse(unparsed_line)
23
+ _, file, number, method = unparsed_line.match(INPUT_FORMAT).to_a
24
+ new(file, number, method)
25
+ end
26
+
27
+ def initialize(file, number, method)
28
+ self.file = file
29
+ self.number = number
30
+ self.method = method
31
+ end
32
+
33
+ # Reconstructs the line in a readable fashion
34
+ def to_s
35
+ "#{file}:#{number}:in `#{method}'"
36
+ end
37
+
38
+ def ==(other)
39
+ to_s == other.to_s
40
+ end
41
+
42
+ def inspect
43
+ "<Line:#{to_s}>"
44
+ end
45
+
46
+ private
47
+
48
+ attr_writer :file, :number, :method
49
+ end
50
+
51
+ # holder for an Array of Backtrace::Line instances
52
+ attr_reader :lines
53
+
54
+ def self.parse(ruby_backtrace, opts = {})
55
+ ruby_lines = split_multiline_backtrace(ruby_backtrace)
56
+
57
+ filters = opts[:filters] || []
58
+ filtered_lines = ruby_lines.to_a.map do |line|
59
+ filters.inject(line) do |line, proc|
60
+ proc.call(line)
61
+ end
62
+ end.compact
63
+
64
+ lines = filtered_lines.collect do |unparsed_line|
65
+ Line.parse(unparsed_line)
66
+ end
67
+
68
+ instance = new(lines)
69
+ end
70
+
71
+ def initialize(lines)
72
+ self.lines = lines
73
+ end
74
+
75
+ def inspect
76
+ "<Backtrace: " + lines.collect { |line| line.inspect }.join(", ") + ">"
77
+ end
78
+
79
+ def ==(other)
80
+ if other.respond_to?(:lines)
81
+ lines == other.lines
82
+ else
83
+ false
84
+ end
85
+ end
86
+
87
+ private
88
+
89
+ attr_writer :lines
90
+
91
+ def self.split_multiline_backtrace(backtrace)
92
+ if backtrace.to_a.size == 1
93
+ backtrace.to_a.first.split(/\n\s*/)
94
+ else
95
+ backtrace
96
+ end
97
+ end
98
+ end
99
+ end
@@ -0,0 +1,20 @@
1
+ # Defines deploy:notify_hoptoad which will send information about the deploy to Hoptoad.
2
+
3
+ Capistrano::Configuration.instance(:must_exist).load do
4
+ after "deploy", "deploy:notify_hoptoad"
5
+ after "deploy:migrations", "deploy:notify_hoptoad"
6
+
7
+ namespace :deploy do
8
+ desc "Notify Hoptoad of the deployment"
9
+ task :notify_hoptoad, :except => { :no_release => true } do
10
+ rails_env = fetch(:hoptoad_env, fetch(:rails_env, "production"))
11
+ local_user = ENV['USER'] || ENV['USERNAME']
12
+ executable = RUBY_PLATFORM.downcase.include?('mswin') ? 'rake.bat' : 'rake'
13
+ notify_command = "#{executable} hoptoad:deploy TO=#{rails_env} REVISION=#{current_revision} REPO=#{repository} USER=#{local_user}"
14
+ notify_command << " API_KEY=#{ENV['API_KEY']}" if ENV['API_KEY']
15
+ puts "Notifying Hoptoad of Deploy (#{notify_command})"
16
+ `#{notify_command}`
17
+ puts "Hoptoad Notification Complete."
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,232 @@
1
+ module HoptoadNotifier
2
+ # Used to set up and modify settings for the notifier.
3
+ class Configuration
4
+
5
+ OPTIONS = [:api_key, :backtrace_filters, :development_environments,
6
+ :development_lookup, :environment_name, :host,
7
+ :http_open_timeout, :http_read_timeout, :ignore, :ignore_by_filters,
8
+ :ignore_user_agent, :notifier_name, :notifier_url, :notifier_version,
9
+ :params_filters, :project_root, :port, :protocol, :proxy_host,
10
+ :proxy_pass, :proxy_port, :proxy_user, :secure, :framework].freeze
11
+
12
+ # The API key for your project, found on the project edit form.
13
+ attr_accessor :api_key
14
+
15
+ # The host to connect to (defaults to hoptoadapp.com).
16
+ attr_accessor :host
17
+
18
+ # The port on which your Hoptoad server runs (defaults to 443 for secure
19
+ # connections, 80 for insecure connections).
20
+ attr_accessor :port
21
+
22
+ # +true+ for https connections, +false+ for http connections.
23
+ attr_accessor :secure
24
+
25
+ # The HTTP open timeout in seconds (defaults to 2).
26
+ attr_accessor :http_open_timeout
27
+
28
+ # The HTTP read timeout in seconds (defaults to 5).
29
+ attr_accessor :http_read_timeout
30
+
31
+ # The hostname of your proxy server (if using a proxy)
32
+ attr_accessor :proxy_host
33
+
34
+ # The port of your proxy server (if using a proxy)
35
+ attr_accessor :proxy_port
36
+
37
+ # The username to use when logging into your proxy server (if using a proxy)
38
+ attr_accessor :proxy_user
39
+
40
+ # The password to use when logging into your proxy server (if using a proxy)
41
+ attr_accessor :proxy_pass
42
+
43
+ # A list of parameters that should be filtered out of what is sent to Hoptoad.
44
+ # By default, all "password" attributes will have their contents replaced.
45
+ attr_reader :params_filters
46
+
47
+ # A list of filters for cleaning and pruning the backtrace. See #filter_backtrace.
48
+ attr_reader :backtrace_filters
49
+
50
+ # A list of filters for ignoring exceptions. See #ignore_by_filter.
51
+ attr_reader :ignore_by_filters
52
+
53
+ # A list of exception classes to ignore. The array can be appended to.
54
+ attr_reader :ignore
55
+
56
+ # A list of user agents that are being ignored. The array can be appended to.
57
+ attr_reader :ignore_user_agent
58
+
59
+ # A list of environments in which notifications should not be sent.
60
+ attr_accessor :development_environments
61
+
62
+ # +true+ if you want to check for production errors matching development errors, +false+ otherwise.
63
+ attr_accessor :development_lookup
64
+
65
+ # The name of the environment the application is running in
66
+ attr_accessor :environment_name
67
+
68
+ # The path to the project in which the error occurred, such as the RAILS_ROOT
69
+ attr_accessor :project_root
70
+
71
+ # The name of the notifier library being used to send notifications (such as "Hoptoad Notifier")
72
+ attr_accessor :notifier_name
73
+
74
+ # The version of the notifier library being used to send notifications (such as "1.0.2")
75
+ attr_accessor :notifier_version
76
+
77
+ # The url of the notifier library being used to send notifications
78
+ attr_accessor :notifier_url
79
+
80
+ # The logger used by HoptoadNotifier
81
+ attr_accessor :logger
82
+
83
+ # The framework HoptoadNotifier is configured to use
84
+ attr_accessor :framework
85
+
86
+ DEFAULT_PARAMS_FILTERS = %w(password password_confirmation).freeze
87
+
88
+ DEFAULT_BACKTRACE_FILTERS = [
89
+ lambda { |line|
90
+ if defined?(HoptoadNotifier.configuration.project_root) && HoptoadNotifier.configuration.project_root.to_s != ''
91
+ line.gsub(/#{HoptoadNotifier.configuration.project_root}/, "[PROJECT_ROOT]")
92
+ else
93
+ line
94
+ end
95
+ },
96
+ lambda { |line| line.gsub(/^\.\//, "") },
97
+ lambda { |line|
98
+ if defined?(Gem)
99
+ Gem.path.inject(line) do |line, path|
100
+ line.gsub(/#{path}/, "[GEM_ROOT]")
101
+ end
102
+ end
103
+ },
104
+ lambda { |line| line if line !~ %r{lib/hoptoad_notifier} }
105
+ ].freeze
106
+
107
+ IGNORE_DEFAULT = ['ActiveRecord::RecordNotFound',
108
+ 'ActionController::RoutingError',
109
+ 'ActionController::InvalidAuthenticityToken',
110
+ 'CGI::Session::CookieStore::TamperedWithCookie',
111
+ 'ActionController::UnknownAction']
112
+
113
+ alias_method :secure?, :secure
114
+
115
+ def initialize
116
+ @secure = false
117
+ @host = 'hoptoadapp.com'
118
+ @http_open_timeout = 2
119
+ @http_read_timeout = 5
120
+ @params_filters = DEFAULT_PARAMS_FILTERS.dup
121
+ @backtrace_filters = DEFAULT_BACKTRACE_FILTERS.dup
122
+ @ignore_by_filters = []
123
+ @ignore = IGNORE_DEFAULT.dup
124
+ @ignore_user_agent = []
125
+ @development_environments = %w(development test cucumber)
126
+ @development_lookup = true
127
+ @notifier_name = 'Hoptoad Notifier'
128
+ @notifier_version = VERSION
129
+ @notifier_url = 'http://hoptoadapp.com'
130
+ @framework = 'Standalone'
131
+ end
132
+
133
+ # Takes a block and adds it to the list of backtrace filters. When the filters
134
+ # run, the block will be handed each line of the backtrace and can modify
135
+ # it as necessary.
136
+ #
137
+ # @example
138
+ # config.filter_bracktrace do |line|
139
+ # line.gsub(/^#{Rails.root}/, "[RAILS_ROOT]")
140
+ # end
141
+ #
142
+ # @param [Proc] block The new backtrace filter.
143
+ # @yieldparam [String] line A line in the backtrace.
144
+ def filter_backtrace(&block)
145
+ self.backtrace_filters << block
146
+ end
147
+
148
+ # Takes a block and adds it to the list of ignore filters.
149
+ # When the filters run, the block will be handed the exception.
150
+ # @example
151
+ # config.ignore_by_filter do |exception_data|
152
+ # true if exception_data[:error_class] == "RuntimeError"
153
+ # end
154
+ #
155
+ # @param [Proc] block The new ignore filter
156
+ # @yieldparam [Hash] data The exception data given to +HoptoadNotifier.notify+
157
+ # @yieldreturn [Boolean] If the block returns true the exception will be ignored, otherwise it will be processed by hoptoad.
158
+ def ignore_by_filter(&block)
159
+ self.ignore_by_filters << block
160
+ end
161
+
162
+ # Overrides the list of default ignored errors.
163
+ #
164
+ # @param [Array<Exception>] names A list of exceptions to ignore.
165
+ def ignore_only=(names)
166
+ @ignore = [names].flatten
167
+ end
168
+
169
+ # Overrides the list of default ignored user agents
170
+ #
171
+ # @param [Array<String>] A list of user agents to ignore
172
+ def ignore_user_agent_only=(names)
173
+ @ignore_user_agent = [names].flatten
174
+ end
175
+
176
+ # Allows config options to be read like a hash
177
+ #
178
+ # @param [Symbol] option Key for a given attribute
179
+ def [](option)
180
+ send(option)
181
+ end
182
+
183
+ # Returns a hash of all configurable options
184
+ def to_hash
185
+ OPTIONS.inject({}) do |hash, option|
186
+ hash.merge(option.to_sym => send(option))
187
+ end
188
+ end
189
+
190
+ # Returns a hash of all configurable options merged with +hash+
191
+ #
192
+ # @param [Hash] hash A set of configuration options that will take precedence over the defaults
193
+ def merge(hash)
194
+ to_hash.merge(hash)
195
+ end
196
+
197
+ # Determines if the notifier will send notices.
198
+ # @return [Boolean] Returns +false+ if in a development environment, +true+ otherwise.
199
+ def public?
200
+ !development_environments.include?(environment_name)
201
+ end
202
+
203
+ def port
204
+ @port || default_port
205
+ end
206
+
207
+ def protocol
208
+ if secure?
209
+ 'https'
210
+ else
211
+ 'http'
212
+ end
213
+ end
214
+
215
+ def environment_filters
216
+ warn 'config.environment_filters has been deprecated and has no effect.'
217
+ []
218
+ end
219
+
220
+ private
221
+
222
+ def default_port
223
+ if secure?
224
+ 443
225
+ else
226
+ 80
227
+ end
228
+ end
229
+
230
+ end
231
+
232
+ end
@@ -0,0 +1,287 @@
1
+ module HoptoadNotifier
2
+ class Notice
3
+
4
+ # The exception that caused this notice, if any
5
+ attr_reader :exception
6
+
7
+ # The API key for the project to which this notice should be sent
8
+ attr_reader :api_key
9
+
10
+ # The backtrace from the given exception or hash.
11
+ attr_reader :backtrace
12
+
13
+ # The name of the class of error (such as RuntimeError)
14
+ attr_reader :error_class
15
+
16
+ # The name of the server environment (such as "production")
17
+ attr_reader :environment_name
18
+
19
+ # CGI variables such as HTTP_METHOD
20
+ attr_reader :cgi_data
21
+
22
+ # The message from the exception, or a general description of the error
23
+ attr_reader :error_message
24
+
25
+ # See Configuration#backtrace_filters
26
+ attr_reader :backtrace_filters
27
+
28
+ # See Configuration#params_filters
29
+ attr_reader :params_filters
30
+
31
+ # A hash of parameters from the query string or post body.
32
+ attr_reader :parameters
33
+ alias_method :params, :parameters
34
+
35
+ # The component (if any) which was used in this request (usually the controller)
36
+ attr_reader :component
37
+ alias_method :controller, :component
38
+
39
+ # The action (if any) that was called in this request
40
+ attr_reader :action
41
+
42
+ # A hash of session data from the request
43
+ attr_reader :session_data
44
+
45
+ # The path to the project that caused the error (usually RAILS_ROOT)
46
+ attr_reader :project_root
47
+
48
+ # The URL at which the error occurred (if any)
49
+ attr_reader :url
50
+
51
+ # See Configuration#ignore
52
+ attr_reader :ignore
53
+
54
+ # See Configuration#ignore_by_filters
55
+ attr_reader :ignore_by_filters
56
+
57
+ # The name of the notifier library sending this notice, such as "Hoptoad Notifier"
58
+ attr_reader :notifier_name
59
+
60
+ # The version number of the notifier library sending this notice, such as "2.1.3"
61
+ attr_reader :notifier_version
62
+
63
+ # A URL for more information about the notifier library sending this notice
64
+ attr_reader :notifier_url
65
+
66
+ def initialize(args)
67
+ self.args = args
68
+ self.exception = args[:exception]
69
+ self.api_key = args[:api_key]
70
+ self.project_root = args[:project_root]
71
+ self.url = args[:url] || rack_env(:url)
72
+
73
+ self.notifier_name = args[:notifier_name]
74
+ self.notifier_version = args[:notifier_version]
75
+ self.notifier_url = args[:notifier_url]
76
+
77
+ self.ignore = args[:ignore] || []
78
+ self.ignore_by_filters = args[:ignore_by_filters] || []
79
+ self.backtrace_filters = args[:backtrace_filters] || []
80
+ self.params_filters = args[:params_filters] || []
81
+ self.parameters = args[:parameters] || rack_env(:params) || {}
82
+ self.component = args[:component] || args[:controller]
83
+ self.action = args[:action]
84
+
85
+ self.environment_name = args[:environment_name]
86
+ self.cgi_data = args[:cgi_data] || args[:rack_env]
87
+ self.backtrace = Backtrace.parse(exception_attribute(:backtrace, caller), :filters => self.backtrace_filters)
88
+ self.error_class = exception_attribute(:error_class) {|exception| exception.class.name }
89
+ self.error_message = exception_attribute(:error_message, 'Notification') do |exception|
90
+ "#{exception.class.name}: #{exception.message}"
91
+ end
92
+
93
+ find_session_data
94
+ clean_params
95
+ end
96
+
97
+ # Converts the given notice to XML
98
+ # TODO:
99
+ # ErroNot specific, not transform to XML now,
100
+ # but in Hash. need change name
101
+ def to_xml
102
+ error = {
103
+ 'message' => error_message,
104
+ 'raised_at' => Time.now,
105
+ 'backtrace' => self.backtrace.lines}
106
+ if url ||
107
+ controller ||
108
+ action ||
109
+ !parameters.blank? ||
110
+ !cgi_data.blank? ||
111
+ !session_data.blank?
112
+
113
+ error['request'] = {'url' => url,
114
+ 'component' => controller,
115
+ 'action' => action}
116
+
117
+ unless parameters.blank?
118
+ error['request']['params'] = parameters
119
+ end
120
+
121
+ unless session_data.blank?
122
+ error['session'] = session_data
123
+ end
124
+
125
+ unless cgi_data.blank?
126
+ error['request']['cgi-data'] = cgi_data
127
+ end
128
+ error['environment'] = {'root' => project_root,
129
+ 'name' => environment_name}
130
+ end
131
+ {'error' => error,
132
+ 'api_key' => api_key,
133
+ 'version' => '0.1.0'}
134
+ end
135
+
136
+ # Determines if this notice should be ignored
137
+ def ignore?
138
+ ignored_class_names.include?(error_class) ||
139
+ ignore_by_filters.any? {|filter| filter.call(self) }
140
+ end
141
+
142
+ # Allows properties to be accessed using a hash-like syntax
143
+ #
144
+ # @example
145
+ # notice[:error_message]
146
+ # @param [String] method The given key for an attribute
147
+ # @return The attribute value, or self if given +:request+
148
+ def [](method)
149
+ case method
150
+ when :request
151
+ self
152
+ else
153
+ send(method)
154
+ end
155
+ end
156
+
157
+ private
158
+
159
+ attr_writer :exception, :api_key, :backtrace, :error_class, :error_message,
160
+ :backtrace_filters, :parameters, :params_filters,
161
+ :environment_filters, :session_data, :project_root, :url, :ignore,
162
+ :ignore_by_filters, :notifier_name, :notifier_url, :notifier_version,
163
+ :component, :action, :cgi_data, :environment_name
164
+
165
+ # Arguments given in the initializer
166
+ attr_accessor :args
167
+
168
+ # Gets a property named +attribute+ of an exception, either from an actual
169
+ # exception or a hash.
170
+ #
171
+ # If an exception is available, #from_exception will be used. Otherwise,
172
+ # a key named +attribute+ will be used from the #args.
173
+ #
174
+ # If no exception or hash key is available, +default+ will be used.
175
+ def exception_attribute(attribute, default = nil, &block)
176
+ (exception && from_exception(attribute, &block)) || args[attribute] || default
177
+ end
178
+
179
+ # Gets a property named +attribute+ from an exception.
180
+ #
181
+ # If a block is given, it will be used when getting the property from an
182
+ # exception. The block should accept and exception and return the value for
183
+ # the property.
184
+ #
185
+ # If no block is given, a method with the same name as +attribute+ will be
186
+ # invoked for the value.
187
+ def from_exception(attribute)
188
+ if block_given?
189
+ yield(exception)
190
+ else
191
+ exception.send(attribute)
192
+ end
193
+ end
194
+
195
+ # Removes non-serializable data from the given attribute.
196
+ # See #clean_unserializable_data
197
+ def clean_unserializable_data_from(attribute)
198
+ self.send(:"#{attribute}=", clean_unserializable_data(send(attribute)))
199
+ end
200
+
201
+ # Removes non-serializable data. Allowed data types are strings, arrays,
202
+ # and hashes. All other types are converted to strings.
203
+ # TODO: move this onto Hash
204
+ def clean_unserializable_data(data)
205
+ if data.respond_to?(:to_hash)
206
+ data.to_hash.inject({}) do |result, (key, value)|
207
+ result.merge(key => clean_unserializable_data(value))
208
+ end
209
+ elsif data.respond_to?(:to_ary)
210
+ data.collect do |value|
211
+ clean_unserializable_data(value)
212
+ end
213
+ else
214
+ data.to_s
215
+ end
216
+ end
217
+
218
+ # Replaces the contents of params that match params_filters.
219
+ # TODO: extract this to a different class
220
+ def clean_params
221
+ clean_unserializable_data_from(:parameters)
222
+ filter(parameters)
223
+ if cgi_data
224
+ clean_unserializable_data_from(:cgi_data)
225
+ filter(cgi_data)
226
+ end
227
+ if session_data
228
+ clean_unserializable_data_from(:session_data)
229
+ end
230
+ end
231
+
232
+ def filter(hash)
233
+ if params_filters
234
+ hash.each do |key, value|
235
+ if filter_key?(key)
236
+ hash[key] = "[FILTERED]"
237
+ elsif value.respond_to?(:to_hash)
238
+ filter(hash[key])
239
+ end
240
+ end
241
+ end
242
+ end
243
+
244
+ def filter_key?(key)
245
+ params_filters.any? do |filter|
246
+ key.to_s.include?(filter)
247
+ end
248
+ end
249
+
250
+ def find_session_data
251
+ self.session_data = args[:session_data] || args[:session] || {}
252
+ self.session_data = session_data[:data] if session_data[:data]
253
+ end
254
+
255
+ # Converts the mixed class instances and class names into just names
256
+ # TODO: move this into Configuration or another class
257
+ def ignored_class_names
258
+ ignore.collect do |string_or_class|
259
+ if string_or_class.respond_to?(:name)
260
+ string_or_class.name
261
+ else
262
+ string_or_class
263
+ end
264
+ end
265
+ end
266
+
267
+ def xml_vars_for(builder, hash)
268
+ hash.each do |key, value|
269
+ if value.respond_to?(:to_hash)
270
+ builder.var(:key => key){|b| xml_vars_for(b, value.to_hash) }
271
+ else
272
+ builder.var(value.to_s, :key => key)
273
+ end
274
+ end
275
+ end
276
+
277
+ def rack_env(method)
278
+ rack_request.send(method) if rack_request
279
+ end
280
+
281
+ def rack_request
282
+ @rack_request ||= if args[:rack_env]
283
+ ::Rack::Request.new(args[:rack_env])
284
+ end
285
+ end
286
+ end
287
+ end
@@ -0,0 +1,40 @@
1
+ module HoptoadNotifier
2
+ # Middleware for Rack applications. Any errors raised by the upstream
3
+ # application will be delivered to Hoptoad and re-raised.
4
+ #
5
+ # Synopsis:
6
+ #
7
+ # require 'rack'
8
+ # require 'hoptoad_notifier'
9
+ #
10
+ # HoptoadNotifier.configure do |config|
11
+ # config.api_key = 'my_api_key'
12
+ # end
13
+ #
14
+ # app = Rack::Builder.app do
15
+ # use HoptoadNotifier::Rack
16
+ # run lambda { |env| raise "Rack down" }
17
+ # end
18
+ #
19
+ # Use a standard HoptoadNotifier.configure call to configure your api key.
20
+ class Rack
21
+ def initialize(app)
22
+ @app = app
23
+ end
24
+
25
+ def call(env)
26
+ begin
27
+ response = @app.call(env)
28
+ rescue Exception => raised
29
+ HoptoadNotifier.notify_or_ignore(raised, :rack_env => env)
30
+ raise
31
+ end
32
+
33
+ if env['rack.exception']
34
+ HoptoadNotifier.notify_or_ignore(env['rack.exception'], :rack_env => env)
35
+ end
36
+
37
+ response
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,29 @@
1
+ module HoptoadNotifier
2
+ module Rails
3
+ module ActionControllerCatcher
4
+
5
+ # Sets up an alias chain to catch exceptions when Rails does
6
+ def self.included(base) #:nodoc:
7
+ base.send(:alias_method, :rescue_action_in_public_without_hoptoad, :rescue_action_in_public)
8
+ base.send(:alias_method, :rescue_action_in_public, :rescue_action_in_public_with_hoptoad)
9
+ end
10
+
11
+ private
12
+
13
+ # Overrides the rescue_action method in ActionController::Base, but does not inhibit
14
+ # any custom processing that is defined with Rails 2's exception helpers.
15
+ def rescue_action_in_public_with_hoptoad(exception)
16
+ unless hoptoad_ignore_user_agent?
17
+ HoptoadNotifier.notify_or_ignore(exception, hoptoad_request_data)
18
+ end
19
+ rescue_action_in_public_without_hoptoad(exception)
20
+ end
21
+
22
+ def hoptoad_ignore_user_agent? #:nodoc:
23
+ # Rails 1.2.6 doesn't have request.user_agent, so check for it here
24
+ user_agent = request.respond_to?(:user_agent) ? request.user_agent : request.env["HTTP_USER_AGENT"]
25
+ HoptoadNotifier.configuration.ignore_user_agent.flatten.any? { |ua| ua === user_agent }
26
+ end
27
+ end
28
+ end
29
+ end