airbrakeV4rails5 4.3.8
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.
- checksums.yaml +7 -0
- data/CHANGELOG +1716 -0
- data/Gemfile +3 -0
- data/Guardfile +6 -0
- data/INSTALL +20 -0
- data/LICENSE +61 -0
- data/README.md +148 -0
- data/README_FOR_HEROKU_ADDON.md +102 -0
- data/Rakefile +179 -0
- data/TESTED_AGAINST +7 -0
- data/airbrake.gemspec +41 -0
- data/bin/airbrake +12 -0
- data/features/metal.feature +34 -0
- data/features/rack.feature +60 -0
- data/features/rails.feature +324 -0
- data/features/rake.feature +33 -0
- data/features/sinatra.feature +126 -0
- data/features/step_definitions/file_steps.rb +14 -0
- data/features/step_definitions/rack_steps.rb +27 -0
- data/features/step_definitions/rails_application_steps.rb +267 -0
- data/features/step_definitions/rake_steps.rb +22 -0
- data/features/support/airbrake_shim.rb.template +11 -0
- data/features/support/aruba.rb +5 -0
- data/features/support/env.rb +39 -0
- data/features/support/matchers.rb +35 -0
- data/features/support/rails.rb +156 -0
- data/features/support/rake/Rakefile +77 -0
- data/features/user_informer.feature +57 -0
- data/generators/airbrake/airbrake_generator.rb +94 -0
- data/generators/airbrake/lib/insert_commands.rb +34 -0
- data/generators/airbrake/lib/rake_commands.rb +24 -0
- data/generators/airbrake/templates/airbrake_tasks.rake +25 -0
- data/generators/airbrake/templates/capistrano_hook.rb +6 -0
- data/generators/airbrake/templates/initializer.rb +4 -0
- data/install.rb +1 -0
- data/lib/airbrake.rb +191 -0
- data/lib/airbrake/backtrace.rb +103 -0
- data/lib/airbrake/capistrano.rb +103 -0
- data/lib/airbrake/capistrano3.rb +3 -0
- data/lib/airbrake/cli/client.rb +76 -0
- data/lib/airbrake/cli/options.rb +45 -0
- data/lib/airbrake/cli/printer.rb +33 -0
- data/lib/airbrake/cli/project.rb +17 -0
- data/lib/airbrake/cli/project_factory.rb +33 -0
- data/lib/airbrake/cli/runner.rb +49 -0
- data/lib/airbrake/cli/validator.rb +8 -0
- data/lib/airbrake/configuration.rb +366 -0
- data/lib/airbrake/jobs/send_job.rb +7 -0
- data/lib/airbrake/notice.rb +411 -0
- data/lib/airbrake/rack.rb +64 -0
- data/lib/airbrake/rails.rb +45 -0
- data/lib/airbrake/rails/action_controller_catcher.rb +32 -0
- data/lib/airbrake/rails/controller_methods.rb +146 -0
- data/lib/airbrake/rails/error_lookup.rb +35 -0
- data/lib/airbrake/rails/middleware.rb +63 -0
- data/lib/airbrake/rails3_tasks.rb +126 -0
- data/lib/airbrake/railtie.rb +44 -0
- data/lib/airbrake/rake_handler.rb +75 -0
- data/lib/airbrake/response.rb +29 -0
- data/lib/airbrake/sender.rb +213 -0
- data/lib/airbrake/shared_tasks.rb +59 -0
- data/lib/airbrake/sidekiq.rb +8 -0
- data/lib/airbrake/sinatra.rb +40 -0
- data/lib/airbrake/tasks.rb +81 -0
- data/lib/airbrake/tasks/airbrake.cap +28 -0
- data/lib/airbrake/user_informer.rb +36 -0
- data/lib/airbrake/utils/params_cleaner.rb +141 -0
- data/lib/airbrake/utils/rack_filters.rb +45 -0
- data/lib/airbrake/version.rb +3 -0
- data/lib/airbrake_tasks.rb +62 -0
- data/lib/rails/generators/airbrake/airbrake_generator.rb +155 -0
- data/lib/templates/rescue.erb +91 -0
- data/rails/init.rb +1 -0
- data/resources/README.md +34 -0
- data/resources/airbrake_2_4.xsd +89 -0
- data/resources/airbrake_3_0.json +52 -0
- data/resources/ca-bundle.crt +3376 -0
- data/script/integration_test.rb +35 -0
- data/test/airbrake_tasks_test.rb +161 -0
- data/test/backtrace_test.rb +215 -0
- data/test/capistrano_test.rb +44 -0
- data/test/configuration_test.rb +303 -0
- data/test/controller_methods_test.rb +230 -0
- data/test/helper.rb +233 -0
- data/test/integration.rb +13 -0
- data/test/integration/catcher_test.rb +371 -0
- data/test/logger_test.rb +79 -0
- data/test/notice_test.rb +494 -0
- data/test/notifier_test.rb +288 -0
- data/test/params_cleaner_test.rb +204 -0
- data/test/rack_test.rb +62 -0
- data/test/rails_initializer_test.rb +36 -0
- data/test/recursion_test.rb +10 -0
- data/test/response_test.rb +18 -0
- data/test/sender_test.rb +335 -0
- data/test/support/response_shim.xml +4 -0
- data/test/user_informer_test.rb +29 -0
- metadata +469 -0
@@ -0,0 +1,411 @@
|
|
1
|
+
require 'builder'
|
2
|
+
require 'socket'
|
3
|
+
require 'multi_json'
|
4
|
+
|
5
|
+
module Airbrake
|
6
|
+
class Notice
|
7
|
+
|
8
|
+
class << self
|
9
|
+
def attr_reader_with_tracking(*names)
|
10
|
+
attr_readers.concat(names)
|
11
|
+
attr_reader_without_tracking(*names)
|
12
|
+
end
|
13
|
+
|
14
|
+
alias_method :attr_reader_without_tracking, :attr_reader
|
15
|
+
alias_method :attr_reader, :attr_reader_with_tracking
|
16
|
+
|
17
|
+
|
18
|
+
def attr_readers
|
19
|
+
@attr_readers ||= []
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
# The exception that caused this notice, if any
|
24
|
+
attr_reader :exception
|
25
|
+
|
26
|
+
# The API key for the project to which this notice should be sent
|
27
|
+
attr_reader :api_key
|
28
|
+
|
29
|
+
# The backtrace from the given exception or hash.
|
30
|
+
attr_reader :backtrace
|
31
|
+
|
32
|
+
# The name of the class of error (such as RuntimeError)
|
33
|
+
attr_reader :error_class
|
34
|
+
|
35
|
+
# The name of the server environment (such as "production")
|
36
|
+
attr_reader :environment_name
|
37
|
+
|
38
|
+
# CGI variables such as HTTP_METHOD
|
39
|
+
attr_reader :cgi_data
|
40
|
+
|
41
|
+
# The message from the exception, or a general description of the error
|
42
|
+
attr_reader :error_message
|
43
|
+
|
44
|
+
# See Configuration#backtrace_filters
|
45
|
+
attr_reader :backtrace_filters
|
46
|
+
|
47
|
+
# See Configuration#params_filters
|
48
|
+
attr_reader :params_filters
|
49
|
+
|
50
|
+
# See Configuration#params_whitelist_filters
|
51
|
+
attr_reader :params_whitelist_filters
|
52
|
+
|
53
|
+
# A hash of parameters from the query string or post body.
|
54
|
+
attr_reader :parameters
|
55
|
+
alias_method :params, :parameters
|
56
|
+
|
57
|
+
# The component (if any) which was used in this request (usually the controller)
|
58
|
+
attr_reader :component
|
59
|
+
alias_method :controller, :component
|
60
|
+
|
61
|
+
# The action (if any) that was called in this request
|
62
|
+
attr_reader :action
|
63
|
+
|
64
|
+
# A hash of session data from the request
|
65
|
+
attr_reader :session_data
|
66
|
+
|
67
|
+
# The path to the project that caused the error (usually Rails.root)
|
68
|
+
attr_reader :project_root
|
69
|
+
|
70
|
+
# The URL at which the error occurred (if any)
|
71
|
+
attr_reader :url
|
72
|
+
|
73
|
+
# See Configuration#ignore
|
74
|
+
attr_reader :ignore
|
75
|
+
|
76
|
+
# See Configuration#ignore_by_filters
|
77
|
+
attr_reader :ignore_by_filters
|
78
|
+
|
79
|
+
# The name of the notifier library sending this notice, such as "Airbrake Notifier"
|
80
|
+
attr_reader :notifier_name
|
81
|
+
|
82
|
+
# The version number of the notifier library sending this notice, such as "2.1.3"
|
83
|
+
attr_reader :notifier_version
|
84
|
+
|
85
|
+
# A URL for more information about the notifier library sending this notice
|
86
|
+
attr_reader :notifier_url
|
87
|
+
|
88
|
+
# The host name where this error occurred (if any)
|
89
|
+
attr_reader :hostname
|
90
|
+
|
91
|
+
# Details about the user who experienced the error
|
92
|
+
attr_reader :user
|
93
|
+
|
94
|
+
# Instance that's used for cleaning out data that should be filtered out, should respond to #clean
|
95
|
+
attr_accessor :cleaner
|
96
|
+
|
97
|
+
# An array of the exception classes for this error (including wrapped ones)
|
98
|
+
attr_reader :exception_classes
|
99
|
+
|
100
|
+
public
|
101
|
+
|
102
|
+
def initialize(args)
|
103
|
+
@args = args
|
104
|
+
@exception = args[:exception]
|
105
|
+
@api_key = args[:api_key]
|
106
|
+
@project_root = args[:project_root]
|
107
|
+
@url = args[:url] || rack_env(:url)
|
108
|
+
@notifier_name = args[:notifier_name]
|
109
|
+
@notifier_version = args[:notifier_version]
|
110
|
+
@notifier_url = args[:notifier_url]
|
111
|
+
|
112
|
+
@ignore = args[:ignore] || []
|
113
|
+
@ignore_by_filters = args[:ignore_by_filters] || []
|
114
|
+
@backtrace_filters = args[:backtrace_filters] || []
|
115
|
+
@params_filters = args[:params_filters] || []
|
116
|
+
@params_whitelist_filters = args[:params_whitelist_filters] || []
|
117
|
+
|
118
|
+
@parameters = args[:parameters] ||
|
119
|
+
action_dispatch_params ||
|
120
|
+
rack_env(:params) ||
|
121
|
+
{}
|
122
|
+
@component = args[:component] || args[:controller] || parameters['controller']
|
123
|
+
@action = args[:action] || parameters['action']
|
124
|
+
|
125
|
+
@environment_name = args[:environment_name]
|
126
|
+
@cgi_data = (args[:cgi_data].respond_to?(:to_hash) && args[:cgi_data].to_hash.dup) || args[:rack_env] || {}
|
127
|
+
@backtrace = Backtrace.parse(exception_attribute(:backtrace, caller), :filters => @backtrace_filters)
|
128
|
+
@error_class = exception_attribute(:error_class) {|exception| exception.class.name }
|
129
|
+
@error_message = exception_attribute(:error_message, 'Notification') do |exception|
|
130
|
+
"#{exception.class.name}: #{args[:error_message] || exception.message}"
|
131
|
+
end
|
132
|
+
|
133
|
+
@hostname = local_hostname
|
134
|
+
@user = args[:user] || {}
|
135
|
+
|
136
|
+
@exception_classes= Array(args[:exception_classes])
|
137
|
+
if @exception
|
138
|
+
@exception_classes << @exception.class
|
139
|
+
end
|
140
|
+
if @error_class
|
141
|
+
@exception_classes << @error_class
|
142
|
+
end
|
143
|
+
|
144
|
+
|
145
|
+
also_use_rack_params_filters
|
146
|
+
find_session_data
|
147
|
+
|
148
|
+
@cleaner = args[:cleaner] ||
|
149
|
+
Airbrake::Utils::ParamsCleaner.new(:blacklist_filters => params_filters,
|
150
|
+
:whitelist_filters => params_whitelist_filters,
|
151
|
+
:to_clean => data_to_clean)
|
152
|
+
|
153
|
+
clean_data!
|
154
|
+
end
|
155
|
+
|
156
|
+
# Converts the given notice to XML
|
157
|
+
def to_xml
|
158
|
+
builder = Builder::XmlMarkup.new
|
159
|
+
builder.instruct!
|
160
|
+
xml = builder.notice(:version => Airbrake::API_VERSION) do |notice|
|
161
|
+
notice.tag!("api-key", api_key)
|
162
|
+
notice.notifier do |notifier|
|
163
|
+
notifier.name(notifier_name)
|
164
|
+
notifier.version(notifier_version)
|
165
|
+
notifier.url(notifier_url)
|
166
|
+
end
|
167
|
+
notice.tag!('error') do |error|
|
168
|
+
error.tag!('class', error_class)
|
169
|
+
error.message(error_message)
|
170
|
+
error.backtrace do |backtrace|
|
171
|
+
self.backtrace.lines.each do |line|
|
172
|
+
backtrace.line(
|
173
|
+
:number => line.number,
|
174
|
+
:file => line.file,
|
175
|
+
:method => line.method_name
|
176
|
+
)
|
177
|
+
end
|
178
|
+
end
|
179
|
+
end
|
180
|
+
if request_present?
|
181
|
+
notice.request do |request|
|
182
|
+
request.url(url)
|
183
|
+
request.component(controller)
|
184
|
+
request.action(action)
|
185
|
+
unless parameters.empty?
|
186
|
+
request.params do |params|
|
187
|
+
xml_vars_for(params, parameters)
|
188
|
+
end
|
189
|
+
end
|
190
|
+
unless session_data.empty?
|
191
|
+
request.session do |session|
|
192
|
+
xml_vars_for(session, session_data)
|
193
|
+
end
|
194
|
+
end
|
195
|
+
unless cgi_data.empty?
|
196
|
+
request.tag!("cgi-data") do |cgi_datum|
|
197
|
+
xml_vars_for(cgi_datum, cgi_data)
|
198
|
+
end
|
199
|
+
end
|
200
|
+
end
|
201
|
+
end
|
202
|
+
notice.tag!("server-environment") do |env|
|
203
|
+
env.tag!("project-root", project_root)
|
204
|
+
env.tag!("environment-name", environment_name)
|
205
|
+
env.tag!("hostname", hostname)
|
206
|
+
end
|
207
|
+
unless user.empty?
|
208
|
+
notice.tag!("current-user") do |u|
|
209
|
+
user.each do |attr, value|
|
210
|
+
u.tag!(attr.to_s, value)
|
211
|
+
end
|
212
|
+
end
|
213
|
+
end
|
214
|
+
if framework =~ /\S/
|
215
|
+
notice.tag!("framework", framework)
|
216
|
+
end
|
217
|
+
end
|
218
|
+
xml.to_s
|
219
|
+
end
|
220
|
+
|
221
|
+
def to_json
|
222
|
+
MultiJson.dump({
|
223
|
+
'notifier' => {
|
224
|
+
'name' => 'airbrake',
|
225
|
+
'version' => Airbrake::VERSION,
|
226
|
+
'url' => 'https://github.com/airbrake/airbrake'
|
227
|
+
},
|
228
|
+
'errors' => [{
|
229
|
+
'type' => error_class,
|
230
|
+
'message' => error_message,
|
231
|
+
'backtrace' => backtrace.lines.map do |line|
|
232
|
+
{
|
233
|
+
'file' => line.file,
|
234
|
+
'line' => line.number.to_i,
|
235
|
+
'function' => line.method_name
|
236
|
+
}
|
237
|
+
end
|
238
|
+
}],
|
239
|
+
'context' => {}.tap do |hash|
|
240
|
+
if request_present?
|
241
|
+
hash['url'] = url
|
242
|
+
hash['component'] = controller
|
243
|
+
hash['action'] = action
|
244
|
+
hash['rootDirectory'] = File.dirname(project_root)
|
245
|
+
hash['environment'] = environment_name
|
246
|
+
end
|
247
|
+
end.tap do |hash|
|
248
|
+
next if user.empty?
|
249
|
+
|
250
|
+
hash['userId'] = user[:id]
|
251
|
+
hash['userName'] = user[:name]
|
252
|
+
hash['userEmail'] = user[:email]
|
253
|
+
end
|
254
|
+
|
255
|
+
}.tap do |hash|
|
256
|
+
hash['environment'] = cgi_data unless cgi_data.empty?
|
257
|
+
hash['params'] = parameters unless parameters.empty?
|
258
|
+
hash['session'] = session_data unless session_data.empty?
|
259
|
+
end)
|
260
|
+
end
|
261
|
+
|
262
|
+
# Determines if this notice should be ignored
|
263
|
+
def ignore?
|
264
|
+
exception_classes.each do |klass|
|
265
|
+
if ignored_class_names.include?(klass)
|
266
|
+
return true
|
267
|
+
end
|
268
|
+
end
|
269
|
+
|
270
|
+
ignore_by_filters.any? {|filter| filter.call(self) }
|
271
|
+
end
|
272
|
+
|
273
|
+
# Allows properties to be accessed using a hash-like syntax
|
274
|
+
#
|
275
|
+
# @example
|
276
|
+
# notice[:error_message]
|
277
|
+
# @param [String] method The given key for an attribute
|
278
|
+
# @return The attribute value, or self if given +:request+
|
279
|
+
def [](method)
|
280
|
+
case method
|
281
|
+
when :request
|
282
|
+
self
|
283
|
+
else
|
284
|
+
send(method)
|
285
|
+
end
|
286
|
+
end
|
287
|
+
|
288
|
+
private
|
289
|
+
|
290
|
+
def request_present?
|
291
|
+
url ||
|
292
|
+
controller ||
|
293
|
+
action ||
|
294
|
+
!parameters.empty? ||
|
295
|
+
!cgi_data.empty? ||
|
296
|
+
!session_data.empty?
|
297
|
+
end
|
298
|
+
|
299
|
+
# Gets a property named +attribute+ of an exception, either from an actual
|
300
|
+
# exception or a hash.
|
301
|
+
#
|
302
|
+
# If an exception is available, #from_exception will be used. Otherwise,
|
303
|
+
# a key named +attribute+ will be used from the #args.
|
304
|
+
#
|
305
|
+
# If no exception or hash key is available, +default+ will be used.
|
306
|
+
def exception_attribute(attribute, default = nil, &block)
|
307
|
+
(exception && from_exception(attribute, &block)) || @args[attribute] || default
|
308
|
+
end
|
309
|
+
|
310
|
+
# Gets a property named +attribute+ from an exception.
|
311
|
+
#
|
312
|
+
# If a block is given, it will be used when getting the property from an
|
313
|
+
# exception. The block should accept and exception and return the value for
|
314
|
+
# the property.
|
315
|
+
#
|
316
|
+
# If no block is given, a method with the same name as +attribute+ will be
|
317
|
+
# invoked for the value.
|
318
|
+
def from_exception(attribute)
|
319
|
+
if block_given?
|
320
|
+
yield(exception)
|
321
|
+
else
|
322
|
+
exception.send(attribute)
|
323
|
+
end
|
324
|
+
end
|
325
|
+
|
326
|
+
# Replaces the contents of params that match params_filters.
|
327
|
+
def clean_data!
|
328
|
+
cleaner.clean.tap do |c|
|
329
|
+
@parameters = c.parameters
|
330
|
+
@cgi_data = c.cgi_data
|
331
|
+
@session_data = c.session_data
|
332
|
+
end
|
333
|
+
end
|
334
|
+
|
335
|
+
def data_to_clean
|
336
|
+
{:parameters => parameters,
|
337
|
+
:cgi_data => cgi_data,
|
338
|
+
:session_data => session_data}
|
339
|
+
end
|
340
|
+
|
341
|
+
def find_session_data
|
342
|
+
@session_data = @args[:session_data] || @args[:session] || rack_session || {}
|
343
|
+
@session_data = session_data[:data] if session_data[:data]
|
344
|
+
end
|
345
|
+
|
346
|
+
# Converts the mixed class instances and class names into just names
|
347
|
+
# TODO: move this into Configuration or another class
|
348
|
+
def ignored_class_names
|
349
|
+
ignore.collect do |string_or_class|
|
350
|
+
if string_or_class.respond_to?(:name)
|
351
|
+
string_or_class.name
|
352
|
+
else
|
353
|
+
string_or_class
|
354
|
+
end
|
355
|
+
end
|
356
|
+
end
|
357
|
+
|
358
|
+
def xml_vars_for(builder, hash)
|
359
|
+
hash.each do |key, value|
|
360
|
+
if value.respond_to?(:to_hash)
|
361
|
+
builder.var(:key => key){|b| xml_vars_for(b, value.to_hash) }
|
362
|
+
else
|
363
|
+
builder.var(value.to_s, :key => key)
|
364
|
+
end
|
365
|
+
end
|
366
|
+
end
|
367
|
+
|
368
|
+
def rack_env(method)
|
369
|
+
rack_request.send(method) if rack_request
|
370
|
+
rescue
|
371
|
+
{:message => "failed to call #{method} on Rack::Request -- #{$!.message}"}
|
372
|
+
end
|
373
|
+
|
374
|
+
def rack_request
|
375
|
+
@rack_request ||= if @args[:rack_env]
|
376
|
+
::Rack::Request.new(@args[:rack_env])
|
377
|
+
end
|
378
|
+
end
|
379
|
+
|
380
|
+
def action_dispatch_params
|
381
|
+
@args[:rack_env]['action_dispatch.request.parameters'] if @args[:rack_env]
|
382
|
+
end
|
383
|
+
|
384
|
+
def rack_session
|
385
|
+
@args[:rack_env]['rack.session'] if @args[:rack_env]
|
386
|
+
end
|
387
|
+
|
388
|
+
def also_use_rack_params_filters
|
389
|
+
if cgi_data
|
390
|
+
@params_filters ||= []
|
391
|
+
@params_filters += cgi_data["action_dispatch.parameter_filter"] || []
|
392
|
+
end
|
393
|
+
end
|
394
|
+
|
395
|
+
def local_hostname
|
396
|
+
Socket.gethostname
|
397
|
+
end
|
398
|
+
|
399
|
+
def framework
|
400
|
+
Airbrake.configuration.framework
|
401
|
+
end
|
402
|
+
|
403
|
+
def to_s
|
404
|
+
content = []
|
405
|
+
self.class.attr_readers.each do |attr|
|
406
|
+
content << " #{attr}: #{send(attr)}"
|
407
|
+
end
|
408
|
+
content.join("\n")
|
409
|
+
end
|
410
|
+
end
|
411
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
module Airbrake
|
2
|
+
# Middleware for Rack applications. Any errors raised by the upstream
|
3
|
+
# application will be delivered to Airbrake and re-raised.
|
4
|
+
#
|
5
|
+
# Synopsis:
|
6
|
+
#
|
7
|
+
# require 'rack'
|
8
|
+
# require 'airbrake'
|
9
|
+
#
|
10
|
+
# Airbrake.configure do |config|
|
11
|
+
# config.api_key = 'my_api_key'
|
12
|
+
# #can also set the environment over here
|
13
|
+
# config.environment_name = ENV['RACK_ENV'] || "development"
|
14
|
+
# end
|
15
|
+
#
|
16
|
+
# app = Rack::Builder.app do
|
17
|
+
# run lambda { |env| raise "Rack down" }
|
18
|
+
# end
|
19
|
+
#
|
20
|
+
# use Airbrake::Rack
|
21
|
+
# run app
|
22
|
+
#
|
23
|
+
# Use a standard Airbrake.configure call to configure your api key.
|
24
|
+
class Rack
|
25
|
+
def initialize(app)
|
26
|
+
@app = app
|
27
|
+
Airbrake.configuration.framework = "Rack: #{::Rack.release}"
|
28
|
+
end
|
29
|
+
|
30
|
+
def ignored_user_agent?(env)
|
31
|
+
true if Airbrake.
|
32
|
+
configuration.
|
33
|
+
ignore_user_agent.
|
34
|
+
flatten.
|
35
|
+
any? { |ua| ua === env['HTTP_USER_AGENT'] }
|
36
|
+
end
|
37
|
+
|
38
|
+
def notify_airbrake(exception, env)
|
39
|
+
unless ignored_user_agent?(env)
|
40
|
+
Airbrake.notify_or_ignore(exception, :rack_env => env)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def call(env)
|
45
|
+
begin
|
46
|
+
response = @app.call(env)
|
47
|
+
rescue Exception => raised
|
48
|
+
env['airbrake.error_id'] = notify_airbrake(raised, env)
|
49
|
+
raise raised
|
50
|
+
end
|
51
|
+
|
52
|
+
if framework_exception(env)
|
53
|
+
env['airbrake.error_id'] = notify_airbrake(framework_exception(env), env)
|
54
|
+
end
|
55
|
+
|
56
|
+
response
|
57
|
+
end
|
58
|
+
|
59
|
+
def framework_exception(env)
|
60
|
+
env['rack.exception'] || env['sinatra.error']
|
61
|
+
end
|
62
|
+
|
63
|
+
end
|
64
|
+
end
|