bugsnag 4.2.1 → 6.27.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.
Files changed (106) hide show
  1. checksums.yaml +5 -5
  2. data/.yardopts +12 -0
  3. data/CHANGELOG.md +814 -0
  4. data/README.md +21 -25
  5. data/VERSION +1 -1
  6. data/bugsnag.gemspec +19 -8
  7. data/lib/bugsnag/breadcrumb_type.rb +14 -0
  8. data/lib/bugsnag/breadcrumbs/breadcrumb.rb +109 -0
  9. data/lib/bugsnag/breadcrumbs/breadcrumbs.rb +13 -0
  10. data/lib/bugsnag/breadcrumbs/on_breadcrumb_callback_list.rb +48 -0
  11. data/lib/bugsnag/breadcrumbs/validator.rb +29 -0
  12. data/lib/bugsnag/cleaner.rb +170 -59
  13. data/lib/bugsnag/code_extractor.rb +137 -0
  14. data/lib/bugsnag/configuration.rb +670 -45
  15. data/lib/bugsnag/delivery/synchronous.rb +31 -14
  16. data/lib/bugsnag/delivery/thread_queue.rb +23 -6
  17. data/lib/bugsnag/delivery.rb +13 -0
  18. data/lib/bugsnag/endpoint_configuration.rb +11 -0
  19. data/lib/bugsnag/endpoint_validator.rb +80 -0
  20. data/lib/bugsnag/error.rb +25 -0
  21. data/lib/bugsnag/event.rb +5 -0
  22. data/lib/bugsnag/feature_flag.rb +74 -0
  23. data/lib/bugsnag/helpers.rb +121 -25
  24. data/lib/bugsnag/integrations/delayed_job.rb +51 -0
  25. data/lib/bugsnag/integrations/mailman.rb +43 -0
  26. data/lib/bugsnag/integrations/mongo.rb +133 -0
  27. data/lib/bugsnag/integrations/que.rb +53 -0
  28. data/lib/bugsnag/integrations/rack.rb +83 -0
  29. data/lib/bugsnag/integrations/rails/active_job.rb +100 -0
  30. data/lib/bugsnag/{rails → integrations/rails}/active_record_rescue.rb +10 -1
  31. data/lib/bugsnag/{rails → integrations/rails}/controller_methods.rb +1 -9
  32. data/lib/bugsnag/integrations/rails/rails_breadcrumbs.rb +115 -0
  33. data/lib/bugsnag/integrations/railtie.rb +153 -0
  34. data/lib/bugsnag/integrations/rake.rb +74 -0
  35. data/lib/bugsnag/integrations/resque.rb +94 -0
  36. data/lib/bugsnag/integrations/shoryuken.rb +50 -0
  37. data/lib/bugsnag/integrations/sidekiq.rb +68 -0
  38. data/lib/bugsnag/meta_data.rb +1 -0
  39. data/lib/bugsnag/middleware/active_job.rb +18 -0
  40. data/lib/bugsnag/middleware/breadcrumbs.rb +21 -0
  41. data/lib/bugsnag/middleware/callbacks.rb +6 -8
  42. data/lib/bugsnag/middleware/classify_error.rb +50 -0
  43. data/lib/bugsnag/middleware/clearance_user.rb +33 -0
  44. data/lib/bugsnag/middleware/delayed_job.rb +93 -0
  45. data/lib/bugsnag/middleware/discard_error_class.rb +30 -0
  46. data/lib/bugsnag/middleware/exception_meta_data.rb +42 -0
  47. data/lib/bugsnag/middleware/ignore_error_class.rb +26 -0
  48. data/lib/bugsnag/middleware/mailman.rb +6 -4
  49. data/lib/bugsnag/middleware/rack_request.rb +126 -30
  50. data/lib/bugsnag/middleware/rails3_request.rb +15 -17
  51. data/lib/bugsnag/middleware/rake.rb +7 -5
  52. data/lib/bugsnag/middleware/session_data.rb +25 -0
  53. data/lib/bugsnag/middleware/sidekiq.rb +9 -4
  54. data/lib/bugsnag/middleware/suggestion_data.rb +34 -0
  55. data/lib/bugsnag/middleware/warden_user.rb +11 -6
  56. data/lib/bugsnag/middleware_stack.rb +62 -9
  57. data/lib/bugsnag/on_error_callbacks.rb +33 -0
  58. data/lib/bugsnag/report.rb +516 -0
  59. data/lib/bugsnag/session_tracker.rb +182 -0
  60. data/lib/bugsnag/stacktrace.rb +82 -0
  61. data/lib/bugsnag/tasks/bugsnag.rake +2 -70
  62. data/lib/bugsnag/utility/circular_buffer.rb +62 -0
  63. data/lib/bugsnag/utility/duplicator.rb +124 -0
  64. data/lib/bugsnag/utility/feature_data_store.rb +41 -0
  65. data/lib/bugsnag/utility/feature_flag_delegate.rb +89 -0
  66. data/lib/bugsnag/utility/metadata_delegate.rb +102 -0
  67. data/lib/bugsnag.rb +528 -80
  68. metadata +61 -123
  69. data/.document +0 -5
  70. data/.gitignore +0 -52
  71. data/.rspec +0 -3
  72. data/.travis.yml +0 -14
  73. data/CONTRIBUTING.md +0 -47
  74. data/Gemfile +0 -2
  75. data/Rakefile +0 -29
  76. data/lib/bugsnag/capistrano.rb +0 -7
  77. data/lib/bugsnag/capistrano2.rb +0 -32
  78. data/lib/bugsnag/delay/resque.rb +0 -21
  79. data/lib/bugsnag/delayed_job.rb +0 -57
  80. data/lib/bugsnag/deploy.rb +0 -34
  81. data/lib/bugsnag/mailman.rb +0 -28
  82. data/lib/bugsnag/middleware/rails2_request.rb +0 -52
  83. data/lib/bugsnag/notification.rb +0 -459
  84. data/lib/bugsnag/rack.rb +0 -53
  85. data/lib/bugsnag/rails/action_controller_rescue.rb +0 -62
  86. data/lib/bugsnag/rails.rb +0 -66
  87. data/lib/bugsnag/railtie.rb +0 -80
  88. data/lib/bugsnag/rake.rb +0 -25
  89. data/lib/bugsnag/resque.rb +0 -40
  90. data/lib/bugsnag/sidekiq.rb +0 -42
  91. data/lib/bugsnag/tasks/bugsnag.cap +0 -48
  92. data/rails/init.rb +0 -7
  93. data/spec/cleaner_spec.rb +0 -138
  94. data/spec/code_spec.rb +0 -86
  95. data/spec/fixtures/crashes/end_of_file.rb +0 -9
  96. data/spec/fixtures/crashes/short_file.rb +0 -1
  97. data/spec/fixtures/crashes/start_of_file.rb +0 -9
  98. data/spec/fixtures/middleware/internal_info_setter.rb +0 -11
  99. data/spec/fixtures/middleware/public_info_setter.rb +0 -11
  100. data/spec/fixtures/tasks/Rakefile +0 -15
  101. data/spec/helper_spec.rb +0 -163
  102. data/spec/integration_spec.rb +0 -132
  103. data/spec/middleware_spec.rb +0 -181
  104. data/spec/notification_spec.rb +0 -877
  105. data/spec/rack_spec.rb +0 -56
  106. data/spec/spec_helper.rb +0 -53
