bugsnag 6.6.3 → 6.6.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (41) hide show
  1. checksums.yaml +4 -4
  2. data/.rdoc_options +27 -0
  3. data/.rubocop.yml +2 -0
  4. data/.rubocop_todo.yml +672 -0
  5. data/.travis.yml +46 -11
  6. data/CHANGELOG.md +12 -0
  7. data/Gemfile +8 -0
  8. data/Rakefile +5 -4
  9. data/VERSION +1 -1
  10. data/lib/bugsnag.rb +19 -4
  11. data/lib/bugsnag/configuration.rb +20 -2
  12. data/lib/bugsnag/delivery.rb +4 -0
  13. data/lib/bugsnag/delivery/synchronous.rb +2 -0
  14. data/lib/bugsnag/delivery/thread_queue.rb +2 -0
  15. data/lib/bugsnag/helpers.rb +10 -0
  16. data/lib/bugsnag/integrations/mailman.rb +4 -0
  17. data/lib/bugsnag/integrations/rack.rb +4 -0
  18. data/lib/bugsnag/integrations/rake.rb +2 -0
  19. data/lib/bugsnag/integrations/resque.rb +6 -0
  20. data/lib/bugsnag/integrations/shoryuken.rb +2 -0
  21. data/lib/bugsnag/integrations/sidekiq.rb +2 -0
  22. data/lib/bugsnag/middleware/callbacks.rb +2 -0
  23. data/lib/bugsnag/middleware/classify_error.rb +2 -0
  24. data/lib/bugsnag/middleware/clearance_user.rb +2 -0
  25. data/lib/bugsnag/middleware/exception_meta_data.rb +14 -14
  26. data/lib/bugsnag/middleware/ignore_error_class.rb +3 -0
  27. data/lib/bugsnag/middleware/mailman.rb +2 -0
  28. data/lib/bugsnag/middleware/rack_request.rb +2 -0
  29. data/lib/bugsnag/middleware/rails3_request.rb +2 -0
  30. data/lib/bugsnag/middleware/rake.rb +2 -0
  31. data/lib/bugsnag/middleware/session_data.rb +2 -0
  32. data/lib/bugsnag/middleware/sidekiq.rb +2 -0
  33. data/lib/bugsnag/middleware/suggestion_data.rb +2 -0
  34. data/lib/bugsnag/middleware/warden_user.rb +2 -0
  35. data/lib/bugsnag/middleware_stack.rb +20 -2
  36. data/lib/bugsnag/report.rb +16 -3
  37. data/lib/bugsnag/session_tracker.rb +13 -1
  38. data/lib/bugsnag/stacktrace.rb +6 -2
  39. data/spec/middleware/exception_meta_data_spec.rb +104 -0
  40. data/spec/report_spec.rb +9 -1
  41. metadata +7 -3
@@ -1,4 +1,6 @@
1
1
  module Bugsnag::Middleware
2
+ ##
3
+ # Extracts and appends clearance user information
2
4
  class ClearanceUser
3
5
  COMMON_USER_FIELDS = [:email, :name, :first_name, :last_name, :created_at, :id]
4
6
 
@@ -1,4 +1,6 @@
1
1
  module Bugsnag::Middleware
2
+ ##
3
+ # Extracts data from the exception.
2
4
  class ExceptionMetaData
3
5
  def initialize(bugsnag)
4
6
  @bugsnag = bugsnag
@@ -7,23 +9,21 @@ module Bugsnag::Middleware
7
9
  def call(report)
8
10
  # Apply the user's information attached to the exceptions
9
11
  report.raw_exceptions.each do |exception|
10
- if exception.class.include?(Bugsnag::MetaData)
11
- if exception.bugsnag_user_id.is_a?(String)
12
- report.user = {id: exception.bugsnag_user_id}
13
- end
12
+ if exception.respond_to?(:bugsnag_user_id) && exception.bugsnag_user_id.is_a?(String)
13
+ report.user = {id: exception.bugsnag_user_id}
14
+ end
14
15
 
15
- if exception.bugsnag_context.is_a?(String)
16
- report.context = exception.bugsnag_context
17
- end
16
+ if exception.respond_to?(:bugsnag_context) && exception.bugsnag_context.is_a?(String)
17
+ report.context = exception.bugsnag_context
18
+ end
18
19
 
