sentry-raven 2.1.3 → 3.1.2

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 (66) hide show
  1. checksums.yaml +5 -5
  2. data/.craft.yml +19 -0
  3. data/.scripts/bump-version.rb +5 -0
  4. data/CHANGELOG.md +703 -0
  5. data/Gemfile +37 -0
  6. data/Makefile +3 -0
  7. data/README.md +116 -18
  8. data/Rakefile +30 -0
  9. data/exe/raven +32 -0
  10. data/lib/raven/backtrace.rb +16 -6
  11. data/lib/raven/base.rb +17 -4
  12. data/lib/raven/breadcrumbs/{activesupport.rb → active_support_logger.rb} +9 -3
  13. data/lib/raven/breadcrumbs/logger.rb +2 -92
  14. data/lib/raven/breadcrumbs/sentry_logger.rb +73 -0
  15. data/lib/raven/breadcrumbs.rb +3 -1
  16. data/lib/raven/cli.rb +31 -43
  17. data/lib/raven/client.rb +39 -17
  18. data/lib/raven/configuration.rb +277 -37
  19. data/lib/raven/context.rb +17 -11
  20. data/lib/raven/core_ext/object/deep_dup.rb +57 -0
  21. data/lib/raven/core_ext/object/duplicable.rb +153 -0
  22. data/lib/raven/event.rb +172 -233
  23. data/lib/raven/helpers/deprecation_helper.rb +17 -0
  24. data/lib/raven/instance.rb +51 -25
  25. data/lib/raven/integrations/delayed_job.rb +18 -18
  26. data/lib/raven/integrations/rack-timeout.rb +11 -5
  27. data/lib/raven/integrations/rack.rb +36 -19
  28. data/lib/raven/integrations/rails/active_job.rb +52 -20
  29. data/lib/raven/integrations/rails/backtrace_cleaner.rb +29 -0
  30. data/lib/raven/integrations/rails/controller_transaction.rb +13 -0
  31. data/lib/raven/integrations/rails/overrides/debug_exceptions_catcher.rb +2 -2
  32. data/lib/raven/integrations/rails.rb +24 -8
  33. data/lib/raven/integrations/rake.rb +6 -1
  34. data/lib/raven/integrations/sidekiq/cleanup_middleware.rb +13 -0
  35. data/lib/raven/integrations/sidekiq/error_handler.rb +38 -0
  36. data/lib/raven/integrations/sidekiq.rb +6 -57
  37. data/lib/raven/interface.rb +2 -2
  38. data/lib/raven/interfaces/exception.rb +0 -2
  39. data/lib/raven/interfaces/http.rb +0 -2
  40. data/lib/raven/interfaces/message.rb +1 -1
  41. data/lib/raven/interfaces/single_exception.rb +0 -2
  42. data/lib/raven/interfaces/stack_trace.rb +19 -27
  43. data/lib/raven/linecache.rb +34 -17
  44. data/lib/raven/logger.rb +11 -18
  45. data/lib/raven/processor/cookies.rb +27 -7
  46. data/lib/raven/processor/http_headers.rb +18 -5
  47. data/lib/raven/processor/post_data.rb +16 -3
  48. data/lib/raven/processor/removecircularreferences.rb +12 -8
  49. data/lib/raven/processor/removestacktrace.rb +17 -6
  50. data/lib/raven/processor/sanitizedata.rb +88 -29
  51. data/lib/raven/processor/utf8conversion.rb +39 -14
  52. data/lib/raven/processor.rb +1 -1
  53. data/lib/raven/transports/http.rb +29 -21
  54. data/lib/raven/transports/stdout.rb +20 -0
  55. data/lib/raven/transports.rb +4 -8
  56. data/lib/raven/utils/context_filter.rb +42 -0
  57. data/lib/raven/utils/deep_merge.rb +6 -12
  58. data/lib/raven/utils/exception_cause_chain.rb +20 -0
  59. data/lib/raven/utils/real_ip.rb +1 -1
  60. data/lib/raven/utils/request_id.rb +16 -0
  61. data/lib/raven/version.rb +2 -2
  62. data/lib/sentry-raven-without-integrations.rb +6 -1
  63. data/lib/sentry_raven_without_integrations.rb +1 -0
  64. data/sentry-raven.gemspec +28 -0
  65. metadata +37 -103
  66. data/lib/raven/error.rb +0 -4