data/lib/bugsnag.rb CHANGED
@@ -1,138 +1,586 @@
1
1
  require "rubygems"
2
2
  require "thread"
3
+ require "set"
4
+ require "json"
5
+ require "uri"
6
+ require "socket"
7
+ require "logger"
3
8
 
4
9
  require "bugsnag/version"
10
+ require "bugsnag/utility/feature_data_store"
5
11
  require "bugsnag/configuration"
6
12
  require "bugsnag/meta_data"
7
- require "bugsnag/notification"
13
+ require "bugsnag/report"
14
+ require "bugsnag/event"
8
15
  require "bugsnag/cleaner"
9
16
  require "bugsnag/helpers"
10
- require "bugsnag/deploy"
17
+ require "bugsnag/session_tracker"
11
18
 
12
19
  require "bugsnag/delivery"
13
20
  require "bugsnag/delivery/synchronous"
14
21
  require "bugsnag/delivery/thread_queue"
15
22
 
16
- require "bugsnag/rack"
17
- require "bugsnag/railtie" if defined?(Rails::Railtie)
23
+ require "bugsnag/feature_flag"
18
24
 
19
- require "bugsnag/middleware/rack_request"
20
- require "bugsnag/middleware/warden_user"
21
- require "bugsnag/middleware/callbacks"
22
- require "bugsnag/middleware/rails3_request"
23
- require "bugsnag/middleware/sidekiq"
24
- require "bugsnag/middleware/mailman"
25
- require "bugsnag/middleware/rake"
26
- require "bugsnag/middleware/callbacks"
25
+ # Rack is not bundled with the other integrations
26
+ # as it doesn't auto-configure when loaded
27
+ require "bugsnag/integrations/rack"
27
28
 
