sentry-raven 2.9.0 → 3.1.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (58) 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 → CHANGELOG.md} +239 -2
  5. data/Gemfile +24 -25
  6. data/Makefile +3 -0
  7. data/README.md +46 -18
  8. data/lib/raven/backtrace.rb +9 -5
  9. data/lib/raven/base.rb +8 -3
  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 +28 -10
  16. data/lib/raven/configuration.rb +103 -13
  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 +32 -22
  21. data/lib/raven/helpers/deprecation_helper.rb +17 -0
  22. data/lib/raven/instance.rb +29 -5
  23. data/lib/raven/integrations/delayed_job.rb +16 -16
  24. data/lib/raven/integrations/rack-timeout.rb +7 -4
  25. data/lib/raven/integrations/rack.rb +9 -7
  26. data/lib/raven/integrations/rails.rb +13 -3
  27. data/lib/raven/integrations/rails/active_job.rb +11 -7
  28. data/lib/raven/integrations/rails/backtrace_cleaner.rb +29 -0
  29. data/lib/raven/integrations/rails/controller_transaction.rb +1 -1
  30. data/lib/raven/integrations/rails/overrides/debug_exceptions_catcher.rb +2 -2
  31. data/lib/raven/integrations/sidekiq.rb +4 -78
  32. data/lib/raven/integrations/sidekiq/cleanup_middleware.rb +13 -0
  33. data/lib/raven/integrations/sidekiq/error_handler.rb +38 -0
  34. data/lib/raven/interface.rb +2 -2
  35. data/lib/raven/interfaces/stack_trace.rb +1 -1
  36. data/lib/raven/linecache.rb +5 -2
  37. data/lib/raven/logger.rb +3 -2
  38. data/lib/raven/processor/cookies.rb +16 -6
  39. data/lib/raven/processor/post_data.rb +2 -0
  40. data/lib/raven/processor/removecircularreferences.rb +3 -1
  41. data/lib/raven/processor/sanitizedata.rb +65 -17
  42. data/lib/raven/processor/utf8conversion.rb +3 -1
  43. data/lib/raven/transports.rb +4 -0
  44. data/lib/raven/transports/http.rb +7 -8
  45. data/lib/raven/utils/context_filter.rb +42 -0
  46. data/lib/raven/utils/exception_cause_chain.rb +1 -0
  47. data/lib/raven/utils/real_ip.rb +1 -1
  48. data/lib/raven/utils/request_id.rb +16 -0
  49. data/lib/raven/version.rb +2 -2
  50. data/lib/sentry-raven-without-integrations.rb +6 -1
  51. data/lib/sentry_raven_without_integrations.rb +1 -0
  52. data/sentry-raven.gemspec +10 -3
  53. metadata +25 -20
  54. data/.gitignore +0 -13
  55. data/.gitmodules +0 -0
  56. data/.rspec +0 -1
  57. data/.rubocop.yml +0 -74
  58. data/.travis.yml +0 -47
data/lib/raven/context.rb CHANGED
@@ -1,4 +1,5 @@
1
1
  require 'rbconfig'
2
+ require 'etc'
2
3
 
3
4
  module Raven
4
5
  class Context
@@ -24,18 +25,22 @@ module Raven
24
25
 
25
26
  class << self
26
27
  def os_context
27
- @os_context ||= {
28
- :name => Raven.sys_command("uname -s") || RbConfig::CONFIG["host_os"],
29
- :version => Raven.sys_command("uname -v"),
30
- :build => Raven.sys_command("uname -r"),
31
- :kernel_version => Raven.sys_command("uname -a") || Raven.sys_command("ver") # windows
32
- }
28
+ @os_context ||=
29
+ begin
30
+ uname = Etc.uname
31
+ {
32
+ name: uname[:sysname] || RbConfig::CONFIG["host_os"],
33
+ version: uname[:version],
34
+ build: uname[:release],
35
+ kernel_version: uname[:version]
36
+ }
37
+ end
33
38
  end
34
39
 
35
40
  def runtime_context
36
41
  @runtime_context ||= {
37
- :name => RbConfig::CONFIG["ruby_install_name"],
38
- :version => Raven.sys_command("ruby -v")
42
+ name: RbConfig::CONFIG["ruby_install_name"],
43
+ version: RUBY_DESCRIPTION || Raven.sys_command("ruby -v")
39
44
  }
