bugsnag 6.12.1 → 6.15.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (141) hide show
  1. checksums.yaml +4 -4
  2. data/.buildkite/pipeline.yml +470 -0
  3. data/.rubocop.yml +55 -0
  4. data/.rubocop_todo.yml +530 -160
  5. data/CHANGELOG.md +73 -0
  6. data/CONTRIBUTING.md +1 -9
  7. data/Gemfile +14 -7
  8. data/TESTING.md +81 -0
  9. data/VERSION +1 -1
  10. data/docker-compose.yml +46 -0
  11. data/dockerfiles/Dockerfile.jruby-unit-tests +13 -0
  12. data/dockerfiles/Dockerfile.ruby-maze-runner +26 -0
  13. data/dockerfiles/Dockerfile.ruby-unit-tests +12 -0
  14. data/features/delayed_job.feature +6 -22
  15. data/features/fixtures/delayed_job/Dockerfile +2 -4
  16. data/features/fixtures/delayed_job/app/Gemfile +1 -1
  17. data/features/fixtures/delayed_job/app/Rakefile +18 -0
  18. data/features/fixtures/docker-compose.yml +32 -40
  19. data/features/fixtures/expected_breadcrumbs/active_job.json +9 -0
  20. data/features/fixtures/expected_breadcrumbs/mongo_failed.json +15 -0
  21. data/features/fixtures/expected_breadcrumbs/mongo_filtered_request.json +15 -0
  22. data/features/fixtures/expected_breadcrumbs/mongo_filtered_result.json +15 -0
  23. data/features/fixtures/expected_breadcrumbs/mongo_success.json +14 -0
  24. data/features/fixtures/expected_breadcrumbs/request.json +13 -0
  25. data/features/fixtures/expected_breadcrumbs/sql_with_bindings.json +12 -0
  26. data/features/fixtures/expected_breadcrumbs/sql_without_bindings.json +11 -0
  27. data/features/fixtures/plain/Dockerfile +2 -2
  28. data/features/fixtures/plain/app/app.rb +1 -3
  29. data/features/fixtures/plain/app/delivery/fork_threadpool.rb +3 -1
  30. data/features/fixtures/plain/app/report_modification/initiators/handled_on_error.rb +10 -0
  31. data/features/fixtures/plain/app/report_modification/initiators/unhandled_on_error.rb +11 -0
  32. data/features/fixtures/plain/app/stack_frame_modification/initiators/handled_on_error.rb +29 -0
  33. data/features/fixtures/plain/app/stack_frame_modification/initiators/unhandled_on_error.rb +26 -0
  34. data/features/fixtures/plain/app/unhandled/{Interrupt.rb → interrupt.rb} +0 -0
  35. data/features/fixtures/rack1/Dockerfile +2 -2
  36. data/features/fixtures/rack2/Dockerfile +2 -2
  37. data/features/fixtures/rails3/Dockerfile +2 -2
  38. data/features/fixtures/rails3/app/Gemfile +4 -0
  39. data/features/fixtures/rails3/app/config/initializers/bugsnag.rb +11 -2
  40. data/features/fixtures/rails4/Dockerfile +2 -5
  41. data/features/fixtures/rails4/app/config/initializers/bugsnag.rb +10 -1
  42. data/features/fixtures/rails5/Dockerfile +2 -2
  43. data/features/fixtures/rails5/app/Gemfile +3 -2
  44. data/features/fixtures/rails5/app/config/initializers/bugsnag.rb +10 -1
  45. data/features/fixtures/rails6/Dockerfile +2 -2
  46. data/features/fixtures/rails6/app/Gemfile +3 -2
  47. data/features/fixtures/rails6/app/app/controllers/mongo_controller.rb +22 -0
  48. data/features/fixtures/rails6/app/app/models/mongo_model.rb +6 -0
  49. data/features/fixtures/rails6/app/config/environments/development.rb +2 -0
  50. data/features/fixtures/rails6/app/config/environments/production.rb +1 -0
  51. data/features/fixtures/rails6/app/config/environments/rails_env.rb +1 -0
  52. data/features/fixtures/rails6/app/config/environments/test.rb +1 -0
  53. data/features/fixtures/rails6/app/config/initializers/bugsnag.rb +10 -1
  54. data/features/fixtures/rails6/app/config/mongoid.yml +23 -0
  55. data/features/fixtures/rails6/app/config/routes.rb +4 -0
  56. data/features/fixtures/resque/Dockerfile +2 -2
  57. data/features/fixtures/sidekiq/Dockerfile +5 -7
  58. data/features/fixtures/sidekiq/app/Gemfile +2 -1
  59. data/features/fixtures/sidekiq/app/Rakefile.rb +14 -0
  60. data/features/fixtures/sinatra1/Dockerfile +2 -2
  61. data/features/fixtures/sinatra2/Dockerfile +2 -2
  62. data/features/plain_features/add_tab.feature +30 -97
  63. data/features/plain_features/app_type.feature +6 -25
  64. data/features/plain_features/app_version.feature +6 -25
  65. data/features/plain_features/auto_notify.feature +4 -20
  66. data/features/plain_features/delivery.feature +12 -60
  67. data/features/plain_features/exception_data.feature +24 -94
  68. data/features/plain_features/filters.feature +9 -43
  69. data/features/plain_features/handled_errors.feature +16 -78
  70. data/features/plain_features/ignore_classes.feature +5 -23
  71. data/features/plain_features/ignore_report.feature +8 -24
  72. data/features/plain_features/proxies.feature +13 -56
  73. data/features/plain_features/release_stages.feature +9 -40
  74. data/features/plain_features/report_api_key.feature +11 -35
  75. data/features/plain_features/report_severity.feature +10 -35
  76. data/features/plain_features/report_stack_frames.feature +29 -93
  77. data/features/plain_features/report_user.feature +29 -96
  78. data/features/plain_features/unhandled_errors.feature +17 -88
  79. data/features/rails_features/api_key.feature +12 -58
  80. data/features/rails_features/app_type.feature +13 -58
  81. data/features/rails_features/app_version.feature +19 -80
  82. data/features/rails_features/auto_capture_sessions.feature +31 -112
  83. data/features/rails_features/auto_notify.feature +28 -105
  84. data/features/rails_features/before_notify.feature +18 -83
  85. data/features/rails_features/breadcrumbs.feature +40 -137
  86. data/features/rails_features/handled.feature +18 -82
  87. data/features/rails_features/ignore_classes.feature +12 -51
  88. data/features/rails_features/meta_data_filters.feature +9 -33
  89. data/features/rails_features/mongo_breadcrumbs.feature +22 -96
  90. data/features/rails_features/on_error.feature +29 -0
  91. data/features/rails_features/project_root.feature +19 -84
  92. data/features/rails_features/release_stage.feature +20 -82
  93. data/features/rails_features/send_code.feature +13 -55
  94. data/features/rails_features/send_environment.feature +7 -33
  95. data/features/rails_features/unhandled.feature +6 -31
  96. data/features/rails_features/user_info.feature +27 -65
  97. data/features/sidekiq.feature +12 -79
  98. data/features/steps/ruby_notifier_steps.rb +59 -15
  99. data/features/support/env.rb +12 -45
  100. data/lib/bugsnag.rb +109 -21
  101. data/lib/bugsnag/breadcrumbs/breadcrumbs.rb +0 -2
  102. data/lib/bugsnag/breadcrumbs/validator.rb +0 -6
  103. data/lib/bugsnag/cleaner.rb +129 -60
  104. data/lib/bugsnag/code_extractor.rb +137 -0
  105. data/lib/bugsnag/configuration.rb +58 -1
  106. data/lib/bugsnag/helpers.rb +2 -4
  107. data/lib/bugsnag/integrations/que.rb +7 -4
  108. data/lib/bugsnag/middleware/discard_error_class.rb +30 -0
  109. data/lib/bugsnag/middleware/exception_meta_data.rb +15 -9
  110. data/lib/bugsnag/middleware/ignore_error_class.rb +2 -0
  111. data/lib/bugsnag/middleware/rack_request.rb +2 -4
  112. data/lib/bugsnag/middleware_stack.rb +38 -3
  113. data/lib/bugsnag/on_error_callbacks.rb +33 -0
  114. data/lib/bugsnag/report.rb +4 -14
  115. data/lib/bugsnag/session_tracker.rb +3 -3
  116. data/lib/bugsnag/stacktrace.rb +28 -75
  117. data/spec/breadcrumbs/breadcrumb_spec.rb +1 -1
  118. data/spec/breadcrumbs/validator_spec.rb +1 -26
  119. data/spec/bugsnag_spec.rb +2 -2
  120. data/spec/cleaner_spec.rb +202 -10
  121. data/spec/code_extractor_spec.rb +129 -0
  122. data/spec/configuration_spec.rb +16 -1
  123. data/spec/fixtures/apps/rails-initializer-config/Gemfile +5 -1
  124. data/spec/fixtures/apps/rails-invalid-initializer-config/Gemfile +5 -1
  125. data/spec/fixtures/apps/rails-no-config/Gemfile +5 -1
  126. data/spec/fixtures/crashes/file1.rb +29 -0
  127. data/spec/fixtures/crashes/file2.rb +25 -0
  128. data/spec/fixtures/crashes/file_with_long_lines.rb +7 -0
  129. data/spec/fixtures/crashes/functions.rb +29 -0
  130. data/spec/fixtures/crashes/short_file.rb +2 -0
  131. data/spec/helper_spec.rb +0 -31
  132. data/spec/integrations/logger_spec.rb +1 -1
  133. data/spec/integrations/rack_spec.rb +8 -6
  134. data/spec/integrations/rake_spec.rb +1 -1
  135. data/spec/on_error_spec.rb +332 -0
  136. data/spec/report_spec.rb +331 -30
  137. data/spec/spec_helper.rb +14 -1
  138. data/spec/stacktrace_spec.rb +427 -74
  139. metadata +36 -7
  140. data/.travis.yml +0 -117
  141. data/features/plain_features/api_key.feature +0 -25
