sentry-raven 3.0.0 → 3.1.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (57) hide show
  1. checksums.yaml +4 -4
  2. data/.craft.yml +19 -0
  3. data/.scripts/bump-version.rb +5 -0
  4. data/{changelog.md → CHANGELOG.md} +174 -1
  5. data/Gemfile +24 -25
  6. data/Makefile +3 -0
  7. data/README.md +44 -16
  8. data/lib/raven/backtrace.rb +9 -5
  9. data/lib/raven/base.rb +7 -2
  10. data/lib/raven/breadcrumbs.rb +1 -1
  11. data/lib/raven/breadcrumbs/{activesupport.rb → active_support_logger.rb} +9 -3
  12. data/lib/raven/breadcrumbs/logger.rb +2 -92
  13. data/lib/raven/breadcrumbs/sentry_logger.rb +73 -0
  14. data/lib/raven/cli.rb +10 -21
  15. data/lib/raven/client.rb +9 -4
  16. data/lib/raven/configuration.rb +95 -10
  17. data/lib/raven/context.rb +13 -8
  18. data/lib/raven/core_ext/object/deep_dup.rb +57 -0
  19. data/lib/raven/core_ext/object/duplicable.rb +153 -0
  20. data/lib/raven/event.rb +31 -15
  21. data/lib/raven/helpers/deprecation_helper.rb +17 -0
  22. data/lib/raven/instance.rb +21 -5
  23. data/lib/raven/integrations/delayed_job.rb +15 -15
  24. data/lib/raven/integrations/rack-timeout.rb +7 -4
  25. data/lib/raven/integrations/rack.rb +8 -6
  26. data/lib/raven/integrations/rails.rb +13 -3
  27. data/lib/raven/integrations/rails/active_job.rb +6 -4
  28. data/lib/raven/integrations/rails/backtrace_cleaner.rb +29 -0
  29. data/lib/raven/integrations/rails/overrides/debug_exceptions_catcher.rb +2 -2
  30. data/lib/raven/integrations/sidekiq.rb +4 -78
  31. data/lib/raven/integrations/sidekiq/cleanup_middleware.rb +13 -0
  32. data/lib/raven/integrations/sidekiq/error_handler.rb +38 -0
  33. data/lib/raven/interface.rb +2 -2
  34. data/lib/raven/interfaces/stack_trace.rb +1 -1
  35. data/lib/raven/linecache.rb +5 -2
  36. data/lib/raven/logger.rb +3 -2
  37. data/lib/raven/processor/cookies.rb +16 -6
  38. data/lib/raven/processor/post_data.rb +2 -0
  39. data/lib/raven/processor/removecircularreferences.rb +3 -1
  40. data/lib/raven/processor/sanitizedata.rb +65 -17
  41. data/lib/raven/processor/utf8conversion.rb +2 -0
  42. data/lib/raven/transports.rb +4 -0
  43. data/lib/raven/transports/http.rb +7 -8
  44. data/lib/raven/utils/context_filter.rb +42 -0
  45. data/lib/raven/utils/exception_cause_chain.rb +1 -0
  46. data/lib/raven/utils/real_ip.rb +1 -1
  47. data/lib/raven/utils/request_id.rb +16 -0
  48. data/lib/raven/version.rb +2 -2
  49. data/lib/sentry-raven-without-integrations.rb +6 -1
  50. data/lib/sentry_raven_without_integrations.rb +1 -0
  51. data/sentry-raven.gemspec +7 -0
  52. metadata +22 -10
  53. data/.gitignore +0 -13
  54. data/.gitmodules +0 -0
  55. data/.rspec +0 -1
  56. data/.rubocop.yml +0 -74
  57. data/.travis.yml +0 -43
@@ -1,4 +1,5 @@
1
1
  require 'delayed_job'
2
+ require 'raven/utils/context_filter'
2
3
 
3
4
  module Delayed
4
5
  module Plugins
@@ -8,19 +9,18 @@ module Delayed
8
9
  begin