29
+ require "bugsnag/breadcrumb_type"
30
+ require "bugsnag/breadcrumbs/validator"
31
+ require "bugsnag/breadcrumbs/breadcrumb"
32
+
33
+ require "bugsnag/utility/duplicator"
34
+ require "bugsnag/utility/metadata_delegate"
35
+ require "bugsnag/utility/feature_flag_delegate"
36
+
37
+ # rubocop:todo Metrics/ModuleLength
28
38
  module Bugsnag
29
- LOG_PREFIX = "** [Bugsnag] "
30
39
  LOCK = Mutex.new
40
+ INTEGRATIONS = [:resque, :sidekiq, :mailman, :delayed_job, :shoryuken, :que, :mongo]
41
+
42
+ NIL_EXCEPTION_DESCRIPTION = "'nil' was notified as an exception"
31
43
 
32
44
  class << self
45
+ include Utility::FeatureDataStore
46
+
47
+ ##
33
48
  # Configure the Bugsnag notifier application-wide settings.
34
- def configure(config_hash=nil)
35
- if config_hash
36
- config_hash.each do |k,v|
37
- configuration.send("#{k}=", v) rescue nil if configuration.respond_to?("#{k}=")
38
- end
49
+ #
50
+ # Yields a {Configuration} object to use to set application settings.
51
+ #
52
+ # @yieldparam configuration [Configuration]
53
+ # @return [void]
54
+ def configure(validate_api_key=true)
55
+ yield(configuration) if block_given?
56
+
57
+ # Create the session tracker if sessions are enabled to avoid the overhead
58
+ # of creating it on the first request. We skip this if we're not validating
59
+ # the API key as we use this internally before the user's configure block
60
+ # has run, so we don't know if sessions are enabled yet.
61
+ session_tracker if validate_api_key && configuration.auto_capture_sessions
62
+
63
+ check_key_valid if validate_api_key
64
+ check_endpoint_setup
65
+
66
+ register_at_exit
67
+ end
68
+
69
+ ##
70
+ # Explicitly notify of an exception.
71
+ #
72
+ # Optionally accepts a block to append metadata to the yielded report.
73
+ def notify(exception, auto_notify=false, &block)
74
+ unless false.equal? auto_notify or true.equal? auto_notify
75
+ configuration.warn("Adding metadata/severity using a hash is no longer supported, please use block syntax instead")
76
+ auto_notify = false
39
77
  end
40
78
 
41
- yield(configuration) if block_given?
79
+ return unless should_deliver_notification?(exception, auto_notify)
80
+
81
+ exception = NIL_EXCEPTION_DESCRIPTION if exception.nil?
82
+
83
+ report = Report.new(exception, configuration, auto_notify)
84
+
85
+ # If this is an auto_notify we yield the block before the any middleware is run
86
+ begin
87
+ yield(report) if block_given? && auto_notify
88
+ rescue StandardError => e
89
+ configuration.warn("Error in internal notify block: #{e}")
90
+ configuration.warn("Error in internal notify block stacktrace: #{e.backtrace.inspect}")
91
+ end
92
+
93
+ if report.ignore?
94
+ configuration.debug("Not notifying #{report.exceptions.last[:errorClass]} due to ignore being signified in auto_notify block")
95
+ return
96
+ end
97
+
98
+ # Run internal middleware
99
+ configuration.internal_middleware.run(report)
100
+ if report.ignore?
101
+ configuration.debug("Not notifying #{report.exceptions.last[:errorClass]} due to ignore being signified in internal middlewares")
102
+ return
103
+ end
104
+
105
+ # Store before_middleware severity reason for future reference
106
+ initial_severity = report.severity
107
+ initial_reason = report.severity_reason
108
+
109
+ # Run users middleware
110
+ configuration.middleware.run(report) do
111
+ if report.ignore?
112
+ configuration.debug("Not notifying #{report.exceptions.last[:errorClass]} due to ignore being signified in user provided middleware")
113
+ return
114
+ end
115
+
116
+ # If this is not an auto_notify then the block was provided by the user. This should be the last
117
+ # block that is run as it is the users "most specific" block.
118
+ begin
119
+ yield(report) if block_given? && !auto_notify
120
+ rescue StandardError => e
121
+ configuration.warn("Error in notify block: #{e}")
122
+ configuration.warn("Error in notify block stacktrace: #{e.backtrace.inspect}")
123
+ end
124
+
125
+ if report.ignore?
126
+ configuration.debug("Not notifying #{report.exceptions.last[:errorClass]} due to ignore being signified in user provided block")
127
+ return
128
+ end
42
129
 
