bugsnag 6.10.0 → 6.11.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (55) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop_todo.yml +4 -0
  3. data/CHANGELOG.md +20 -0
  4. data/Gemfile +1 -0
  5. data/README.md +1 -0
  6. data/VERSION +1 -1
  7. data/features/fixtures/docker-compose.yml +13 -0
  8. data/features/fixtures/rails3/app/app/controllers/breadcrumbs_controller.rb +19 -0
  9. data/features/fixtures/rails3/app/app/controllers/session_tracking_controller.rb +10 -6
  10. data/features/fixtures/rails3/app/config/initializers/bugsnag.rb +8 -2
  11. data/features/fixtures/rails3/app/config/routes.rb +1 -0
  12. data/features/fixtures/rails4/app/Gemfile +5 -1
  13. data/features/fixtures/rails4/app/app/controllers/breadcrumbs_controller.rb +26 -0
  14. data/features/fixtures/rails4/app/app/controllers/mongo_controller.rb +23 -0
  15. data/features/fixtures/rails4/app/app/controllers/session_tracking_controller.rb +9 -5
  16. data/features/fixtures/rails4/app/app/jobs/application_job.rb +2 -0
  17. data/features/fixtures/rails4/app/app/jobs/notify_job.rb +5 -0
  18. data/features/fixtures/rails4/app/app/models/mongo_model.rb +6 -0
  19. data/features/fixtures/rails4/app/config/initializers/bugsnag.rb +7 -1
  20. data/features/fixtures/rails4/app/config/mongoid.yml +22 -0
  21. data/features/fixtures/rails4/app/config/routes.rb +2 -0
  22. data/features/fixtures/rails5/app/Gemfile +4 -0
  23. data/features/fixtures/rails5/app/app/controllers/breadcrumbs_controller.rb +24 -0
  24. data/features/fixtures/rails5/app/app/controllers/mongo_controller.rb +22 -0
  25. data/features/fixtures/rails5/app/app/controllers/session_tracking_controller.rb +9 -5
  26. data/features/fixtures/rails5/app/app/jobs/notify_job.rb +5 -0
  27. data/features/fixtures/rails5/app/app/models/mongo_model.rb +6 -0
  28. data/features/fixtures/rails5/app/config/initializers/bugsnag.rb +7 -1
  29. data/features/fixtures/rails5/app/config/mongoid.yml +23 -0
  30. data/features/fixtures/rails5/app/config/routes.rb +11 -1
  31. data/features/rails_features/auto_capture_sessions.feature +55 -5
  32. data/features/rails_features/breadcrumbs.feature +135 -0
  33. data/features/rails_features/mongo_breadcrumbs.feature +100 -0
  34. data/features/steps/ruby_notifier_steps.rb +6 -0
  35. data/lib/bugsnag.rb +59 -3
  36. data/lib/bugsnag/breadcrumbs/breadcrumb.rb +76 -0
  37. data/lib/bugsnag/breadcrumbs/breadcrumbs.rb +14 -0
  38. data/lib/bugsnag/breadcrumbs/validator.rb +59 -0
  39. data/lib/bugsnag/configuration.rb +103 -6
  40. data/lib/bugsnag/integrations/mongo.rb +132 -0
  41. data/lib/bugsnag/integrations/rails/rails_breadcrumbs.rb +118 -0
  42. data/lib/bugsnag/integrations/railtie.rb +28 -1
  43. data/lib/bugsnag/middleware/breadcrumbs.rb +21 -0
  44. data/lib/bugsnag/report.rb +30 -1
  45. data/lib/bugsnag/session_tracker.rb +1 -0
  46. data/lib/bugsnag/utility/circular_buffer.rb +62 -0
  47. data/spec/breadcrumbs/breadcrumb_spec.rb +93 -0
  48. data/spec/breadcrumbs/validator_spec.rb +200 -0
  49. data/spec/bugsnag_spec.rb +230 -0
  50. data/spec/configuration_spec.rb +176 -2
  51. data/spec/integrations/mongo_spec.rb +262 -0
  52. data/spec/report_spec.rb +149 -0
  53. data/spec/session_tracker_spec.rb +24 -2
  54. data/spec/utility/circular_buffer_spec.rb +98 -0
  55. metadata +27 -2
