bugsnag 6.21.0 → 6.24.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (35) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +102 -0
  3. data/VERSION +1 -1
  4. data/lib/bugsnag/breadcrumb_type.rb +14 -0
  5. data/lib/bugsnag/breadcrumbs/breadcrumb.rb +34 -1
  6. data/lib/bugsnag/breadcrumbs/breadcrumbs.rb +1 -0
  7. data/lib/bugsnag/breadcrumbs/on_breadcrumb_callback_list.rb +50 -0
  8. data/lib/bugsnag/cleaner.rb +31 -18
  9. data/lib/bugsnag/configuration.rb +240 -22
  10. data/lib/bugsnag/delivery/synchronous.rb +2 -2
  11. data/lib/bugsnag/delivery/thread_queue.rb +2 -2
  12. data/lib/bugsnag/endpoint_configuration.rb +11 -0
  13. data/lib/bugsnag/endpoint_validator.rb +80 -0
  14. data/lib/bugsnag/error.rb +25 -0
  15. data/lib/bugsnag/event.rb +7 -0
  16. data/lib/bugsnag/integrations/rack.rb +3 -3
  17. data/lib/bugsnag/integrations/rails/active_job.rb +102 -0
  18. data/lib/bugsnag/integrations/railtie.rb +9 -1
  19. data/lib/bugsnag/integrations/resque.rb +17 -3
  20. data/lib/bugsnag/middleware/active_job.rb +18 -0
  21. data/lib/bugsnag/middleware/delayed_job.rb +21 -2
  22. data/lib/bugsnag/middleware/exception_meta_data.rb +2 -0
  23. data/lib/bugsnag/middleware/rack_request.rb +84 -19
  24. data/lib/bugsnag/middleware/rails3_request.rb +2 -2
  25. data/lib/bugsnag/middleware/rake.rb +1 -1
  26. data/lib/bugsnag/middleware/session_data.rb +3 -1
  27. data/lib/bugsnag/middleware/sidekiq.rb +1 -1
  28. data/lib/bugsnag/report.rb +166 -6
  29. data/lib/bugsnag/session_tracker.rb +52 -12
  30. data/lib/bugsnag/stacktrace.rb +10 -1
  31. data/lib/bugsnag/tasks/bugsnag.rake +1 -1
  32. data/lib/bugsnag/utility/duplicator.rb +124 -0
  33. data/lib/bugsnag/utility/metadata_delegate.rb +102 -0
  34. data/lib/bugsnag.rb +126 -4
  35. metadata +16 -6
@@ -12,6 +12,9 @@ require "bugsnag/middleware/session_data"
12
12
  require "bugsnag/middleware/breadcrumbs"
13
13
  require "bugsnag/utility/circular_buffer"
14
14
  require "bugsnag/breadcrumbs/breadcrumbs"
15
+ require "bugsnag/breadcrumbs/on_breadcrumb_callback_list"
16
+ require "bugsnag/endpoint_configuration"
17
+ require "bugsnag/endpoint_validator"
15
18
 
16
19
  module Bugsnag
17
20
  class Configuration
@@ -24,6 +27,7 @@ module Bugsnag
24
27
  attr_accessor :release_stage
25
28
 
26
29
  # A list of which release stages should cause notifications to be sent
30
+ # @deprecated Use {#enabled_release_stages} instead
27
31
  # @return [Array<String>, nil]
28
32
  attr_accessor :notify_release_stages
29
33
 
@@ -52,9 +56,20 @@ module Bugsnag
52
56
 
53
57
  # A list of keys that should be filtered out from the report and breadcrumb
54
58
  # metadata before sending them to Bugsnag
59
+ # @deprecated Use {#redacted_keys} instead
55
60
  # @return [Set<String, Regexp>]
56
61
  attr_accessor :meta_data_filters
57
62
 