43
- # Use resque for asynchronous notification if required
44
- require "bugsnag/delay/resque" if configuration.delay_with_resque && defined?(Resque)
130
+ # Test whether severity has been changed and ensure severity_reason is consistant in auto_notify case
131
+ if report.severity != initial_severity
132
+ report.severity_reason = {
133
+ :type => Report::USER_CALLBACK_SET_SEVERITY
134
+ }
135
+ else
136
+ report.severity_reason = initial_reason
137
+ end
45
138
 
46
- # Log that we are ready to rock
47
- @logged_ready = false unless defined?(@logged_ready)
139
+ if report.unhandled_overridden?
140
+ # let the dashboard know that the unhandled flag was overridden
141
+ report.severity_reason[:unhandledOverridden] = true
142
+ end
48
143
 
49
- if configuration.api_key && !@logged_ready
50
- log "Bugsnag exception handler #{VERSION} ready"
51
- @logged_ready = true
144
+ deliver_notification(report)
52
145
  end
53
146
  end
54
147
 
55
- # Explicitly notify of an exception
56
- def notify(exception, overrides=nil, request_data=nil, &block)
57
- notification = Notification.new(exception, configuration, overrides, request_data)
148
+ ##
149
+ # Registers an at_exit function to automatically catch errors on exit.
150
+ #
151
+ # @return [void]
152
+ def register_at_exit
153
+ return if at_exit_handler_installed?
154
+ @exit_handler_added = true
155
+ at_exit do
156
+ if $!
157
+ exception = unwrap_bundler_exception($!)
158
+
159
+ Bugsnag.notify(exception, true) do |report|
160
+ report.severity = 'error'
161
+ report.severity_reason = {
162
+ :type => Bugsnag::Report::UNHANDLED_EXCEPTION
163
+ }
164
+ end
165
+ end
166
+ end
167
+ end
168
+
169
+ ##
170
+ # Checks if an at_exit handler has been added.
171
+ #
172
+ # The {Bugsnag#configure} method will add this automatically, but it can be
173
+ # added manually using {Bugsnag#register_at_exit}.
174
+ #
175
+ # @return [Boolean]
176
+ def at_exit_handler_installed?
177
+ @exit_handler_added ||= false
178
+ end
179
+
180
+ ##
181
+ # Returns the client's Configuration object, or creates one if not yet created.
182
+ #
183
+ # @return [Configuration]
184
+ def configuration
185
+ @configuration = nil unless defined?(@configuration)
186
+ @configuration || LOCK.synchronize { @configuration ||= Bugsnag::Configuration.new }
187
+ end
188
+
189
+ ##
190
+ # Returns the client's SessionTracker object, or creates one if not yet created.
191
+ #
192
+ # @return [SessionTracker]
193
+ def session_tracker
194
+ @session_tracker = nil unless defined?(@session_tracker)
195
+ @session_tracker || LOCK.synchronize { @session_tracker ||= Bugsnag::SessionTracker.new}
196
+ end
58
197
 
59
- yield(notification) if block_given?
198
+ ##
199
+ # Starts a new session, which allows Bugsnag to track error rates across
200
+ # releases
201
+ #
202
+ # @return [void]
203
+ def start_session
204
+ session_tracker.start_session
205
+ end
60
206
 