40
45
  end
41
46
  end
@@ -0,0 +1,57 @@
1
+ require 'raven/core_ext/object/duplicable'
2
+
3
+ #########################################
4
+ # This file was copied from Rails 5.2 #
5
+ #########################################
6
+
7
+ class Object
8
+ # Returns a deep copy of object if it's duplicable. If it's
9
+ # not duplicable, returns +self+.
10
+ #
11
+ # object = Object.new
12
+ # dup = object.deep_dup
13
+ # dup.instance_variable_set(:@a, 1)
14
+ #
15
+ # object.instance_variable_defined?(:@a) # => false
16
+ # dup.instance_variable_defined?(:@a) # => true
17
+ def deep_dup
18
+ duplicable? ? dup : self
19
+ end
20
+ end
21
+
22
+ class Array
23
+ # Returns a deep copy of array.
24
+ #
25
+ # array = [1, [2, 3]]
26
+ # dup = array.deep_dup
27
+ # dup[1][2] = 4
28
+ #
29
+ # array[1][2] # => nil
30
+ # dup[1][2] # => 4
31
+ def deep_dup
32
+ map(&:deep_dup)
33
+ end
34
+ end
35
+
36
+ class Hash
37
+ # Returns a deep copy of hash.
38
+ #
39
+ # hash = { a: { b: 'b' } }
40
+ # dup = hash.deep_dup
41
+ # dup[:a][:c] = 'c'
42
+ #
43
+ # hash[:a][:c] # => nil
44
+ # dup[:a][:c] # => "c"
45
+ def deep_dup
46
+ hash = dup
47
+ each_pair do |key, value|
48
+ if key.frozen? && ::String === key
49
+ hash[key] = value.deep_dup
50
+ else
51
+ hash.delete(key)
52
+ hash[key.deep_dup] = value.deep_dup
53
+ end
54
+ end
55
+ hash
56
+ end
57
+ end
@@ -0,0 +1,153 @@
1
+ # frozen_string_literal: true
2
+
3
+ #########################################
4
+ # This file was copied from Rails 5.2 #
5
+ #########################################
6
+
7
+ #--
8
+ # Most objects are cloneable, but not all. For example you can't dup methods:
9
+ #
10
+ # method(:puts).dup # => TypeError: allocator undefined for Method
11
+ #
12
+ # Classes may signal their instances are not duplicable removing +dup+/+clone+
13
+ # or raising exceptions from them. So, to dup an arbitrary object you normally
14
+ # use an optimistic approach and are ready to catch an exception, say:
15
+ #
16
+ # arbitrary_object.dup rescue object
17
+ #
18
+ # Rails dups objects in a few critical spots where they are not that arbitrary.
19
+ # That rescue is very expensive (like 40 times slower than a predicate), and it
20
+ # is often triggered.
21
+ #
22
+ # That's why we hardcode the following cases and check duplicable? instead of
23
+ # using that rescue idiom.
24
+ #++
25
+ class Object
26
+ # Can you safely dup this object?
27
+ #
28
+ # False for method objects;
29
+ # true otherwise.
30
+ def duplicable?
31
+ true
32
+ end
33
+ end
34
+
35
+ class NilClass
36
+ begin
37
+ nil.dup
38
+ rescue TypeError
39
+ # +nil+ is not duplicable:
40
+ #
41
+ # nil.duplicable? # => false
42
+ # nil.dup # => TypeError: can't dup NilClass
43
+ def duplicable?
44
+ false
45
+ end
46
+ end
47
+ end
48
+
49
+ class FalseClass
50
+ begin
51
+ false.dup
52
+ rescue TypeError
53
+ # +false+ is not duplicable:
54
+ #
55
+ # false.duplicable? # => false
56
+ # false.dup # => TypeError: can't dup FalseClass
57
+ def duplicable?
58
+ false
59
+ end
60
+ end
61
+ end
62
+
63
+ class TrueClass
64
+ begin
65
+ true.dup
66
+ rescue TypeError
67
+ # +true+ is not duplicable:
68
+ #
69
+ # true.duplicable? # => false
70
+ # true.dup # => TypeError: can't dup TrueClass
71
+ def duplicable?
72
+ false
73
+ end
74
+ end
75
+ end
76
+
77
+ class Symbol
78
+ begin
79
+ :symbol.dup # Ruby 2.4.x.
80
+ "symbol_from_string".to_sym.dup # Some symbols can't `dup` in Ruby 2.4.0.
81
+ rescue TypeError
82
+ # Symbols are not duplicable:
83
+ #
84
+ # :my_symbol.duplicable? # => false
85
+ # :my_symbol.dup # => TypeError: can't dup Symbol
86
+ def duplicable?
87
+ false
88
+ end
89
+ end
90
+ end
91
+
92
+ class Numeric
93
+ begin
94
+ 1.dup
95
+ rescue TypeError
96
+ # Numbers are not duplicable:
97
+ #
98
+ # 3.duplicable? # => false
99
+ # 3.dup # => TypeError: can't dup Integer
100
+ def duplicable?
101
+ false
102
+ end
103
+ end
104
+ end
105
+
106
+ require "bigdecimal"
107
+ class BigDecimal
108
+ # BigDecimals are duplicable:
109
+ #
110
+ # BigDecimal("1.2").duplicable? # => true
111
+ # BigDecimal("1.2").dup # => #<BigDecimal:...,'0.12E1',18(18)>
112
+ def duplicable?
113
+ true
114
+ end
115
+ end
116
+
117
+ class Method
118
+ # Methods are not duplicable:
119
+ #
120
+ # method(:puts).duplicable? # => false
121
+ # method(:puts).dup # => TypeError: allocator undefined for Method
122
+ def duplicable?
123
+ false
124
+ end
125
+ end
126
+
127
+ class Complex
128
+ begin
129
+ Complex(1).dup
130
+ rescue TypeError
131
+ # Complexes are not duplicable:
132
+ #
133
+ # Complex(1).duplicable? # => false
134
+ # Complex(1).dup # => TypeError: can't copy Complex
135
+ def duplicable?
136
+ false
137
+ end
138
+ end
139
+ end
140
+
141
+ class Rational
142
+ begin
143
+ Rational(1).dup
144
+ rescue TypeError
145
+ # Rationals are not duplicable:
146
+ #
147
+ # Rational(1).duplicable? # => false
148
+ # Rational(1).dup # => TypeError: can't copy Rational
149
+ def duplicable?
150
+ false
151
+ end
152
+ end
153
+ end
data/lib/raven/event.rb CHANGED
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  require 'socket'
3
4
  require 'securerandom'