@@ -1,64 +1,13 @@
1
1
  require 'time'
2
2
  require 'sidekiq'
3
-
4
- module Raven
5
- class Sidekiq
6
- ACTIVEJOB_RESERVED_PREFIX = "_aj_".freeze
7
-
8
- def call(ex, context)
9
- context = filter_context(context)
10
- Raven.capture_exception(
11
- ex,
12
- :message => ex.message,
13
- :extra => { :sidekiq => context },
14
- :culprit => culprit_from_context(context)
15
- )
16
- ensure
17
- Context.clear!
18
- BreadcrumbBuffer.clear!
19
- end
20
-
21
- private
22
-
23
- # Once an ActiveJob is queued, ActiveRecord references get serialized into
24
- # some internal reserved keys, such as _aj_globalid.
25
- #
26
- # The problem is, if this job in turn gets queued back into ActiveJob with
27
- # these magic reserved keys, ActiveJob will throw up and error. We want to
28
- # capture these and mutate the keys so we can sanely report it.
29
- def filter_context(context)
30
- case context
31
- when Array
32
- context.map { |arg| filter_context(arg) }
33
- when Hash
34
- Hash[context.map { |key, value| filter_context_hash(key, value) }]
35
- else
36
- context
37
- end
38
- end
39
-
40
- def filter_context_hash(key, value)
41
- (key = key[3..-1]) if key [0..3] == ACTIVEJOB_RESERVED_PREFIX
42
- [key, filter_context(value)]
43
- end
44
-
45
- # this will change in the future:
46
- # https://github.com/mperham/sidekiq/pull/3161
47
- def culprit_from_context(context)
48
- classname = (context["class"] || (context["job"] && context["job"]["class"]))
49
- if classname
50
- "Sidekiq/#{classname}"
51
- elsif context["event"]
52
- "Sidekiq/#{context['event']}"
53
- else
54
- "Sidekiq"
55
- end
56
- end
57
- end
58
- end
3
+ require 'raven/integrations/sidekiq/cleanup_middleware'
4
+ require 'raven/integrations/sidekiq/error_handler'
59
5
 
60
6
  if Sidekiq::VERSION > '3'
61
7
  Sidekiq.configure_server do |config|
62
- config.error_handlers << Raven::Sidekiq.new
8
+ config.error_handlers << Raven::Sidekiq::ErrorHandler.new
9
+ config.server_middleware do |chain|
10
+ chain.add Raven::Sidekiq::CleanupMiddleware
11
+ end
63
12
  end
64
13
  end
@@ -1,9 +1,9 @@
1
1
  module Raven
2
2
  class Interface
3
3
  def initialize(attributes = nil)
4
- attributes.each do |attr, value|
4
+ attributes&.each do |attr, value|
5
5
  public_send "#{attr}=", value
6
- end if attributes
6
+ end
7
7
 
8
8
  yield self if block_given?
9
9
  end
@@ -1,5 +1,3 @@
1
- require 'raven/interface'
2
-
3
1
  module Raven
4
2
  class ExceptionInterface < Interface
5
3
  attr_accessor :values
@@ -1,5 +1,3 @@
1
- require 'raven/interface'
2
-
3
1
  module Raven
4
2
  class HttpInterface < Interface
5
3
  attr_accessor :url, :method, :data, :query_string, :cookies, :headers, :env
@@ -10,7 +10,7 @@ module Raven
10
10
  end
11
11
 
12
12
  def unformatted_message
13
- params.nil? ? message : message % params
13
+ Array(params).empty? ? message : message % params
14
14
  end
15
15
 
16
16
  def self.sentry_alias
@@ -1,5 +1,3 @@
1
- require 'raven/interface'
2
-
3
1
  module Raven
4
2
  class SingleExceptionInterface < Interface