9
10
  # Forward the call to the next callback in the callback chain
10
11
  block.call(job, *args)
11
-
12
- rescue Exception => exception
12
+ rescue Exception => e
13
13
  # Log error to Sentry
14
14
  extra = {
15
15
  :delayed_job => {
16
- :id => job.id.to_s,
17
- :priority => job.priority,
18
- :attempts => job.attempts,
19
- :run_at => job.run_at,
20
- :locked_at => job.locked_at,
21
- :locked_by => job.locked_by,
22
- :queue => job.queue,
23
- :created_at => job.created_at
16
+ :id => job.id.to_s,
17
+ :priority => job.priority,
18
+ :attempts => job.attempts,
19
+ :run_at => job.run_at,
20
+ :locked_at => job.locked_at,
21
+ :locked_by => job.locked_by,
22
+ :queue => job.queue,
23
+ :created_at => job.created_at
24
24
  }
25
25
  }
26
26
  # last_error can be nil
@@ -30,18 +30,18 @@ module Delayed
30
30
  extra[:handler] = job.handler[0...1000] if job.handler
31
31
 
32
32
  if job.respond_to?('payload_object') && job.payload_object.respond_to?('job_data')
33
- extra[:active_job] = job.payload_object.job_data
33
+ extra[:active_job] = ::Raven::Utils::ContextFilter.filter_context(job.payload_object.job_data)
34
34
  end