63
+ # A set of keys that should be redacted from the report and breadcrumb
64
+ # metadata before sending them to Bugsnag
65
+ #
66
+ # When adding strings, keys that are equal to the string (ignoring case)
67
+ # will be redacted. When adding regular expressions, any keys which match
68
+ # the regular expression will be redacted
69
+ #
70
+ # @return [Set<String, Regexp>]
71
+ attr_accessor :redacted_keys
72
+
58
73
  # The logger to use for Bugsnag log messages
59
74
  # @return [Logger]
60
75
  attr_accessor :logger
@@ -104,6 +119,7 @@ module Bugsnag
104
119
  attr_accessor :discard_classes
105
120
 
106
121
  # Whether Bugsnag should automatically record sessions
122
+ # @deprecated Use {#auto_track_sessions} instead
107
123
  # @return [Boolean]
108
124
  attr_accessor :auto_capture_sessions
109
125
 
@@ -111,21 +127,23 @@ module Bugsnag
111
127
  # @return [Set<Class, Proc>]
112
128
  attr_accessor :ignore_classes
113
129
 
114
- # The URL error notifications will be delivered to
115
- # @return [String]
116
- attr_reader :notify_endpoint
117
- alias :endpoint :notify_endpoint
130
+ # The URLs to send events and sessions to
131
+ # @return [EndpointConfiguration]
132
+ attr_reader :endpoints
118
133
 
119
- # The URL session notifications will be delivered to
120
- # @return [String]
121
- attr_reader :session_endpoint
134
+ # Whether events will be delivered
135
+ # @api private
136
+ # @return [Boolean]
137
+ attr_reader :enable_events
122
138
 
123
139
  # Whether sessions will be delivered
140
+ # @api private
124
141
  # @return [Boolean]
125
142
  attr_reader :enable_sessions
126
143
 
127
144
  # A list of strings indicating allowable automatic breadcrumb types
128
- # @see Bugsnag::Breadcrumbs::VALID_BREADCRUMB_TYPES
145
+ # @deprecated Use {#enabled_breadcrumb_types} instead
146
+ # @see Bugsnag::BreadcrumbType
129
147
  # @return [Array<String>]
130
148
  attr_accessor :enabled_automatic_breadcrumb_types
131
149
 
@@ -137,14 +155,37 @@ module Bugsnag
137
155
  # @return [Integer]
138
156
  attr_reader :max_breadcrumbs
139
157
 
140
- #
158
+ # @deprecated Use {vendor_paths} instead
141
159
  # @return [Regexp]
142
160
  attr_accessor :vendor_path
143
161
 
162
+ # An array of paths within the {project_root} that should not be considered
163
+ # as "in project"
164
+ #
165
+ # These paths should be relative to the {project_root} and will only match
166
+ # whole directory names
167
+ #
168
+ # @return [Array<String>]
169
+ attr_accessor :vendor_paths
170
+
171
+ # The default context for all future events
172
+ # Setting this will disable automatic context setting
173
+ # @return [String, nil]
174
+ attr_accessor :context
175
+
176
+ # Global metadata added to every event
177
+ # @return [Hash]
178
+ attr_reader :metadata
179
+
144
180
  # @api private
145
181
  # @return [Array<String>]
146
182
  attr_reader :scopes_to_filter
147
183
 
184
+ # Expose on_breadcrumb_callbacks internally for Bugsnag.leave_breadcrumb
185
+ # @api private
186
+ # @return [Breadcrumbs::OnBreadcrumbCallbackList]
187
+ attr_reader :on_breadcrumb_callbacks
188
+
148
189
  API_KEY_REGEX = /[0-9a-f]{32}/i
149
190
  THREAD_LOCAL_NAME = "bugsnag_req_data"
150
191
 
@@ -180,6 +221,7 @@ module Bugsnag
180
221
  self.send_environment = false
181
222
  self.send_code = true
182
223
  self.meta_data_filters = Set.new(DEFAULT_META_DATA_FILTERS)
224
+ @redacted_keys = Set.new
183
225
  self.scopes_to_filter = DEFAULT_SCOPES_TO_FILTER