@@ -0,0 +1,137 @@
1
+ module Bugsnag
2
+ # @api private
3
+ class CodeExtractor
4
+ MAXIMUM_LINES_TO_KEEP = 7
5
+
6
+ ##
7
+ # @param configuration [Configuration]
8
+ def initialize(configuration)
9
+ @files = {}
10
+ @configuration = configuration
11
+ end
12
+
13
+ ##
14
+ # Add a file and its corresponding trace hash to be processed.
15
+ #
16
+ # @param path [String] The full path to the file
17
+ # @param trace [Hash]
18
+ # @return [void]
19
+ def add_file(path, trace)
20
+ # If the file doesn't exist we can't extract code from it, so we can skip
21
+ # this file entirely
22
+ unless File.exist?(path)
23
+ trace[:code] = nil
24
+
25
+ return
26
+ end
27
+
28
+ @files[path] ||= []
29
+ @files[path].push(trace)
30
+
31
+ # Record the line numbers we want to fetch for this trace
32
+ # We grab extra lines so that we can compensate if the error is on the
33
+ # first or last line of a file
34
+ first_line_number = trace[:lineNumber] - MAXIMUM_LINES_TO_KEEP
35
+
36
+ trace[:first_line_number] = first_line_number < 1 ? 1 : first_line_number
37
+ trace[:last_line_number] = trace[:lineNumber] + MAXIMUM_LINES_TO_KEEP
38
+ end
39
+
40
+ ##
41
+ # Add the code to the hashes that were given in {#add_file} by modifying
42
+ # them in-place. They will have a new ':code' key containing a hash of line
43
+ # number => string of code for that line
44
+ #
45
+ # @return [void]
46
+ def extract!
47
+ @files.each do |path, traces|
48
+ begin
49
+ line_numbers = Set.new
50
+
51
+ traces.each do |trace|
52
+ trace[:first_line_number].upto(trace[:last_line_number]) do |line_number|
53
+ line_numbers << line_number
54
+ end
55
+ end
56
+
57
+ extract_from(path, traces, line_numbers)
58
+ rescue StandardError => e
59
+ # Clean up after ourselves
60
+ traces.each do |trace|
61
+ trace[:code] ||= nil
62
+ trace.delete(:first_line_number)
63
+ trace.delete(:last_line_number)
64
+ end
65
+
66
+ @configuration.warn("Error extracting code: #{e.inspect}")
67
+ end
68
+ end
69
+ end
70
+
71
+ private
72
+
73
+ ##
74
+ # @param path [String]
75
+ # @param traces [Array<Hash>]
76
+ # @param line_numbers [Set<Integer>]
77
+ # @return [void]
78
+ def extract_from(path, traces, line_numbers)
79
+ code = {}
80
+
81
+ File.open(path) do |file|
82
+ current_line_number = 0
83
+
84
+ file.each_line do |line|
85
+ current_line_number += 1
86
+
87
+ next unless line_numbers.include?(current_line_number)
88
+
89
+ code[current_line_number] = line[0...200].rstrip
90
+ end
91
+ end
92
+
93
+ associate_code_with_trace(code, traces)
94
+ end
95
+
96
+ ##
97
+ # @param code [Hash{Integer => String}]
98
+ # @param traces [Array<Hash>]
99
+ # @return [void]
100
+ def associate_code_with_trace(code, traces)
101
+ traces.each do |trace|
102
+ trace[:code] = {}
103
+
104
+ code.each do |line_number, line|
105
+ # If we've gone past the last line we care about, we can stop iterating
106
+ break if line_number > trace[:last_line_number]
107
+
108
+ # Skip lines that aren't in the range we want
109
+ next unless line_number >= trace[:first_line_number]
110
+
111
+ trace[:code][line_number] = line
112
+ end
113
+
114
+ trim_excess_lines(trace[:code], trace[:lineNumber])
115
+ trace.delete(:first_line_number)
116
+ trace.delete(:last_line_number)
117
+ end
118
+ end
119
+
120
+ ##
121
+ # @param code [Hash{Integer => String}]
122
+ # @param line_number [Integer]
123
+ # @return [void]
124
+ def trim_excess_lines(code, line_number)
125
+ while code.length > MAXIMUM_LINES_TO_KEEP
126
+ last_line = code.keys.max
127
+ first_line = code.keys.min
128
+
129
+ if (last_line - line_number) > (line_number - first_line)
130
+ code.delete(last_line)
131
+ else
132
+ code.delete(first_line)
133
+ end
134
+ end
135
+ end
136
+ end
137
+ end
@@ -3,6 +3,7 @@ require "socket"
3
3
  require "logger"
