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
@@ -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
- def deliver(url, body, configuration)
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
- Bugsnag.debug("Notification to #{url} finished, response was #{response.code}, payload was #{body}")
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
- Bugsnag.warn("Notification to #{url} failed, #{e.inspect}")
19
- Bugsnag.warn(e.backtrace)
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
- http = Net::HTTP.new(uri.host, uri.port, configuration.proxy_host, configuration.proxy_port, configuration.proxy_user, configuration.proxy_password)
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
- request = Net::HTTP::Post.new(path(uri), HEADERS)
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
- def deliver(url, body, configuration)
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
- Bugsnag.warn("Dropping notification, #{@queue.length} outstanding requests")
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 proc { super(url, body, configuration) }
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
- Bugsnag.warn("Waiting for #{@queue.length} outstanding request(s)") unless @queue.empty?
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
@@ -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,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,5 @@
1
+ module Bugsnag
2
+ # For now Event is just an alias of Report. This points to the same object so
3
+ # any changes to Report will also affect Event
4
+ Event = Report
5
+ 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
@@ -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 = 128000
10
- MAX_ARRAY_LENGTH = 40
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
- sanitized_value = Bugsnag::Cleaner.clean_object_encoding(value)
17
- return sanitized_value unless payload_too_long?(sanitized_value)
18
- reduced_value = trim_strings_in_value(sanitized_value)
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
- reduced_value = truncate_arrays_in_value(reduced_value)
20
+
21
+ # Trim metadata
22
+ reduced_value = trim_metadata(reduced_value)
21
23
  return reduced_value unless payload_too_long?(reduced_value)
22
- remove_metadata_from_events(reduced_value)
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
- def self.flatten_meta_data(overrides)
26
- return nil unless overrides
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
- meta_data = overrides.delete(:meta_data)
29
- if meta_data.is_a?(Hash)
30
- overrides.merge(meta_data)
31
- else
32
- overrides
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
- private
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
- ::JSON.dump(value).length >= MAX_PAYLOAD_LENGTH
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