@@ -1,12 +1,14 @@
1
1
  # Rails 3.x hooks
2
2
 
3
+ require "json"
3
4
  require "rails"
4
5
  require "bugsnag"
5
6
  require "bugsnag/middleware/rails3_request"
6
7
  require "bugsnag/middleware/rack_request"
8
+ require "bugsnag/integrations/rails/rails_breadcrumbs"
7
9
 
8
10
  module Bugsnag
9
- class Railtie < Rails::Railtie
11
+ class Railtie < ::Rails::Railtie
10
12
 
11
13
  FRAMEWORK_ATTRIBUTES = {
12
14
  :framework => "Rails"
@@ -38,6 +40,8 @@ module Bugsnag
38
40
  include Bugsnag::Rails::ActiveRecordRescue
39
41
  end
40
42
 
43
+ Bugsnag::Rails::DEFAULT_RAILS_BREADCRUMBS.each { |event| event_subscription(event) }
44
+
41
45
  Bugsnag.configuration.app_type = "rails"
42
46
  end
43
47
 
@@ -63,5 +67,28 @@ module Bugsnag
63
67
  app.config.middleware.use Bugsnag::Rack
64
68
  end
65
69
  end
70
+
71
+ ##
72
+ # Subscribes to an ActiveSupport event, leaving a breadcrumb when it triggers
73
+ #
74
+ # @api private
75
+ # @param event [Hash] details of the event to subscribe to
76
+ def event_subscription(event)
77
+ ActiveSupport::Notifications.subscribe(event[:id]) do |*, event_id, data|
78
+ filtered_data = data.slice(*event[:allowed_data])
79
+ filtered_data[:event_name] = event[:id]
80
+ filtered_data[:event_id] = event_id
81
+ if event[:id] == "sql.active_record"
82
+ binds = data[:binds].each_with_object({}) { |bind, output| output[bind.name] = '?' if defined?(bind.name) }
83
+ filtered_data[:binds] = JSON.dump(binds) unless binds.empty?
84
+ end
85
+ Bugsnag.leave_breadcrumb(
86
+ event[:message],
87
+ filtered_data,
88
+ event[:type],
89
+ :auto
90
+ )
91
+ end
92
+ end
66
93
  end
67
94
  end
@@ -0,0 +1,21 @@
1
+ module Bugsnag::Middleware
2
+ ##
3
+ # Adds breadcrumbs to the report
4
+ class Breadcrumbs
5
+ ##
6
+ # @param next_callable [#call] the next callable middleware
7
+ def initialize(next_callable)
8
+ @next = next_callable
9
+ end
10
+
11
+ ##
12
+ # Execute this middleware
13
+ #
14
+ # @param report [Bugsnag::Report] the report being iterated over
15
+ def call(report)
16
+ breadcrumbs = report.configuration.breadcrumbs.to_a
17
+ report.breadcrumbs = breadcrumbs unless breadcrumbs.empty?
18
+ @next.call(report)
19
+ end
20
+ end
21
+ end
@@ -23,6 +23,7 @@ module Bugsnag
23
23
  attr_accessor :api_key
24
24
  attr_accessor :app_type
25
25
  attr_accessor :app_version
26
+ attr_accessor :breadcrumbs
26
27
  attr_accessor :configuration
27
28
  attr_accessor :context
28
29
  attr_accessor :delivery_method
@@ -51,6 +52,7 @@ module Bugsnag
51
52
  self.api_key = configuration.api_key
52
53
  self.app_type = configuration.app_type
53
54
  self.app_version = configuration.app_version
55
+ self.breadcrumbs = []
54
56
  self.delivery_method = configuration.delivery_method
55
57
  self.hostname = configuration.hostname
56
58
  self.meta_data = {}
@@ -110,7 +112,14 @@ module Bugsnag
110
112
  payload_event = Bugsnag::Cleaner.clean_object_encoding(payload_event)
111
113
 
112
114
  # filter out sensitive values in (and cleanup encodings) metaData
113
- payload_event[:metaData] = Bugsnag::Cleaner.new(configuration.meta_data_filters).clean_object(meta_data)
115
+ filter_cleaner = Bugsnag::Cleaner.new(configuration.meta_data_filters)
116
+ payload_event[:metaData] = filter_cleaner.clean_object(meta_data)
117
+ payload_event[:breadcrumbs] = breadcrumbs.map do |breadcrumb|
118
+ breadcrumb_hash = breadcrumb.to_h
119
+ breadcrumb_hash[:metaData] = filter_cleaner.clean_object(breadcrumb_hash[:metaData])
120
+ breadcrumb_hash
121
+ end
122
+
114
123
  payload_event.reject! {|k,v| v.nil? }
115
124
 
116
125
  # return the payload hash
@@ -153,6 +162,26 @@ module Bugsnag
153
162
  @should_ignore = true
154
163
  end
155
164
 
165
+ ##
166
+ # Generates a summary to be attached as a breadcrumb
167
+ #
168
+ # @return [Hash] a Hash containing the report's error class, error message, and severity
169
+ def summary
170
+ # Guard against the exceptions array being removed/changed or emptied here
171
+ if exceptions.respond_to?(:first) && exceptions.first
172
+ {
173
+ :error_class => exceptions.first[:errorClass],
174
+ :message => exceptions.first[:message],
175
+ :severity => severity
176
+ }
177
+ else
178
+ {
179
+ :error_class => "Unknown",
180
+ :severity => severity
181
+ }
182
+ end
183
+ end
184
+
156
185
  private
157
186
 
158
187
  def generate_exception_list
@@ -35,6 +35,7 @@ module Bugsnag
35
35
  #
36
36
  # This allows Bugsnag to track error rates for a release.
37
37
  def start_session
38
+ return unless Bugsnag.configuration.enable_sessions
38
39
  start_delivery_thread
39
40
  start_time = Time.now().utc().strftime('%Y-%m-%dT%H:%M:00')
40
41
  new_session = {
@@ -0,0 +1,62 @@
1
+ module Bugsnag::Utility
2
+ ##
3
+ # A container class with a maximum size, that removes oldest items as required.
4
+ #
5
+ # @api private
6
+ class CircularBuffer
7
+ include Enumerable
8
+
9
+ # @return [Integer] the current maximum allowable number of items
10
+ attr_reader :max_items
11
+
12
+ ##
13
+ # @param max_items [Integer] the initial maximum number of items
14
+ def initialize(max_items = 25)
15
+ @max_items = max_items
16
+ @buffer = []
17
+ end
18
+
19
+ ##
20
+ # Adds an item to the circular buffer
21
+ #
22
+ # If this causes the buffer to exceed its maximum items, the oldest item will be removed
23
+ #
24
+ # @param item [Object] the item to add to the buffer
25
+ # @return [self] returns itself to allow method chaining
26
+ def <<(item)
27
+ @buffer << item
28
+ trim_buffer
29
+ self
30
+ end
31
+
32
+ ##
33
+ # Iterates over the buffer
34
+ #
35
+ # @yield [Object] sequentially gives stored items to the block
36
+ def each(&block)
37
+ @buffer.each(&block)
38
+ end
39
+
40
+ ##
41
+ # Sets the maximum allowable number of items
42
+ #
43
+ # If the current number of items exceeds the new maximum, oldest items will be removed
44
+ # until this is no longer the case
45
+ #
46
+ # @param new_max_items [Integer] the new allowed item maximum
47
+ def max_items=(new_max_items)
48
+ @max_items = new_max_items
49
+ trim_buffer
50
+ end
51
+
52
+ private
53
+
54
+ ##
55
+ # Trims the buffer down to the current maximum allowable item number
56
+ def trim_buffer
57
+ trim_size = @buffer.size - @max_items
58
+ trim_size = 0 if trim_size < 0
59
+ @buffer.shift(trim_size)
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,93 @@
1
+ # encoding: utf-8
2
+
3
+ require 'spec_helper'
4
+
5
+ require 'bugsnag/breadcrumbs/breadcrumb'
6
+
7
+ RSpec.describe Bugsnag::Breadcrumbs::Breadcrumb do
8
+ describe "#name" do
9
+ it "is assigned in #initialize" do
10
+ breadcrumb = Bugsnag::Breadcrumbs::Breadcrumb.new("my message", nil, nil, nil)
11
+
12
+ expect(breadcrumb.name).to eq("my message")
13
+ end
14
+ end
15
+
16
+ describe "#type" do
17
+ it "is assigned in #initialize" do
18
+ breadcrumb = Bugsnag::Breadcrumbs::Breadcrumb.new(nil, "test type", nil, nil)
19
+
20
+ expect(breadcrumb.type).to eq("test type")
21
+ end
22
+ end
23
+
24
+ describe "#meta_data" do
25
+ it "is assigned in #initialize" do
26
+ breadcrumb = Bugsnag::Breadcrumbs::Breadcrumb.new(nil, nil, {:a => 1, :b => 2}, nil)
27
+
28
+ expect(breadcrumb.meta_data).to eq({:a => 1, :b => 2})
29
+ end
30
+ end
31
+
32
+ describe "#auto" do
33
+ it "defaults to false" do
34
+ breadcrumb = Bugsnag::Breadcrumbs::Breadcrumb.new(nil, nil, nil, nil)
35
+
36
+ expect(breadcrumb.auto).to eq(false)
37
+ end
38
+
39
+ it "is true if auto argument == :auto" do
40
+ breadcrumb = Bugsnag::Breadcrumbs::Breadcrumb.new(nil, nil, nil, :auto)
41
+
42
+ expect(breadcrumb.auto).to eq(true)
43
+ end
44
+
45
+ it "is false if auto argument is anything else" do
46
+ breadcrumb = Bugsnag::Breadcrumbs::Breadcrumb.new(nil, nil, nil, :manual)
47
+
48
+ expect(breadcrumb.auto).to eq(false)
49
+ end
50
+ end
51
+
52
+ describe "#timestamp" do
53
+ it "is stored as a timestamp" do
54
+ breadcrumb = Bugsnag::Breadcrumbs::Breadcrumb.new(nil, nil, nil, nil)
55
+
56
+ expect(breadcrumb.timestamp).to be_within(0.5).of Time.now.utc
57
+ end
58
+ end
59
+
60
+ describe "#ignore?" do
61
+ it "is not true by default" do
62
+ breadcrumb = Bugsnag::Breadcrumbs::Breadcrumb.new("my message", "test type", {:a => 1, :b => 2}, :manual)
63
+
64
+ expect(breadcrumb.ignore?).to eq(false)
65
+ end
66
+
67
+ it "is able to be set" do
68
+ breadcrumb = Bugsnag::Breadcrumbs::Breadcrumb.new("my message", "test type", {:a => 1, :b => 2}, :manual)
69
+ breadcrumb.ignore!
70
+
71
+ expect(breadcrumb.ignore?).to eq(true)
72
+ end
73
+ end
74
+
75
+ describe "#to_h" do
76
+ it "outputs as a hash" do
77
+ breadcrumb = Bugsnag::Breadcrumbs::Breadcrumb.new("my message", "test type", {:a => 1, :b => 2}, :manual)
78
+ output = breadcrumb.to_h
79
+
80
+ timestamp_regex = /^\d{4}\-\d{2}\-\d{2}T\d{2}:\d{2}:[\d\.]+Z$/
81
+
82
+ expect(output).to match(
83
+ :name => "my message",
84
+ :type => "test type",
85
+ :metaData => {
86
+ :a => 1,
87
+ :b => 2
88
+ },
89
+ :timestamp => eq(breadcrumb.timestamp.iso8601)
90
+ )
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,200 @@
1
+ # encoding: utf-8
2
+ require 'spec_helper'
3
+
4
+ require 'bugsnag/breadcrumbs/breadcrumb'
5
+ require 'bugsnag/breadcrumbs/validator'
6
+
7
+ RSpec.describe Bugsnag::Breadcrumbs::Validator do
8
+ let(:enabled_automatic_breadcrumb_types) { Bugsnag::Breadcrumbs::VALID_BREADCRUMB_TYPES }
9
+ let(:auto) { false }
10
+ let(:name) { "Valid message" }
11
+ let(:type) { Bugsnag::Breadcrumbs::ERROR_BREADCRUMB_TYPE }
12
+ let(:meta_data) { {} }
13
+
14
+ describe "#validate" do
15
+ it "does not 'ignore!' a valid breadcrumb" do
16
+ config = instance_double(Bugsnag::Configuration)
17
+ allow(config).to receive(:enabled_automatic_breadcrumb_types).and_return(enabled_automatic_breadcrumb_types)
18
+ validator = Bugsnag::Breadcrumbs::Validator.new(config)
19
+
20
+ breadcrumb = instance_double(Bugsnag::Breadcrumbs::Breadcrumb, {
21
+ :auto => auto,
22
+ :name => name,
23
+ :type => type,
24
+ :meta_data => meta_data,
25
+ :meta_data= => nil
26
+ })
27
+
28
+ expect(breadcrumb).to_not receive(:ignore!)
29
+ expect(config).to_not receive(:warn)
30
+
31
+ validator.validate(breadcrumb)
32
+ end
33
+
34
+ it "trims long messages to length and warns" do
35
+ config = instance_double(Bugsnag::Configuration)
36
+ allow(config).to receive(:enabled_automatic_breadcrumb_types).and_return(enabled_automatic_breadcrumb_types)
37
+ validator = Bugsnag::Breadcrumbs::Validator.new(config)
38
+
39
+ name = "1234567890123456789012345678901234567890"
40
+
41
+ breadcrumb = instance_double(Bugsnag::Breadcrumbs::Breadcrumb, {
42
+ :auto => auto,
43
+ :name => name,
44
+ :type => type,
45
+ :meta_data => meta_data,
46
+ :meta_data= => nil
47
+ })
48
+
49
+ expect(breadcrumb).to_not receive(:ignore!)
50
+ expect(breadcrumb).to receive(:name=).with("123456789012345678901234567890")
51
+ expected_string = "Breadcrumb name trimmed to length 30. Original name: #{name}"
52
+ expect(config).to receive(:warn).with(expected_string)
53
+
54
+ validator.validate(breadcrumb)
55
+ # Check the original message has not been modified
56
+ expect(name).to eq("1234567890123456789012345678901234567890")
57
+ end
58
+
59
+ describe "tests meta_data types" do
60
+ it "accepts Strings, Numerics, Booleans, & nil" do
61
+ config = instance_double(Bugsnag::Configuration)
62
+ allow(config).to receive(:enabled_automatic_breadcrumb_types).and_return(enabled_automatic_breadcrumb_types)
63
+ validator = Bugsnag::Breadcrumbs::Validator.new(config)
64
+
65
+ meta_data = {
66
+ :string => "This is a string",
67
+ :integer => 12345,
68
+ :float => 12345.6789,
69
+ :false => false,
70
+ :true => true,
71
+ :nil => nil
72
+ }
73
+
74
+ breadcrumb = instance_double(Bugsnag::Breadcrumbs::Breadcrumb, {
75
+ :auto => auto,
76
+ :name => name,
77
+ :type => type,
78
+ :meta_data => meta_data,
79
+ :meta_data= => nil
80
+ })
81
+
82
+ expect(breadcrumb).to_not receive(:ignore!)
83
+ expect(config).to_not receive(:warn)
84
+
85
+ validator.validate(breadcrumb)
86
+ end
87
+
88
+ it "rejects Arrays, Hashes, and non-primitive objects" do
89
+ config = instance_double(Bugsnag::Configuration)
90
+ allow(config).to receive(:enabled_automatic_breadcrumb_types).and_return(enabled_automatic_breadcrumb_types)
91
+ validator = Bugsnag::Breadcrumbs::Validator.new(config)
92
+
93
+ class TestClass
94
+ end
95
+
96
+ meta_data = {
97
+ :fine => 1,
98
+ :array => [1, 2, 3],
99
+ :hash => {
100
+ :a => 1
101
+ },
102
+ :object => TestClass.new
103
+ }
104
+
105
+ breadcrumb = instance_double(Bugsnag::Breadcrumbs::Breadcrumb, {
106
+ :auto => auto,
107
+ :name => name,
108
+ :type => type,
109
+ :meta_data => meta_data
110
+ })
111
+
112
+ expect(breadcrumb).to_not receive(:ignore!)
113
+ expected_string_1 = "Breadcrumb #{breadcrumb.name} meta_data array:#{meta_data[:array]} has been dropped for having an invalid data type"
114
+ expected_string_2 = "Breadcrumb #{breadcrumb.name} meta_data hash:#{meta_data[:hash]} has been dropped for having an invalid data type"
115
+ expected_string_3 = "Breadcrumb #{breadcrumb.name} meta_data object:#{ meta_data[:object]} has been dropped for having an invalid data type"
116
+ expect(config).to receive(:warn).with(expected_string_1)
117
+ expect(config).to receive(:warn).with(expected_string_2)
118
+ expect(config).to receive(:warn).with(expected_string_3)
119
+
120
+ # Confirms that the meta_data is being filtered
121
+ expect(breadcrumb).to receive(:meta_data=).with({
122
+ :fine => 1
123
+ })
124
+
125
+ validator.validate(breadcrumb)
126
+ end
127
+ end
128
+
129
+ it "tests type, defaulting to 'manual' if invalid" do
130
+ config = instance_double(Bugsnag::Configuration)
131
+ allow(config).to receive(:enabled_automatic_breadcrumb_types).and_return(enabled_automatic_breadcrumb_types)
132
+ validator = Bugsnag::Breadcrumbs::Validator.new(config)
133
+
134
+ type = "Not a valid type"
135
+
136
+ breadcrumb = instance_double(Bugsnag::Breadcrumbs::Breadcrumb, {
137
+ :auto => auto,
138
+ :name => name,
139
+ :type => type,
140
+ :meta_data => meta_data,
141
+ :meta_data= => nil
142
+ })
143
+
144
+ expect(breadcrumb).to receive(:type=).with(Bugsnag::Breadcrumbs::MANUAL_BREADCRUMB_TYPE)
145
+ expect(breadcrumb).to_not receive(:ignore!)
146
+ expected_string = "Invalid type: #{type} for breadcrumb: #{breadcrumb.name}, defaulting to #{Bugsnag::Breadcrumbs::MANUAL_BREADCRUMB_TYPE}"
147
+ expect(config).to receive(:warn).with(expected_string)
148
+
149
+ validator.validate(breadcrumb)
150
+ end
151
+
152
+ describe "with enabled_automatic_breadcrumb_types set" do
153
+ it "rejects automatic breadcrumbs with rejected types" do
154
+ config = instance_double(Bugsnag::Configuration)
155
+ allowed_breadcrumb_types = []
156
+ allow(config).to receive(:enabled_automatic_breadcrumb_types).and_return(allowed_breadcrumb_types)
157
+ validator = Bugsnag::Breadcrumbs::Validator.new(config)
158
+
159
+ auto = true
160
+ type = Bugsnag::Breadcrumbs::ERROR_BREADCRUMB_TYPE
161
+
162
+ breadcrumb = instance_double(Bugsnag::Breadcrumbs::Breadcrumb, {
163
+ :auto => auto,
164
+ :name => name,
165
+ :type => type,
166
+ :meta_data => meta_data,
167
+ :meta_data= => nil
168
+ })
169
+
170
+ expect(breadcrumb).to receive(:ignore!)
171
+ expected_string = "Automatic breadcrumb of type #{Bugsnag::Breadcrumbs::ERROR_BREADCRUMB_TYPE} ignored: #{breadcrumb.name}"
172
+ expect(config).to receive(:warn).with(expected_string)
173
+
174
+ validator.validate(breadcrumb)
175
+ end
176
+
177
+ it "does not reject manual breadcrumbs with rejected types" do
178
+ config = instance_double(Bugsnag::Configuration)
179
+ allowed_breadcrumb_types = []
180
+ allow(config).to receive(:enabled_automatic_breadcrumb_types).and_return(allowed_breadcrumb_types)
181
+ validator = Bugsnag::Breadcrumbs::Validator.new(config)
182
+
183
+ type = Bugsnag::Breadcrumbs::ERROR_BREADCRUMB_TYPE
184
+
185
+ breadcrumb = instance_double(Bugsnag::Breadcrumbs::Breadcrumb, {
186
+ :auto => auto,
187
+ :name => name,
188
+ :type => type,
189
+ :meta_data => meta_data,
190
+ :meta_data= => nil
191
+ })
192
+
193
+ expect(breadcrumb).to_not receive(:ignore!)
194
+ expect(config).to_not receive(:warn)
195
+
196
+ validator.validate(breadcrumb)
197
+ end
198
+ end
199
+ end
200
+ end