61
- notification.deliver
62
- notification
207
+ ##
208
+ # Stop any events being attributed to the current session until it is
209
+ # resumed or a new session is started
210
+ #
211
+ # @see resume_session
212
+ #
213
+ # @return [void]
214
+ def pause_session
215
+ session_tracker.pause_session
63
216
  end
64
217
 
65
- # Notify of an exception unless it should be ignored
66
- def notify_or_ignore(exception, overrides=nil, request_data=nil, &block)
67
- notification = Notification.new(exception, configuration, overrides, request_data)
218
+ ##
219
+ # Resume the current session if it was previously paused. If there is no
220
+ # current session, a new session will be started
221
+ #
222
+ # @see pause_session
223
+ #
224
+ # @return [Boolean] true if a paused session was resumed
225
+ def resume_session
226
+ session_tracker.resume_session
227
+ end
68
228
 
69
- yield(notification) if block_given?
229
+ ##
230
+ # Allow access to "before notify" callbacks as an array.
231
+ #
232
+ # These callbacks will be called whenever an error notification is being made.
233
+ #
234
+ # @deprecated Use {Bugsnag#add_on_error} instead
235
+ def before_notify_callbacks
236
+ Bugsnag.configuration.request_data[:before_callbacks] ||= []
237
+ end
238
+
239
+ ##
240
+ # Attempts to load all integrations through auto-discovery.
241
+ #
242
+ # @return [void]
243
+ def load_integrations
244
+ require "bugsnag/integrations/railtie" if defined?(Rails::Railtie)
245
+ INTEGRATIONS.each do |integration|
246
+ begin
247
+ require "bugsnag/integrations/#{integration}"
248
+ rescue LoadError
249
+ end
250
+ end
251
+ end
70
252
 
71
- unless notification.ignore?
72
- notification.deliver
73
- notification
253
+ ##
254
+ # Load a specific integration.
255
+ #
256
+ # @param integration [Symbol] One of the integrations in {INTEGRATIONS}
257
+ # @return [void]
258
+ def load_integration(integration)
259
+ integration = :railtie if integration == :rails
260
+ if INTEGRATIONS.include?(integration) || integration == :railtie
261
+ require "bugsnag/integrations/#{integration}"
74
262
  else
75
- false
263
+ configuration.debug("Integration #{integration} is not currently supported")
76
264
  end
77
265
  end
78
266
 
79
- # Auto notify of an exception, called from rails and rack exception
80
- # rescuers, unless auto notification is disabled, or we should ignore this
81
- # error class
82
- def auto_notify(exception, overrides=nil, request_data=nil, &block)
83
- overrides ||= {}
84
- overrides.merge!({:severity => "error"})
85
- notify_or_ignore(exception, overrides, request_data, &block) if configuration.auto_notify
267
+ ##
268
+ # Leave a breadcrumb to be attached to subsequent reports
269
+ #
270
+ # @param name [String] the main breadcrumb name/message
271
+ # @param meta_data [Hash] String, Numeric, or Boolean meta data to attach
272
+ # @param type [String] the breadcrumb type, see {Bugsnag::BreadcrumbType}
273
+ # @param auto [Symbol] set to :auto if the breadcrumb is automatically created
274
+ # @return [void]
275
+ def leave_breadcrumb(name, meta_data={}, type=Bugsnag::Breadcrumbs::MANUAL_BREADCRUMB_TYPE, auto=:manual)
276
+ breadcrumb = Bugsnag::Breadcrumbs::Breadcrumb.new(name, type, meta_data, auto)
277
+ validator = Bugsnag::Breadcrumbs::Validator.new(configuration)
278
+
279
+ # Initial validation
280
+ validator.validate(breadcrumb)
281
+
282
+ # Skip if it's already invalid
283
+ return if breadcrumb.ignore?
284
+
285
+ # Run before_breadcrumb_callbacks
286
+ configuration.before_breadcrumb_callbacks.each do |c|
287
+ c.arity > 0 ? c.call(breadcrumb) : c.call
288
+ break if breadcrumb.ignore?
289
+ end
290
+
291
+ # Return early if ignored
292
+ return if breadcrumb.ignore?
293
+
294
+ # Run on_breadcrumb callbacks
295
+ configuration.on_breadcrumb_callbacks.call(breadcrumb)
296
+ return if breadcrumb.ignore?
297
+
298
+ # Validate again in case of callback alteration
299
+ validator.validate(breadcrumb)
300
+
301
+ # Add to breadcrumbs buffer if still valid
302
+ configuration.breadcrumbs << breadcrumb unless breadcrumb.ignore?
86
303
  end