5
3
  attr_accessor :type
@@ -1,11 +1,8 @@
1
- require 'raven/interface'
2
-
3
1
  module Raven
4
2
  class StacktraceInterface < Interface
5
3
  attr_accessor :frames
6
4
 
7
5
  def initialize(*arguments)
8
- self.frames = []
9
6
  super(*arguments)
10
7
  end
11
8
 
@@ -21,23 +18,16 @@ module Raven
21
18
 
22
19
  # Not actually an interface, but I want to use the same style
23
20
  class Frame < Interface
24
- attr_accessor :abs_path
25
- attr_accessor :function
26
- attr_accessor :vars
27
- attr_accessor :pre_context
28
- attr_accessor :post_context
29
- attr_accessor :context_line
30
- attr_accessor :module
31
- attr_accessor :lineno
32
- attr_accessor :in_app
21
+ attr_accessor :abs_path, :context_line, :function, :in_app,
22
+ :lineno, :module, :pre_context, :post_context, :vars
33
23
 
34
24
  def initialize(*arguments)
35
- self.vars, self.pre_context, self.post_context = [], [], []
36
25
  super(*arguments)
37
26
  end
38
27
 
39
28
  def filename
40
- return nil if abs_path.nil?
29
+ return if abs_path.nil?
30
+ return @filename if instance_variable_defined?(:@filename)
41
31
 
42
32
  prefix =
43
33
  if under_project_root? && in_app
@@ -48,19 +38,7 @@ module Raven
48
38
  longest_load_path
49
39
  end
50
40
 
51
- prefix ? abs_path[prefix.to_s.chomp(File::SEPARATOR).length + 1..-1] : abs_path
52
- end
53
-
54
- def under_project_root?
55
- project_root && abs_path.start_with?(project_root)
56
- end
57
-
58
- def project_root
59
- @project_root ||= Raven.configuration.project_root && Raven.configuration.project_root.to_s
60
- end
61
-
62
- def longest_load_path
63
- $LOAD_PATH.select { |s| abs_path.start_with?(s.to_s) }.sort_by { |s| s.to_s.length }.last
41
+ @filename = prefix ? abs_path[prefix.to_s.chomp(File::SEPARATOR).length + 1..-1] : abs_path
64
42
  end
65
43
 
66
44
  def to_hash(*args)
@@ -72,6 +50,20 @@ module Raven
72
50
  data.delete(:context_line) unless context_line && !context_line.empty?
73
51
  data
74
52
  end
53
+
54
+ private
55
+
56
+ def under_project_root?
57
+ project_root && abs_path.start_with?(project_root)
58
+ end
59
+
60
+ def project_root
61
+ @project_root ||= Raven.configuration.project_root&.to_s
62
+ end
63
+
64
+ def longest_load_path
65
+ $LOAD_PATH.select { |path| abs_path.start_with?(path.to_s) }.max_by(&:size)
66
+ end
75
67
  end
76
68
  end
77
69
  end
@@ -1,27 +1,44 @@
1
1
  module Raven
2
2
  class LineCache
3
- class << self
4
- CACHE = {} # rubocop:disable Style/MutableConstant
3
+ def initialize
4
+ @cache = {}
5
+ end
5
6
 
6
- def valid_file?(path)
7
- lines = getlines(path)
8
- !lines.nil?
9
- end
7
+ # Any linecache you provide to Raven must implement this method.
8
+ # Returns an Array of Strings representing the lines in the source
9
+ # file. The number of lines retrieved is (2 * context) + 1, the middle
10
+ # line should be the line requested by lineno. See specs for more information.
11
+ def get_file_context(filename, lineno, context)
12
+ return nil, nil, nil unless valid_path?(filename)
10
13
 
11
- def getlines(path)
12
- CACHE[path] ||= begin
13
- IO.readlines(path)
14
- rescue
15
- nil
16
- end
14
+ lines = Array.new(2 * context + 1) do |i|
15
+ getline(filename, lineno - context + i)
17
16
  end
