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
@@ -0,0 +1,133 @@
1
+ require 'mongo'
2
+
3
+ module Bugsnag
4
+ ##
5
+ # Subscribes to, and creates breadcrumbs from, mongo_ruby_driver events
6
+ #
7
+ # @api private
8
+ class MongoBreadcrumbSubscriber
9
+ MONGO_MESSAGE_PREFIX = "Mongo query "
10
+ MONGO_EVENT_PREFIX = "mongo."
11
+ MONGO_COMMAND_KEY = :bugsnag_mongo_commands
12
+ MAX_FILTER_DEPTH = 5
13
+
14
+ ##
15
+ # Listens to the 'started' event, storing the command for later usage
16
+ #
17
+ # @param event [Mongo::Event::Base] the mongo_ruby_driver generated event
18
+ def started(event)
19
+ leave_command(event)
20
+ end
21
+
22
+ ##
23
+ # Listens to the 'succeeded' event, leaving a breadcrumb
24
+ #
25
+ # @param event [Mongo::Event::Base] the mongo_ruby_driver generated event
26
+ def succeeded(event)
27
+ leave_mongo_breadcrumb("succeeded", event)
28
+ end
29
+
30
+ ##
31
+ # Listens to the 'failed' event, leaving a breadcrumb
32
+ #
33
+ # @param event [Mongo::Event::Base] the mongo_ruby_driver generated event
34
+ def failed(event)
35
+ leave_mongo_breadcrumb("failed", event)
36
+ end
37
+
38
+ private
39
+
40
+ ##
41
+ # Generates breadcrumb data from an event
42
+ #
43
+ # @param event_name [String] the type of event
44
+ # @param event [Mongo::Event::Base] the mongo_ruby_driver generated event
45
+ def leave_mongo_breadcrumb(event_name, event)
46
+ message = MONGO_MESSAGE_PREFIX + event_name
47
+ meta_data = {
48
+ :event_name => MONGO_EVENT_PREFIX + event_name,
49
+ :command_name => event.command_name,
50
+ :database_name => event.database_name,
51
+ :operation_id => event.operation_id,
52
+ :request_id => event.request_id,
53
+ :duration => event.duration
54
+ }
55
+ if (command = pop_command(event.request_id))
56
+ collection_key = event.command_name == "getMore" ? "collection" : event.command_name
57
+ meta_data[:collection] = command[collection_key]
58
+ unless command["filter"].nil?
59
+ filter = sanitize_filter_hash(command["filter"])
60
+ meta_data[:filter] = JSON.dump(filter)
61
+ end
62
+ end
63
+ meta_data[:message] = event.message if defined?(event.message)
64
+
65
+ Bugsnag.leave_breadcrumb(message, meta_data, Bugsnag::Breadcrumbs::PROCESS_BREADCRUMB_TYPE, :auto)
66
+ end
67
+
68
+ ##
69
+ # Removes values from filter hashes, replacing them with '?'
70
+ #
71
+ # @param filter_hash [Hash] the filter hash for the mongo transaction
72
+ # @param depth [Integer] the current filter depth
73
+ #
74
+ # @return [Hash] the filtered hash
75
+ def sanitize_filter_hash(filter_hash, depth = 0)
76
+ filter_hash.each_with_object({}) do |(key, value), output|
77
+ output[key] = sanitize_filter_value(value, depth)
78
+ end
79
+ end
80
+
81
+ ##
82
+ # Transforms a value element into a useful, redacted, version
83
+ #
84
+ # @param value [Object] the filter value
85
+ # @param depth [Integer] the current filter depth
86
+ #
87
+ # @return [Array, Hash, String] the sanitized value
88
+ def sanitize_filter_value(value, depth)
89
+ depth += 1
90
+ if depth >= MAX_FILTER_DEPTH
91
+ '[MAX_FILTER_DEPTH_REACHED]'
92
+ elsif value.is_a?(Array)
93
+ value.map { |array_value| sanitize_filter_value(array_value, depth) }
94
+ elsif value.is_a?(Hash)
95
+ sanitize_filter_hash(value, depth)
96
+ else
97
+ '?'
98
+ end
99
+ end
100
+
101
+ ##
102
+ # Stores the mongo command in the request data by the request_id
103
+ #
104
+ # @param event [Mongo::Event::Base] the mongo_ruby_driver generated event
105
+ def leave_command(event)
106
+ event_commands[event.request_id] = event.command
107
+ end
108
+
109
+ ##
110
+ # Removes and retrieves a stored command from the request data
111
+ #
112
+ # @param request_id [String] the id of the mongo_ruby_driver event
113
+ #
114
+ # @return [Hash, nil] the requested command, or nil if not found
115
+ def pop_command(request_id)
116
+ event_commands.delete(request_id)
117
+ end
118
+
119
+ ##
120
+ # Provides access to a thread-based mongo event command hash
121
+ #
122
+ # @return [Hash] the hash of mongo event commands
123
+ def event_commands
124
+ Bugsnag.configuration.request_data[MONGO_COMMAND_KEY] ||= {}
125
+ end
126
+ end
127
+ end
128
+
129
+ if defined?(Mongo::Monitoring)
130
+ ##
131
+ # Add the subscriber to the global Mongo monitoring object
132
+ Mongo::Monitoring::Global.subscribe(Mongo::Monitoring::COMMAND, Bugsnag::MongoBreadcrumbSubscriber.new)
133
+ end
@@ -0,0 +1,53 @@
1
+ require 'que'
2
+ if defined?(::Que)
3
+ handler = proc do |error, job|
4
+ begin
5
+ job &&= job.dup # Make sure the original job object is not mutated.
6
+
7
+ Bugsnag.notify(error, true) do |report|
8
+ if job
9
+ job[:error_count] += 1
10
+
11
+ # If the job was scheduled using ActiveJob then unwrap the job details for clarity:
12
+ if job[:job_class] == "ActiveJob::QueueAdapters::QueAdapter::JobWrapper"
13
+ wrapped_job = job[:args].last
14
+ wrapped_job = wrapped_job.each_with_object({}) { |(k, v), result| result[k.to_sym] = v } # Symbolize keys
15
+
16
+ # Align key names with keys in `job`
17
+ wrapped_job[:queue] = wrapped_job.delete(:queue_name)
18
+ wrapped_job[:args] = wrapped_job.delete(:arguments)
19
+
20
+ job.merge!(wrapper_job_class: job[:job_class], wrapper_job_id: job[:job_id]).merge!(wrapped_job)
21
+ end
22
+ end
23
+
24
+ report.add_tab(:job, job)
25
+ report.severity = 'error'
26
+ report.severity_reason = {
27
+ :type => Bugsnag::Report::UNHANDLED_EXCEPTION_MIDDLEWARE,
28
+ :attributes => {
29
+ :framework => 'Que'
30
+ }
31
+ }
32
+ end
33
+ rescue => e
34
+ # Que supresses errors raised by its error handler to avoid killing the worker. Log them somewhere:
35
+ Bugsnag.configuration.warn("Failed to notify Bugsnag of error in Que job (#{e.class}): #{e.message} \n#{e.backtrace[0..9].join("\n")}")
36
+ raise
37
+ end
38
+ end
39
+
40
+ Bugsnag.configuration.detected_app_type = "que"
41
+
42
+ if defined?(::Que::Version)
43
+ Bugsnag.configuration.runtime_versions["que"] = ::Que::Version
44
+ elsif defined?(::Que::VERSION)
45
+ Bugsnag.configuration.runtime_versions["que"] = ::Que::VERSION
46
+ end
47
+
48
+ if Que.respond_to?(:error_notifier=)
49
+ Que.error_notifier = handler
50
+ elsif Que.respond_to?(:error_handler=)
51
+ Que.error_handler = handler
52
+ end
53
+ end
@@ -0,0 +1,83 @@
1
+ module Bugsnag
2
+ ##
3
+ # Automatically captures and adds Rack request information to error reports
4
+ class Rack
5
+
6
+ FRAMEWORK_ATTRIBUTES = {
7
+ :framework => "Rack"
8
+ }
9
+
10
+ def initialize(app)
11
+ @app = app
12
+
13
+ # Configure bugsnag rack defaults
14
+ Bugsnag.configure do |config|
15
+ # Try to set the release_stage automatically if it hasn't already been set
16
+ config.release_stage ||= ENV["RACK_ENV"] if ENV["RACK_ENV"]
17
+
18
+ # Try to set the project_root if it hasn't already been set, or show a warning if we can't
19
+ unless config.project_root && !config.project_root.to_s.empty?
20
+ if defined?(settings)
21
+ config.project_root = settings.root
22
+ else
23
+ config.warn("You should set your app's project_root (see https://docs.bugsnag.com/platforms/ruby/rails/configuration-options/#project_root).")
24
+ end
25
+ end
26
+
27
+ # Hook up rack-based notification middlewares
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
+
32
+ # Set environment data for payload
33
+ # Note we only set the detected app_type if it's not already set. This
34
+ # ensures we don't overwrite the value set by the Railtie
35
+ config.detected_app_type ||= "rack"
36
+ config.runtime_versions["rack"] = ::Rack.release if defined?(::Rack)
37
+ config.runtime_versions["sinatra"] = ::Sinatra::VERSION if defined?(::Sinatra)
38
+ end
39
+ end
40
+
41
+ ##
42
+ # Wraps a call to the application with error capturing
43
+ def call(env)
44
+ # Set the request data for bugsnag middleware to use
45
+ Bugsnag.configuration.set_request_data(:rack_env, env)
46
+ if Bugsnag.configuration.auto_capture_sessions
47
+ Bugsnag.start_session
48
+ end
49
+
50
+ begin
51
+ response = @app.call(env)
52
+ rescue Exception => raised
53
+ # Notify bugsnag of rack exceptions
54
+ Bugsnag.notify(raised, true) do |report|
55
+ report.severity = "error"
56
+ report.severity_reason = {
57
+ :type => Bugsnag::Report::UNHANDLED_EXCEPTION_MIDDLEWARE,
58
+ :attributes => Bugsnag::Rack::FRAMEWORK_ATTRIBUTES
59
+ }
60
+ end
61
+
62
+ # Re-raise the exception
63
+ raise
64
+ end
65
+
66
+ # Notify bugsnag of rack exceptions
67
+ if env["rack.exception"]
68
+ Bugsnag.notify(env["rack.exception"], true) do |report|
69
+ report.severity = "error"
70
+ report.severity_reason = {
71
+ :type => Bugsnag::Report::UNHANDLED_EXCEPTION_MIDDLEWARE,
72
+ :attributes => FRAMEWORK_ATTRIBUTES
73
+ }
74
+ end
75
+ end
76
+
77
+ response
78
+ ensure
79
+ # Clear per-request data after processing the each request
80
+ Bugsnag.configuration.clear_request_data
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,100 @@
1
+ module Bugsnag::Rails
2
+ module ActiveJob
3
+ SEVERITY = 'error'
4
+ SEVERITY_REASON = {
5
+ type: Bugsnag::Report::UNHANDLED_EXCEPTION_MIDDLEWARE,
6
+ attributes: { framework: 'Active Job' }
7
+ }
8
+
9
+ EXISTING_INTEGRATIONS = Set[
10
+ 'ActiveJob::QueueAdapters::DelayedJobAdapter',
11
+ 'ActiveJob::QueueAdapters::QueAdapter',
12
+ 'ActiveJob::QueueAdapters::ResqueAdapter',
13
+ 'ActiveJob::QueueAdapters::ShoryukenAdapter',
14
+ 'ActiveJob::QueueAdapters::SidekiqAdapter'
15
+ ]
16
+
17
+ INLINE_ADAPTER = 'ActiveJob::QueueAdapters::InlineAdapter'
18
+
19
+ # these methods were added after the first Active Job release so
20
+ # may not be present, depending on the Rails version
21
+ MAYBE_MISSING_METHODS = [
22
+ :provider_job_id,
23
+ :priority,
24
+ :executions,
25
+ :enqueued_at,
26
+ :timezone
27
+ ]
28
+
29
+ def self.included(base)
30
+ base.class_eval do
31
+ around_perform do |job, block|
32
+ adapter = _bugsnag_get_adapter_name(job)
33
+
34
+ # if we have an integration for this queue adapter already then we should
35
+ # leave this job alone or we'll end up with duplicate metadata
36
+ next block.call if EXISTING_INTEGRATIONS.include?(adapter)
37
+
38
+ Bugsnag.configuration.detected_app_type = 'active job'
39
+
40
+ begin
41
+ Bugsnag.configuration.set_request_data(:active_job, _bugsnag_extract_metadata(job))
42
+
43
+ block.call
44
+ rescue Exception => e
45
+ Bugsnag.notify(e, true) do |report|
46
+ report.severity = SEVERITY
47
+ report.severity_reason = SEVERITY_REASON
48
+ end
49
+
50
+ # when using the "inline" adapter the job is run immediately, which
51
+ # will result in our Rack integration catching the re-raised error
52
+ # and reporting it a second time if it's run in a web request
53
+ if adapter == INLINE_ADAPTER
54
+ e.instance_eval do
55
+ def skip_bugsnag
56
+ true
57
+ end
58
+ end
59
+ end
60
+
61
+ raise
62
+ ensure
63
+ Bugsnag.configuration.clear_request_data
64
+ end
65
+ end
66
+ end
67
+ end
68
+
69
+ private
70
+
71
+ def _bugsnag_get_adapter_name(job)
72
+ adapter = job.class.queue_adapter
73
+
74
+ # in Rails 4 queue adapters were references to a class. In Rails 5+
75
+ # they are an instance of that class instead
76
+ return adapter.name if adapter.is_a?(Class)
77
+
78
+ adapter.class.name
79
+ end
80
+
81
+ def _bugsnag_extract_metadata(job)
82
+ metadata = {
83
+ job_id: job.job_id,
84
+ job_name: job.class.name,
85
+ queue: job.queue_name,
86
+ arguments: job.arguments,
87
+ locale: job.locale
88
+ }
89
+
90
+ MAYBE_MISSING_METHODS.each do |method_name|
91
+ next unless job.respond_to?(method_name)
92
+
93
+ metadata[method_name] = job.send(method_name)
94
+ end
95
+
96
+ metadata.compact!
97
+ metadata
98
+ end
99
+ end
100
+ end
@@ -1,6 +1,9 @@
1
1
  module Bugsnag::Rails