4
5
 
@@ -7,6 +8,7 @@ module Raven
7
8
  # See Sentry server default limits at
8
9
  # https://github.com/getsentry/sentry/blob/master/src/sentry/conf/server.py
9
10
  MAX_MESSAGE_SIZE_IN_BYTES = 1024 * 8
11
+ REQUIRED_OPTION_KEYS = [:configuration, :context, :breadcrumbs].freeze
10
12
 
11
13
  SDK = { "name" => "raven-ruby", "version" => Raven::VERSION }.freeze
12
14
 
@@ -18,7 +20,7 @@ module Raven
18
20
 
19
21
  attr_reader :level, :timestamp, :time_spent
20
22
 
21
- def initialize(init = {})
23
+ def initialize(options)
22
24
  # Set some simple default values
23
25
  self.id = SecureRandom.uuid.delete("-")
24
26
  self.timestamp = Time.now.utc
@@ -35,11 +37,17 @@ module Raven
35
37
  self.runtime = {} # TODO: contexts
36
38
  self.tags = {} # TODO: contexts
37
39
 
38
- copy_initial_state
40
+ unless REQUIRED_OPTION_KEYS.all? { |key| options.key?(key) }
41
+ raise "you must provide configuration, context, and breadcrumbs when initializing a Raven::Event"
42
+ end
43
+
44
+ self.configuration = options[:configuration]
45
+ self.context = options[:context]
46
+ self.breadcrumbs = options[:breadcrumbs]
39
47
 
40
48
  # Allow attributes to be set on the event at initialization
41
49
  yield self if block_given?
42
- init.each_pair { |key, val| public_send("#{key}=", val) unless val.nil? }
50
+ options.each_pair { |key, val| public_send("#{key}=", val) unless val.nil? }
43
51
 
