sentry-raven 2.13.0 → 3.1.0
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 +4 -4
- data/.craft.yml +15 -0
- data/.github/ISSUE_TEMPLATE/bug_report.md +32 -0
- data/.github/pull_request_template.md +16 -0
- data/.github/workflows/test.yml +92 -0
- data/.github/workflows/zeus_upload.yml +32 -0
- data/.gitignore +3 -0
- data/.rubocop.yml +50 -12
- data/.scripts/bump-version.sh +9 -0
- data/{changelog.md → CHANGELOG.md} +155 -1
- data/CONTRIBUTING.md +71 -0
- data/Gemfile +20 -25
- data/README.md +26 -16
- data/lib/raven/backtrace.rb +9 -5
- data/lib/raven/base.rb +6 -2
- data/lib/raven/breadcrumbs/{activesupport.rb → active_support_logger.rb} +9 -3
- data/lib/raven/breadcrumbs/logger.rb +2 -92
- data/lib/raven/breadcrumbs/sentry_logger.rb +73 -0
- data/lib/raven/breadcrumbs.rb +1 -1
- data/lib/raven/cli.rb +10 -21
- data/lib/raven/client.rb +9 -4
- data/lib/raven/configuration.rb +86 -10
- data/lib/raven/context.rb +13 -8
- data/lib/raven/core_ext/object/deep_dup.rb +57 -0
- data/lib/raven/core_ext/object/duplicable.rb +153 -0
- data/lib/raven/event.rb +27 -15
- data/lib/raven/helpers/deprecation_helper.rb +17 -0
- data/lib/raven/instance.rb +9 -4
- data/lib/raven/integrations/delayed_job.rb +13 -14
- data/lib/raven/integrations/rack-timeout.rb +7 -4
- data/lib/raven/integrations/rack.rb +4 -3
- data/lib/raven/integrations/rails/active_job.rb +6 -4
- data/lib/raven/integrations/rails/backtrace_cleaner.rb +29 -0
- data/lib/raven/integrations/rails/overrides/debug_exceptions_catcher.rb +2 -2
- data/lib/raven/integrations/rails.rb +13 -3
- data/lib/raven/integrations/sidekiq/cleanup_middleware.rb +13 -0
- data/lib/raven/integrations/sidekiq/context_filter.rb +42 -0
- data/lib/raven/integrations/sidekiq/error_handler.rb +38 -0
- data/lib/raven/integrations/sidekiq.rb +4 -78
- data/lib/raven/interface.rb +2 -2
- data/lib/raven/interfaces/stack_trace.rb +1 -1
- data/lib/raven/linecache.rb +5 -2
- data/lib/raven/logger.rb +3 -2
- data/lib/raven/processor/cookies.rb +16 -6
- data/lib/raven/processor/post_data.rb +2 -0
- data/lib/raven/processor/removecircularreferences.rb +3 -1
- data/lib/raven/processor/sanitizedata.rb +65 -17
- data/lib/raven/processor/utf8conversion.rb +2 -0
- data/lib/raven/transports/http.rb +5 -5
- data/lib/raven/transports.rb +4 -0
- data/lib/raven/utils/exception_cause_chain.rb +1 -0
- data/lib/raven/utils/real_ip.rb +1 -1
- data/lib/raven/version.rb +2 -2
- data/lib/sentry-raven-without-integrations.rb +6 -1
- data/lib/sentry_raven_without_integrations.rb +1 -0
- data/sentry-raven.gemspec +2 -2
- metadata +21 -12
- data/.travis.yml +0 -47
@@ -8,19 +8,18 @@ module Delayed
|
|
8
8
|
begin
|
9
9
|
# Forward the call to the next callback in the callback chain
|
10
10
|
block.call(job, *args)
|
11
|
-
|
12
|
-
rescue Exception => exception
|
11
|
+
rescue Exception => e
|
13
12
|
# Log error to Sentry
|
14
13
|
extra = {
|
15
14
|
:delayed_job => {
|
16
|
-
:id
|
17
|
-
:priority
|
18
|
-
:attempts
|
19
|
-
:run_at
|
20
|
-
:locked_at
|
21
|
-
:locked_by
|
22
|
-
:queue
|
23
|
-
:created_at
|
15
|
+
:id => job.id.to_s,
|
16
|
+
:priority => job.priority,
|
17
|
+
:attempts => job.attempts,
|
18
|
+
:run_at => job.run_at,
|
19
|
+
:locked_at => job.locked_at,
|
20
|
+
:locked_by => job.locked_by,
|
21
|
+
:queue => job.queue,
|
22
|
+
:created_at => job.created_at
|
24
23
|
}
|
25
24
|
}
|
26
25
|
# last_error can be nil
|
@@ -32,16 +31,16 @@ module Delayed
|
|
32
31
|
if job.respond_to?('payload_object') && job.payload_object.respond_to?('job_data')
|
33
32
|
extra[:active_job] = job.payload_object.job_data
|
34
33
|
end
|
35
|
-
::Raven.capture_exception(
|
36
|
-
:logger
|
37
|
-
:tags
|
34
|
+
::Raven.capture_exception(e,
|
35
|
+
:logger => 'delayed_job',
|
36
|
+
:tags => {
|
38
37
|
:delayed_job_queue => job.queue,
|
39
38
|
:delayed_job_id => job.id.to_s
|
40
39
|
},
|
41
40
|
:extra => extra)
|
42
41
|
|
43
42
|
# Make sure we propagate the failure!
|
44
|
-
raise
|
43
|
+
raise e
|
45
44
|
ensure
|
46
45
|
::Raven::Context.clear!
|
47
46
|
::Raven::BreadcrumbBuffer.clear!
|
@@ -10,10 +10,13 @@ module RackTimeoutExtensions
|
|
10
10
|
# Only rack-timeout 0.3.0+ provides the request environment, but we can't
|
11
11
|
# gate this based on a gem version constant because rack-timeout does
|
12
12
|
# not provide one.
|
13
|
-
|
13
|
+
if defined?(env)
|
14
|
+
{ :fingerprint => ["{{ default }}", env["REQUEST_URI"]] }
|
15
|
+
else
|
16
|
+
{}
|
17
|
+
end
|
14
18
|
end
|
15
19
|
end
|
16
20
|
|
17
|
-
|
18
|
-
Rack::Timeout::
|
19
|
-
Rack::Timeout::RequestTimeoutException.__send__(:include, RackTimeoutExtensions)
|
21
|
+
Rack::Timeout::Error.include(RackTimeoutExtensions)
|
22
|
+
Rack::Timeout::RequestTimeoutException.include(RackTimeoutExtensions)
|
@@ -92,8 +92,8 @@ module Raven
|
|
92
92
|
request.body.rewind
|
93
93
|
data
|
94
94
|
end
|
95
|
-
rescue IOError =>
|
96
|
-
|
95
|
+
rescue IOError => e
|
96
|
+
e.message
|
97
97
|
end
|
98
98
|
|
99
99
|
def format_headers_for_sentry(env_hash)
|
@@ -112,8 +112,9 @@ module Raven
|
|
112
112
|
next if key == 'HTTP_COOKIE' # Cookies don't go here, they go somewhere else
|
113
113
|
|
114
114
|
next unless key.start_with?('HTTP_') || %w(CONTENT_TYPE CONTENT_LENGTH).include?(key)
|
115
|
+
|
115
116
|
# Rack stores headers as HTTP_WHAT_EVER, we need What-Ever
|
116
|
-
key = key.
|
117
|
+
key = key.sub(/^HTTP_/, "")
|
117
118
|
key = key.split('_').map(&:capitalize).join('-')
|
118
119
|
memo[key] = value
|
119
120
|
rescue StandardError => e
|
@@ -20,10 +20,12 @@ module Raven
|
|
20
20
|
|
21
21
|
def capture_and_reraise_with_sentry(job, block)
|
22
22
|
block.call
|
23
|
-
rescue Exception =>
|
24
|
-
|
25
|
-
|
26
|
-
|
23
|
+
rescue Exception => e # rubocop:disable Lint/RescueException
|
24
|
+
rescue_handler_result = rescue_with_handler(e)
|
25
|
+
return rescue_handler_result if rescue_handler_result
|
26
|
+
|
27
|
+
Raven.capture_exception(e, :extra => raven_context(job))
|
28
|
+
raise e
|
27
29
|
ensure
|
28
30
|
Context.clear!
|
29
31
|
BreadcrumbBuffer.clear!
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require "active_support/backtrace_cleaner"
|
2
|
+
require "active_support/core_ext/string/access"
|
3
|
+
|
4
|
+
module Raven
|
5
|
+
class Rails
|
6
|
+
class BacktraceCleaner < ActiveSupport::BacktraceCleaner
|
7
|
+
APP_DIRS_PATTERN = /\A(?:\.\/)?(?:app|config|lib|test|\(\w*\))/.freeze
|
8
|
+
RENDER_TEMPLATE_PATTERN = /:in `.*_\w+_{2,3}\d+_\d+'/.freeze
|
9
|
+
|
10
|
+
def initialize
|
11
|
+
super
|
12
|
+
# we don't want any default silencers because they're too aggressive
|
13
|
+
remove_silencers!
|
14
|
+
|
15
|
+
@root = "#{Raven.configuration.project_root}/"
|
16
|
+
add_filter do |line|
|
17
|
+
line.start_with?(@root) ? line.from(@root.size) : line
|
18
|
+
end
|
19
|
+
add_filter do |line|
|
20
|
+
if line =~ RENDER_TEMPLATE_PATTERN
|
21
|
+
line.sub(RENDER_TEMPLATE_PATTERN, "")
|
22
|
+
else
|
23
|
+
line
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -6,7 +6,7 @@ module Raven
|
|
6
6
|
begin
|
7
7
|
env = env_or_request.respond_to?(:env) ? env_or_request.env : env_or_request
|
8
8
|
Raven::Rack.capture_exception(exception, env)
|
9
|
-
rescue
|
9
|
+
rescue
|
10
10
|
end
|
11
11
|
super
|
12
12
|
end
|
@@ -21,7 +21,7 @@ module Raven
|
|
21
21
|
begin
|
22
22
|
env = env_or_request.respond_to?(:env) ? env_or_request.env : env_or_request
|
23
23
|
Raven::Rack.capture_exception(exception, env)
|
24
|
-
rescue
|
24
|
+
rescue
|
25
25
|
end
|
26
26
|
render_exception_without_raven(env_or_request, exception)
|
27
27
|
end
|
@@ -5,6 +5,8 @@ module Raven
|
|
5
5
|
require 'raven/integrations/rails/overrides/streaming_reporter'
|
6
6
|
require 'raven/integrations/rails/controller_methods'
|
7
7
|
require 'raven/integrations/rails/controller_transaction'
|
8
|
+
require 'raven/integrations/rails/backtrace_cleaner'
|
9
|
+
require 'raven/integrations/rack'
|
8
10
|
|
9
11
|
initializer "raven.use_rack_middleware" do |app|
|
10
12
|
app.config.middleware.insert 0, Raven::Rack
|
@@ -36,12 +38,20 @@ module Raven
|
|
36
38
|
|
37
39
|
config.before_initialize do
|
38
40
|
Raven.configuration.logger = ::Rails.logger
|
41
|
+
|
42
|
+
backtrace_cleaner = Raven::Rails::BacktraceCleaner.new
|
43
|
+
|
44
|
+
Raven.configuration.backtrace_cleanup_callback = lambda do |backtrace|
|
45
|
+
backtrace_cleaner.clean(backtrace)
|
46
|
+
end
|
39
47
|
end
|
40
48
|
|
41
49
|
config.after_initialize do
|
42
|
-
if Raven.configuration.
|
43
|
-
|
44
|
-
|
50
|
+
if Raven.configuration.breadcrumbs_logger.include?(:active_support_logger) ||
|
51
|
+
Raven.configuration.rails_activesupport_breadcrumbs
|
52
|
+
|
53
|
+
require 'raven/breadcrumbs/active_support_logger'
|
54
|
+
Raven::Breadcrumbs::ActiveSupportLogger.inject
|
45
55
|
end
|
46
56
|
|
47
57
|
if Raven.configuration.rails_report_rescued_exceptions
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module Raven
|
2
|
+
module Sidekiq
|
3
|
+
class CleanupMiddleware
|
4
|
+
def call(_worker, job, queue)
|
5
|
+
Raven.context.transaction.push "Sidekiq/#{job['class']}"
|
6
|
+
Raven.extra_context(:sidekiq => job.merge("queue" => queue))
|
7
|
+
yield
|
8
|
+
Context.clear!
|
9
|
+
BreadcrumbBuffer.clear!
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
module Raven
|
2
|
+
module Sidekiq
|
3
|
+
module ContextFilter
|
4
|
+
class << self
|
5
|
+
ACTIVEJOB_RESERVED_PREFIX = "_aj_".freeze
|
6
|
+
HAS_GLOBALID = const_defined?('GlobalID')
|
7
|
+
|
8
|
+
# Once an ActiveJob is queued, ActiveRecord references get serialized into
|
9
|
+
# some internal reserved keys, such as _aj_globalid.
|
10
|
+
#
|
11
|
+
# The problem is, if this job in turn gets queued back into ActiveJob with
|
12
|
+
# these magic reserved keys, ActiveJob will throw up and error. We want to
|
13
|
+
# capture these and mutate the keys so we can sanely report it.
|
14
|
+
def filter_context(context)
|
15
|
+
case context
|
16
|
+
when Array
|
17
|
+
context.map { |arg| filter_context(arg) }
|
18
|
+
when Hash
|
19
|
+
Hash[context.map { |key, value| filter_context_hash(key, value) }]
|
20
|
+
else
|
21
|
+
format_globalid(context)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def filter_context_hash(key, value)
|
28
|
+
(key = key[3..-1]) if key [0..3] == ACTIVEJOB_RESERVED_PREFIX
|
29
|
+
[key, filter_context(value)]
|
30
|
+
end
|
31
|
+
|
32
|
+
def format_globalid(context)
|
33
|
+
if HAS_GLOBALID && context.is_a?(GlobalID)
|
34
|
+
context.to_s
|
35
|
+
else
|
36
|
+
context
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'raven/integrations/sidekiq/context_filter'
|
2
|
+
|
3
|
+
module Raven
|
4
|
+
module Sidekiq
|
5
|
+
class ErrorHandler
|
6
|
+
SIDEKIQ_NAME = "Sidekiq".freeze
|
7
|
+
|
8
|
+
def call(ex, context)
|
9
|
+
context = ContextFilter.filter_context(context)
|
10
|
+
Raven.context.transaction.push transaction_from_context(context)
|
11
|
+
Raven.capture_exception(
|
12
|
+
ex,
|
13
|
+
:message => ex.message,
|
14
|
+
:extra => { :sidekiq => context }
|
15
|
+
)
|
16
|
+
Context.clear!
|
17
|
+
BreadcrumbBuffer.clear!
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
# this will change in the future:
|
23
|
+
# https://github.com/mperham/sidekiq/pull/3161
|
24
|
+
def transaction_from_context(context)
|
25
|
+
classname = (context["wrapped"] || context["class"] ||
|
26
|
+
(context[:job] && (context[:job]["wrapped"] || context[:job]["class"]))
|
27
|
+
)
|
28
|
+
if classname
|
29
|
+
"#{SIDEKIQ_NAME}/#{classname}"
|
30
|
+
elsif context[:event]
|
31
|
+
"#{SIDEKIQ_NAME}/#{context[:event]}"
|
32
|
+
else
|
33
|
+
SIDEKIQ_NAME
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -1,87 +1,13 @@
|
|
1
1
|
require 'time'
|
2
2
|
require 'sidekiq'
|
3
|
-
|
4
|
-
|
5
|
-
class SidekiqCleanupMiddleware
|
6
|
-
def call(_worker, job, queue)
|
7
|
-
Raven.context.transaction.push "Sidekiq/#{job['class']}"
|
8
|
-
Raven.extra_context(:sidekiq => job.merge("queue" => queue))
|
9
|
-
yield
|
10
|
-
Context.clear!
|
11
|
-
BreadcrumbBuffer.clear!
|
12
|
-
end
|
13
|
-
end
|
14
|
-
|
15
|
-
class SidekiqErrorHandler
|
16
|
-
ACTIVEJOB_RESERVED_PREFIX = "_aj_".freeze
|
17
|
-
HAS_GLOBALID = const_defined?('GlobalID')
|
18
|
-
|
19
|
-
def call(ex, context)
|
20
|
-
context = filter_context(context)
|
21
|
-
Raven.context.transaction.push transaction_from_context(context)
|
22
|
-
Raven.capture_exception(
|
23
|
-
ex,
|
24
|
-
:message => ex.message,
|
25
|
-
:extra => { :sidekiq => context }
|
26
|
-
)
|
27
|
-
Context.clear!
|
28
|
-
BreadcrumbBuffer.clear!
|
29
|
-
end
|
30
|
-
|
31
|
-
private
|
32
|
-
|
33
|
-
# Once an ActiveJob is queued, ActiveRecord references get serialized into
|
34
|
-
# some internal reserved keys, such as _aj_globalid.
|
35
|
-
#
|
36
|
-
# The problem is, if this job in turn gets queued back into ActiveJob with
|
37
|
-
# these magic reserved keys, ActiveJob will throw up and error. We want to
|
38
|
-
# capture these and mutate the keys so we can sanely report it.
|
39
|
-
def filter_context(context)
|
40
|
-
case context
|
41
|
-
when Array
|
42
|
-
context.map { |arg| filter_context(arg) }
|
43
|
-
when Hash
|
44
|
-
Hash[context.map { |key, value| filter_context_hash(key, value) }]
|
45
|
-
else
|
46
|
-
format_globalid(context)
|
47
|
-
end
|
48
|
-
end
|
49
|
-
|
50
|
-
def filter_context_hash(key, value)
|
51
|
-
(key = key[3..-1]) if key [0..3] == ACTIVEJOB_RESERVED_PREFIX
|
52
|
-
[key, filter_context(value)]
|
53
|
-
end
|
54
|
-
|
55
|
-
# this will change in the future:
|
56
|
-
# https://github.com/mperham/sidekiq/pull/3161
|
57
|
-
def transaction_from_context(context)
|
58
|
-
classname = (context["wrapped"] || context["class"] ||
|
59
|
-
(context[:job] && (context[:job]["wrapped"] || context[:job]["class"]))
|
60
|
-
)
|
61
|
-
if classname
|
62
|
-
"Sidekiq/#{classname}"
|
63
|
-
elsif context[:event]
|
64
|
-
"Sidekiq/#{context[:event]}"
|
65
|
-
else
|
66
|
-
"Sidekiq"
|
67
|
-
end
|
68
|
-
end
|
69
|
-
|
70
|
-
def format_globalid(context)
|
71
|
-
if HAS_GLOBALID && context.is_a?(GlobalID)
|
72
|
-
context.to_s
|
73
|
-
else
|
74
|
-
context
|
75
|
-
end
|
76
|
-
end
|
77
|
-
end
|
78
|
-
end
|
3
|
+
require 'raven/integrations/sidekiq/cleanup_middleware'
|
4
|
+
require 'raven/integrations/sidekiq/error_handler'
|
79
5
|
|
80
6
|
if Sidekiq::VERSION > '3'
|
81
7
|
Sidekiq.configure_server do |config|
|
82
|
-
config.error_handlers << Raven::
|
8
|
+
config.error_handlers << Raven::Sidekiq::ErrorHandler.new
|
83
9
|
config.server_middleware do |chain|
|
84
|
-
chain.add Raven::
|
10
|
+
chain.add Raven::Sidekiq::CleanupMiddleware
|
85
11
|
end
|
86
12
|
end
|
87
13
|
end
|
data/lib/raven/interface.rb
CHANGED
data/lib/raven/linecache.rb
CHANGED
@@ -10,6 +10,7 @@ module Raven
|
|
10
10
|
# line should be the line requested by lineno. See specs for more information.
|
11
11
|
def get_file_context(filename, lineno, context)
|
12
12
|
return nil, nil, nil unless valid_path?(filename)
|
13
|
+
|
13
14
|
lines = Array.new(2 * context + 1) do |i|
|
14
15
|
getline(filename, lineno - context + i)
|
15
16
|
end
|
@@ -26,15 +27,17 @@ module Raven
|
|
26
27
|
def getlines(path)
|
27
28
|
@cache[path] ||= begin
|
28
29
|
IO.readlines(path)
|
29
|
-
|
30
|
-
|
30
|
+
rescue
|
31
|
+
nil
|
31
32
|
end
|
32
33
|
end
|
33
34
|
|
34
35
|
def getline(path, n)
|
35
36
|
return nil if n < 1
|
37
|
+
|
36
38
|
lines = getlines(path)
|
37
39
|
return nil if lines.nil?
|
40
|
+
|
38
41
|
lines[n - 1]
|
39
42
|
end
|
40
43
|
end
|
data/lib/raven/logger.rb
CHANGED
@@ -10,17 +10,27 @@ module Raven
|
|
10
10
|
private
|
11
11
|
|
12
12
|
def process_if_symbol_keys(data)
|
13
|
-
|
13
|
+
if cookies = data.dig(:request, :cookies)
|
14
|
+
data[:request][:cookies] = generate_masked_cookies(cookies)
|
15
|
+
end
|
14
16
|
|
15
|
-
|
16
|
-
|
17
|
+
if cookies_header = data[:request][:headers]["Cookie"]
|
18
|
+
data[:request][:headers]["Cookie"] = generate_masked_cookies(cookies_header)
|
19
|
+
end
|
17
20
|
end
|
18
21
|
|
19
22
|
def process_if_string_keys(data)
|
20
|
-
|
23
|
+
if cookies = data.dig("request", "cookies")
|
24
|
+
data["request"]["cookies"] = generate_masked_cookies(cookies)
|
25
|
+
end
|
21
26
|
|
22
|
-
|
23
|
-
|
27
|
+
if cookies_header = data.dig("request", "headers", "Cookie")
|
28
|
+
data["request"]["headers"]["Cookie"] = generate_masked_cookies(cookies_header)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def generate_masked_cookies(cookies)
|
33
|
+
cookies.merge(cookies) { STRING_MASK } if cookies.respond_to?(:merge)
|
24
34
|
end
|
25
35
|
end
|
26
36
|
end
|
@@ -11,11 +11,13 @@ module Raven
|
|
11
11
|
|
12
12
|
def process_if_symbol_keys(data)
|
13
13
|
return unless data[:request][:method] == "POST"
|
14
|
+
|
14
15
|
data[:request][:data] = STRING_MASK
|
15
16
|
end
|
16
17
|
|
17
18
|
def process_if_string_keys(data)
|
18
19
|
return unless data["request"]["method"] == "POST"
|
20
|
+
|
19
21
|
data["request"]["data"] = STRING_MASK
|
20
22
|
end
|
21
23
|
end
|
@@ -1,7 +1,9 @@
|
|
1
1
|
module Raven
|
2
2
|
class Processor::RemoveCircularReferences < Processor
|
3
|
+
ELISION_STRING = "(...)".freeze
|
3
4
|
def process(value, visited = [])
|
4
|
-
return
|
5
|
+
return ELISION_STRING if visited.include?(value.__id__)
|
6
|
+
|
5
7
|
visited << value.__id__ if value.is_a?(Array) || value.is_a?(Hash)
|
6
8
|
|
7
9
|
case value
|
@@ -1,10 +1,11 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
|
2
3
|
require 'json'
|
3
4
|
|
4
5
|
module Raven
|
5
6
|
class Processor::SanitizeData < Processor
|
6
7
|
DEFAULT_FIELDS = %w(authorization password passwd secret ssn social(.*)?sec).freeze
|
7
|
-
CREDIT_CARD_RE = /\b(?:3[47]\d|(?:4\d|5[1-5]|65)\d{2}|6011)\d{12}\b
|
8
|
+
CREDIT_CARD_RE = /\b(?:3[47]\d|(?:4\d|5[1-5]|65)\d{2}|6011)\d{12}\b/.freeze
|
8
9
|
QUERY_STRING = ['query_string', :query_string].freeze
|
9
10
|
JSON_STARTS_WITH = ["[", "{"].freeze
|
10
11
|
|
@@ -20,22 +21,13 @@ module Raven
|
|
20
21
|
def process(value, key = nil)
|
21
22
|
case value
|
22
23
|
when Hash
|
23
|
-
|
24
|
+
sanitize_hash_value(key, value)
|
24
25
|
when Array
|
25
|
-
|
26
|
+
sanitize_array_value(key, value)
|
26
27
|
when Integer
|
27
28
|
matches_regexes?(key, value.to_s) ? INT_MASK : value
|
28
29
|
when String
|
29
|
-
|
30
|
-
# if this string is actually a json obj, convert and sanitize
|
31
|
-
process(json).to_json
|
32
|
-
elsif matches_regexes?(key, value)
|
33
|
-
STRING_MASK
|
34
|
-
elsif QUERY_STRING.include?(key)
|
35
|
-
sanitize_query_string(value)
|
36
|
-
else
|
37
|
-
value
|
38
|
-
end
|
30
|
+
sanitize_string_value(key, value)
|
39
31
|
else
|
40
32
|
value
|
41
33
|
end
|
@@ -49,6 +41,39 @@ module Raven
|
|
49
41
|
@utf8_processor ||= Processor::UTF8Conversion.new
|
50
42
|
end
|
51
43
|
|
44
|
+
def sanitize_hash_value(key, value)
|
45
|
+
if key =~ sensitive_fields
|
46
|
+
STRING_MASK
|
47
|
+
elsif value.frozen?
|
48
|
+
value.merge(value) { |k, v| process v, k }
|
49
|
+
else
|
50
|
+
value.merge!(value) { |k, v| process v, k }
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def sanitize_array_value(key, value)
|
55
|
+
if value.frozen?
|
56
|
+
value.map { |v| process v, key }
|
57
|
+
else
|
58
|
+
value.map! { |v| process v, key }
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def sanitize_string_value(key, value)
|
63
|
+
if value =~ sensitive_fields && (json = parse_json_or_nil(value))
|
64
|
+
# if this string is actually a json obj, convert and sanitize
|
65
|
+
process(json).to_json
|
66
|
+
elsif matches_regexes?(key, value)
|
67
|
+
STRING_MASK
|
68
|
+
elsif QUERY_STRING.include?(key)
|
69
|
+
sanitize_query_string(value)
|
70
|
+
elsif value =~ sensitive_fields
|
71
|
+
sanitize_sensitive_string_content(value)
|
72
|
+
else
|
73
|
+
value
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
52
77
|
def sanitize_query_string(query_string)
|
53
78
|
query_hash = CGI.parse(query_string)
|
54
79
|
sanitized = utf8_processor.process(query_hash)
|
@@ -56,16 +81,38 @@ module Raven
|
|
56
81
|
URI.encode_www_form(processed_query_hash)
|
57
82
|
end
|
58
83
|
|
84
|
+
# this scrubs some sensitive info from the string content. for example:
|
85
|
+
#
|
86
|
+
# ```
|
87
|
+
# unexpected token at '{
|
88
|
+
# "role": "admin","password": "Abc@123","foo": "bar"
|
89
|
+
# }'
|
90
|
+
# ```
|
91
|
+
#
|
92
|
+
# will become
|
93
|
+
#
|
94
|
+
# ```
|
95
|
+
# unexpected token at '{
|
96
|
+
# "role": "admin","password": *******,"foo": "bar"
|
97
|
+
# }'
|
98
|
+
# ```
|
99
|
+
#
|
100
|
+
# it's particularly useful in hash or param-parsing related errors
|
101
|
+
def sanitize_sensitive_string_content(value)
|
102
|
+
value.gsub(/(#{sensitive_fields}['":]\s?(:|=>)?\s?)(".*?"|'.*?')/, '\1' + STRING_MASK)
|
103
|
+
end
|
104
|
+
|
59
105
|
def matches_regexes?(k, v)
|
60
106
|
(sanitize_credit_cards && v =~ CREDIT_CARD_RE) ||
|
61
|
-
k =~
|
107
|
+
k =~ sensitive_fields
|
62
108
|
end
|
63
109
|
|
64
|
-
def
|
65
|
-
return @
|
110
|
+
def sensitive_fields
|
111
|
+
return @sensitive_fields if instance_variable_defined?(:@sensitive_fields)
|
112
|
+
|
66
113
|
fields = DEFAULT_FIELDS | sanitize_fields
|
67
114
|
fields -= sanitize_fields_excluded
|
68
|
-
@
|
115
|
+
@sensitive_fields = /#{fields.map do |f|
|
69
116
|
use_boundary?(f) ? "\\b#{f}\\b" : f
|
70
117
|
end.join("|")}/i
|
71
118
|
end
|
@@ -80,6 +127,7 @@ module Raven
|
|
80
127
|
|
81
128
|
def parse_json_or_nil(string)
|
82
129
|
return unless string.start_with?(*JSON_STARTS_WITH)
|
130
|
+
|
83
131
|
JSON.parse(string)
|
84
132
|
rescue JSON::ParserError, NoMethodError
|
85
133
|
nil
|
@@ -14,6 +14,7 @@ module Raven
|
|
14
14
|
!value.frozen? ? value.map! { |v| process v } : value.map { |v| process v }
|
15
15
|
when Exception
|
16
16
|
return value if value.message.valid_encoding?
|
17
|
+
|
17
18
|
clean_exc = value.class.new(remove_invalid_bytes(value.message))
|
18
19
|
clean_exc.set_backtrace(value.backtrace)
|
19
20
|
clean_exc
|
@@ -27,6 +28,7 @@ module Raven
|
|
27
28
|
value.force_encoding(Encoding::UTF_8)
|
28
29
|
end
|
29
30
|
return value if value.valid_encoding?
|
31
|
+
|
30
32
|
remove_invalid_bytes(value)
|
31
33
|
else
|
32
34
|
value
|