2
2
  module ActiveRecordRescue
3
3
  KINDS = [:commit, :rollback].freeze
4
+ FRAMEWORK_ATTRIBUTES = {
5
+ :framework => "Rails"
6
+ }
4
7
 
5
8
  def run_callbacks(kind, *args, &block)
6
9
  if KINDS.include?(kind)
@@ -8,7 +11,13 @@ module Bugsnag::Rails
8
11
  super
9
12
  rescue StandardError => exception
10
13
  # This exception will NOT be escalated, so notify it here.
11
- Bugsnag.auto_notify(exception)
14
+ Bugsnag.notify(exception, true) do |report|
15
+ report.severity = "error"
16
+ report.severity_reason = {
17
+ :type => Bugsnag::Report::UNHANDLED_EXCEPTION_MIDDLEWARE,
18
+ :attributes => FRAMEWORK_ATTRIBUTES
19
+ }
20
+ end
12
21
  raise
13
22
  end
14
23
  else
@@ -5,15 +5,12 @@ module Bugsnag::Rails
5
5
  end
6
6
 
7
7
  module ClassMethods
8
+
8
9
  private
9
10
  def before_bugsnag_notify(*methods, &block)
10
11
  _add_bugsnag_notify_callback(:before_callbacks, *methods, &block)