44
52
  set_core_attributes_from_configuration
45
53
  set_core_attributes_from_context
@@ -55,14 +63,10 @@ module Raven
55
63
  end
56
64
  options = Raven::Utils::DeepMergeHash.deep_merge(exception_context, options)
57
65
 
58
- configuration = options[:configuration] || Raven.configuration
59
- return unless configuration.exception_class_allowed?(exc)
66
+ return unless options[:configuration].exception_class_allowed?(exc)
60
67
 
61
68
  new(options) do |evt|
62
- evt.message = "#{exc.class}: #{exc.message}"
63
-
64
69
  evt.add_exception_interface(exc)
65
-
66
70
  yield evt if block
67
71
  end
68
72
  end
@@ -79,11 +83,21 @@ module Raven
79
83
  end
80
84
 
81
85
  def message
82
- @interfaces[:logentry] && @interfaces[:logentry].unformatted_message
86
+ @interfaces[:logentry]&.unformatted_message
83
87
  end
84
88
 
85
89
  def message=(args)
86
- message, params = *args
90
+ if args.is_a?(Array)
91
+ message, params = args[0], args[0..-1]
92
+ else
93
+ message = args
94
+ end
95
+
96
+ unless message.is_a?(String)
97
+ configuration.logger.debug("You're passing a non-string message")
98
+ message = message.to_s
99
+ end
100
+
87
101
  interface(:message) do |int|
88
102
  int.message = message.byteslice(0...MAX_MESSAGE_SIZE_IN_BYTES) # Messages limited to 10kb
89
103
  int.params = params
@@ -99,12 +113,13 @@ module Raven
99
113
  end
100
114
 
101
115
  def level=(new_level) # needed to meet the Sentry spec
102
- @level = new_level == "warn" || new_level == :warn ? :warning : new_level
116
+ @level = new_level.to_s == "warn" ? :warning : new_level
103
117
  end
104
118
 
105
119
  def interface(name, value = nil, &block)
106
120
  int = Interface.registered[name]
107
121
  raise(Error, "Unknown interface: #{name}") unless int
122
+
108
123
  @interfaces[int.sentry_alias] = int.new(value, &block) if value || block
109
124
  @interfaces[int.sentry_alias]
110
125
  end
@@ -160,7 +175,7 @@ module Raven
160
175
  end
161
176
 
162
177
  def stacktrace_interface_from(backtrace)
163
- Backtrace.parse(backtrace).lines.reverse.each_with_object([]) do |line, memo|
178
+ Backtrace.parse(backtrace, { configuration: configuration }).lines.reverse.each_with_object([]) do |line, memo|
164
179
  frame = StacktraceInterface::Frame.new
165
180
  frame.abs_path = line.file if line.file
166
181
  frame.function = line.method if line.method
@@ -187,12 +202,6 @@ module Raven
187
202
 
188
203
  private
189
204
 
190
- def copy_initial_state
191
- self.configuration = Raven.configuration
192
- self.breadcrumbs = Raven.breadcrumbs
193
- self.context = Raven.context
194
- end
195
-
196
205
  def set_core_attributes_from_configuration
197
206
  self.server_name ||= configuration.server_name
198
207
  self.release ||= configuration.release
@@ -217,6 +226,10 @@ module Raven
217
226
  int.from_rack(context.rack_env)
218
227
  end
219
228
  context.user[:ip_address] = calculate_real_ip_from_rack
229
+
230
+ if request_id = Utils::RequestId.read_from(context.rack_env)
231
+ context.tags[:request_id] = request_id
232
+ end
220
233
  end
221
234
 
222
235
  # When behind a proxy (or if the user is using a proxy), we can't use
@@ -231,10 +244,7 @@ module Raven
231
244
  end
232
245
 
233
246
  def async_json_processors
234
- [
235
- Raven::Processor::RemoveCircularReferences,
236
- Raven::Processor::UTF8Conversion
237
- ].map { |v| v.new(self) }
247
+ configuration.processors.map { |v| v.new(self) }
238
248
  end
239
249
 
240
250
  def list_gem_specs