4
4
  require "bugsnag/middleware_stack"
5
5
  require "bugsnag/middleware/callbacks"
6
+ require "bugsnag/middleware/discard_error_class"
6
7
  require "bugsnag/middleware/exception_meta_data"
7
8
  require "bugsnag/middleware/ignore_error_class"
8
9
  require "bugsnag/middleware/suggestion_data"
@@ -35,9 +36,13 @@ module Bugsnag
35
36
  attr_accessor :timeout
36
37
  attr_accessor :hostname
37
38
  attr_accessor :runtime_versions
38
- attr_accessor :ignore_classes
39
+ attr_accessor :discard_classes
39
40
  attr_accessor :auto_capture_sessions
40
41
 
42
+ ##
43
+ # @deprecated Use {#discard_classes} instead
44
+ attr_accessor :ignore_classes
45
+
41
46
  ##
42
47
  # @return [String] URL error notifications will be delivered to
43
48
  attr_reader :notify_endpoint
@@ -63,6 +68,14 @@ module Bugsnag
63
68
  # @return [Integer] the maximum allowable amount of breadcrumbs per thread
64
69
  attr_reader :max_breadcrumbs
65
70
 
71
+ ##
72
+ # @return [Regexp] matching file paths out of project
73
+ attr_accessor :vendor_path
74
+
75
+ ##
76
+ # @return [Array]
77
+ attr_reader :scopes_to_filter
78
+
66
79
  API_KEY_REGEX = /[0-9a-f]{32}/i