184
226
  self.hostname = default_hostname
185
227
  self.runtime_versions = {}
@@ -193,16 +235,20 @@ module Bugsnag
193
235
  # All valid breadcrumb types should be allowable initially
194
236
  self.enabled_automatic_breadcrumb_types = Bugsnag::Breadcrumbs::VALID_BREADCRUMB_TYPES.dup
195
237
  self.before_breadcrumb_callbacks = []
238
+ @on_breadcrumb_callbacks = Breadcrumbs::OnBreadcrumbCallbackList.new(self)
196
239
 
197
240
  # Store max_breadcrumbs here instead of outputting breadcrumbs.max_items
198
241
  # to avoid infinite recursion when creating breadcrumb buffer
199
242
  @max_breadcrumbs = DEFAULT_MAX_BREADCRUMBS
200
243
 
201
- # These are set exclusively using the "set_endpoints" method
202
- @notify_endpoint = DEFAULT_NOTIFY_ENDPOINT
203
- @session_endpoint = DEFAULT_SESSION_ENDPOINT
244
+ @endpoints = EndpointConfiguration.new(DEFAULT_NOTIFY_ENDPOINT, DEFAULT_SESSION_ENDPOINT)
245
+
246
+ @enable_events = true
204
247
  @enable_sessions = true
205
248
 
249
+ @metadata = {}
250
+ @metadata_delegate = Utility::MetadataDelegate.new
251
+
206
252
  # SystemExit and SignalException are common Exception types seen with
207
253
  # successful exits and are not automatically reported to Bugsnag
208
254
  # TODO move these defaults into `discard_classes` when `ignore_classes`
@@ -222,6 +268,7 @@ module Bugsnag
222
268
  # Stacktrace lines that matches regex will be marked as "out of project"
223
269
  # will only appear in the full trace.
224
270
  self.vendor_path = DEFAULT_VENDOR_PATH
271
+ @vendor_paths = []
225
272
 
226
273
  # Set up logging
227
274
  self.logger = Logger.new(STDOUT)
@@ -389,15 +436,23 @@ module Bugsnag
389
436
  ##
390
437
  # Logs a warning level message
391
438
  #
392
- # @param (see info)
439
+ # @param message [String, #to_s] The message to log
393
440
  def warn(message)
394
441
  logger.warn(PROG_NAME) { message }
395
442
  end
396
443
 
444
+ ##
445
+ # Logs an error level message
446
+ #
447
+ # @param message [String, #to_s] The message to log
448
+ def error(message)
449
+ logger.error(PROG_NAME) { message }
450
+ end
451
+
397
452
  ##
398
453
  # Logs a debug level message
399
454
  #
400
- # @param (see info)
455
+ # @param message [String, #to_s] The message to log
401
456
  def debug(message)
402
457
  logger.debug(PROG_NAME) { message }
403
458
  end
@@ -426,33 +481,54 @@ module Bugsnag
426
481
  end
427
482
 
428
483
  ##
429
- # Returns the breadcrumb circular buffer
484
+ # Returns the current list of breadcrumbs
485
+ #
486
+ # This is a per-thread circular buffer, containing at most 'max_breadcrumbs'
487
+ # breadcrumbs
430
488
  #
431
- # @return [Bugsnag::Utility::CircularBuffer] a thread based circular buffer containing breadcrumbs
489
+ # @return [Bugsnag::Utility::CircularBuffer]
432
490
  def breadcrumbs
433
491
  request_data[:breadcrumbs] ||= Bugsnag::Utility::CircularBuffer.new(@max_breadcrumbs)
434
492
  end
435
493
 
494
+ # The URL error notifications will be delivered to
495
+ # @!attribute notify_endpoint
496
+ # @return [String]
497
+ # @deprecated Use {#endpoints} instead
498
+ def notify_endpoint
499
+ @endpoints.notify
500
+ end
501
+
502
+ alias :endpoint :notify_endpoint
503
+
436
504
  # Sets the notification endpoint
437
505
  #