17
+ [lines[0..(context - 1)], lines[context], lines[(context + 1)..-1]]
18
+ end
19
+
20
+ private
21
+
22
+ def valid_path?(path)
23
+ lines = getlines(path)
24
+ !lines.nil?
25
+ end
18
26
 
19
- def getline(path, n)
20
- return nil if n < 1
21
- lines = getlines(path)
22
- return nil if lines.nil?
23
- lines[n - 1]
27
+ def getlines(path)
28
+ @cache[path] ||= begin
29
+ IO.readlines(path)
30
+ rescue
31
+ nil
24
32
  end
25
33
  end
34
+
35
+ def getline(path, n)
36
+ return nil if n < 1
37
+
38
+ lines = getlines(path)
39
+ return nil if lines.nil?
40
+
41
+ lines[n - 1]
42
+ end
26
43
  end
27
44
  end
data/lib/raven/logger.rb CHANGED
@@ -1,26 +1,19 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  require 'logger'
3
4
 
4
5
  module Raven
5
- class Logger
6
- LOG_PREFIX = "** [Raven] ".freeze
7
-
8
- [
9
- :fatal,
10
- :error,
11
- :warn,
12
- :info,
13
- :debug
14
- ].each do |level|
15
- define_method level do |*args, &block|
16
- logger = Raven.configuration[:logger]
17
- logger = ::Logger.new(STDOUT) if logger.nil?
18
- return unless logger
19
-
20
- msg = args[0] # Block-level default args is a 1.9 feature
21
- msg ||= block.call if block
6
+ class Logger < ::Logger
7
+ LOG_PREFIX = "** [Raven] "
8
+ PROGNAME = "sentry"
22
9
 
23
- logger.send(level, "sentry") { "#{LOG_PREFIX}#{msg}" }
10
+ def initialize(*)
11
+ super
12
+ @level = ::Logger::INFO
13
+ original_formatter = ::Logger::Formatter.new
14
+ @default_formatter = proc do |severity, datetime, _progname, msg|
15
+ msg = "#{LOG_PREFIX}#{msg}"
16
+ original_formatter.call(severity, datetime, PROGNAME, msg)
24
17
  end
25
18
  end
26
19
  end
@@ -1,16 +1,36 @@
1
1
  module Raven
2
2
  class Processor::Cookies < Processor
3
3
  def process(data)
4
- if data[:request]
5
- # Remove possibly sensitive cookies
6
- data[:request][:cookies] = STRING_MASK if data[:request][:cookies]
4
+ process_if_symbol_keys(data) if data[:request]
5
+ process_if_string_keys(data) if data["request"]
7
6
 
8
- if data[:request][:headers] && data[:request][:headers]["Cookie"]
9
- data[:request][:headers]["Cookie"] = STRING_MASK
10
- end
7
+ data
8
+ end
9
+
10
+ private
11
+
12
+ def process_if_symbol_keys(data)
13
+ if cookies = data.dig(:request, :cookies)
14
+ data[:request][:cookies] = generate_masked_cookies(cookies)
11
15
  end
12
16
 
13
- data
17
+ if cookies_header = data[:request][:headers]["Cookie"]
18
+ data[:request][:headers]["Cookie"] = generate_masked_cookies(cookies_header)
19
+ end
20
+ end
21
+
22
+ def process_if_string_keys(data)
23
+ if cookies = data.dig("request", "cookies")
24
+ data["request"]["cookies"] = generate_masked_cookies(cookies)
25
+ end
26
+
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)
14
34
  end
15
35
  end
16
36
  end
@@ -10,17 +10,30 @@ module Raven
10
10
  end
11
11
 
12
12
  def process(data)
13
- if data[:request] && data[:request][:headers]
14
- data[:request][:headers].keys.select { |k| fields_re.match(k.to_s) }.each do |k|
15
- data[:request][:headers][k] = STRING_MASK
16
- end
17
- end
13
+ process_if_symbol_keys(data) if data[:request]
14
+ process_if_string_keys(data) if data["request"]
18
15
 
19
16
  data
20
17
  end
21
18
 
22
19
  private
23
20
 