19
- if exception.bugsnag_grouping_hash.is_a?(String)
20
- report.grouping_hash = exception.bugsnag_grouping_hash
21
- end
20
+ if exception.respond_to?(:bugsnag_grouping_hash) && exception.bugsnag_grouping_hash.is_a?(String)
21
+ report.grouping_hash = exception.bugsnag_grouping_hash
22
+ end
22
23
 
23
- if exception.respond_to?(:bugsnag_meta_data) && exception.bugsnag_meta_data
24
- exception.bugsnag_meta_data.each do |key, value|
25
- report.add_tab key, value
26
- end
24
+ if exception.respond_to?(:bugsnag_meta_data) && exception.bugsnag_meta_data.is_a?(Hash)
25
+ exception.bugsnag_meta_data.each do |key, value|
26
+ report.add_tab key, value
27
27
  end
28
28
  end
29
29
  end
@@ -1,4 +1,7 @@
1
1
  module Bugsnag::Middleware
2
+ ##
3
+ # Determines if the exception should be ignored based on the configured
4
+ # `ignore_classes`
2
5
  class IgnoreErrorClass
3
6
  def initialize(bugsnag)
4
7
  @bugsnag = bugsnag
@@ -1,4 +1,6 @@
1
1
  module Bugsnag::Middleware
2
+ ##
3
+ # Extracts and attaches mailman data to an error report
2
4
  class Mailman
3
5
  def initialize(bugsnag)
4
6
  @bugsnag = bugsnag
@@ -1,4 +1,6 @@
1
1
  module Bugsnag::Middleware
2
+ ##
3
+ # Extracts and attaches rack data to an error report
2
4
  class RackRequest
3
5
  SPOOF = "[SPOOF]".freeze
4
6
 
@@ -1,4 +1,6 @@
1
1
  module Bugsnag::Middleware
2
+ ##
3
+ # Extracts and attaches rails and rack environment data to an error report
2
4
  class Rails3Request
3
5
  SPOOF = "[SPOOF]".freeze
4
6
 
@@ -1,4 +1,6 @@
1
1
  module Bugsnag::Middleware
2
+ ##
3
+ # Extracts and attaches rake task information to an error report
2
4
  class Rake
3
5
  def initialize(bugsnag)
4
6
  @bugsnag = bugsnag
@@ -1,4 +1,6 @@
1
1
  module Bugsnag::Middleware
2
+ ##
3
+ # Attaches information about current session to an error report
2
4
  class SessionData
3
5
  def initialize(bugsnag)
4
6
  @bugsnag = bugsnag
@@ -1,4 +1,6 @@
1
1
  module Bugsnag::Middleware
2
+ ##
3
+ # Attaches Sidekiq job information to an error report
2
4
  class Sidekiq
3
5
  def initialize(bugsnag)
4
6
  @bugsnag = bugsnag
@@ -1,4 +1,6 @@
1
1
  module Bugsnag::Middleware
2
+ ##
3
+ # Attaches any "Did you mean?" suggestions to the report
2
4
  class SuggestionData
3
5
 
4
6
  CAPTURE_REGEX = /Did you mean\?([\s\S]+)$/
@@ -1,4 +1,6 @@
1
1
  module Bugsnag::Middleware
2
+ ##
3
+ # Extracts and attaches user information from Warden to an error report
2
4
  class WardenUser
3
5
  SCOPE_PATTERN = /^warden\.user\.([^.]+)\.key$/
4
6
  COMMON_USER_FIELDS = [:email, :name, :first_name, :last_name, :created_at, :id]
@@ -1,11 +1,17 @@
1
1
  module Bugsnag
2
2
  class MiddlewareStack
3
+ ##
4
+ # Creates the middleware stack.
3
5
  def initialize
4
6
  @middlewares = []
5
7
  @disabled_middleware = []
6
8
  @mutex = Mutex.new
7
9
  end
8
10
 
11
+ ##
12
+ # Defines a new middleware to use in the middleware call sequence.
13
+ #
14
+ # Will return early if given middleware is disabled or already included.
9
15
  def use(new_middleware)