438
- # @deprecated Use {#set_endpoints} instead
506
+ # @deprecated Use {#endpoints} instead
439
507
  #
440
508
  # @param new_notify_endpoint [String] The URL to deliver error notifications to
441
509
  # @return [void]
442
510
  def endpoint=(new_notify_endpoint)
443
- warn("The 'endpoint' configuration option is deprecated. The 'set_endpoints' method should be used instead")
511
+ warn("The 'endpoint' configuration option is deprecated. Set both endpoints with the 'endpoints=' method instead")
444
512
  set_endpoints(new_notify_endpoint, session_endpoint) # Pass the existing session_endpoint through so it doesn't get overwritten
445
513
  end
446
514
 
515
+ # The URL session notifications will be delivered to
516
+ # @!attribute session_endpoint
517
+ # @return [String]
518
+ # @deprecated Use {#endpoints} instead
519
+ def session_endpoint
520
+ @endpoints.sessions
521
+ end
522
+
447
523
  ##
448
524
  # Sets the sessions endpoint
449
525
  #
450
- # @deprecated Use {#set_endpoints} instead
526
+ # @deprecated Use {#endpoints} instead
451
527
  #
452
528
  # @param new_session_endpoint [String] The URL to deliver session notifications to
453
529
  # @return [void]
454
530
  def session_endpoint=(new_session_endpoint)
455
- warn("The 'session_endpoint' configuration option is deprecated. The 'set_endpoints' method should be used instead")
531
+ warn("The 'session_endpoint' configuration option is deprecated. Set both endpoints with the 'endpoints=' method instead")
456
532
  set_endpoints(notify_endpoint, new_session_endpoint) # Pass the existing notify_endpoint through so it doesn't get overwritten
457
533
  end
458
534
 
@@ -462,9 +538,26 @@ module Bugsnag
462
538
  # @param new_notify_endpoint [String] The URL to deliver error notifications to
463
539
  # @param new_session_endpoint [String] The URL to deliver session notifications to
464
540
  # @return [void]
541
+ # @deprecated Use {#endpoints} instead
465
542
  def set_endpoints(new_notify_endpoint, new_session_endpoint)
466
- @notify_endpoint = new_notify_endpoint
467
- @session_endpoint = new_session_endpoint
543
+ self.endpoints = EndpointConfiguration.new(new_notify_endpoint, new_session_endpoint)
544
+ end
545
+
546
+ def endpoints=(endpoint_configuration)
547
+ result = EndpointValidator.validate(endpoint_configuration)
548
+
549
+ if result.valid?
550
+ @enable_events = true
551
+ @enable_sessions = true
552
+ else
553
+ warn(result.reason)
554
+
555
+ @enable_events = result.keep_events_enabled_for_backwards_compatibility?
556
+ @enable_sessions = false
557
+ end
558
+
559
+ # use the given endpoints even if they are invalid
560
+ @endpoints = endpoint_configuration
468
561
  end
469
562
 
470
563
  ##
@@ -503,6 +596,131 @@ module Bugsnag
503
596
  middleware.remove(callback)
504
597
  end
505
598
 
