sentry-raven 2.1.3 → 3.1.2

Sign up to get free protection for your applications and to get access to all the features.
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