10
16
  @mutex.synchronize do
11
17
  return if @disabled_middleware.include?(new_middleware)
@@ -15,6 +21,11 @@ module Bugsnag
15
21
  end
16
22
  end
17
23
 
24
+ ##
25
+ # Inserts a new middleware to use after a given middleware already added.
26
+ #
27
+ # Will return early if given middleware is disabled or already added.
28
+ # New middleware will be inserted last if the existing middleware is not already included.
18
29
  def insert_after(after, new_middleware)
19
30
  @mutex.synchronize do
20
31
  return if @disabled_middleware.include?(new_middleware)
@@ -34,6 +45,11 @@ module Bugsnag
34
45
  end
35
46
  end
36
47
 
48
+ ##
49
+ # Inserts a new middleware to use before a given middleware already added.
50
+ #
51
+ # Will return early if given middleware is disabled or already added.
52
+ # New middleware will be inserted last if the existing middleware is not already included.
37
53
  def insert_before(before, new_middleware)
38
54
  @mutex.synchronize do
39
55
  return if @disabled_middleware.include?(new_middleware)
@@ -57,12 +73,14 @@ module Bugsnag
57
73
  end
58
74
  end
59
75
 
60
- # This allows people to proxy methods to the array if they want to do more complex stuff
76
+ ##
77
+ # Allows the user to proxy methods for more complex functionality.
61
78
  def method_missing(method, *args, &block)
62
79
  @middlewares.send(method, *args, &block)
63
80
  end
64
81
 
65
- # Runs the middleware stack and calls
82
+ ##
83
+ # Runs the middleware stack.
66
84
  def run(report)
67
85
  # The final lambda is the termination of the middleware stack. It calls deliver on the notification
68
86
  lambda_has_run = false
@@ -37,6 +37,8 @@ module Bugsnag
37
37
  attr_accessor :severity_reason
38
38
  attr_accessor :user
39
39
 
40
+ ##
41
+ # Initializes a new report from an exception.
40
42
  def initialize(exception, passed_configuration, auto_notify=false)
41
43
  @should_ignore = false
42
44
  @unhandled = auto_notify
@@ -58,7 +60,8 @@ module Bugsnag
58
60
  self.user = {}
59
61
  end
60
62
 
61
- # Add a new tab to this notification
63
+ ##
64
+ # Add a new metadata tab to this notification.
62
65
  def add_tab(name, value)
63
66
  return if name.nil?
64
67
 
@@ -72,14 +75,16 @@ module Bugsnag
72
75
  end
73
76
  end
74
77
 
75
- # Remove a tab from this notification
78
+ ##
79
+ # Removes a metadata tab from this notification.
76
80
  def remove_tab(name)
77
81
  return if name.nil?
78
82
 
79
83
  meta_data.delete(name)
80
84
  end
81
85
 
82
- # Build an exception payload
86
+ ##
87
+ # Builds and returns the exception payload for this notification.
83
88
  def as_json
84
89
  # Build the payload's exception event
85
90
  payload_event = {
@@ -120,6 +125,8 @@ module Bugsnag
120
125
  }
121
126
  end
122
127
 
128
+ ##
129
+ # Returns the headers required for the notification.
123
130
  def headers
124
131
  {
125
132
  "Bugsnag-Api-Key" => api_key,
@@ -128,14 +135,20 @@ module Bugsnag
128
135
  }
129
136
  end
130
137
 
138
+ ##
139
+ # Whether this report should be ignored and not sent.
131
140
  def ignore?
132
141
  @should_ignore
133
142
  end
134
143
 
144
+ ##
145
+ # Data set on the configuration to be attached to every error notification.
135
146
  def request_data
136
147
  configuration.request_data
137
148
  end
138
149
 
150
+ ##
151
+ # Tells the client this report should not be sent.
139
152
  def ignore!
140
153
  @should_ignore = true
141
154
  end
@@ -12,18 +12,28 @@ module Bugsnag
12
12
 
13
13
  attr_reader :session_counts
14
14
 
15
+ ##
16
+ # Sets the session information for this thread.
15
17
  def self.set_current_session(session)