67
80
  THREAD_LOCAL_NAME = "bugsnag_req_data"
68
81
 
@@ -81,6 +94,11 @@ module Bugsnag
81
94
 
82
95
  DEFAULT_MAX_BREADCRUMBS = 25
83
96
 
97
+ # Path to vendored code. Used to mark file paths as out of project.
98
+ DEFAULT_VENDOR_PATH = %r{^(vendor/|\.bundle/)}
99
+
100
+ DEFAULT_SCOPES_TO_FILTER = ['events.metaData', 'events.breadcrumbs.metaData'].freeze
101
+
84
102
  alias :track_sessions :auto_capture_sessions
85
103
  alias :track_sessions= :auto_capture_sessions=
86
104
 
@@ -92,6 +110,7 @@ module Bugsnag
92
110
  self.send_environment = false
93
111
  self.send_code = true
94
112
  self.meta_data_filters = Set.new(DEFAULT_META_DATA_FILTERS)
113
+ self.scopes_to_filter = DEFAULT_SCOPES_TO_FILTER
95
114
  self.hostname = default_hostname
96
115
  self.runtime_versions = {}
97
116
  self.runtime_versions["ruby"] = RUBY_VERSION
@@ -115,7 +134,10 @@ module Bugsnag
115
134
 
116
135
  # SystemExit and SignalException are common Exception types seen with