87
304
 
88
- # Log wrapper
89
- def log(message)
90
- configuration.logger.info("#{LOG_PREFIX}#{message}")
305
+ ##
306
+ # Add the given block to the list of on_error callbacks
307
+ #
308
+ # The on_error callbacks will be called when an error is captured or reported
309
+ # and are passed a {Bugsnag::Report} object
310
+ #
311
+ # Returning false from an on_error callback will cause the error to be ignored
312
+ # and will prevent any remaining callbacks from being called
313
+ #
314
+ # @return [void]
315
+ def on_error(&block)
316
+ configuration.on_error(&block)
91
317
  end
92
318
 
93
- # Warning logger
94
- def warn(message)
95
- configuration.logger.warn("#{LOG_PREFIX}#{message}")
319
+ ##
320
+ # Add the given callback to the list of on_error callbacks
321
+ #
322
+ # The on_error callbacks will be called when an error is captured or reported
323
+ # and are passed a {Bugsnag::Report} object
324
+ #
325
+ # Returning false from an on_error callback will cause the error to be ignored
326
+ # and will prevent any remaining callbacks from being called
327
+ #
328
+ # @param callback [Proc, Method, #call]
329
+ # @return [void]
330
+ def add_on_error(callback)
331
+ configuration.add_on_error(callback)
96
332
  end
97
333
 
98
- # Debug logger
99
- def debug(message)
100
- configuration.logger.info("#{LOG_PREFIX}#{message}") if configuration.debug
334
+ ##
335
+ # Remove the given callback from the list of on_error callbacks
336
+ #
337
+ # Note that this must be the same Proc instance that was passed to
338
+ # {Bugsnag#add_on_error}, otherwise it will not be removed
339
+ #
340
+ # @param callback [Proc]
341
+ # @return [void]
342
+ def remove_on_error(callback)
343
+ configuration.remove_on_error(callback)
101
344
  end
102
345
 
103
- # Configuration getters
104
- def configuration
105
- @configuration = nil unless defined?(@configuration)
106
- @configuration || LOCK.synchronize { @configuration ||= Bugsnag::Configuration.new }
346
+ ##
347
+ # Add the given callback to the list of on_breadcrumb callbacks
348
+ #
349
+ # The on_breadcrumb callbacks will be called when a breadcrumb is left and
350
+ # are passed the {Breadcrumbs::Breadcrumb Breadcrumb} object
351
+ #
352
+ # Returning false from an on_breadcrumb callback will cause the breadcrumb
353
+ # to be ignored and will prevent any remaining callbacks from being called
354
+ #
355
+ # @param callback [Proc, Method, #call]
356
+ # @return [void]
357
+ def add_on_breadcrumb(callback)
358
+ configuration.add_on_breadcrumb(callback)
107
359
  end
108
360
 
109
- # Set "per-request" data, temporal data for use in bugsnag middleware
110
- def set_request_data(key, value)
111
- Bugsnag.configuration.set_request_data(key, value)
361
+ ##
362
+ # Remove the given callback from the list of on_breadcrumb callbacks
363
+ #
364
+ # Note that this must be the same instance that was passed to
365
+ # {add_on_breadcrumb}, otherwise it will not be removed
366
+ #
367
+ # @param callback [Proc, Method, #call]
368
+ # @return [void]
369
+ def remove_on_breadcrumb(callback)
370
+ configuration.remove_on_breadcrumb(callback)
112
371
  end
113
372
 
114
- # Clear all "per-request" data, temporal data for use in bugsnag middleware
115
- # This method should be called after each distinct request or session ends
116
- # Eg. After completing a page request in a web app
117
- def clear_request_data
118
- Bugsnag.configuration.clear_request_data
373
+ ##
374
+ # Returns the current list of breadcrumbs
375
+ #
376
+ # This is a per-thread circular buffer, containing at most 'max_breadcrumbs'
377
+ # breadcrumbs
378
+ #
379
+ # @return [Bugsnag::Utility::CircularBuffer]
380
+ def breadcrumbs
381
+ configuration.breadcrumbs
119
382
  end
120
383
 