16
18
  Thread.current[THREAD_SESSION] = session
17
19
  end
18
20
 
21
+ ##
22
+ # Returns the session information for this thread.
19
23
  def self.get_current_session
20
24
  Thread.current[THREAD_SESSION]
21
25
  end
22
26
 
27
+ ##
28
+ # Initializes the session tracker.
23
29
  def initialize
24
30
  @session_counts = Concurrent::Hash.new(0)
25
31
  end
26
32
 
33
+ ##
34
+ # Starts a new session, storing it on the current thread.
35
+ #
36
+ # This allows Bugsnag to track error rates for a release.
27
37
  def start_session
28
38
  start_delivery_thread
29
39
  start_time = Time.now().utc().strftime('%Y-%m-%dT%H:%M:00')
@@ -41,6 +51,8 @@ module Bugsnag
41
51
 
42
52
  alias_method :create_session, :start_session
43
53
 
54
+ ##
55
+ # Delivers the current session_counts lists to the session endpoint.
44
56
  def send_sessions
45
57
  sessions = []
46
58
  counts = @session_counts
@@ -54,6 +66,7 @@ module Bugsnag
54
66
  deliver(sessions)
55
67
  end
56
68
 
69
+ private
57
70
  def start_delivery_thread
58
71
  MUTEX.synchronize do
59
72
  @started = nil unless defined?(@started)
@@ -77,7 +90,6 @@ module Bugsnag
77
90
  end
78
91
  end
79
92
 
80
- private
81
93
  def add_session(min)
82
94
  @session_counts[min] += 1
83
95
  end
@@ -8,8 +8,10 @@ module Bugsnag
8
8
  JAVA_BACKTRACE_REGEX = /^(.*)\((.*)(?::([0-9]+))?\)$/
9
9
 
10
10
  # Path to vendored code. Used to mark file paths as out of project.
11
- VENDOR_PATH = 'vendor/'
11
+ VENDOR_PATH = /^(vendor\/|\.bundle\/)/
12
12
 
13
+ ##
14
+ # Process a backtrace and the configuration into a parsed stacktrace.
13
15
  def initialize(backtrace, configuration)
14
16
  @configuration = configuration
15
17
 
@@ -44,7 +46,7 @@ module Bugsnag
44
46
  if defined?(@configuration.project_root) && @configuration.project_root.to_s != ''
45
47
  trace_hash[:inProject] = true if file.start_with?(@configuration.project_root.to_s)