117
136
  # successful exits and are not automatically reported to Bugsnag
137
+ # TODO move these defaults into `discard_classes` when `ignore_classes`
138
+ # is removed
118
139
  self.ignore_classes = Set.new([SystemExit, SignalException])
140
+ self.discard_classes = Set.new([])
119
141
 
120
142
  # Read the API key from the environment
121
143
  self.api_key = ENV["BUGSNAG_API_KEY"]
@@ -125,6 +147,11 @@ module Bugsnag
125
147
  parse_proxy(proxy_uri)
126
148
  end
127
149
 
150
+ # Set up vendor_path regex to mark stacktrace file paths as out of project.
151
+ # Stacktrace lines that matches regex will be marked as "out of project"
152
+ # will only appear in the full trace.
153
+ self.vendor_path = DEFAULT_VENDOR_PATH
154
+
128
155
  # Set up logging
129
156
  self.logger = Logger.new(STDOUT)
130
157
  self.logger.level = Logger::INFO
@@ -135,6 +162,7 @@ module Bugsnag
135
162
  # Configure the bugsnag middleware stack
136
163
  self.internal_middleware = Bugsnag::MiddlewareStack.new
137
164
  self.internal_middleware.use Bugsnag::Middleware::ExceptionMetaData
165
+ self.internal_middleware.use Bugsnag::Middleware::DiscardErrorClass
138
166
  self.internal_middleware.use Bugsnag::Middleware::IgnoreErrorClass
139
167
  self.internal_middleware.use Bugsnag::Middleware::SuggestionData
140
168
  self.internal_middleware.use Bugsnag::Middleware::ClassifyError
@@ -290,8 +318,37 @@ module Bugsnag
290
318
  @enable_sessions = false
291
319
  end
292
320
 
321
+ ##
322
+ # Add the given callback to the list of on_error callbacks
323
+ #
324
+ # The on_error callbacks will be called when an error is captured or reported
325
+ # and are passed a {Bugsnag::Report} object
326
+ #
327
+ # Returning false from an on_error callback will cause the error to be ignored
328
+ # and will prevent any remaining callbacks from being called
329
+ #
330
+ # @param callback [Proc]
331
+ # @return [void]
332
+ def add_on_error(callback)
333
+ middleware.use(callback)
334
+ end
335
+
336
+ ##
337
+ # Remove the given callback from the list of on_error callbacks
338
+ #
339
+ # Note that this must be the same Proc instance that was passed to
340
+ # {#add_on_error}, otherwise it will not be removed
341
+ #
342
+ # @param callback [Proc]
343
+ # @return [void]
344
+ def remove_on_error(callback)
345
+ middleware.remove(callback)
346
+ end
347
+
293
348
  private
294
349
 
350
+ attr_writer :scopes_to_filter
351
+
295
352
  PROG_NAME = "[Bugsnag]"
296
353
 
297
354
  def default_hostname
@@ -17,12 +17,10 @@ module Bugsnag
17
17
  def self.trim_if_needed(value)
18
18
  value = "" if value.nil?
19
19
 
20
- # Sanitize object
21
- sanitized_value = Bugsnag::Cleaner.clean_object_encoding(value)
22
- return sanitized_value unless payload_too_long?(sanitized_value)
20
+ return value unless payload_too_long?(value)
23
21
 
24
22
  # Trim metadata
25
- reduced_value = trim_metadata(sanitized_value)
23
+ reduced_value = trim_metadata(value)
26
24
  return reduced_value unless payload_too_long?(reduced_value)