21
+ def process_if_symbol_keys(data)
22
+ return unless data[:request][:headers]
23
+
24
+ data[:request][:headers].keys.select { |k| fields_re.match(k.to_s) }.each do |k|
25
+ data[:request][:headers][k] = STRING_MASK
26
+ end
27
+ end
28
+
29
+ def process_if_string_keys(data)
30
+ return unless data["request"]["headers"]
31
+
32
+ data["request"]["headers"].keys.select { |k| fields_re.match(k) }.each do |k|
33
+ data["request"]["headers"][k] = STRING_MASK
34
+ end
35
+ end
36
+
24
37
  def matches_regexes?(k)
25
38
  fields_re.match(k.to_s)
26
39
  end
@@ -1,11 +1,24 @@
1
1
  module Raven
2
2
  class Processor::PostData < Processor
3
3
  def process(data)
4
- if data[:request] && data[:request][:method] == "POST"
5
- data[:request][:data] = STRING_MASK # Remove possibly sensitive POST data
6
- end
4
+ process_if_symbol_keys(data) if data[:request]
5
+ process_if_string_keys(data) if data["request"]
7
6
 
8
7
  data
9
8
  end
9
+
10
+ private
11
+
12
+ def process_if_symbol_keys(data)
13
+ return unless data[:request][:method] == "POST"
14
+
15
+ data[:request][:data] = STRING_MASK
16
+ end
17
+
18
+ def process_if_string_keys(data)
19
+ return unless data["request"]["method"] == "POST"
20
+
21
+ data["request"]["data"] = STRING_MASK
22
+ end
10
23
  end
11
24
  end
@@ -1,14 +1,18 @@
1
1
  module Raven
2
2
  class Processor::RemoveCircularReferences < Processor
3
- def process(v, visited = [])
4
- return "(...)" if visited.include?(v.__id__)
5
- visited += [v.__id__]
6
- if v.is_a?(Hash)
7
- v.each_with_object({}) { |(k, v_), memo| memo[k] = process(v_, visited) }
8
- elsif v.is_a?(Array)
9
- v.map { |v_| process(v_, visited) }
3
+ ELISION_STRING = "(...)".freeze
4
+ def process(value, visited = [])
5
+ return ELISION_STRING if visited.include?(value.__id__)
6
+
7
+ visited << value.__id__ if value.is_a?(Array) || value.is_a?(Hash)
8
+
9
+ case value
10
+ when Hash
11
+ !value.frozen? ? value.merge!(value) { |_, v| process v, visited } : value.merge(value) { |_, v| process v, visited }
12
+ when Array
13
+ !value.frozen? ? value.map! { |v| process v, visited } : value.map { |v| process v, visited }
10
14
  else
11
- v
15
+ value
12
16
  end
13
17
  end
14
18
  end
@@ -1,13 +1,24 @@
1
1
  module Raven
2
2
  class Processor::RemoveStacktrace < Processor
3
- def process(value)
4
- if value[:exception]
5
- value[:exception][:values].map do |single_exception|
6
- single_exception.delete(:stacktrace) if single_exception[:stacktrace]
7
- end
3
+ def process(data)
4
+ process_if_symbol_keys(data) if data[:exception]
5
+ process_if_string_keys(data) if data["exception"]
6
+
7
+ data
8
+ end
9
+
10
+ private
11
+
12
+ def process_if_symbol_keys(data)
13
+ data[:exception][:values].map do |single_exception|
14
+ single_exception.delete(:stacktrace) if single_exception[:stacktrace]
8
15
  end
16
+ end
9
17
 
10
- value
18
+ def process_if_string_keys(data)
19
+ data["exception"]["values"].map do |single_exception|
20
+ single_exception.delete("stacktrace") if single_exception["stacktrace"]
21
+ end
11
22
  end
12
23
  end
13
24
  end
@@ -1,61 +1,118 @@
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 = /^(?:\d[ -]*?){13,16}$/
8
+ CREDIT_CARD_RE = /\b(?:3[47]\d|(?:4\d|5[1-5]|65)\d{2}|6011)\d{12}\b/.freeze
9
+ QUERY_STRING = ['query_string', :query_string].freeze
10
+ JSON_STARTS_WITH = ["[", "{"].freeze
8
11
 