11
12
  end
12
13
 
13
- def after_bugsnag_notify(*methods, &block)
14
- _add_bugsnag_notify_callback(:after_callbacks, *methods, &block)
15
- end
16
-
17
14
  def _add_bugsnag_notify_callback(callback_key, *methods, &block)
18
15
  options = methods.last.is_a?(Hash) ? methods.pop : {}
19
16
 
@@ -40,10 +37,5 @@ module Bugsnag::Rails
40
37
  end
41
38
  end
42
39
  end
43
-
44
- private
45
- def notify_bugsnag(exception, custom_data=nil)
46
- Bugsnag.notify(exception, custom_data)
47
- end
48
40
  end
49
41
  end
@@ -0,0 +1,115 @@
1
+ module Bugsnag::Rails
2
+ DEFAULT_RAILS_BREADCRUMBS = [
3
+ {
4
+ :id => "perform_action.action_cable",
5
+ :message => "Perform ActionCable",
6
+ :type => Bugsnag::Breadcrumbs::PROCESS_BREADCRUMB_TYPE,
7
+ :allowed_data => [
8
+ :channel_class,
9
+ :action
10
+ ]
11
+ },
12
+ {
13
+ :id => "perform_start.active_job",
14
+ :message => "Start perform ActiveJob",
15
+ :type => Bugsnag::Breadcrumbs::PROCESS_BREADCRUMB_TYPE,
16
+ :allowed_data => []
17
+ },
18
+ {
19
+ :id => "cache_read.active_support",
20
+ :message => "Read cache",
21
+ :type => Bugsnag::Breadcrumbs::PROCESS_BREADCRUMB_TYPE,
22
+ :allowed_data => [
23
+ :hit,
24
+ :super_operation
25
+ ]
26
+ },
27
+ {
28
+ :id => "cache_fetch_hit.active_support",
29
+ :message => "Fetch cache hit",
30
+ :type => Bugsnag::Breadcrumbs::PROCESS_BREADCRUMB_TYPE,
31
+ :allowed_data => []
32
+ },
33
+ {
34
+ :id => "sql.active_record",
35
+ :message => "ActiveRecord SQL query",
36
+ :type => Bugsnag::Breadcrumbs::PROCESS_BREADCRUMB_TYPE,
37
+ :allowed_data => [
38
+ :name,
39
+ # :connection_id is no longer provided in Rails 6.1+ but we can get it
40
+ # from the :connection key of the event instead
41
+ :connection_id,
42
+ :cached
43
+ ]
44
+ },
45
+ {
46
+ :id => "start_processing.action_controller",
47
+ :message => "Controller started processing",
48
+ :type => Bugsnag::Breadcrumbs::REQUEST_BREADCRUMB_TYPE,
49
+ :allowed_data => [
50
+ :controller,
51
+ :action,
52
+ :method,
53
+ :path
54
+ ]
55
+ },
56
+ {
57
+ :id => "process_action.action_controller",
58
+ :message => "Controller action processed",
59
+ :type => Bugsnag::Breadcrumbs::REQUEST_BREADCRUMB_TYPE,
60
+ :allowed_data => [
61
+ :controller,
62
+ :action,
63
+ :method,
64
+ :status,
65
+ :db_runtime
66
+ ]
67
+ },
68
+ {
69
+ :id => "redirect_to.action_controller",
70
+ :message => "Controller redirect",
71
+ :type => Bugsnag::Breadcrumbs::REQUEST_BREADCRUMB_TYPE,
72
+ :allowed_data => [
73
+ :status,
74
+ :location
75
+ ]
76
+ },
77
+ {
78
+ :id => "halted_callback.action_controller",
79
+ :message => "Controller halted via callback",
80
+ :type => Bugsnag::Breadcrumbs::REQUEST_BREADCRUMB_TYPE,
81
+ :allowed_data => [
82
+ :filter
83
+ ]
84
+ },
85
+ {
86
+ :id => "render_template.action_view",
87
+ :message => "ActionView template rendered",
88
+ :type => Bugsnag::Breadcrumbs::REQUEST_BREADCRUMB_TYPE,
89
+ :allowed_data => [
90
+ :identifier,
91
+ :layout
92
+ ]
93
+ },
94
+ {
95
+ :id => "render_partial.action_view",
96
+ :message => "ActionView partial rendered",
97
+ :type => Bugsnag::Breadcrumbs::REQUEST_BREADCRUMB_TYPE,
98
+ :allowed_data => [
99
+ :identifier
100
+ ]
101
+ },
102
+ {
103
+ :id => "deliver.action_mailer",
104
+ :message => "ActionMail delivered",
105
+ :type => Bugsnag::Breadcrumbs::REQUEST_BREADCRUMB_TYPE,
106
+ :allowed_data => [
107
+ :mailer,
108
+ :message_id,
109
+ :from,
110
+ :date,
111
+ :perform_deliveries
112
+ ]
113
+ }
114
+ ]
115
+ end
@@ -0,0 +1,153 @@
1
+ # Rails 3.x hooks
2
+
3
+ require "rails"
4
+ require "bugsnag/integrations/rails/rails_breadcrumbs"
5
+
6
+ module Bugsnag
7
+ class Railtie < ::Rails::Railtie
8
+ FRAMEWORK_ATTRIBUTES = {
9
+ :framework => "Rails"
10
+ }
11
+
12
+ ##
13
+ # Subscribes to an ActiveSupport event, leaving a breadcrumb when it triggers
14
+ #
15
+ # @api private
16
+ # @param event [Hash] details of the event to subscribe to
17
+ def event_subscription(event)
18
+ ActiveSupport::Notifications.subscribe(event[:id]) do |*, event_id, data|
19
+ filtered_data = data.slice(*event[:allowed_data])
20
+ filtered_data[:event_name] = event[:id]
21
+ filtered_data[:event_id] = event_id
22
+
23
+ case event[:id]
24
+ when "sql.active_record"
25
+ if data.key?(:binds)
26
+ binds = data[:binds].each_with_object({}) { |bind, output| output[bind.name] = '?' if defined?(bind.name) }
27
+ filtered_data[:binds] = JSON.dump(binds) unless binds.empty?
28
+ end
29
+
30
+ # Rails < 6.1 included connection_id in the event data, but now
31
+ # includes the connection object instead
32
+ if data.key?(:connection) && !data.key?(:connection_id)
33
+ # the connection ID is the object_id of the connection object
34
+ filtered_data[:connection_id] = data[:connection].object_id
35
+ end
36
+
37
+ when "start_processing.action_controller"
38
+ filtered_data[:path] = Bugsnag.cleaner.clean_url(data[:path]) if data.key?(:path)
39
+
40
+ when "redirect_to.action_controller"
41
+ filtered_data[:location] = Bugsnag.cleaner.clean_url(data[:location]) if data.key?(:location)
42
+ end
43
+
44
+ Bugsnag.leave_breadcrumb(
45
+ event[:message],
46
+ filtered_data,
47
+ event[:type],
48
+ :auto
49
+ )
50
+ end
51
+ end
52
+
53
+ ##
54
+ # Do we need to rescue (& notify) in Active Record callbacks?
55
+ #
56
+ # On Rails versions < 4.2, Rails did not raise errors in AR callbacks
57
+ # On Rails version 4.2, a config option was added to control this
58
+ # On Rails version 5.0, the config option was removed and errors in callbacks
59
+ # always bubble up
60
+ #
61
+ # @api private
62
+ def self.rescue_in_active_record_callbacks?
63
+ # Rails 5+ will re-raise errors in callbacks, so we don't need to rescue them
64
+ return false if ::Rails::VERSION::MAJOR > 4
65
+
66
+ # before 4.2, errors were always swallowed, so we need to rescue them
67
+ return true if ::Rails::VERSION::MAJOR < 4
68
+
69
+ # a config option was added in 4.2 to control this, but won't exist in 4.0 & 4.1
70
+ return true unless ActiveRecord::Base.respond_to?(:raise_in_transactional_callbacks)
71
+
72
+ # if the config option is false, we need to rescue and notify
73
+ ActiveRecord::Base.raise_in_transactional_callbacks == false
74
+ end
75
+
76
+ rake_tasks do
77
+ require "bugsnag/integrations/rake"
78
+ load "bugsnag/tasks/bugsnag.rake"
79
+ end
80
+
81
+ config.before_initialize do
82
+ # Configure bugsnag rails defaults
83
+ # Skipping API key validation as the key may be set later in an
84
+ # initializer. If not, the key will be validated in after_initialize.
85
+ Bugsnag.configure(false) do |config|
86
+ config.logger = ::Rails.logger
87
+ config.release_stage ||= ::Rails.env.to_s
88
+ config.project_root = ::Rails.root.to_s
89
+ config.internal_middleware.use(Bugsnag::Middleware::Rails3Request)
90
+ config.runtime_versions["rails"] = ::Rails::VERSION::STRING
91
+ end
92
+
93
+ ActiveSupport.on_load(:action_controller) do
94
+ require "bugsnag/integrations/rails/controller_methods"
95
+ include Bugsnag::Rails::ControllerMethods
96
+ end
97
+
98
+ ActiveSupport.on_load(:active_record) do
99
+ if Bugsnag::Railtie.rescue_in_active_record_callbacks?
100
+ require "bugsnag/integrations/rails/active_record_rescue"
101
+ include Bugsnag::Rails::ActiveRecordRescue
102
+ end
103
+ end
104
+
105
+ ActiveSupport.on_load(:active_job) do
106
+ require "bugsnag/middleware/active_job"
107
+ Bugsnag.configuration.internal_middleware.use(Bugsnag::Middleware::ActiveJob)
108
+
109
+ require "bugsnag/integrations/rails/active_job"
110
+ include Bugsnag::Rails::ActiveJob
111
+ end
112
+
113
+ Bugsnag::Rails::DEFAULT_RAILS_BREADCRUMBS.each { |event| event_subscription(event) }
114
+
115
+ # Make sure we don't overwrite the value set by another integration because
116
+ # Rails is a less specific app_type (e.g. Que sets this earlier than us)
117
+ Bugsnag.configuration.detected_app_type ||= "rails"
118
+ end
119
+
120
+ # Configure meta_data_filters after initialization, so that rails initializers
121
+ # may set filter_parameters which will be picked up by Bugsnag.
122
+ config.after_initialize do
123
+ Bugsnag.configure do |config|
124
+ config.meta_data_filters += ::Rails.configuration.filter_parameters.map do |filter|
125
+ case filter
126
+ when String, Symbol
127
+ /\A#{filter}\z/
128
+ else
129
+ filter
130
+ end
131
+ end
132
+ end
133
+ end
134
+
135
+ initializer "bugsnag.use_rack_middleware" do |app|
136
+ begin
137
+ begin
138
+ app.config.middleware.insert_after ActionDispatch::DebugExceptions, Bugsnag::Rack
139
+ rescue
140
+ app.config.middleware.use Bugsnag::Rack
141
+ end
142
+ rescue FrozenError
143
+ # This can happen when running RSpec if there is a crash after Rails has
144
+ # started booting but before we've added our middleware. If we don't ignore
145
+ # this error then the stacktrace blames Bugsnag, which isn't accurate as
146
+ # the middleware will only be frozen if an earlier error occurs
147
+ # See this comment for more info:
148
+ # https://github.com/thoughtbot/factory_bot_rails/issues/303#issuecomment-434560625
149
+ Bugsnag.configuration.warn("Unable to add Bugsnag::Rack middleware as the middleware stack is frozen")
150
+ end
151
+ end
152
+ end
153
+ end