27
25
 
28
26
  # Trim code from stacktrace
@@ -37,13 +37,16 @@ if defined?(::Que)
37
37
  end
38
38
  end
39
39
 
40
- if Que.respond_to?(:error_notifier=)
41
- Bugsnag.configuration.app_type ||= "que"
40
+ Bugsnag.configuration.app_type ||= "que"
41
+ if defined?(::Que::Version)
42
42
  Bugsnag.configuration.runtime_versions["que"] = ::Que::Version
43
+ elsif defined?(::Que::VERSION)
44
+ Bugsnag.configuration.runtime_versions["que"] = ::Que::VERSION
45
+ end
46
+
47
+ if Que.respond_to?(:error_notifier=)
43
48
  Que.error_notifier = handler
44
49
  elsif Que.respond_to?(:error_handler=)
45
- Bugsnag.configuration.app_type ||= "que"
46
- Bugsnag.configuration.runtime_versions["que"] = ::Que::Version
47
50
  Que.error_handler = handler
48
51
  end
49
52
  end
@@ -0,0 +1,30 @@
1
+ module Bugsnag::Middleware
2
+ ##
3
+ # Determines if the exception should be ignored based on the configured
4
+ # `discard_classes`
5
+ class DiscardErrorClass
6
+ ##
7
+ # @param middleware [#call] The next middleware to call
8
+ def initialize(middleware)
9
+ @middleware = middleware
10
+ end
11
+
12
+ ##
13
+ # @param report [Report]
14
+ def call(report)
15
+ should_discard = report.raw_exceptions.any? do |ex|
16
+ report.configuration.discard_classes.any? do |to_ignore|
17
+ case to_ignore
18
+ when String then to_ignore == ex.class.name
19
+ when Regexp then to_ignore =~ ex.class.name
20
+ else false
21
+ end
22
+ end
23
+ end
24
+
25
+ report.ignore! if should_discard
26
+
27
+ @middleware.call(report)
28
+ end
29
+ end
30
+ end
@@ -9,21 +9,27 @@ module Bugsnag::Middleware
9
9
  def call(report)
10
10
  # Apply the user's information attached to the exceptions
11
11
  report.raw_exceptions.each do |exception|
12
- if exception.respond_to?(:bugsnag_user_id) && exception.bugsnag_user_id.is_a?(String)
13
- report.user = {id: exception.bugsnag_user_id}
12
+ if exception.respond_to?(:bugsnag_user_id)
13
+ user_id = exception.bugsnag_user_id
14
+ report.user = {id: user_id} if user_id.is_a?(String)
14
15
  end
15
16
 
16
- if exception.respond_to?(:bugsnag_context) && exception.bugsnag_context.is_a?(String)
17
- report.context = exception.bugsnag_context
17
+ if exception.respond_to?(:bugsnag_context)
18
+ context = exception.bugsnag_context
19
+ report.context = context if context.is_a?(String)
18
20
  end
19
21
 
20
- if exception.respond_to?(:bugsnag_grouping_hash) && exception.bugsnag_grouping_hash.is_a?(String)
21
- report.grouping_hash = exception.bugsnag_grouping_hash
22
+ if exception.respond_to?(:bugsnag_grouping_hash)
23
+ group_hash = exception.bugsnag_grouping_hash
24
+ report.grouping_hash = group_hash if group_hash.is_a?(String)
22
25
  end
23
26
 
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
+ if exception.respond_to?(:bugsnag_meta_data)
28
+ meta_data = exception.bugsnag_meta_data
29
+ if meta_data.is_a?(Hash)
30
+ meta_data.each do |key, value|
31
+ report.add_tab key, value
32
+ end
27
33
  end
28
34
  end
29
35
  end
@@ -2,6 +2,8 @@ module Bugsnag::Middleware
2
2
  ##
3
3
  # Determines if the exception should be ignored based on the configured
4
4
  # `ignore_classes`
5
+ #
6
+ # @deprecated Use {DiscardErrorClass} instead
5
7
  class IgnoreErrorClass
6
8
  def initialize(bugsnag)
7
9
  @bugsnag = bugsnag
