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
data/README.md CHANGED
@@ -1,50 +1,46 @@
1
- # Bugsnag exception reporter for Ruby
2
- [![build status](https://travis-ci.org/bugsnag/bugsnag-ruby.svg?branch=master)](https://travis-ci.org/bugsnag/bugsnag-ruby)
1
+ # Bugsnag error monitoring & exception reporter for Ruby
2
+ [![build status](https://github.com/bugsnag/bugsnag-ruby/actions/workflows/test-package.yml/badge.svg)](https://github.com/bugsnag/bugsnag-ruby/actions/workflows/test-package.yml?query=branch%3Amaster)
3
3
 
4
4
 
5
- The Bugsnag exception reporter for Ruby gives you instant notification of exceptions
6
- thrown from your **[Rails](https://bugsnag.com/platforms/rails)**, **Sinatra**, **Rack** or **plain Ruby** app.
7
- Any uncaught exceptions will trigger a notification to be sent to your
8
- Bugsnag project.
5
+ The Bugsnag exception reporter for Ruby gives you instant notification of exceptions thrown from your **[Rails](https://www.bugsnag.com/platforms/rails)**, **Sinatra**, **Rack** or **plain Ruby** app. Any uncaught exceptions will trigger a notification to be sent to your Bugsnag project.
9
6
 
10
7
  ## Features
11
8
 
12
9
  * Automatically report unhandled exceptions and crashes
13
- * Report handled exceptions
10
+ * Report handled exceptions
14
11
  * Attach user information to determine how many people are affected by a crash
15
12
  * Send customized diagnostic data
13
+ * Track events that occur leading up to a crash
16
14
 
17
15
  ## Getting started
18
16
 
19
- 1. [Create a Bugsnag account](https://bugsnag.com)
17
+ 1. [Create a Bugsnag account](https://www.bugsnag.com)
20
18
  2. Complete the instructions in the integration guide for your framework:
21
- * [Rack](http://docs.bugsnag.com/platforms/ruby/rack)
22
- * [Rails](http://docs.bugsnag.com/platforms/ruby/rails)
23
- * [Rake](http://docs.bugsnag.com/platforms/ruby/rake)
24
- * [Sidekiq](http://docs.bugsnag.com/platforms/ruby/sidekiq)
25
- * [Other Ruby apps](http://docs.bugsnag.com/platforms/ruby/other)
26
- * For [EventMachine](http://rubyeventmachine.com) integration, see [`bugsnag-em`](https://github.com/bugsnag/bugsnag-em)
19
+ * [Que](https://docs.bugsnag.com/platforms/ruby/que)
20
+ * [Rack](https://docs.bugsnag.com/platforms/ruby/rack)
21
+ * [Rails](https://docs.bugsnag.com/platforms/ruby/rails)
22
+ * [Rake](https://docs.bugsnag.com/platforms/ruby/rake)
23
+ * [Sidekiq](https://docs.bugsnag.com/platforms/ruby/sidekiq)
24
+ * [Other Ruby apps](https://docs.bugsnag.com/platforms/ruby/other)
27
25
  3. Relax!
28
26
 
29
27
  ## Support
30
28
 
31
29
  * Read the configuration reference:
32
- * [Rack](http://docs.bugsnag.com/platforms/ruby/rack/configuration-options)
33
- * [Rails](http://docs.bugsnag.com/platforms/ruby/rails/configuration-options)
34
- * [Rake](http://docs.bugsnag.com/platforms/ruby/rake/configuration-options)
35
- * [Sidekiq](http://docs.bugsnag.com/platforms/ruby/sidekiq/configuration-options)
36
- * [Other Ruby apps](http://docs.bugsnag.com/platforms/ruby/other/configuration-options)
37
- * Check out some [example apps integrated with Bugsnag](https://github.com/bugsnag/bugsnag-example-apps/tree/master/apps/ruby) using Rails, Sinatra, Padrino, and more.
30
+ * [Que](https://docs.bugsnag.com/platforms/ruby/que/configuration-options)
31
+ * [Rack](https://docs.bugsnag.com/platforms/ruby/rack/configuration-options)
32
+ * [Rails](https://docs.bugsnag.com/platforms/ruby/rails/configuration-options)
33
+ * [Rake](https://docs.bugsnag.com/platforms/ruby/rake/configuration-options)
34
+ * [Sidekiq](https://docs.bugsnag.com/platforms/ruby/sidekiq/configuration-options)
35
+ * [Other Ruby apps](https://docs.bugsnag.com/platforms/ruby/other/configuration-options)
36
+ * Check out some [example apps integrated with Bugsnag](https://github.com/bugsnag/bugsnag-ruby/tree/master/example) using Rails, Sinatra, Padrino, and more.
38
37
  * [Search open and closed issues](https://github.com/bugsnag/bugsnag-ruby/issues?utf8=✓&q=is%3Aissue) for similar problems
39
38
  * [Report a bug or request a feature](https://github.com/bugsnag/bugsnag-ruby/issues/new)
40
39
 
41
40
  ## Contributing
42
41
 
43
- All contributors are welcome! For information on how to build, test
44
- and release `bugsnag-ruby`, see our
45
- [contributing guide](https://github.com/bugsnag/bugsnag-ruby/blob/master/CONTRIBUTING.md). Feel free to comment on [existing issues](https://github.com/bugsnag/bugsnag-ruby/issues) for clarification or starting points.
42
+ All contributors are welcome! For information on how to build, test and release `bugsnag-ruby`, see our [contributing guide](https://github.com/bugsnag/bugsnag-ruby/blob/master/CONTRIBUTING.md). Feel free to comment on [existing issues](https://github.com/bugsnag/bugsnag-ruby/issues) for clarification or starting points.
46
43
 
47
44
  ## License
48
45
 
49
- The Bugsnag ruby notifier is free software released under the MIT License.
50
- See [LICENSE.txt](LICENSE.txt) for details.
46
+ The Bugsnag ruby notifier is free software released under the MIT License. See [LICENSE.txt](LICENSE.txt) for details.
data/VERSION CHANGED
@@ -1 +1 @@
1
- 4.2.1
1
+ 6.27.1
data/bugsnag.gemspec CHANGED
@@ -7,10 +7,10 @@ Gem::Specification.new do |s|
7
7
 
8
8
  s.description = "Ruby notifier for bugsnag.com"
9
9
  s.summary = "Ruby notifier for bugsnag.com"
10
- s.homepage = "http://github.com/bugsnag/bugsnag-ruby"
10
+ s.homepage = "https://github.com/bugsnag/bugsnag-ruby"
11
11
  s.licenses = ["MIT"]
12
12
 
13
- s.files = `git ls-files`.split("\n").reject {|file| file.start_with? "example/"}
13
+ s.files = `git ls-files -z lib bugsnag.gemspec VERSION .yardopts`.split("\x0")
14
14
  s.extra_rdoc_files = [
15
15
  "LICENSE.txt",
16
16
  "README.md",
@@ -19,10 +19,21 @@ Gem::Specification.new do |s|
19
19
  s.require_paths = ["lib"]
20
20
  s.required_ruby_version = '>= 1.9.2'
21
21
 
22
- s.add_development_dependency 'rake', '~> 10.1.1'
23
- s.add_development_dependency 'rspec'
24
- s.add_development_dependency 'rdoc'
25
- s.add_development_dependency 'pry'
26
- s.add_development_dependency 'addressable', '~> 2.3.8'
27
- s.add_development_dependency 'webmock'
22
+ ruby_version = Gem::Version.new(RUBY_VERSION.dup)
23
+
24
+ if ruby_version < Gem::Version.new('2.2.0')
25
+ # concurrent-ruby 1.1.10 requires Ruby 2.2+
26
+ s.add_runtime_dependency 'concurrent-ruby', '~> 1.0', '< 1.1.10'
27
+ else
28
+ s.add_runtime_dependency 'concurrent-ruby', '~> 1.0'
29
+ end
30
+
31
+ if s.respond_to?(:metadata=)
32
+ s.metadata = {
33
+ "changelog_uri" => "https://github.com/bugsnag/bugsnag-ruby/blob/v#{File.read("VERSION").strip}/CHANGELOG.md",
34
+ "documentation_uri" => "https://docs.bugsnag.com/platforms/ruby/",
35
+ "source_code_uri" => "https://github.com/bugsnag/bugsnag-ruby/",
36
+ "rubygems_mfa_required" => "true"
37
+ }
38
+ end
28
39
  end
@@ -0,0 +1,14 @@
1
+ require "bugsnag/breadcrumbs/breadcrumbs"
2
+
3
+ module Bugsnag
4
+ module BreadcrumbType
5
+ ERROR = Bugsnag::Breadcrumbs::ERROR_BREADCRUMB_TYPE
6
+ LOG = Bugsnag::Breadcrumbs::LOG_BREADCRUMB_TYPE
7
+ MANUAL = Bugsnag::Breadcrumbs::MANUAL_BREADCRUMB_TYPE
8
+ NAVIGATION = Bugsnag::Breadcrumbs::NAVIGATION_BREADCRUMB_TYPE
9
+ PROCESS = Bugsnag::Breadcrumbs::PROCESS_BREADCRUMB_TYPE
10
+ REQUEST = Bugsnag::Breadcrumbs::REQUEST_BREADCRUMB_TYPE
11
+ STATE = Bugsnag::Breadcrumbs::STATE_BREADCRUMB_TYPE
12
+ USER = Bugsnag::Breadcrumbs::USER_BREADCRUMB_TYPE
13
+ end
14
+ end
@@ -0,0 +1,109 @@
1
+ module Bugsnag::Breadcrumbs
2
+ class Breadcrumb
3
+ # @deprecated Use {#message} instead
4
+ # @return [String] the breadcrumb name
5
+ attr_accessor :name
6
+
7
+ # @return [String] the breadcrumb type
8
+ attr_accessor :type
9
+
10
+ # @deprecated Use {#metadata} instead
11
+ # @return [Hash, nil] metadata hash containing strings, numbers, or booleans, or nil
12
+ attr_accessor :meta_data
13
+
14
+ # @return [Boolean] set to `true` if the breadcrumb was automatically generated
15
+ attr_reader :auto
16
+
17
+ # @return [Time] a Time object referring to breadcrumb creation time
18
+ attr_reader :timestamp
19
+
20
+ ##
21
+ # Creates a breadcrumb
22
+ #
23
+ # This will not have been validated, which must occur before this is attached to a report
24
+ #
25
+ # @api private
26
+ #
27
+ # @param name [String] the breadcrumb name
28
+ # @param type [String] the breadcrumb type from Bugsnag::BreadcrumbType
29
+ # @param meta_data [Hash, nil] a hash containing strings, numbers, or booleans, or nil
30
+ # @param auto [Symbol] set to `:auto` if the breadcrumb is automatically generated
31
+ def initialize(name, type, meta_data, auto)
32
+ @should_ignore = false
33
+ self.name = name
34
+ self.type = type
35
+ self.meta_data = meta_data
36
+
37
+ # Use the symbol comparison to improve readability of breadcrumb creation
38
+ @auto = auto == :auto
39
+
40
+ # Store it as a timestamp for now
41
+ @timestamp = Time.now.utc
42
+ end
43
+
44
+ ##
45
+ # Flags the breadcrumb to be ignored
46
+ #
47
+ # Ignored breadcrumbs will not be attached to a report
48
+ def ignore!
49
+ @should_ignore = true
50
+ end
51
+
52
+ ##
53
+ # Checks if the `ignore!` method has been called
54
+ #
55
+ # Ignored breadcrumbs will not be attached to a report
56
+ #
57
+ # @return [True] if `ignore!` has been called
58
+ # @return [nil] if `ignore` has not been called
59
+ def ignore?
60
+ @should_ignore
61
+ end
62
+
63
+ ##
64
+ # Outputs the breadcrumb data in a formatted hash
65
+ #
66
+ # These adhere to the breadcrumb format as defined in the Bugsnag error reporting API
67
+ #
68
+ # @return [Hash] Hash representation of the breadcrumb
69
+ def to_h
70
+ {
71
+ :name => @name,
72
+ :type => @type,
73
+ :metaData => @meta_data,
74
+ :timestamp => @timestamp.iso8601(3)
75
+ }
76
+ end
77
+
78
+ # TODO: "message" and "metadata" can be simple attr_accessors when they
79
+ # replace "name" and "meta_data"
80
+ # NOTE: these are not aliases as YARD doesn't allow documenting the non-alias
81
+ # as deprecated without also marking the alias as deprecated
82
+
83
+ # The breadcrumb message
84
+ # @!attribute message
85
+ # @return [String]
86
+ def message
87
+ @name
88
+ end
89
+
90
+ # @param message [String]
91
+ # @return [void]
92
+ def message=(message)
93
+ @name = message
94
+ end
95
+
96
+ # A Hash containing arbitrary metadata associated with this breadcrumb
97
+ # @!attribute metadata
98
+ # @return [Hash, nil]
99
+ def metadata
100
+ @meta_data
101
+ end
102
+
103
+ # @param metadata [Hash, nil]
104
+ # @return [void]
105
+ def metadata=(metadata)
106
+ @meta_data = metadata
107
+ end
108
+ end
109
+ end
@@ -0,0 +1,13 @@
1
+ module Bugsnag::Breadcrumbs
2
+ # @deprecated Use {Bugsnag::BreadcrumbType} instead
3
+ VALID_BREADCRUMB_TYPES = [
4
+ ERROR_BREADCRUMB_TYPE = "error",
5
+ MANUAL_BREADCRUMB_TYPE = "manual",
6
+ NAVIGATION_BREADCRUMB_TYPE = "navigation",
7
+ REQUEST_BREADCRUMB_TYPE = "request",
8
+ PROCESS_BREADCRUMB_TYPE = "process",
9
+ LOG_BREADCRUMB_TYPE = "log",
10
+ USER_BREADCRUMB_TYPE = "user",
11
+ STATE_BREADCRUMB_TYPE = "state"
12
+ ].freeze
13
+ end
@@ -0,0 +1,48 @@
1
+ module Bugsnag::Breadcrumbs
2
+ class OnBreadcrumbCallbackList
3
+ def initialize(configuration)
4
+ @callbacks = Set.new
5
+ @mutex = Mutex.new
6
+ @configuration = configuration
7
+ end
8
+
9
+ ##
10
+ # @param callback [Proc, Method, #call]
11
+ # @return [void]
12
+ def add(callback)
13
+ @mutex.synchronize do
14
+ @callbacks.add(callback)
15
+ end
16
+ end
17
+
18
+ ##
19
+ # @param callback [Proc, Method, #call]
20
+ # @return [void]
21
+ def remove(callback)
22
+ @mutex.synchronize do
23
+ @callbacks.delete(callback)
24
+ end
25
+ end
26
+
27
+ ##
28
+ # @param breadcrumb [Breadcrumb]
29
+ # @return [void]
30
+ def call(breadcrumb)
31
+ @callbacks.each do |callback|
32
+ begin
33
+ should_continue = callback.call(breadcrumb)
34
+ rescue StandardError => e
35
+ @configuration.warn("Error occurred in on_breadcrumb callback: '#{e}'")
36
+ @configuration.warn("on_breadcrumb callback stacktrace: #{e.backtrace.inspect}")
37
+ end
38
+
39
+ # only stop if should_continue is explicity 'false' to allow callbacks
40
+ # to return 'nil'
41
+ if should_continue == false
42
+ breadcrumb.ignore!
43
+ break
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,29 @@
1
+ module Bugsnag::Breadcrumbs
2
+ ##
3
+ # Validates a given breadcrumb before it is stored
4
+ class Validator
5
+ ##
6
+ # @param configuration [Bugsnag::Configuration] The current configuration
7
+ def initialize(configuration)
8
+ @configuration = configuration
9
+ end
10
+
11
+ ##
12
+ # Validates a given breadcrumb.
13
+ #
14
+ # @param breadcrumb [Bugsnag::Breadcrumbs::Breadcrumb] the breadcrumb to be validated
15
+ def validate(breadcrumb)
16
+ # Check type is valid, set to manual otherwise
17
+ unless Bugsnag::Breadcrumbs::VALID_BREADCRUMB_TYPES.include?(breadcrumb.type)
18
+ @configuration.debug("Invalid type: #{breadcrumb.type} for breadcrumb: #{breadcrumb.name}, defaulting to #{Bugsnag::Breadcrumbs::MANUAL_BREADCRUMB_TYPE}")
19
+ breadcrumb.type = Bugsnag::Breadcrumbs::MANUAL_BREADCRUMB_TYPE
20
+ end
21
+
22
+ # If auto is true, check type is in enabled_automatic_breadcrumb_types
23
+ return unless breadcrumb.auto && !@configuration.enabled_automatic_breadcrumb_types.include?(breadcrumb.type)
24
+
25
+ @configuration.debug("Automatic breadcrumb of type #{breadcrumb.type} ignored: #{breadcrumb.name}")
26
+ breadcrumb.ignore!
27
+ end
28
+ end
29
+ end
@@ -1,19 +1,105 @@
1
- require 'uri'
2
-
3
1
  module Bugsnag
2
+ # @api private
4
3
  class Cleaner
5
- ENCODING_OPTIONS = {:invalid => :replace, :undef => :replace}.freeze
6
4
  FILTERED = '[FILTERED]'.freeze
7
5
  RECURSION = '[RECURSION]'.freeze
8
6
  OBJECT = '[OBJECT]'.freeze
7
+ RAISED = '[RAISED]'.freeze
8
+ OBJECT_WITH_ID_AND_CLASS = '[OBJECT]: [Class]: %<class_name>s [ID]: %<id>d'.freeze
9
+
10
+ ##
11
+ # @param configuration [Configuration]
12
+ def initialize(configuration)
13
+ @configuration = configuration
14
+ end
9
15
 
10
- def initialize(filters)
11
- @filters = Array(filters)
12
- @deep_filters = @filters.any? {|f| f.kind_of?(Regexp) && f.to_s.include?("\\.".freeze) }
16
+ def clean_object(object)
17
+ @deep_filters = deep_filters?
18
+
19
+ traverse_object(object, {}, nil)
13
20
  end
14
21
 
15
- def clean_object(obj)
16
- traverse_object(obj, {}, nil)
22
+ ##
23
+ # @param url [String]
24
+ # @return [String]
25
+ def clean_url(url)
26
+ return url if @configuration.meta_data_filters.empty? && @configuration.redacted_keys.empty?
27
+ return url unless url.include?('?')
28
+
29
+ begin
30
+ uri = URI(url)
31
+
32
+ if uri.is_a?(URI::MailTo)
33
+ clean_mailto_url(url, uri)
34
+ else
35
+ clean_generic_url(url, uri)
36
+ end
37
+ rescue URI::InvalidURIError
38
+ pre_query_string, _query_string = url.split('?', 2)
39
+
40
+ "#{pre_query_string}?#{FILTERED}"
41
+ rescue StandardError
42
+ FILTERED
43
+ end
44
+ end
45
+
46
+ ##
47
+ # @param key [String, #to_s]
48
+ # @return [Boolean]
49
+ def filters_match?(key)
50
+ str = key.to_s
51
+
52
+ matched = @configuration.meta_data_filters.any? do |filter|
53
+ case filter
54
+ when Regexp
55
+ str.match(filter)
56
+ else
57
+ str.include?(filter.to_s)
58
+ end
59
+ end
60
+
61
+ return true if matched
62
+
63
+ @configuration.redacted_keys.any? do |redaction_pattern|
64
+ case redaction_pattern
65
+ when Regexp
66
+ str.match(redaction_pattern)
67
+ when String
68
+ str.downcase == redaction_pattern.downcase
69
+ end
70
+ end
71
+ end
72
+
73
+ private
74
+
75
+ ##
76
+ # This method calculates whether we need to filter deeply or not; i.e. whether
77
+ # we should match both with and without 'request.params'
78
+ #
79
+ # This is cached on the instance variable '@deep_filters' for performance
80
+ # reasons
81
+ #
82
+ # @return [Boolean]
83
+ def deep_filters?
84
+ is_deep_filter = proc do |filter|
85
+ filter.is_a?(Regexp) && filter.to_s.include?("\\.".freeze)
86
+ end
87
+
88
+ @configuration.meta_data_filters.any?(&is_deep_filter) || @configuration.redacted_keys.any?(&is_deep_filter)
89
+ end
90
+
91
+ def clean_string(str)
92
+ if defined?(str.encoding) && defined?(Encoding::UTF_8)
93
+ if str.encoding == Encoding::UTF_8
94
+ str.valid_encoding? ? str : str.encode('utf-16', invalid: :replace, undef: :replace).encode('utf-8')
95
+ else
96
+ str.encode('utf-8', invalid: :replace, undef: :replace)
97
+ end
98
+ elsif defined?(Iconv)
99
+ Iconv.conv('UTF-8//IGNORE', 'UTF-8', str) || str
100
+ else
101
+ str
102
+ end
17
103
  end
18
104
 
19
105
  def traverse_object(obj, seen, scope)
@@ -28,25 +114,49 @@ module Bugsnag
28
114
  value = case obj
29
115
  when Hash
30
116
  clean_hash = {}
31
- obj.each do |k,v|
32
- if filters_match_deeply?(k, scope)
33
- clean_hash[k] = FILTERED
34
- else
35
- clean_hash[k] = traverse_object(v, seen, [scope, k].compact.join('.'))
117
+ obj.each do |k, v|
118
+ begin
119
+ current_scope = [scope, k].compact.join('.')
120
+
121
+ if filters_match_deeply?(k, current_scope)
122
+ clean_hash[k] = FILTERED
123
+ else
124
+ clean_hash[k] = traverse_object(v, seen, current_scope)
125
+ end
126
+ # If we get an error here, we assume the key needs to be filtered
127
+ # to avoid leaking things we shouldn't. We also remove the key itself
128
+ # because it may cause issues later e.g. when being converted to JSON
129
+ rescue StandardError
130
+ clean_hash[RAISED] = FILTERED
131
+ rescue SystemStackError
132
+ clean_hash[RECURSION] = FILTERED
36
133
  end
37
134
  end
38
135
  clean_hash
39
136
  when Array, Set
40
- obj.map { |el| traverse_object(el, seen, scope) }.compact
137
+ obj.map { |el| traverse_object(el, seen, scope) }
41
138
  when Numeric, TrueClass, FalseClass
42
139
  obj
43
140
  when String
44
141
  clean_string(obj)
45
142
  else
46
- str = obj.to_s
143
+ # guard against objects that raise or blow the stack when stringified
144
+ begin
145
+ str = obj.to_s
146
+ rescue StandardError
147
+ str = RAISED
148
+ rescue SystemStackError
149
+ str = RECURSION
150
+ end
151
+
47
152
  # avoid leaking potentially sensitive data from objects' #inspect output
48
153
  if str =~ /#<.*>/
49
- OBJECT
154
+ # Use id of the object if available
155
+ if obj.respond_to?(:id)
156
+ format(OBJECT_WITH_ID_AND_CLASS, class_name: obj.class, id: obj.id)
157
+ else
158
+ OBJECT
159
+ end
50
160
  else
51
161
  clean_string(str)
52
162
  end
@@ -56,67 +166,68 @@ module Bugsnag
56
166
  value
57
167
  end
58
168
 
59
- def clean_string(str)
60
- if defined?(str.encoding) && defined?(Encoding::UTF_8)
61
- if str.encoding == Encoding::UTF_8
62
- str.valid_encoding? ? str : str.encode('utf-16', ENCODING_OPTIONS).encode('utf-8')
169
+ ##
170
+ # If someone has a Rails filter like /^stuff\.secret/, it won't match
171
+ # "request.params.stuff.secret", so we try it both with and without the
172
+ # "request.params." bit.
173
+ #
174
+ # @param key [String, #to_s]
175
+ # @param scope [String]
176
+ # @return [Boolean]
177
+ def filters_match_deeply?(key, scope)
178
+ return false unless scope_should_be_filtered?(scope)
179
+
180
+ return true if filters_match?(key)
181
+ return false unless @deep_filters
182
+
183
+ return true if filters_match?(scope)
184
+
185
+ @configuration.scopes_to_filter.any? do |scope_to_filter|
186
+ if scope.start_with?("#{scope_to_filter}.request.params.")
187
+ filters_match?(scope.sub("#{scope_to_filter}.request.params.", ''))
63
188
  else
64
- str.encode('utf-8', ENCODING_OPTIONS)
189
+ filters_match?(scope.sub("#{scope_to_filter}.", ''))
65
190
  end
66
- elsif defined?(Iconv)
67
- Iconv.conv('UTF-8//IGNORE', 'UTF-8', str) || str
68
- else
69
- str
70
191
  end
71
192
  end
72
193
 
73
- def self.clean_object_encoding(obj)
74
- new(nil).clean_object(obj)
194
+ ##
195
+ # Should the given scope be filtered?
196
+ #
197
+ # @param scope [String]
198
+ # @return [Boolean]
199
+ def scope_should_be_filtered?(scope)
200
+ @configuration.scopes_to_filter.any? do |scope_to_filter|
201
+ scope.start_with?("#{scope_to_filter}.")
202
+ end
75
203
  end
76
204
 
77
- def clean_url(url)
78
- return url if @filters.empty?
79
-
80
- uri = URI(url)
81
- return url unless uri.query
205
+ def clean_generic_url(original_url, uri)
206
+ return original_url unless uri.query
82
207
 
83
208
  query_params = uri.query.split('&').map { |pair| pair.split('=') }
84
- query_params.map! do |key, val|
85
- if filters_match?(key)
86
- "#{key}=#{FILTERED}"
87
- else
88
- "#{key}=#{val}"
89
- end
90
- end
91
209
 
92
- uri.query = query_params.join('&')
210
+ uri.query = filter_uri_parameter_array(query_params).join('&')
93
211
  uri.to_s
94
212
  end
95
213
 
96
- private
214
+ def clean_mailto_url(original_url, uri)
215
+ return original_url unless uri.headers
97
216
 
98
- def filters_match?(key)
99
- str = key.to_s
217
+ # headers in mailto links can't contain square brackets so we replace
218
+ # filtered parameters with 'FILTERED' instead of '[FILTERED]'
219
+ uri.headers = filter_uri_parameter_array(uri.headers, 'FILTERED').join('&')
220
+ uri.to_s
221
+ end
100
222
 
101
- @filters.any? do |f|
102
- case f
103
- when Regexp
104
- str.match(f)
223
+ def filter_uri_parameter_array(parameters, replacement = FILTERED)
224
+ parameters.map do |key, value|
225
+ if filters_match?(key)
226
+ "#{key}=#{replacement}"
105
227
  else
106
- str.include?(f.to_s)
228
+ "#{key}=#{value}"
107
229
  end
108
230
  end
109
231
  end
110
-
111
- # If someone has a Rails filter like /^stuff\.secret/, it won't match "request.params.stuff.secret",
112
- # so we try it both with and without the "request.params." bit.
113
- def filters_match_deeply?(key, scope)
114
- return true if filters_match?(key)
115
- return false unless @deep_filters
116
-
117
- long = [scope, key].compact.join('.')
118
- short = long.sub(/^request\.params\./, '')
119
- filters_match?(long) || filters_match?(short)
120
- end
121
232
  end
122
233
  end