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.
- checksums.yaml +5 -5
- data/.yardopts +12 -0
- data/CHANGELOG.md +814 -0
- data/README.md +21 -25
- data/VERSION +1 -1
- data/bugsnag.gemspec +19 -8
- data/lib/bugsnag/breadcrumb_type.rb +14 -0
- data/lib/bugsnag/breadcrumbs/breadcrumb.rb +109 -0
- data/lib/bugsnag/breadcrumbs/breadcrumbs.rb +13 -0
- data/lib/bugsnag/breadcrumbs/on_breadcrumb_callback_list.rb +48 -0
- data/lib/bugsnag/breadcrumbs/validator.rb +29 -0
- data/lib/bugsnag/cleaner.rb +170 -59
- data/lib/bugsnag/code_extractor.rb +137 -0
- data/lib/bugsnag/configuration.rb +670 -45
- data/lib/bugsnag/delivery/synchronous.rb +31 -14
- data/lib/bugsnag/delivery/thread_queue.rb +23 -6
- data/lib/bugsnag/delivery.rb +13 -0
- data/lib/bugsnag/endpoint_configuration.rb +11 -0
- data/lib/bugsnag/endpoint_validator.rb +80 -0
- data/lib/bugsnag/error.rb +25 -0
- data/lib/bugsnag/event.rb +5 -0
- data/lib/bugsnag/feature_flag.rb +74 -0
- data/lib/bugsnag/helpers.rb +121 -25
- data/lib/bugsnag/integrations/delayed_job.rb +51 -0
- data/lib/bugsnag/integrations/mailman.rb +43 -0
- data/lib/bugsnag/integrations/mongo.rb +133 -0
- data/lib/bugsnag/integrations/que.rb +53 -0
- data/lib/bugsnag/integrations/rack.rb +83 -0
- data/lib/bugsnag/integrations/rails/active_job.rb +100 -0
- data/lib/bugsnag/{rails → integrations/rails}/active_record_rescue.rb +10 -1
- data/lib/bugsnag/{rails → integrations/rails}/controller_methods.rb +1 -9
- data/lib/bugsnag/integrations/rails/rails_breadcrumbs.rb +115 -0
- data/lib/bugsnag/integrations/railtie.rb +153 -0
- data/lib/bugsnag/integrations/rake.rb +74 -0
- data/lib/bugsnag/integrations/resque.rb +94 -0
- data/lib/bugsnag/integrations/shoryuken.rb +50 -0
- data/lib/bugsnag/integrations/sidekiq.rb +68 -0
- data/lib/bugsnag/meta_data.rb +1 -0
- data/lib/bugsnag/middleware/active_job.rb +18 -0
- data/lib/bugsnag/middleware/breadcrumbs.rb +21 -0
- data/lib/bugsnag/middleware/callbacks.rb +6 -8
- data/lib/bugsnag/middleware/classify_error.rb +50 -0
- data/lib/bugsnag/middleware/clearance_user.rb +33 -0
- data/lib/bugsnag/middleware/delayed_job.rb +93 -0
- data/lib/bugsnag/middleware/discard_error_class.rb +30 -0
- data/lib/bugsnag/middleware/exception_meta_data.rb +42 -0
- data/lib/bugsnag/middleware/ignore_error_class.rb +26 -0
- data/lib/bugsnag/middleware/mailman.rb +6 -4
- data/lib/bugsnag/middleware/rack_request.rb +126 -30
- data/lib/bugsnag/middleware/rails3_request.rb +15 -17
- data/lib/bugsnag/middleware/rake.rb +7 -5
- data/lib/bugsnag/middleware/session_data.rb +25 -0
- data/lib/bugsnag/middleware/sidekiq.rb +9 -4
- data/lib/bugsnag/middleware/suggestion_data.rb +34 -0
- data/lib/bugsnag/middleware/warden_user.rb +11 -6
- data/lib/bugsnag/middleware_stack.rb +62 -9
- data/lib/bugsnag/on_error_callbacks.rb +33 -0
- data/lib/bugsnag/report.rb +516 -0
- data/lib/bugsnag/session_tracker.rb +182 -0
- data/lib/bugsnag/stacktrace.rb +82 -0
- data/lib/bugsnag/tasks/bugsnag.rake +2 -70
- data/lib/bugsnag/utility/circular_buffer.rb +62 -0
- data/lib/bugsnag/utility/duplicator.rb +124 -0
- data/lib/bugsnag/utility/feature_data_store.rb +41 -0
- data/lib/bugsnag/utility/feature_flag_delegate.rb +89 -0
- data/lib/bugsnag/utility/metadata_delegate.rb +102 -0
- data/lib/bugsnag.rb +528 -80
- metadata +61 -123
- data/.document +0 -5
- data/.gitignore +0 -52
- data/.rspec +0 -3
- data/.travis.yml +0 -14
- data/CONTRIBUTING.md +0 -47
- data/Gemfile +0 -2
- data/Rakefile +0 -29
- data/lib/bugsnag/capistrano.rb +0 -7
- data/lib/bugsnag/capistrano2.rb +0 -32
- data/lib/bugsnag/delay/resque.rb +0 -21
- data/lib/bugsnag/delayed_job.rb +0 -57
- data/lib/bugsnag/deploy.rb +0 -34
- data/lib/bugsnag/mailman.rb +0 -28
- data/lib/bugsnag/middleware/rails2_request.rb +0 -52
- data/lib/bugsnag/notification.rb +0 -459
- data/lib/bugsnag/rack.rb +0 -53
- data/lib/bugsnag/rails/action_controller_rescue.rb +0 -62
- data/lib/bugsnag/rails.rb +0 -66
- data/lib/bugsnag/railtie.rb +0 -80
- data/lib/bugsnag/rake.rb +0 -25
- data/lib/bugsnag/resque.rb +0 -40
- data/lib/bugsnag/sidekiq.rb +0 -42
- data/lib/bugsnag/tasks/bugsnag.cap +0 -48
- data/rails/init.rb +0 -7
- data/spec/cleaner_spec.rb +0 -138
- data/spec/code_spec.rb +0 -86
- data/spec/fixtures/crashes/end_of_file.rb +0 -9
- data/spec/fixtures/crashes/short_file.rb +0 -1
- data/spec/fixtures/crashes/start_of_file.rb +0 -9
- data/spec/fixtures/middleware/internal_info_setter.rb +0 -11
- data/spec/fixtures/middleware/public_info_setter.rb +0 -11
- data/spec/fixtures/tasks/Rakefile +0 -15
- data/spec/helper_spec.rb +0 -163
- data/spec/integration_spec.rb +0 -132
- data/spec/middleware_spec.rb +0 -181
- data/spec/notification_spec.rb +0 -877
- data/spec/rack_spec.rb +0 -56
- data/spec/spec_helper.rb +0 -53
data/README.md
CHANGED
@@ -1,50 +1,46 @@
|
|
1
|
-
# Bugsnag exception reporter for Ruby
|
2
|
-
[](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
|
-
* [
|
22
|
-
* [
|
23
|
-
* [
|
24
|
-
* [
|
25
|
-
* [
|
26
|
-
*
|
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
|
-
* [
|
33
|
-
* [
|
34
|
-
* [
|
35
|
-
* [
|
36
|
-
* [
|
37
|
-
*
|
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
|
-
|
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 = "
|
10
|
+
s.homepage = "https://github.com/bugsnag/bugsnag-ruby"
|
11
11
|
s.licenses = ["MIT"]
|
12
12
|
|
13
|
-
s.files = `git ls-files`.split("\
|
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
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
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
|
data/lib/bugsnag/cleaner.rb
CHANGED
@@ -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
|
11
|
-
@
|
12
|
-
|
16
|
+
def clean_object(object)
|
17
|
+
@deep_filters = deep_filters?
|
18
|
+
|
19
|
+
traverse_object(object, {}, nil)
|
13
20
|
end
|
14
21
|
|
15
|
-
|
16
|
-
|
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
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
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) }
|
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
|
-
|
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
|
-
|
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
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
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
|
-
|
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
|
-
|
74
|
-
|
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
|
78
|
-
return
|
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
|
-
|
214
|
+
def clean_mailto_url(original_url, uri)
|
215
|
+
return original_url unless uri.headers
|
97
216
|
|
98
|
-
|
99
|
-
|
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
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
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
|
-
|
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
|