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.
- checksums.yaml +5 -5
- data/.yardopts +12 -0
- data/CHANGELOG.md +814 -0
- data/README.md +21 -25
- data/VERSION +1 -1
- data/bugsnag.gemspec +19 -8
- data/lib/bugsnag/breadcrumb_type.rb +14 -0
- data/lib/bugsnag/breadcrumbs/breadcrumb.rb +109 -0
- data/lib/bugsnag/breadcrumbs/breadcrumbs.rb +13 -0
- data/lib/bugsnag/breadcrumbs/on_breadcrumb_callback_list.rb +48 -0
- data/lib/bugsnag/breadcrumbs/validator.rb +29 -0
- data/lib/bugsnag/cleaner.rb +170 -59
- data/lib/bugsnag/code_extractor.rb +137 -0
- data/lib/bugsnag/configuration.rb +670 -45
- data/lib/bugsnag/delivery/synchronous.rb +31 -14
- data/lib/bugsnag/delivery/thread_queue.rb +23 -6
- data/lib/bugsnag/delivery.rb +13 -0
- data/lib/bugsnag/endpoint_configuration.rb +11 -0
- data/lib/bugsnag/endpoint_validator.rb +80 -0
- data/lib/bugsnag/error.rb +25 -0
- data/lib/bugsnag/event.rb +5 -0
- data/lib/bugsnag/feature_flag.rb +74 -0
- data/lib/bugsnag/helpers.rb +121 -25
- data/lib/bugsnag/integrations/delayed_job.rb +51 -0
- data/lib/bugsnag/integrations/mailman.rb +43 -0
- data/lib/bugsnag/integrations/mongo.rb +133 -0
- data/lib/bugsnag/integrations/que.rb +53 -0
- data/lib/bugsnag/integrations/rack.rb +83 -0
- data/lib/bugsnag/integrations/rails/active_job.rb +100 -0
- data/lib/bugsnag/{rails → integrations/rails}/active_record_rescue.rb +10 -1
- data/lib/bugsnag/{rails → integrations/rails}/controller_methods.rb +1 -9
- data/lib/bugsnag/integrations/rails/rails_breadcrumbs.rb +115 -0
- data/lib/bugsnag/integrations/railtie.rb +153 -0
- data/lib/bugsnag/integrations/rake.rb +74 -0
- data/lib/bugsnag/integrations/resque.rb +94 -0
- data/lib/bugsnag/integrations/shoryuken.rb +50 -0
- data/lib/bugsnag/integrations/sidekiq.rb +68 -0
- data/lib/bugsnag/meta_data.rb +1 -0
- data/lib/bugsnag/middleware/active_job.rb +18 -0
- data/lib/bugsnag/middleware/breadcrumbs.rb +21 -0
- data/lib/bugsnag/middleware/callbacks.rb +6 -8
- data/lib/bugsnag/middleware/classify_error.rb +50 -0
- data/lib/bugsnag/middleware/clearance_user.rb +33 -0
- data/lib/bugsnag/middleware/delayed_job.rb +93 -0
- data/lib/bugsnag/middleware/discard_error_class.rb +30 -0
- data/lib/bugsnag/middleware/exception_meta_data.rb +42 -0
- data/lib/bugsnag/middleware/ignore_error_class.rb +26 -0
- data/lib/bugsnag/middleware/mailman.rb +6 -4
- data/lib/bugsnag/middleware/rack_request.rb +126 -30
- data/lib/bugsnag/middleware/rails3_request.rb +15 -17
- data/lib/bugsnag/middleware/rake.rb +7 -5
- data/lib/bugsnag/middleware/session_data.rb +25 -0
- data/lib/bugsnag/middleware/sidekiq.rb +9 -4
- data/lib/bugsnag/middleware/suggestion_data.rb +34 -0
- data/lib/bugsnag/middleware/warden_user.rb +11 -6
- data/lib/bugsnag/middleware_stack.rb +62 -9
- data/lib/bugsnag/on_error_callbacks.rb +33 -0
- data/lib/bugsnag/report.rb +516 -0
- data/lib/bugsnag/session_tracker.rb +182 -0
- data/lib/bugsnag/stacktrace.rb +82 -0
- data/lib/bugsnag/tasks/bugsnag.rake +2 -70
- data/lib/bugsnag/utility/circular_buffer.rb +62 -0
- data/lib/bugsnag/utility/duplicator.rb +124 -0
- data/lib/bugsnag/utility/feature_data_store.rb +41 -0
- data/lib/bugsnag/utility/feature_flag_delegate.rb +89 -0
- data/lib/bugsnag/utility/metadata_delegate.rb +102 -0
- data/lib/bugsnag.rb +528 -80
- metadata +61 -123
- data/.document +0 -5
- data/.gitignore +0 -52
- data/.rspec +0 -3
- data/.travis.yml +0 -14
- data/CONTRIBUTING.md +0 -47
- data/Gemfile +0 -2
- data/Rakefile +0 -29
- data/lib/bugsnag/capistrano.rb +0 -7
- data/lib/bugsnag/capistrano2.rb +0 -32
- data/lib/bugsnag/delay/resque.rb +0 -21
- data/lib/bugsnag/delayed_job.rb +0 -57
- data/lib/bugsnag/deploy.rb +0 -34
- data/lib/bugsnag/mailman.rb +0 -28
- data/lib/bugsnag/middleware/rails2_request.rb +0 -52
- data/lib/bugsnag/notification.rb +0 -459
- data/lib/bugsnag/rack.rb +0 -53
- data/lib/bugsnag/rails/action_controller_rescue.rb +0 -62
- data/lib/bugsnag/rails.rb +0 -66
- data/lib/bugsnag/railtie.rb +0 -80
- data/lib/bugsnag/rake.rb +0 -25
- data/lib/bugsnag/resque.rb +0 -40
- data/lib/bugsnag/sidekiq.rb +0 -42
- data/lib/bugsnag/tasks/bugsnag.cap +0 -48
- data/rails/init.rb +0 -7
- data/spec/cleaner_spec.rb +0 -138
- data/spec/code_spec.rb +0 -86
- data/spec/fixtures/crashes/end_of_file.rb +0 -9
- data/spec/fixtures/crashes/short_file.rb +0 -1
- data/spec/fixtures/crashes/start_of_file.rb +0 -9
- data/spec/fixtures/middleware/internal_info_setter.rb +0 -11
- data/spec/fixtures/middleware/public_info_setter.rb +0 -11
- data/spec/fixtures/tasks/Rakefile +0 -15
- data/spec/helper_spec.rb +0 -163
- data/spec/integration_spec.rb +0 -132
- data/spec/middleware_spec.rb +0 -181
- data/spec/notification_spec.rb +0 -877
- data/spec/rack_spec.rb +0 -56
- 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.
|
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
|