599
+ ##
600
+ # Add the given callback to the list of on_breadcrumb callbacks
601
+ #
602
+ # The on_breadcrumb callbacks will be called when a breadcrumb is left and
603
+ # are passed the {Breadcrumbs::Breadcrumb Breadcrumb} object
604
+ #
605
+ # Returning false from an on_breadcrumb callback will cause the breadcrumb
606
+ # to be ignored and will prevent any remaining callbacks from being called
607
+ #
608
+ # @param callback [Proc, Method, #call]
609
+ # @return [void]
610
+ def add_on_breadcrumb(callback)
611
+ @on_breadcrumb_callbacks.add(callback)
612
+ end
613
+
614
+ ##
615
+ # Remove the given callback from the list of on_breadcrumb callbacks
616
+ #
617
+ # Note that this must be the same instance that was passed to
618
+ # {add_on_breadcrumb}, otherwise it will not be removed
619
+ #
620
+ # @param callback [Proc, Method, #call]
621
+ # @return [void]
622
+ def remove_on_breadcrumb(callback)
623
+ @on_breadcrumb_callbacks.remove(callback)
624
+ end
625
+
626
+ ##
627
+ # Add values to metadata
628
+ #
629
+ # @overload add_metadata(section, data)
630
+ # Merges data into the given section of metadata
631
+ # @param section [String, Symbol]
632
+ # @param data [Hash]
633
+ #
634
+ # @overload add_metadata(section, key, value)
635
+ # Sets key to value in the given section of metadata. If the value is nil
636
+ # the key will be deleted
637
+ # @param section [String, Symbol]
638
+ # @param key [String, Symbol]
639
+ # @param value
640
+ #
641
+ # @return [void]
642
+ def add_metadata(section, key_or_data, *args)
643
+ @mutex.synchronize do
644
+ @metadata_delegate.add_metadata(@metadata, section, key_or_data, *args)
645
+ end
646
+ end
647
+
648
+ ##
649
+ # Clear values from metadata
650
+ #
651
+ # @overload clear_metadata(section)
652
+ # Clears the given section of metadata
653
+ # @param section [String, Symbol]
654
+ #
655
+ # @overload clear_metadata(section, key)
656
+ # Clears the key in the given section of metadata
657
+ # @param section [String, Symbol]
658
+ # @param key [String, Symbol]
659
+ #
660
+ # @return [void]
661
+ def clear_metadata(section, *args)
662
+ @mutex.synchronize do
663
+ @metadata_delegate.clear_metadata(@metadata, section, *args)
664
+ end
665
+ end
666
+
667
+ ##
668
+ # Has the context been explicitly set?
669
+ #
670
+ # This is necessary to differentiate between the context not being set and
671
+ # the context being set to 'nil' explicitly
672
+ #
673
+ # @api private
674
+ # @return [Boolean]
675
+ def context_set?
676
+ defined?(@context) != nil
677
+ end
678
+
679
+ # TODO: These methods can be a simple attr_accessor when they replace the
680
+ # methods they are aliasing
681
+ # NOTE: they are not aliases as YARD doesn't allow documenting the non-alias
682
+ # as deprecated without also marking the alias as deprecated
683
+
684
+ # A list of which release stages should cause notifications to be sent
685
+ # @!attribute enabled_release_stages
686
+ # @return [Array<String>, nil]
687
+ def enabled_release_stages
688
+ @notify_release_stages
689
+ end
690
+
691
+ # @param release_stages [Array<String>, nil]
692
+ # @return [void]
693
+ def enabled_release_stages=(release_stages)
694
+ @notify_release_stages = release_stages
695
+ end
696
+
697
+ # A list of breadcrumb types that Bugsnag will collect automatically
698
+ # @!attribute enabled_breadcrumb_types
699
+ # @see Bugsnag::BreadcrumbType
700
+ # @return [Array<String>]
701
+ def enabled_breadcrumb_types
702
+ @enabled_automatic_breadcrumb_types
703
+ end
704
+
705
+ # @param breadcrumb_types [Array<String>]
706
+ # @return [void]
707
+ def enabled_breadcrumb_types=(breadcrumb_types)
708
+ @enabled_automatic_breadcrumb_types = breadcrumb_types
709
+ end
710
+
711
+ # Whether sessions should be tracked automatically
712
+ # @!attribute auto_track_sessions
713
+ # @return [Boolean]
714
+ def auto_track_sessions
715
+ @auto_capture_sessions
716
+ end
717
+
718
+ # @param track_sessions [Boolean]
719
+ # @return [void]
720
+ def auto_track_sessions=(track_sessions)
721
+ @auto_capture_sessions = track_sessions
722
+ end
723
+
506
724
  private
507
725
 
508
726
  attr_writer :scopes_to_filter