9
- attr_accessor :sanitize_fields, :sanitize_credit_cards
12
+ attr_accessor :sanitize_fields, :sanitize_credit_cards, :sanitize_fields_excluded
10
13
 
11
14
  def initialize(client)
12
15
  super
13
16
  self.sanitize_fields = client.configuration.sanitize_fields
14
17
  self.sanitize_credit_cards = client.configuration.sanitize_credit_cards
18
+ self.sanitize_fields_excluded = client.configuration.sanitize_fields_excluded
19
+ end
20
+
21
+ def process(value, key = nil)
22
+ case value
23
+ when Hash
24
+ sanitize_hash_value(key, value)
25
+ when Array
26
+ sanitize_array_value(key, value)
27
+ when Integer
28
+ matches_regexes?(key, value.to_s) ? INT_MASK : value
29
+ when String
30
+ sanitize_string_value(key, value)
31
+ else
32
+ value
33
+ end
15
34
  end
16
35
 
17
- def process(value)
18
- value.each_with_object(value) { |(k, v), memo| memo[k] = sanitize(k, v) }
36
+ private
37
+
38
+ # CGI.parse takes our nice UTF-8 strings and converts them back to ASCII,
39
+ # so we have to convert them back, again.
40
+ def utf8_processor
41
+ @utf8_processor ||= Processor::UTF8Conversion.new
19
42
  end
20
43
 
21
- def sanitize(k, v)
22
- if v.is_a?(Hash)
23
- process(v)
24
- elsif v.is_a?(Array)
25
- v.map { |a| sanitize(k, a) }
26
- elsif k.to_s == 'query_string'
27
- sanitize_query_string(v)
28
- elsif v.is_a?(Integer) && matches_regexes?(k, v)
29
- INT_MASK
30
- elsif v.is_a?(String)
31
- if fields_re.match(v.to_s) && (json = parse_json_or_nil(v))
32
- # if this string is actually a json obj, convert and sanitize
33
- json.is_a?(Hash) ? process(json).to_json : v
34
- elsif matches_regexes?(k, v)
35
- STRING_MASK
36
- else
37
- v
38
- end
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 }
39
49
  else
40
- v
50
+ value.merge!(value) { |k, v| process v, k }
41
51
  end
42
52
  end
43
53
 
44
- private
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
45
76
 
46
77
  def sanitize_query_string(query_string)
47
78
  query_hash = CGI.parse(query_string)
48
- processed_query_hash = process(query_hash)
79
+ sanitized = utf8_processor.process(query_hash)
80
+ processed_query_hash = process(sanitized)
49
81
  URI.encode_www_form(processed_query_hash)
50
82
  end
51
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
+
52
105
  def matches_regexes?(k, v)
53
- (sanitize_credit_cards && CREDIT_CARD_RE.match(v.to_s)) ||
54
- fields_re.match(k.to_s)
106
+ (sanitize_credit_cards && v =~ CREDIT_CARD_RE) ||
107
+ k =~ sensitive_fields
55
108
  end
56
109
 
57
- def fields_re
58
- @fields_re ||= /#{(DEFAULT_FIELDS | sanitize_fields).map do |f|
110
+ def sensitive_fields
111
+ return @sensitive_fields if instance_variable_defined?(:@sensitive_fields)
112
+
113
+ fields = DEFAULT_FIELDS | sanitize_fields
114
+ fields -= sanitize_fields_excluded
115
+ @sensitive_fields = /#{fields.map do |f|
59
116
  use_boundary?(f) ? "\\b#{f}\\b" : f
60
117
  end.join("|")}/i
61
118
  end
@@ -69,6 +126,8 @@ module Raven
69
126
  end
70
127
 
71
128
  def parse_json_or_nil(string)
129
+ return unless string.start_with?(*JSON_STARTS_WITH)
130
+
72
131
  JSON.parse(string)
73
132
  rescue JSON::ParserError, NoMethodError
74
133
  nil