121
- # Allow access to "before notify" callbacks
122
- def before_notify_callbacks
123
- Bugsnag.configuration.request_data[:before_callbacks] ||= []
384
+ ##
385
+ # Returns the client's Cleaner object, or creates one if not yet created.
386
+ #
387
+ # @api private
388
+ #
389
+ # @return [Cleaner]
390
+ def cleaner
391
+ @cleaner = nil unless defined?(@cleaner)
392
+ @cleaner || LOCK.synchronize do
393
+ @cleaner ||= Bugsnag::Cleaner.new(configuration)
394
+ end
124
395
  end
125
396
 
126
- # Allow access to "after notify" callbacks
127
- def after_notify_callbacks
128
- Bugsnag.configuration.request_data[:after_callbacks] ||= []
397
+ ##
398
+ # Global metadata added to every event
399
+ #
400
+ # @return [Hash]
401
+ def metadata
402
+ configuration.metadata
403
+ end
404
+
405
+ ##
406
+ # Add values to metadata
407
+ #
408
+ # @overload add_metadata(section, data)
409
+ # Merges data into the given section of metadata
410
+ # @param section [String, Symbol]
411
+ # @param data [Hash]
412
+ #
413
+ # @overload add_metadata(section, key, value)
414
+ # Sets key to value in the given section of metadata. If the value is nil
415
+ # the key will be deleted
416
+ # @param section [String, Symbol]
417
+ # @param key [String, Symbol]
418
+ # @param value
419
+ #
420
+ # @return [void]
421
+ def add_metadata(section, key_or_data, *args)
422
+ configuration.add_metadata(section, key_or_data, *args)
129
423
  end
130
- end
131
- end
132
424
 