@@ -0,0 +1,17 @@
1
+ module DeprecationHelper
2
+ def self.deprecate_dasherized_filename(correct_filename)
3
+ warn "[Deprecation Warning] Dasherized filename \"#{correct_filename.gsub('_', '-')}\" is deprecated and will be removed in 4.0; use \"#{correct_filename}\" instead" # rubocop:disable Style/LineLength
4
+ end
5
+
6
+ def self.deprecate_old_breadcrumbs_configuration(logger)
7
+ deprecated_usage =
8
+ if logger == :sentry_logger
9
+ "require \"raven/breadcrumbs/logger\""
10
+ else
11
+ "Raven.configuration.rails_activesupport_breadcrumbs = true"
12
+ end
13
+ recommended_usage = "Raven.configuration.breadcrumbs_logger = :#{logger}"
14
+
15
+ warn "[Deprecation Warning] The way you enable breadcrumbs logger (#{deprecated_usage}) is deprecated and will be removed in 4.0; use '#{recommended_usage}' instead" # rubocop:disable Style/LineLength
16
+ end
17
+ end
@@ -50,7 +50,9 @@ module Raven
50
50
 
51
51
  # Tell the log that the client is good to go
52
52
  def report_status
53
+ return unless configuration.enabled_in_current_env?
53
54
  return if configuration.silence_ready
55
+
54
56
  if configuration.capture_allowed?
55
57
  logger.info "Raven #{VERSION} ready to catch errors"
56
58
  else
@@ -75,7 +77,7 @@ module Raven
75
77
  # Send an event to the configured Sentry server
76
78
  #
77
79
  # @example
78
- # evt = Raven::Event.new(:message => "An error")
80
+ # evt = Raven::Event.new(:message => "An errore)
79
81
  # Raven.send_event(evt)
80
82
  def send_event(event, hint = nil)
81
83
  client.send_event(event, hint)
@@ -109,17 +111,20 @@ module Raven
109
111
  end
110
112
 
111
113
  message_or_exc = obj.is_a?(String) ? "message" : "exception"
114
+ options = options.deep_dup
112
115
  options[:configuration] = configuration
113
116
  options[:context] = context
114
- if (evt = Event.send("from_" + message_or_exc, obj, options))
117
+ options[:breadcrumbs] = breadcrumbs
118
+
119
+ if evt = Event.send("from_" + message_or_exc, obj, options)
115
120
  yield evt if block_given?
116
121
  if configuration.async?
117
122
  begin
118
123
  # We have to convert to a JSON-like hash, because background job
119
124
  # processors (esp ActiveJob) may not like weird types in the event hash
120
125
  configuration.async.call(evt.to_json_compatible)
121
- rescue => ex
122
- logger.error("async event sending failed: #{ex.message}")
126
+ rescue => e
127
+ logger.error("async event sending failed: #{e.message}")
123
128
  send_event(evt, make_hint(obj))
124
129
  end
125
130
  else
@@ -171,7 +176,18 @@ module Raven
171
176
  # @example
172
177
  # Raven.user_context('id' => 1, 'email' => 'foo@example.com')
173
178
  def user_context(options = nil)
174
- context.user = options || {}
179
+ original_user_context = context.user
180
+
181
+ if options
182
+ context.user.merge!(options)
183
+ else
184
+ context.user = {}
185
+ end
186
+
187
+ yield if block_given?
188
+ context.user
189
+ ensure
190
+ context.user = original_user_context if block_given?
175
191
  end
176
192
 
177
193
  # Bind tags context. Merges with existing context (if any).
@@ -183,6 +199,10 @@ module Raven
183
199
  # Raven.tags_context('my_custom_tag' => 'tag_value')
184
200
  def tags_context(options = nil)
185
201
  context.tags.merge!(options || {})
202
+ yield if block_given?
203
+ context.tags
204
+ ensure
205
+ context.tags.delete_if { |k, _| options.keys.include? k } if block_given?
186
206
  end
187
207
 
188
208
  # Bind extra context. Merges with existing context (if any).
@@ -194,6 +214,10 @@ module Raven
194
214
  # Raven.extra_context('my_custom_data' => 'value')
195
215
  def extra_context(options = nil)
196
216
  context.extra.merge!(options || {})
217
+ yield if block_given?
218
+ context.extra
219
+ ensure
220
+ context.extra.delete_if { |k, _| options.keys.include? k } if block_given?
197
221
  end
198
222
 
199
223
  def rack_context(env)