errornot_notifier 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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