133
- [:resque, :sidekiq, :mailman, :delayed_job].each do |integration|
134
- begin
135
- require "bugsnag/#{integration}"
136
- rescue LoadError
425
+ ##
426
+ # Clear values from metadata
427
+ #
428
+ # @overload clear_metadata(section)
429
+ # Clears the given section of metadata
430
+ # @param section [String, Symbol]
431
+ #
432
+ # @overload clear_metadata(section, key)
433
+ # Clears the key in the given section of metadata
434
+ # @param section [String, Symbol]
435
+ # @param key [String, Symbol]
436
+ #
437
+ # @return [void]
438
+ def clear_metadata(section, *args)
439
+ configuration.clear_metadata(section, *args)
440
+ end
441
+
442
+ # Expose the feature flag delegate internally for use when creating new Events
443
+ #
444
+ # The Bugsnag module's feature_flag_delegate is request-specific
445
+ #
446
+ # @return [Bugsnag::Utility::FeatureFlagDelegate]
447
+ # @api private
448
+ def feature_flag_delegate
449
+ configuration.request_data[:feature_flag_delegate] ||= Utility::FeatureFlagDelegate.new
450
+ end
451
+
452
+ private
453
+
454
+ def should_deliver_notification?(exception, auto_notify)
455
+ return false unless configuration.enable_events
456
+
457
+ reason = abort_reason(exception, auto_notify)
458
+ configuration.debug(reason) unless reason.nil?
459
+ reason.nil?
460
+ end
461
+
462
+ def abort_reason(exception, auto_notify)
463
+ if !configuration.auto_notify && auto_notify
464
+ "Not notifying because auto_notify is disabled"
465
+ elsif !configuration.valid_api_key?
466
+ "Not notifying due to an invalid api_key"
467
+ elsif !configuration.should_notify_release_stage?
468
+ "Not notifying due to notify_release_stages :#{configuration.notify_release_stages.inspect}"
469
+ elsif exception.respond_to?(:skip_bugsnag) && exception.skip_bugsnag
470
+ "Not notifying due to skip_bugsnag flag"
471
+ end
472
+ end
473
+
474
+ ##
475
+ # Deliver the notification to Bugsnag
476
+ #
477
+ # @param report [Report]
478
+ # @return void
479
+ def deliver_notification(report)
480
+ configuration.info("Notifying #{configuration.notify_endpoint} of #{report.exceptions.last[:errorClass]}")
481
+
482
+ options = { headers: report.headers }
483
+
484
+ delivery_method = Bugsnag::Delivery[configuration.delivery_method]
485
+
486
+ if delivery_method.respond_to?(:serialize_and_deliver)
487
+ delivery_method.serialize_and_deliver(
488
+ configuration.notify_endpoint,
489
+ proc { report_to_json(report) },
490
+ configuration,
491
+ options
492
+ )
493
+ else
494
+ delivery_method.deliver(
495
+ configuration.notify_endpoint,
496
+ report_to_json(report),
497
+ configuration,
498
+ options
499
+ )
500
+ end
501
+
502
+ leave_breadcrumb(
503
+ report.summary[:error_class],
504
+ report.summary,
505
+ Bugsnag::Breadcrumbs::ERROR_BREADCRUMB_TYPE,
506
+ :auto
507
+ )
508
+ end
509
+
510
+ # Check if the API key is valid and warn (once) if it is not
511
+ def check_key_valid
512
+ @key_warning = false unless defined?(@key_warning)
513
+ if !configuration.valid_api_key? && !@key_warning
514
+ configuration.warn("No valid API key has been set, notifications will not be sent")
515
+ @key_warning = true
516
+ end
517
+ end
518
+
519
+ ##
520
+ # Verifies the current endpoint setup
521
+ #
522
+ # If only a notify_endpoint has been set, session tracking will be disabled
523
+ # If only a session_endpoint has been set, and ArgumentError will be raised
524
+ def check_endpoint_setup
525
+ notify_set = configuration.notify_endpoint && configuration.notify_endpoint != Bugsnag::Configuration::DEFAULT_NOTIFY_ENDPOINT
526
+ session_set = configuration.session_endpoint && configuration.session_endpoint != Bugsnag::Configuration::DEFAULT_SESSION_ENDPOINT
527
+ if notify_set && !session_set
528
+ configuration.warn("The session endpoint has not been set, all further session capturing will be disabled")
529
+ configuration.disable_sessions
530
+ elsif !notify_set && session_set
531
+ raise ArgumentError, "The session endpoint cannot be modified without the notify endpoint"
532
+ end
533
+ end
534
+
535
+ ##
536
+ # Convert the Report object to JSON
537
+ #
538
+ # We ensure the report is safe to send by removing recursion, fixing
539
+ # encoding errors and redacting metadata according to "meta_data_filters"
540
+ #
541
+ # @param report [Report]
542
+ # @return [String]
543
+ def report_to_json(report)
544
+ cleaned = cleaner.clean_object(report.as_json)
545
+ trimmed = Bugsnag::Helpers.trim_if_needed(cleaned)
546
+
547
+ ::JSON.dump(trimmed)
548
+ end
549
+
550
+ ##
551
+ # When running a script with 'bundle exec', uncaught exceptions will be
552
+ # converted to "friendly errors" which has the side effect of wrapping them
553
+ # in a SystemExit
554
+ #
555
+ # By default we ignore SystemExit, so need to unwrap the original exception
556
+ # in order to avoid ignoring real errors
557
+ #
558
+ # @param exception [Exception]
559
+ # @return [Exception]
560
+ def unwrap_bundler_exception(exception)
561
+ running_in_bundler = ENV.include?('BUNDLE_BIN_PATH')
562
+
563
+ # See if this exception came from Bundler's 'with_friendly_errors' method
564
+ return exception unless running_in_bundler
565
+ return exception unless exception.is_a?(SystemExit)
566
+ return exception unless exception.respond_to?(:cause)
567
+ return exception unless exception.backtrace.first.include?('/bundler/friendly_errors.rb')
568
+ return exception if exception.cause.nil?
569
+
570
+ unwrapped = exception.cause
571
+
572
+ # We may need to unwrap another level if the exception came from running
573
+ # an executable file directly (i.e. 'bundle exec <file>'). In this case
574
+ # there can be a SystemExit from 'with_friendly_errors' _and_ a SystemExit
575
+ # from 'kernel_load'
576
+ return unwrapped unless unwrapped.is_a?(SystemExit)
577
+ return unwrapped unless unwrapped.backtrace.first.include?('/bundler/cli/exec.rb')
578
+ return unwrapped if unwrapped.cause.nil?
579
+
580
+ unwrapped.cause
581
+ end
137
582
  end
138
583
  end
584
+ # rubocop:enable Metrics/ModuleLength
585
+
586
+ Bugsnag.load_integrations unless ENV["BUGSNAG_DISABLE_AUTOCONFIGURE"]