@@ -28,18 +28,16 @@ module Bugsnag::Middleware
28
28
  url = "#{request.scheme}://#{request.host}"
29
29
  url << ":#{request.port}" unless [80, 443].include?(request.port)
30
30
 
31
- cleaner = Bugsnag::Cleaner.new(report.configuration.meta_data_filters)
32
-
33
31
  # If app is passed a bad URL, this code will crash attempting to clean it
34
32
  begin
35
- url << cleaner.clean_url(request.fullpath)
33
+ url << Bugsnag.cleaner.clean_url(request.fullpath)
36
34
  rescue StandardError => stde
37
35
  Bugsnag.configuration.warn "RackRequest - Rescued error while cleaning request.fullpath: #{stde}"
38
36
  end
39
37
 
40
38
  referer = nil
41
39
  begin
42
- referer = cleaner.clean_url(request.referer) if request.referer
40
+ referer = Bugsnag.cleaner.clean_url(request.referer) if request.referer
43
41
  rescue StandardError => stde
44
42
  Bugsnag.configuration.warn "RackRequest - Rescued error while cleaning request.referer: #{stde}"
45
43
  end
@@ -1,3 +1,5 @@
1
+ require "bugsnag/on_error_callbacks"
2
+
1
3
  module Bugsnag
2
4
  class MiddlewareStack
3
5
  ##
@@ -65,11 +67,26 @@ module Bugsnag
65
67
  end
66
68
  end
67
69
 
70
+ ##
71
+ # Disable the given middleware. This removes them from the list of
72
+ # middleware and ensures they cannot be added again
73
+ #
74
+ # See also {#remove}
68
75
  def disable(*middlewares)
69
76
  @mutex.synchronize do
70
77
  @disabled_middleware += middlewares
71
78
 
72
- @middlewares.delete_if {|m| @disabled_middleware.include?(m)}
79
+ @middlewares.delete_if {|m| @disabled_middleware.include?(m) }
80
+ end
81
+ end
82
+
83
+ ##
84
+ # Remove the given middleware from the list of middleware
85
+ #
86
+ # This is like {#disable} but allows the middleware to be added again
87
+ def remove(*middlewares)
88
+ @mutex.synchronize do
89
+ @middlewares.delete_if {|m| middlewares.include?(m) }
73
90
  end
74
91
  end
75
92
 
@@ -91,7 +108,7 @@ module Bugsnag
91
108
 
92
109
  begin
93
110
  # We reverse them, so we can call "call" on the first middleware
94
- middleware_procs.reverse.inject(notify_lambda) { |n,e| e.call(n) }.call(report)
111
+ middleware_procs.reverse.inject(notify_lambda) {|n, e| e.call(n) }.call(report)
95
112
  rescue StandardError => e
96
113
  # KLUDGE: Since we don't re-raise middleware exceptions, this breaks rspec
97
114
  raise if e.class.to_s == "RSpec::Expectations::ExpectationNotMetError"
@@ -107,10 +124,28 @@ module Bugsnag
107
124
  end
108
125
 
109
126
  private
127
+
128
+ ##
110
129
  # Generates a list of middleware procs that are ready to be run
111
130
  # Pass each one a reference to the next in the queue
131
+ #
132
+ # @return [Array<Proc>]
112
133
  def middleware_procs
113
- @middlewares.map{|middleware| proc { |next_middleware| middleware.new(next_middleware) } }
134
+ # Split the middleware into separate lists of Procs and Classes
135
+ procs, classes = @middlewares.partition {|middleware| middleware.is_a?(Proc) }
136
+
137
+ # Wrap the classes in a proc that, when called, news up the middleware and
138
+ # passes the next middleware in the queue
139
+ middleware_instances = classes.map do |middleware|
140
+ proc {|next_middleware| middleware.new(next_middleware) }
141
+ end
142
+
143
+ # Wrap the list of procs in a proc that, when called, wraps them in an
144
+ # 'OnErrorCallbacks' instance that also has a reference to the next middleware
145
+ wrapped_procs = proc {|next_middleware| OnErrorCallbacks.new(next_middleware, procs) }
146
+
147
+ # Return the combined middleware and wrapped procs
148
+ middleware_instances.push(wrapped_procs)
114
149
  end
115
150
  end
116
151
  end