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
@@ -1,51 +1,68 @@
|
|
1
1
|
require "net/https"
|
2
|
-
require "uri"
|
3
2
|
|
4
3
|
module Bugsnag
|
5
4
|
module Delivery
|
6
5
|
class Synchronous
|
7
|
-
HEADERS = {"Content-Type" => "application/json"}
|
8
|
-
|
9
6
|
class << self
|
10
|
-
|
7
|
+
##
|
8
|
+
# Attempts to deliver a payload to the given endpoint synchronously.
|
9
|
+
def deliver(url, body, configuration, options={})
|
11
10
|
begin
|
12
|
-
response = request(url, body, configuration)
|
13
|
-
|
11
|
+
response = request(url, body, configuration, options)
|
12
|
+
configuration.debug("Request to #{url} completed, status: #{response.code}")
|
13
|
+
if response.code[0] != "2"
|
14
|
+
configuration.warn("Notifications to #{url} was reported unsuccessful with code #{response.code}")
|
15
|
+
end
|
14
16
|
rescue StandardError => e
|
15
17
|
# KLUDGE: Since we don't re-raise http exceptions, this breaks rspec
|
16
18
|
raise if e.class.to_s == "RSpec::Expectations::ExpectationNotMetError"
|
17
19
|
|
18
|
-
|
19
|
-
|
20
|
+
configuration.error("Unable to send information to Bugsnag (#{url}), #{e.inspect}")
|
21
|
+
configuration.error(e.backtrace)
|
20
22
|
end
|
21
23
|
end
|
22
24
|
|
23
25
|
private
|
24
26
|
|
25
|
-
def request(url, body, configuration)
|
27
|
+
def request(url, body, configuration, options)
|
26
28
|
uri = URI.parse(url)
|
27
|
-
|
29
|
+
|
30
|
+
if configuration.proxy_host
|
31
|
+
http = Net::HTTP.new(uri.host, uri.port, configuration.proxy_host, configuration.proxy_port, configuration.proxy_user, configuration.proxy_password)
|
32
|
+
else
|
33
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
34
|
+
end
|
35
|
+
|
28
36
|
http.read_timeout = configuration.timeout
|
29
37
|
http.open_timeout = configuration.timeout
|
30
38
|
|
31
39
|
if uri.scheme == "https"
|
32
40
|
http.use_ssl = true
|
33
|
-
# the default in 1.9+, but required for 1.8
|
34
|
-
http.verify_mode = OpenSSL::SSL::VERIFY_PEER
|
35
41
|
http.ca_file = configuration.ca_file if configuration.ca_file
|
36
42
|
end
|
37
43
|
|
38
|
-
|
44
|
+
headers = options.key?(:headers) ? options[:headers] : {}
|
45
|
+
headers.merge!(default_headers)
|
46
|
+
|
47
|
+
request = Net::HTTP::Post.new(path(uri), headers)
|
39
48
|
request.body = body
|
49
|
+
|
40
50
|
http.request(request)
|
41
51
|
end
|
42
52
|
|
43
53
|
def path(uri)
|
44
54
|
uri.path == "" ? "/" : uri.path
|
45
55
|
end
|
56
|
+
|
57
|
+
def default_headers
|
58
|
+
{
|
59
|
+
"Content-Type" => "application/json",
|
60
|
+
"Bugsnag-Sent-At" => Time.now.utc.iso8601(3)
|
61
|
+
}
|
62
|
+
end
|
46
63
|
end
|
47
64
|
end
|
48
65
|
end
|
49
66
|
end
|
50
67
|
|
51
|
-
Bugsnag::Delivery.register(:synchronous, Bugsnag::Delivery::Synchronous)
|
68
|
+
Bugsnag::Delivery.register(:synchronous, Bugsnag::Delivery::Synchronous)
|
@@ -1,5 +1,3 @@
|
|
1
|
-
require "thread"
|
2
|
-
|
3
1
|
module Bugsnag
|
4
2
|
module Delivery
|
5
3
|
class ThreadQueue < Synchronous
|
@@ -8,16 +6,35 @@ module Bugsnag
|
|
8
6
|
MUTEX = Mutex.new
|
9
7
|
|
10
8
|
class << self
|
11
|
-
|
9
|
+
##
|
10
|
+
# Queues a given payload to be delivered asynchronously
|
11
|
+
#
|
12
|
+
# @param url [String]
|
13
|
+
# @param get_payload [Proc] A Proc that will return the payload.
|
14
|
+
# @param configuration [Bugsnag::Configuration]
|
15
|
+
# @param options [Hash]
|
16
|
+
# @return [void]
|
17
|
+
def serialize_and_deliver(url, get_payload, configuration, options={})
|
18
|
+
@configuration = configuration
|
19
|
+
|
12
20
|
start_once!
|
13
21
|
|
14
22
|
if @queue.length > MAX_OUTSTANDING_REQUESTS
|
15
|
-
|
23
|
+
@configuration.warn("Dropping notification, #{@queue.length} outstanding requests")
|
16
24
|
return
|
17
25
|
end
|
18
26
|
|
19
27
|
# Add delivery to the worker thread
|
20
|
-
@queue.push
|
28
|
+
@queue.push(proc do
|
29
|
+
begin
|
30
|
+
payload = get_payload.call
|
31
|
+
rescue StandardError => e
|
32
|
+
configuration.error("Unable to send information to Bugsnag (#{url}), #{e.inspect}")
|
33
|
+
configuration.error(e.backtrace)
|
34
|
+
end
|
35
|
+
|
36
|
+
Synchronous.deliver(url, payload, configuration, options) unless payload.nil?
|
37
|
+
end)
|
21
38
|
end
|
22
39
|
|
23
40
|
private
|
@@ -38,7 +55,7 @@ module Bugsnag
|
|
38
55
|
end
|
39
56
|
|
40
57
|
at_exit do
|
41
|
-
|
58
|
+
@configuration.warn("Waiting for #{@queue.length} outstanding request(s)") unless @queue.empty?
|
42
59
|
@queue.push STOP
|
43
60
|
worker_thread.join
|
44
61
|
end
|
data/lib/bugsnag/delivery.rb
CHANGED
@@ -1,10 +1,23 @@
|
|
1
1
|
module Bugsnag
|
2
2
|
module Delivery
|
3
3
|
class << self
|
4
|
+
##
|
5
|
+
# Add a delivery method to the list of supported methods. Any registered
|
6
|
+
# method can then be used by name in Configuration.
|
7
|
+
#
|
8
|
+
# ```
|
9
|
+
# require 'bugsnag'
|
10
|
+
# Bugsnag::Delivery.register(:my_delivery_queue, MyDeliveryQueue)
|
11
|
+
# Bugsnag.configure do |config|
|
12
|
+
# config.delivery_method = :my_delivery_queue
|
13
|
+
# end
|
14
|
+
# ```
|
4
15
|
def register(name, delivery_method)
|
5
16
|
delivery_methods[name.to_sym] = delivery_method
|
6
17
|
end
|
7
18
|
|
19
|
+
##
|
20
|
+
# Reference a delivery method by name
|
8
21
|
def [](name)
|
9
22
|
delivery_methods[name.to_sym]
|
10
23
|
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,74 @@
|
|
1
|
+
module Bugsnag
|
2
|
+
class FeatureFlag
|
3
|
+
# Get the name of this feature flag
|
4
|
+
#
|
5
|
+
# @return [String]
|
6
|
+
attr_reader :name
|
7
|
+
|
8
|
+
# Get the variant of this feature flag
|
9
|
+
#
|
10
|
+
# @return [String, nil]
|
11
|
+
attr_reader :variant
|
12
|
+
|
13
|
+
# @param name [String] The name of this feature flags
|
14
|
+
# @param variant [String, nil] An optional variant for this flag
|
15
|
+
def initialize(name, variant = nil)
|
16
|
+
@name = name
|
17
|
+
@variant = coerce_variant(variant)
|
18
|
+
end
|
19
|
+
|
20
|
+
def ==(other)
|
21
|
+
self.class == other.class && @name == other.name && @variant == other.variant
|
22
|
+
end
|
23
|
+
|
24
|
+
def hash
|
25
|
+
[@name, @variant].hash
|
26
|
+
end
|
27
|
+
|
28
|
+
# Convert this flag to a hash
|
29
|
+
#
|
30
|
+
# @example With no variant
|
31
|
+
# { "featureFlag" => "name" }
|
32
|
+
#
|
33
|
+
# @example With a variant
|
34
|
+
# { "featureFlag" => "name", "variant" => "variant" }
|
35
|
+
#
|
36
|
+
# @return [Hash{String => String}]
|
37
|
+
def to_h
|
38
|
+
if @variant.nil?
|
39
|
+
{ "featureFlag" => @name }
|
40
|
+
else
|
41
|
+
{ "featureFlag" => @name, "variant" => @variant }
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
# Check if this flag is valid, i.e. has a name that's a String and a variant
|
46
|
+
# that's either nil or a String
|
47
|
+
#
|
48
|
+
# @return [Boolean]
|
49
|
+
def valid?
|
50
|
+
@name.is_a?(String) &&
|
51
|
+
!@name.empty? &&
|
52
|
+
(@variant.nil? || @variant.is_a?(String))
|
53
|
+
end
|
54
|
+
|
55
|
+
private
|
56
|
+
|
57
|
+
# Coerce this variant into a valid value (String or nil)
|
58
|
+
#
|
59
|
+
# If the variant is not already a string or nil, we use #to_s to coerce it.
|
60
|
+
# If #to_s raises, the variant will be set to nil
|
61
|
+
#
|
62
|
+
# @param variant [Object]
|
63
|
+
# @return [String, nil]
|
64
|
+
def coerce_variant(variant)
|
65
|
+
if variant.nil? || variant.is_a?(String)
|
66
|
+
variant
|
67
|
+
else
|
68
|
+
variant.to_s
|
69
|
+
end
|
70
|
+
rescue StandardError
|
71
|
+
nil
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
data/lib/bugsnag/helpers.rb
CHANGED
@@ -1,48 +1,134 @@
|
|
1
|
-
require 'uri'
|
2
|
-
require 'set' unless defined?(Set)
|
3
|
-
require 'json' unless defined?(JSON)
|
4
|
-
|
5
|
-
|
6
1
|
module Bugsnag
|
7
|
-
module Helpers
|
2
|
+
module Helpers # rubocop:todo Metrics/ModuleLength
|
8
3
|
MAX_STRING_LENGTH = 3072
|
9
|
-
MAX_PAYLOAD_LENGTH =
|
10
|
-
MAX_ARRAY_LENGTH =
|
4
|
+
MAX_PAYLOAD_LENGTH = 512000
|
5
|
+
MAX_ARRAY_LENGTH = 80
|
6
|
+
MAX_TRIM_STACK_FRAMES = 30
|
11
7
|
RAW_DATA_TYPES = [Numeric, TrueClass, FalseClass]
|
12
8
|
|
9
|
+
##
|
13
10
|
# Trim the size of value if the serialized JSON value is longer than is
|
14
11
|
# accepted by Bugsnag
|
15
12
|
def self.trim_if_needed(value)
|
16
|
-
|
17
|
-
|
18
|
-
|
13
|
+
value = "" if value.nil?
|
14
|
+
|
15
|
+
return value unless payload_too_long?(value)
|
16
|
+
|
17
|
+
# Truncate exception messages
|
18
|
+
reduced_value = truncate_exception_messages(value)
|
19
19
|
return reduced_value unless payload_too_long?(reduced_value)
|
20
|
-
|
20
|
+
|
21
|
+
# Trim metadata
|
22
|
+
reduced_value = trim_metadata(reduced_value)
|
21
23
|
return reduced_value unless payload_too_long?(reduced_value)
|
22
|
-
|
24
|
+
|
25
|
+
# Trim code from stacktrace
|
26
|
+
reduced_value = trim_stacktrace_code(reduced_value)
|
27
|
+
return reduced_value unless payload_too_long?(reduced_value)
|
28
|
+
|
29
|
+
# Remove metadata
|
30
|
+
reduced_value = remove_metadata_from_events(reduced_value)
|
31
|
+
return reduced_value unless payload_too_long?(reduced_value)
|
32
|
+
|
33
|
+
# Remove oldest functions in stacktrace
|
34
|
+
trim_stacktrace_functions(reduced_value)
|
35
|
+
end
|
36
|
+
|
37
|
+
##
|
38
|
+
# Merges r_hash into l_hash recursively, favouring the values in r_hash.
|
39
|
+
#
|
40
|
+
# Returns a new array consisting of the merged values
|
41
|
+
def self.deep_merge(l_hash, r_hash)
|
42
|
+
l_hash.merge(r_hash) do |key, l_val, r_val|
|
43
|
+
if l_val.is_a?(Hash) && r_val.is_a?(Hash)
|
44
|
+
deep_merge(l_val, r_val)
|
45
|
+
elsif l_val.is_a?(Array) && r_val.is_a?(Array)
|
46
|
+
l_val.concat(r_val)
|
47
|
+
else
|
48
|
+
r_val
|
49
|
+
end
|
50
|
+
end
|
23
51
|
end
|
24
52
|
|
25
|
-
|
26
|
-
|
53
|
+
##
|
54
|
+
# Merges r_hash into l_hash recursively, favouring the values in r_hash.
|
55
|
+
#
|
56
|
+
# Overwrites the values in the existing l_hash
|
57
|
+
def self.deep_merge!(l_hash, r_hash)
|
58
|
+
l_hash.merge!(r_hash) do |key, l_val, r_val|
|
59
|
+
if l_val.is_a?(Hash) && r_val.is_a?(Hash)
|
60
|
+
deep_merge(l_val, r_val)
|
61
|
+
elsif l_val.is_a?(Array) && r_val.is_a?(Array)
|
62
|
+
l_val.concat(r_val)
|
63
|
+
else
|
64
|
+
r_val
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
27
68
|
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
69
|
+
private
|
70
|
+
|
71
|
+
TRUNCATION_INFO = '[TRUNCATED]'
|
72
|
+
|
73
|
+
##
|
74
|
+
# Truncate exception messages
|
75
|
+
def self.truncate_exception_messages(payload)
|
76
|
+
extract_exception(payload) do |exception|
|
77
|
+
exception[:message] = trim_as_string(exception[:message])
|
33
78
|
end
|
79
|
+
payload
|
34
80
|
end
|
35
81
|
|
82
|
+
##
|
83
|
+
# Remove all code from stacktraces
|
84
|
+
def self.trim_stacktrace_code(payload)
|
85
|
+
extract_exception(payload) do |exception|
|
86
|
+
exception[:stacktrace].each do |frame|
|
87
|
+
frame.delete(:code)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
payload
|
91
|
+
end
|
92
|
+
|
93
|
+
##
|
94
|
+
# Truncate stacktraces
|
95
|
+
def self.trim_stacktrace_functions(payload)
|
96
|
+
extract_exception(payload) do |exception|
|
97
|
+
stack = exception[:stacktrace]
|
98
|
+
exception[:stacktrace] = stack.take(MAX_TRIM_STACK_FRAMES)
|
99
|
+
end
|
100
|
+
payload
|
101
|
+
end
|
102
|
+
|
103
|
+
##
|
104
|
+
# Wrapper for trimming stacktraces
|
105
|
+
def self.extract_exception(payload, &block)
|
106
|
+
valid_payload = payload.is_a?(Hash) && payload[:events].respond_to?(:map)
|
107
|
+
return unless valid_payload && block_given?
|
108
|
+
payload[:events].each do |event|
|
109
|
+
event[:exceptions].each(&block)
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
##
|
114
|
+
# Take the metadata from the events and trim it down
|
115
|
+
def self.trim_metadata(payload)
|
116
|
+
return payload unless payload.is_a?(Hash) and payload[:events].respond_to?(:map)
|
117
|
+
payload[:events].map do |event|
|
118
|
+
event[:metaData] = truncate_arrays_in_value(event[:metaData])
|
119
|
+
event[:metaData] = trim_strings_in_value(event[:metaData])
|
120
|
+
end
|
121
|
+
payload
|
122
|
+
end
|
123
|
+
|
124
|
+
##
|
36
125
|
# Check if a value is a raw type which should not be trimmed, truncated
|
37
126
|
# or converted to a string
|
38
127
|
def self.is_json_raw_type?(value)
|
39
128
|
RAW_DATA_TYPES.detect {|klass| value.is_a?(klass)} != nil
|
40
129
|
end
|
41
130
|
|
42
|
-
|
43
|
-
|
44
|
-
TRUNCATION_INFO = '[TRUNCATED]'
|
45
|
-
|
131
|
+
##
|
46
132
|
# Shorten array until it fits within the payload size limit when serialized
|
47
133
|
def self.truncate_array(array)
|
48
134
|
return [] unless array.respond_to?(:slice)
|
@@ -51,6 +137,7 @@ module Bugsnag
|
|
51
137
|
end
|
52
138
|
end
|
53
139
|
|
140
|
+
##
|
54
141
|
# Trim all strings to be less than the maximum allowed string length
|
55
142
|
def self.trim_strings_in_value(value)
|
56
143
|
return value if is_json_raw_type?(value)
|
@@ -64,10 +151,19 @@ module Bugsnag
|
|
64
151
|
end
|
65
152
|
end
|
66
153
|
|
154
|
+
##
|
67
155
|
# Validate that the serialized JSON string value is below maximum payload
|
68
156
|
# length
|
69
157
|
def self.payload_too_long?(value)
|
70
|
-
|
158
|
+
get_payload_length(value) >= MAX_PAYLOAD_LENGTH
|
159
|
+
end
|
160
|
+
|
161
|
+
def self.get_payload_length(value)
|
162
|
+
if value.is_a?(String)
|
163
|
+
value.length
|
164
|
+
else
|
165
|
+
::JSON.dump(value).length
|
166
|
+
end
|
71
167
|
end
|
72
168
|
|
73
169
|
def self.trim_strings_in_hash(hash)
|
@@ -0,0 +1,51 @@
|
|
1
|
+
require 'delayed_job'
|
2
|
+
|
3
|
+
# See Issue #99
|
4
|
+
unless defined?(Delayed::Plugin)
|
5
|
+
raise LoadError, "bugsnag requires delayed_job > 3.x"
|
6
|
+
end
|
7
|
+
|
8
|
+
::Bugsnag.configuration.internal_middleware.use(::Bugsnag::Middleware::DelayedJob)
|
9
|
+
|
10
|
+
module Delayed
|
11
|
+
module Plugins
|
12
|
+
class Bugsnag < ::Delayed::Plugin
|
13
|
+
##
|
14
|
+
# DelayedJob doesn't have an easy way to fetch its version, but we can use
|
15
|
+
# Gem.loaded_specs to get the version instead
|
16
|
+
def self.delayed_job_version
|
17
|
+
::Gem.loaded_specs['delayed_job'].version.to_s
|
18
|
+
rescue StandardError
|
19
|
+
# Explicitly return nil to prevent Rubocop complaining of a suppressed exception
|
20
|
+
nil
|
21
|
+
end
|
22
|
+
|
23
|
+
callbacks do |lifecycle|
|
24
|
+
lifecycle.around(:invoke_job) do |job, *args, &block|
|
25
|
+
begin
|
26
|
+
::Bugsnag.configuration.detected_app_type = 'delayed_job'
|
27
|
+
::Bugsnag.configuration.runtime_versions['delayed_job'] = delayed_job_version if defined?(::Gem)
|
28
|
+
::Bugsnag.configuration.set_request_data(:delayed_job, job)
|
29
|
+
|
30
|
+
block.call(job, *args)
|
31
|
+
rescue Exception => exception
|
32
|
+
::Bugsnag.notify(exception, true) do |report|
|
33
|
+
report.severity = "error"
|
34
|
+
report.severity_reason = {
|
35
|
+
:type => ::Bugsnag::Report::UNHANDLED_EXCEPTION_MIDDLEWARE,
|
36
|
+
:attributes => {
|
37
|
+
:framework => "DelayedJob"
|
38
|
+
}
|
39
|
+
}
|
40
|
+
end
|
41
|
+
raise exception
|
42
|
+
ensure
|
43
|
+
::Bugsnag.configuration.clear_request_data
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
Delayed::Worker.plugins << Delayed::Plugins::Bugsnag
|
@@ -0,0 +1,43 @@
|
|
1
|
+
require 'mailman'
|
2
|
+
|
3
|
+
module Bugsnag
|
4
|
+
##
|
5
|
+
# Extracts and appends mailman message information to error reports
|
6
|
+
class Mailman
|
7
|
+
|
8
|
+
FRAMEWORK_ATTRIBUTES = {
|
9
|
+
:framework => "Mailman"
|
10
|
+
}
|
11
|
+
|
12
|
+
def initialize
|
13
|
+
Bugsnag.configuration.internal_middleware.use(Bugsnag::Middleware::Mailman)
|
14
|
+
Bugsnag.configuration.detected_app_type = "mailman"
|
15
|
+
Bugsnag.configuration.runtime_versions["mailman"] = ::Mailman::VERSION
|
16
|
+
end
|
17
|
+
|
18
|
+
##
|
19
|
+
# Calls the mailman middleware.
|
20
|
+
def call(mail)
|
21
|
+
begin
|
22
|
+
Bugsnag.configuration.set_request_data :mailman_msg, mail.to_s
|
23
|
+
yield
|
24
|
+
rescue Exception => ex
|
25
|
+
Bugsnag.notify(ex, true) do |report|
|
26
|
+
report.severity = "error"
|
27
|
+
report.severity_reason = {
|
28
|
+
:type => Bugsnag::Report::UNHANDLED_EXCEPTION_MIDDLEWARE,
|
29
|
+
:attributes => FRAMEWORK_ATTRIBUTES
|
30
|
+
}
|
31
|
+
end
|
32
|
+
raise
|
33
|
+
ensure
|
34
|
+
Bugsnag.configuration.clear_request_data
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
|
41
|
+
if Mailman.config.respond_to?(:middleware)
|
42
|
+
Mailman.config.middleware.add ::Bugsnag::Mailman
|
43
|
+
end
|