@@ -18,8 +18,8 @@ module Bugsnag
18
18
  # KLUDGE: Since we don't re-raise http exceptions, this breaks rspec
19
19
  raise if e.class.to_s == "RSpec::Expectations::ExpectationNotMetError"
20
20
 
21
- configuration.warn("Notification to #{url} failed, #{e.inspect}")
22
- configuration.warn(e.backtrace)
21
+ configuration.error("Unable to send information to Bugsnag (#{url}), #{e.inspect}")
22
+ configuration.error(e.backtrace)
23
23
  end
24
24
  end
25
25
 
@@ -31,8 +31,8 @@ module Bugsnag
31
31
  begin
32
32
  payload = get_payload.call
33
33
  rescue StandardError => e
34
- configuration.warn("Notification to #{url} failed, #{e.inspect}")
35
- configuration.warn(e.backtrace)
34
+ configuration.error("Unable to send information to Bugsnag (#{url}), #{e.inspect}")
35
+ configuration.error(e.backtrace)
36
36
  end
37
37
 
38
38
  Synchronous.deliver(url, payload, configuration, options) unless payload.nil?
@@ -0,0 +1,11 @@
1
+ module Bugsnag
2
+ class EndpointConfiguration
3
+ attr_reader :notify
4
+ attr_reader :sessions
5
+
6
+ def initialize(notify, sessions)
7
+ @notify = notify
8
+ @sessions = sessions
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,80 @@
1
+ module Bugsnag
2
+ # @api private
3
+ class EndpointValidator
4
+ def self.validate(endpoints)
5
+ # ensure we have an EndpointConfiguration object
6
+ return Result.missing_urls unless endpoints.is_a?(EndpointConfiguration)
7
+
8
+ # check for missing URLs
9
+ return Result.missing_urls if endpoints.notify.nil? && endpoints.sessions.nil?
10
+ return Result.missing_notify if endpoints.notify.nil?
11
+ return Result.missing_session if endpoints.sessions.nil?
12
+
13
+ # check for empty URLs
14
+ return Result.invalid_urls if endpoints.notify.empty? && endpoints.sessions.empty?
15
+ return Result.invalid_notify if endpoints.notify.empty?
16
+ return Result.invalid_session if endpoints.sessions.empty?
17
+
18
+ Result.valid
19
+ end
20
+
21
+ # @api private
22
+ class Result
23
+ # rubocop:disable Layout/LineLength
24
+ MISSING_URLS = "Invalid configuration. endpoints must be set with both a notify and session URL. Bugsnag will not send any requests.".freeze
25
+ MISSING_NOTIFY_URL = "Invalid configuration. endpoints.sessions cannot be set without also setting endpoints.notify. Bugsnag will not send any requests.".freeze
26
+ MISSING_SESSION_URL = "Invalid configuration. endpoints.notify cannot be set without also setting endpoints.sessions. Bugsnag will not send any sessions.".freeze
27
+
28
+ INVALID_URLS = "Invalid configuration. endpoints should be valid URLs, got empty strings. Bugsnag will not send any requests.".freeze
29
+ INVALID_NOTIFY_URL = "Invalid configuration. endpoints.notify should be a valid URL, got empty string. Bugsnag will not send any requests.".freeze
30
+ INVALID_SESSION_URL = "Invalid configuration. endpoints.sessions should be a valid URL, got empty string. Bugsnag will not send any sessions.".freeze
31
+ # rubocop:enable Layout/LineLength
32
+
33
+ attr_reader :reason
34
+
35
+ def initialize(valid, keep_events_enabled_for_backwards_compatibility = true, reason = nil)
36
+ @valid = valid
37
+ @keep_events_enabled_for_backwards_compatibility = keep_events_enabled_for_backwards_compatibility
38
+ @reason = reason
39
+ end
40
+
41
+ def valid?
42
+ @valid
43
+ end
44
+
45
+ def keep_events_enabled_for_backwards_compatibility?
46
+ @keep_events_enabled_for_backwards_compatibility
47
+ end
48
+
49
+ # factory functions
50
+
51
+ def self.valid
52
+ new(true)
53
+ end
54
+
55
+ def self.missing_urls
56
+ new(false, false, MISSING_URLS)
57
+ end
58
+
59
+ def self.missing_notify
60
+ new(false, false, MISSING_NOTIFY_URL)
61
+ end
62
+
63
+ def self.missing_session
64
+ new(false, true, MISSING_SESSION_URL)
65
+ end
66
+
67
+ def self.invalid_urls
68
+ new(false, false, INVALID_URLS)
69
+ end
70
+
71
+ def self.invalid_notify
72
+ new(false, false, INVALID_NOTIFY_URL)
73
+ end
74
+
75
+ def self.invalid_session
76
+ new(false, true, INVALID_SESSION_URL)
77
+ end
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,25 @@
1
+ module Bugsnag
2
+ class Error
3
+ # @return [String] the error's class name
4
+ attr_accessor :error_class
5
+
6
+ # @return [String] the error's message
7
+ attr_accessor :error_message
8
+
9
+ # @return [Hash] the error's processed stacktrace
10
+ attr_reader :stacktrace
11
+
12
+ # @return [String] the type of error (always "ruby")
13
+ attr_accessor :type
14
+
15
+ # @api private
16
+ TYPE = "ruby".freeze
17
+
18
+ def initialize(error_class, error_message, stacktrace)
19
+ @error_class = error_class
20
+ @error_message = error_message
21
+ @stacktrace = stacktrace
22
+ @type = TYPE
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,7 @@
1
+ require "bugsnag/report"
2
+
3
+ module Bugsnag
4
+ # For now Event is just an alias of Report. This points to the same object so
5
+ # any changes to Report will also affect Event
6
+ Event = Report
7
+ end
@@ -25,9 +25,9 @@ module Bugsnag
25
25
  end