46
48
  file.sub!(/#{@configuration.project_root}\//, "")
47
- trace_hash.delete(:inProject) if file.start_with?(VENDOR_PATH)
49
+ trace_hash.delete(:inProject) if file.match(VENDOR_PATH)
48
50
  end
49
51
 
50
52
 
@@ -66,6 +68,8 @@ module Bugsnag
66
68
  end.compact
67
69
  end
68
70
 
71
+ ##
72
+ # Returns the processed backtrace
69
73
  def to_a
70
74
  @processed_backtrace
71
75
  end
@@ -0,0 +1,104 @@
1
+ # encoding: utf-8
2
+ require 'spec_helper'
3
+
4
+ describe Bugsnag::Middleware::ExceptionMetaData do
5
+ let(:report_class) do
6
+ Class.new do
7
+ attr_accessor :raw_exceptions, :tabs, :user, :context, :grouping_hash
8
+
9
+ def initialize(errors)
10
+ self.raw_exceptions = Array(errors)
11
+ end
12
+
13
+ def add_tab(key, value)
14
+ self.tabs ||= {}
15
+ tabs[key] = value
16
+ end
17
+ end
18
+ end
19
+
20
+ let(:middleware) { Bugsnag::Middleware::ExceptionMetaData.new(lambda {|_|}) }
21
+ let(:bugsnag_error_class) { Class.new(StandardError) { include Bugsnag::MetaData } }
22
+
23
+ it "adds metadata when exception singleton class extended with Bugsnag::MetaData" do
24
+ error = RuntimeError.new
25
+ error.extend(Bugsnag::MetaData)
26
+ error.bugsnag_meta_data = {"foo" => "bar"}
27
+
28
+ report = report_class.new(error)
29
+
30
+ middleware.call(report)
31
+
32
+ expect(report.tabs).to eq({"foo" => "bar"})
33
+ end
34
+
35
+ it "adds metadata when exception class includes Bugsnag::MetaData" do
36
+ error = bugsnag_error_class.new
37
+ error.bugsnag_meta_data = {"foo" => "bar"}
38
+
39
+ report = report_class.new(error)
40
+
41
+ middleware.call(report)
42
+
43
+ expect(report.tabs).to eq({"foo" => "bar"})
44
+ end
45
+
46
+ it "does nothing when metadata not a Hash" do
47
+ error = bugsnag_error_class.new
48
+ error.bugsnag_meta_data = 4
49
+
50
+ report = report_class.new(error)
51
+
52
+ middleware.call(report)
53
+
54
+ expect(report.user).to eq(nil)
55
+ expect(report.tabs).to eq(nil)
56
+ expect(report.grouping_hash).to eq(nil)
57
+ expect(report.context).to eq(nil)
58
+ end
59
+
60
+ it "sets user ID when a string" do
61
+ error = bugsnag_error_class.new
62
+ error.bugsnag_user_id = "1234"
63
+
64
+ report = report_class.new(error)
65
+
66
+ middleware.call(report)
67
+
68
+ expect(report.user).to eq({id: "1234"})
69
+ end
70
+
71
+ it "sets context when a string" do
72
+ error = bugsnag_error_class.new
73
+ error.bugsnag_context = "Foo#bar"
74
+
75
+ report = report_class.new(error)
76
+
77
+ middleware.call(report)
78
+
79
+ expect(report.context).to eq("Foo#bar")
80
+ end
81
+
82
+ it "sets grouping_hash when a string" do
83
+ error = bugsnag_error_class.new
84
+ error.bugsnag_grouping_hash = "abcdef"
85
+
86
+ report = report_class.new(error)
87
+
88
+ middleware.call(report)
89
+
90
+ expect(report.grouping_hash).to eq("abcdef")
91
+ end
92
+
93
+ it "does nothing when no bugsnag attributes are set" do
94
+ error = bugsnag_error_class.new
95
+ report = report_class.new(error)
96
+
97
+ middleware.call(report)
98
+
99
+ expect(report.user).to eq(nil)
100
+ expect(report.tabs).to eq(nil)
101
+ expect(report.grouping_hash).to eq(nil)
102
+ expect(report.context).to eq(nil)
103
+ end
104
+ end
@@ -536,6 +536,10 @@ describe Bugsnag::Report do
536
536
  File.join(project_root, "lib/helpers/string.rb:32:in `splice'"),
537
537
  File.join(project_root, "lib/vendor/lib/article.rb:158:in `initialize'"),
538
538
  File.join(project_root, "lib/prog.rb:158:in `read_articles'"),
539
+ File.join(project_root, ".bundle/strutils/lib.string.rb:508:in `splice'"),
540
+ File.join(project_root, "abundle/article.rb:158:in `initialize'"),
541
+ File.join(project_root, ".bundles/strutils/lib.string.rb:508:in `splice'"),
542
+ File.join(project_root, "lib/.bundle/article.rb:158:in `initialize'"),
539
543
  "app.rb:10:in `main'",
540
544
  "(pry):3:in `__pry__'"
541
545
  ]}
@@ -549,7 +553,11 @@ describe Bugsnag::Report do
549
553
  expect(exception["stacktrace"][3]["inProject"]).to be true
550
554
  expect(exception["stacktrace"][4]["inProject"]).to be true
551
555
  expect(exception["stacktrace"][5]["inProject"]).to be_nil
552
- expect(exception["stacktrace"][6]["inProject"]).to be_nil
556
+ expect(exception["stacktrace"][6]["inProject"]).to be true
557
+ expect(exception["stacktrace"][7]["inProject"]).to be true
558
+ expect(exception["stacktrace"][8]["inProject"]).to be true
559
+ expect(exception["stacktrace"][9]["inProject"]).to be_nil
560
+ expect(exception["stacktrace"][10]["inProject"]).to be_nil
553
561
  }
554
562
  end
555
563