35
- ::Raven.capture_exception(exception,
36
- :logger => 'delayed_job',
37
- :tags => {
35
+ ::Raven.capture_exception(e,
36
+ :logger => 'delayed_job',
37
+ :tags => {
38
38
  :delayed_job_queue => job.queue,
39
39
  :delayed_job_id => job.id.to_s
40
40
  },
41
41
  :extra => extra)
42
42
 
43
43
  # Make sure we propagate the failure!
44
- raise exception
44
+ raise e
45
45
  ensure
46
46
  ::Raven::Context.clear!
47
47
  ::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
- { :fingerprint => ["{{ default }}", env["REQUEST_URI"]] } if defined?(env)
13
+ if defined?(env)
14
+ { :fingerprint => ["{{ default }}", env["REQUEST_URI"]] }
15
+ else
16
+ {}
17
+ end
14
18
  end
15
19
  end
16
20
 
17
- # Include is private in Ruby 1.9
18
- Rack::Timeout::Error.__send__(:include, RackTimeoutExtensions)
19
- Rack::Timeout::RequestTimeoutException.__send__(:include, RackTimeoutExtensions)
21
+ Rack::Timeout::Error.include(RackTimeoutExtensions)
22
+ Rack::Timeout::RequestTimeoutException.include(RackTimeoutExtensions)
@@ -92,15 +92,15 @@ module Raven
92
92
  request.body.rewind
93
93
  data
94
94
  end
95
- rescue IOError => ex
96
- ex.message
95
+ rescue IOError => e
96
+ e.message
97
97
  end
98
98
 
99
99
  def format_headers_for_sentry(env_hash)
100
100
  env_hash.each_with_object({}) do |(key, value), memo|
101
101
  begin
102
102
  key = key.to_s # rack env can contain symbols
103
- value = value.to_s
103
+ next memo['X-Request-Id'] ||= Utils::RequestId.read_from(env_hash) if Utils::RequestId::REQUEST_ID_HEADERS.include?(key)
104
104
  next unless key.upcase == key # Non-upper case stuff isn't either
105
105
 
106
106
  # Rack adds in an incorrect HTTP_VERSION key, which causes downstream
@@ -110,12 +110,12 @@ module Raven
110
110
  # See: https://github.com/rack/rack/blob/028438f/lib/rack/handler/cgi.rb#L29
111
111
  next if key == 'HTTP_VERSION' && value == env_hash['SERVER_PROTOCOL']
112
112
  next if key == 'HTTP_COOKIE' # Cookies don't go here, they go somewhere else
113
-
114
113
  next unless key.start_with?('HTTP_') || %w(CONTENT_TYPE CONTENT_LENGTH).include?(key)
114
+
115
115
  # Rack stores headers as HTTP_WHAT_EVER, we need What-Ever
116
116
  key = key.sub(/^HTTP_/, "")
117
117
  key = key.split('_').map(&:capitalize).join('-')
118
- memo[key] = value
118
+ memo[key] = value.to_s
119
119
  rescue StandardError => e
120
120
  # Rails adds objects to the Rack env that can sometimes raise exceptions
121
121
  # when `to_s` is called.
@@ -127,8 +127,10 @@ module Raven
127
127
  end
128
128
 
129
129
  def format_env_for_sentry(env_hash)
130
+ return env_hash if Raven.configuration.rack_env_whitelist.empty?
131
+
130
132
  env_hash.select do |k, _v|
131
- %w(REMOTE_ADDR SERVER_NAME SERVER_PORT).include? k.to_s
133
+ Raven.configuration.rack_env_whitelist.include? k.to_s
132
134
  end
133
135
  end
134
136
  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.rails_activesupport_breadcrumbs
43
- require 'raven/breadcrumbs/activesupport'
44
- Raven::ActiveSupportBreadcrumbs.inject
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
@@ -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 => exception # rubocop:disable Lint/RescueException
24
- return if rescue_with_handler(exception)
25
- Raven.capture_exception(exception, :extra => raven_context(job))
26
- raise exception
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 # rubocop:disable Lint/HandleExceptions
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 # rubocop:disable Lint/HandleExceptions
24
+ rescue
25
25
  end
26
26
  render_exception_without_raven(env_or_request, exception)
27
27
  end
@@ -1,87 +1,13 @@
1
1
  require 'time'
2
2
  require 'sidekiq'
3
-
4
- module Raven
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::SidekiqErrorHandler.new
8
+ config.error_handlers << Raven::Sidekiq::ErrorHandler.new
83
9
  config.server_middleware do |chain|
84
- chain.add Raven::SidekiqCleanupMiddleware
10
+ chain.add Raven::Sidekiq::CleanupMiddleware
85
11
  end
86
12
  end
87
13
  end
@@ -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,38 @@
1
+ require 'raven/utils/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 = Utils::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,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
@@ -58,7 +58,7 @@ module Raven
58
58
  end
59
59
 
60
60
  def project_root
61
- @project_root ||= Raven.configuration.project_root && Raven.configuration.project_root.to_s
61
+ @project_root ||= Raven.configuration.project_root&.to_s
62
62
  end
63
63
 
64
64
  def longest_load_path
@@ -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
- rescue
30
- nil
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
@@ -1,10 +1,11 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  require 'logger'
3
4
 
4
5
  module Raven
5
6
  class Logger < ::Logger
6
- LOG_PREFIX = "** [Raven] ".freeze
7
- PROGNAME = "sentry".freeze
7
+ LOG_PREFIX = "** [Raven] "
8
+ PROGNAME = "sentry"
8
9
 
9
10
  def initialize(*)
10
11
  super
@@ -10,17 +10,27 @@ module Raven
10
10
  private
11
11
 
12
12
  def process_if_symbol_keys(data)
13
- data[:request][:cookies] = STRING_MASK if data[:request][:cookies]
13
+ if cookies = data.dig(:request, :cookies)
14
+ data[:request][:cookies] = generate_masked_cookies(cookies)
15
+ end
14
16
 
15
- return unless data[:request][:headers] && data[:request][:headers]["Cookie"]
16
- data[:request][:headers]["Cookie"] = STRING_MASK
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
- data["request"]["cookies"] = STRING_MASK if data["request"]["cookies"]
23
+ if cookies = data.dig("request", "cookies")
24
+ data["request"]["cookies"] = generate_masked_cookies(cookies)
25
+ end
21
26
 
22
- return unless data["request"]["headers"] && data["request"]["headers"]["Cookie"]
23
- data["request"]["headers"]["Cookie"] = STRING_MASK
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