26
26
 
27
27
  # Hook up rack-based notification middlewares
28
- config.middleware.insert_before([Bugsnag::Middleware::Rails3Request,Bugsnag::Middleware::Callbacks], Bugsnag::Middleware::RackRequest) if defined?(::Rack)
29
- config.middleware.insert_before(Bugsnag::Middleware::Callbacks, Bugsnag::Middleware::WardenUser) if defined?(Warden)
30
- config.middleware.insert_before(Bugsnag::Middleware::Callbacks, Bugsnag::Middleware::ClearanceUser) if defined?(Clearance)
28
+ config.internal_middleware.insert_before(Bugsnag::Middleware::Rails3Request, Bugsnag::Middleware::RackRequest) if defined?(::Rack)
29
+ config.internal_middleware.use(Bugsnag::Middleware::WardenUser) if defined?(Warden)
30
+ config.internal_middleware.use(Bugsnag::Middleware::ClearanceUser) if defined?(Clearance)
31
31
 
32
32
  # Set environment data for payload
33
33
  # Note we only set the detected app_type if it's not already set. This
@@ -0,0 +1,102 @@
1
+ require 'set'
2
+
3
+ module Bugsnag::Rails
4
+ module ActiveJob
5
+ SEVERITY = 'error'
6
+ SEVERITY_REASON = {
7
+ type: Bugsnag::Report::UNHANDLED_EXCEPTION_MIDDLEWARE,
8
+ attributes: { framework: 'Active Job' }
9
+ }
10
+
11
+ EXISTING_INTEGRATIONS = Set[
12
+ 'ActiveJob::QueueAdapters::DelayedJobAdapter',
13
+ 'ActiveJob::QueueAdapters::QueAdapter',
14
+ 'ActiveJob::QueueAdapters::ResqueAdapter',
15
+ 'ActiveJob::QueueAdapters::ShoryukenAdapter',
16
+ 'ActiveJob::QueueAdapters::SidekiqAdapter'
17
+ ]
18
+
19
+ INLINE_ADAPTER = 'ActiveJob::QueueAdapters::InlineAdapter'
20
+
21
+ # these methods were added after the first Active Job release so
22
+ # may not be present, depending on the Rails version
23
+ MAYBE_MISSING_METHODS = [
24
+ :provider_job_id,
25
+ :priority,
26
+ :executions,
27
+ :enqueued_at,
28
+ :timezone
29
+ ]
30
+
31
+ def self.included(base)
32
+ base.class_eval do
33
+ around_perform do |job, block|
34
+ adapter = _bugsnag_get_adapter_name(job)
35
+
36
+ # if we have an integration for this queue adapter already then we should
37
+ # leave this job alone or we'll end up with duplicate metadata
38
+ next block.call if EXISTING_INTEGRATIONS.include?(adapter)
39
+
40
+ Bugsnag.configuration.detected_app_type = 'active job'
41
+
42
+ begin
43
+ Bugsnag.configuration.set_request_data(:active_job, _bugsnag_extract_metadata(job))
44
+
45
+ block.call
46
+ rescue Exception => e
47
+ Bugsnag.notify(e, true) do |report|
48
+ report.severity = SEVERITY
49
+ report.severity_reason = SEVERITY_REASON
50
+ end
51
+
52
+ # when using the "inline" adapter the job is run immediately, which
53
+ # will result in our Rack integration catching the re-raised error
54
+ # and reporting it a second time if it's run in a web request
55
+ if adapter == INLINE_ADAPTER
56
+ e.instance_eval do
57
+ def skip_bugsnag
58
+ true
59
+ end
60
+ end
61
+ end
62
+
63
+ raise
64
+ ensure
65
+ Bugsnag.configuration.clear_request_data
66
+ end
67
+ end
68
+ end
69
+ end
70
+
71
+ private
72
+
73
+ def _bugsnag_get_adapter_name(job)
74
+ adapter = job.class.queue_adapter
75
+
76
+ # in Rails 4 queue adapters were references to a class. In Rails 5+
77
+ # they are an instance of that class instead
78
+ return adapter.name if adapter.is_a?(Class)
79
+
80
+ adapter.class.name
81
+ end
82
+
83
+ def _bugsnag_extract_metadata(job)
84
+ metadata = {
85
+ job_id: job.job_id,
86
+ job_name: job.class.name,
87
+ queue: job.queue_name,
88
+ arguments: job.arguments,
89
+ locale: job.locale
90
+ }
91
+
92
+ MAYBE_MISSING_METHODS.each do |method_name|
93
+ next unless job.respond_to?(method_name)
94
+
95
+ metadata[method_name] = job.send(method_name)
96
+ end
97
+
98
+ metadata.compact!
99
+ metadata
100
+ end
101
+ end
102
+ end
@@ -60,7 +60,7 @@ module Bugsnag
60
60
  config.logger = ::Rails.logger
61
61
  config.release_stage ||= ::Rails.env.to_s
62
62
  config.project_root = ::Rails.root.to_s
63
- config.middleware.insert_before Bugsnag::Middleware::Callbacks, Bugsnag::Middleware::Rails3Request
63
+ config.internal_middleware.use(Bugsnag::Middleware::Rails3Request)
64
64
  config.runtime_versions["rails"] = ::Rails::VERSION::STRING
65
65
  end
66
66
 
@@ -74,6 +74,14 @@ module Bugsnag
74
74
  include Bugsnag::Rails::ActiveRecordRescue
75
75
  end
76
76
 
77
+ ActiveSupport.on_load(:active_job) do
78
+ require "bugsnag/middleware/active_job"
79
+ Bugsnag.configuration.internal_middleware.use(Bugsnag::Middleware::ActiveJob)
80
+
81
+ require "bugsnag/integrations/rails/active_job"
82
+ include Bugsnag::Rails::ActiveJob
83
+ end
84
+
77
85
  Bugsnag::Rails::DEFAULT_RAILS_BREADCRUMBS.each { |event| event_subscription(event) }
78
86
 
79
87
  # Make sure we don't overwrite the value set by another integration because