sentry-raven 1.1.0 → 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 (69) 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 +17 -8
  11. data/lib/raven/base.rb +45 -194
  12. data/lib/raven/breadcrumbs/active_support_logger.rb +25 -0
  13. data/lib/raven/breadcrumbs/logger.rb +3 -0
  14. data/lib/raven/breadcrumbs/sentry_logger.rb +73 -0
  15. data/lib/raven/breadcrumbs.rb +76 -0
  16. data/lib/raven/cli.rb +31 -39
  17. data/lib/raven/client.rb +45 -32
  18. data/lib/raven/configuration.rb +427 -130
  19. data/lib/raven/context.rb +33 -6
  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 +194 -206
  23. data/lib/raven/helpers/deprecation_helper.rb +17 -0
  24. data/lib/raven/instance.rb +249 -0
  25. data/lib/raven/integrations/delayed_job.rb +25 -23
  26. data/lib/raven/integrations/rack-timeout.rb +22 -0
  27. data/lib/raven/integrations/rack.rb +40 -24
  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 +39 -7
  33. data/lib/raven/integrations/rake.rb +7 -2
  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 -48
  37. data/lib/raven/integrations/tasks.rb +1 -1
  38. data/lib/raven/interface.rb +25 -0
  39. data/lib/raven/interfaces/exception.rb +5 -8
  40. data/lib/raven/interfaces/http.rb +5 -12
  41. data/lib/raven/interfaces/message.rb +10 -6
  42. data/lib/raven/interfaces/single_exception.rb +1 -5
  43. data/lib/raven/interfaces/stack_trace.rb +23 -30
  44. data/lib/raven/linecache.rb +35 -23
  45. data/lib/raven/logger.rb +13 -16
  46. data/lib/raven/processor/cookies.rb +27 -7
  47. data/lib/raven/processor/http_headers.rb +55 -0
  48. data/lib/raven/processor/post_data.rb +16 -3
  49. data/lib/raven/processor/removecircularreferences.rb +12 -8
  50. data/lib/raven/processor/removestacktrace.rb +17 -6
  51. data/lib/raven/processor/sanitizedata.rb +92 -37
  52. data/lib/raven/processor/utf8conversion.rb +39 -14
  53. data/lib/raven/processor.rb +5 -1
  54. data/lib/raven/transports/http.rb +31 -22
  55. data/lib/raven/transports/stdout.rb +20 -0
  56. data/lib/raven/transports.rb +6 -10
  57. data/lib/raven/utils/context_filter.rb +42 -0
  58. data/lib/raven/utils/deep_merge.rb +6 -12
  59. data/lib/raven/utils/exception_cause_chain.rb +20 -0
  60. data/lib/raven/utils/real_ip.rb +62 -0
  61. data/lib/raven/utils/request_id.rb +16 -0
  62. data/lib/raven/version.rb +2 -1
  63. data/lib/sentry-raven-without-integrations.rb +6 -1
  64. data/lib/sentry_raven_without_integrations.rb +1 -0
  65. data/sentry-raven.gemspec +28 -0
  66. metadata +44 -127
  67. data/lib/raven/error.rb +0 -4
  68. data/lib/raven/interfaces.rb +0 -34
  69. data/lib/raven/okjson.rb +0 -614
data/lib/raven/event.rb CHANGED
@@ -1,200 +1,127 @@
1
1
  # frozen_string_literal: true
2
- require 'rubygems'
2
+
3
3
  require 'socket'
4
4
  require 'securerandom'
5
- require 'digest/md5'
6
-
7
- require 'raven/error'
8
- require 'raven/linecache'
9
5
 
10
6
  module Raven
11
-
12
7
  class Event
13
- LOG_LEVELS = {
14
- "debug" => 10,
15
- "info" => 20,
16
- "warn" => 30,
17
- "warning" => 30,
18
- "error" => 40,
19
- "fatal" => 50,
20
- }.freeze
21
-
22
- BACKTRACE_RE = /^(.+?):(\d+)(?::in `(.+?)')?$/
23
-
24
- PLATFORM = "ruby".freeze
25
-
26
- attr_reader :id
27
- attr_accessor :project, :message, :timestamp, :time_spent, :level, :logger,
28
- :culprit, :server_name, :release, :modules, :extra, :tags, :context, :configuration,
29
- :checksum, :fingerprint
30
-
31
- def initialize(init = {})
32
- @configuration = Raven.configuration
33
- @interfaces = {}
34
- @context = Raven.context
35
- @id = generate_event_id
36
- @project = nil
37
- @message = nil
38
- @timestamp = Time.now.utc
39
- @time_spent = nil
40
- @level = :error
41
- @logger = ''
42
- @culprit = nil
43
- @server_name = @configuration.server_name
44
- @release = @configuration.release
45
- @modules = list_gem_specs if @configuration.send_modules
46
- @user = {}
47
- @extra = {}
48
- @tags = {}
49
- @checksum = nil
50
- @fingerprint = nil
51
-
52
- yield self if block_given?
53
-
54
- if !self[:http] && @context.rack_env
55
- interface :http do |int|
56
- int.from_rack(@context.rack_env)
57
- end
8
+ # See Sentry server default limits at
9
+ # https://github.com/getsentry/sentry/blob/master/src/sentry/conf/server.py
10
+ MAX_MESSAGE_SIZE_IN_BYTES = 1024 * 8
11
+ REQUIRED_OPTION_KEYS = [:configuration, :context, :breadcrumbs].freeze
12
+
13
+ SDK = { "name" => "raven-ruby", "version" => Raven::VERSION }.freeze
14
+
15
+ attr_accessor :id, :logger, :transaction, :server_name, :release, :modules,
16
+ :extra, :tags, :context, :configuration, :checksum,
17
+ :fingerprint, :environment, :server_os, :runtime,
18
+ :breadcrumbs, :user, :backtrace, :platform, :sdk
19
+ alias event_id id
20
+
21
+ attr_reader :level, :timestamp, :time_spent
22
+
23
+ def initialize(options)
24
+ # Set some simple default values
25
+ self.id = SecureRandom.uuid.delete("-")
26
+ self.timestamp = Time.now.utc
27
+ self.level = :error
28
+ self.logger = :ruby
29
+ self.platform = :ruby
30
+ self.sdk = SDK
31
+
32
+ # Set some attributes with empty hashes to allow merging
33
+ @interfaces = {}
34
+ self.user = {} # TODO: contexts
35
+ self.extra = {} # TODO: contexts
36
+ self.server_os = {} # TODO: contexts
37
+ self.runtime = {} # TODO: contexts
38
+ self.tags = {} # TODO: contexts
39
+
40
+ unless REQUIRED_OPTION_KEYS.all? { |key| options.key?(key) }
41
+ raise "you must provide configuration, context, and breadcrumbs when initializing a Raven::Event"
58
42
  end
59
43
 
60
- init.each_pair { |key, val| instance_variable_set('@' + key.to_s, val) }
44
+ self.configuration = options[:configuration]
45
+ self.context = options[:context]
46
+ self.breadcrumbs = options[:breadcrumbs]
61
47
 
62
- @user = @context.user.merge(@user)
63
- @extra = @context.extra.merge(@extra)
64
- @tags = @configuration.tags.merge(@context.tags).merge(@tags)
48
+ # Allow attributes to be set on the event at initialization
49
+ yield self if block_given?
50
+ options.each_pair { |key, val| public_send("#{key}=", val) unless val.nil? }
65
51
 
66
- # Some type coercion
67
- @timestamp = @timestamp.strftime('%Y-%m-%dT%H:%M:%S') if @timestamp.is_a?(Time)
68
- @time_spent = (@time_spent*1000).to_i if @time_spent.is_a?(Float)
69
- @level = LOG_LEVELS[@level.to_s.downcase] if @level.is_a?(String) || @level.is_a?(Symbol)
52
+ set_core_attributes_from_configuration
53
+ set_core_attributes_from_context
70
54
  end
71
55
 
72
- class << self
73
- def from_exception(exc, options = {}, &block)
74
- exception_context = get_exception_context(exc) || {}
75
- options = Raven::Utils::DeepMergeHash.deep_merge(exception_context, options)
76
-
77
- configuration = options[:configuration] || Raven.configuration
78
- if exc.is_a?(Raven::Error)
79
- # Try to prevent error reporting loops
80
- Raven.logger.info "Refusing to capture Raven error: #{exc.inspect}"
81
- return nil
82
- end
83
- if configuration[:excluded_exceptions].any? { |x| (x === exc rescue false) || x == exc.class.name }
84
- Raven.logger.info "User excluded error: #{exc.inspect}"
85
- return nil
86
- end
87
-
88
- new(options) do |evt|
89
- evt.configuration = configuration
90
- evt.message = "#{exc.class}: #{exc.message}"
91
- evt.level = options[:level] || :error
92
-
93
- add_exception_interface(evt, exc)
94
-
95
- yield evt if block
96
- end
56
+ def self.from_exception(exc, options = {}, &block)
57
+ exception_context = if exc.instance_variable_defined?(:@__raven_context)
58
+ exc.instance_variable_get(:@__raven_context)
59
+ elsif exc.respond_to?(:raven_context)
60
+ exc.raven_context
61
+ else
62
+ {}
63
+ end
64
+ options = Raven::Utils::DeepMergeHash.deep_merge(exception_context, options)
65
+
66
+ return unless options[:configuration].exception_class_allowed?(exc)
67
+
68
+ new(options) do |evt|
69
+ evt.add_exception_interface(exc)
70
+ yield evt if block
97
71
  end
72
+ end
98
73
 
99
- def from_message(message, options = {})
100
- configuration = options[:configuration] || Raven.configuration
101
- new(options) do |evt|
102
- evt.configuration = configuration
103
- evt.message = message
104
- evt.level = options[:level] || :error
105
- evt.interface :message do |int|
106
- int.message = message
107
- end
108
- if options[:backtrace]
109
- evt.interface(:stacktrace) do |int|
110
- stacktrace_interface_from(int, evt, options[:backtrace])
111
- end
74
+ def self.from_message(message, options = {})
75
+ new(options) do |evt|
76
+ evt.message = message, options[:message_params] || []
77
+ if options[:backtrace]
78
+ evt.interface(:stacktrace) do |int|
79
+ int.frames = evt.stacktrace_interface_from(options[:backtrace])
112
80
  end
113
81
  end
114
82
  end
83
+ end
115
84
 
116
- private
85
+ def message
86
+ @interfaces[:logentry]&.unformatted_message
87
+ end
117
88
 
118
- def get_exception_context(exc)
119
- if exc.instance_variable_defined?(:@__raven_context)
120
- exc.instance_variable_get(:@__raven_context)
121
- elsif exc.respond_to?(:raven_context)
122
- exc.raven_context
123
- end
89
+ def message=(args)
90
+ if args.is_a?(Array)
91
+ message, params = args[0], args[0..-1]
92
+ else
93
+ message = args
124
94
  end
125
95
 
126
- def add_exception_interface(evt, exc)
127
- evt.interface(:exception) do |exc_int|
128
- exceptions = [exc]
129
- context = Set.new [exc.object_id]
130
- backtraces = Set.new
131
-
132
- while exc.respond_to?(:cause) && exc.cause
133
- exc = exc.cause
134
- if context.include?(exc.object_id)
135
- break
136
- end
137
- exceptions << exc
138
- context.add(exc.object_id)
139
- end
140
- exceptions.reverse!
141
-
142
- exc_int.values = exceptions.map do |e|
143
- SingleExceptionInterface.new do |int|
144
- int.type = e.class.to_s
145
- int.value = e.to_s
146
- int.module = e.class.to_s.split('::')[0...-1].join('::')
147
-
148
- int.stacktrace =
149
- if e.backtrace && !backtraces.include?(e.backtrace.object_id)
150
- backtraces << e.backtrace.object_id
151
- StacktraceInterface.new do |stacktrace|
152
- stacktrace_interface_from(stacktrace, evt, e.backtrace)
153
- end
154
- end
155
- end
156
- end
157
- end
96
+ unless message.is_a?(String)
97
+ configuration.logger.debug("You're passing a non-string message")
98
+ message = message.to_s
158
99
  end
159
100
 
160
- def stacktrace_interface_from(int, evt, backtrace)
161
- backtrace = Backtrace.parse(backtrace)
162
-
163
- int.frames = []
164
- backtrace.lines.reverse_each do |line|
165
- frame = StacktraceInterface::Frame.new
166
- frame.abs_path = line.file if line.file
167
- frame.function = line.method if line.method
168
- frame.lineno = line.number
169
- frame.in_app = line.in_app
170
- frame.module = line.module_name if line.module_name
171
-
172
- if evt.configuration[:context_lines] && frame.abs_path
173
- frame.pre_context, frame.context_line, frame.post_context = \
174
- evt.get_file_context(frame.abs_path, frame.lineno, evt.configuration[:context_lines])
175
- end
176
-
177
- int.frames << frame if frame.filename
178
- end
179
-
180
- evt.culprit = evt.get_culprit(int.frames)
101
+ interface(:message) do |int|
102
+ int.message = message.byteslice(0...MAX_MESSAGE_SIZE_IN_BYTES) # Messages limited to 10kb
103
+ int.params = params
181
104
  end
105
+ end
182
106
 
183
- # Because linecache can go to hell
184
- def _source_lines(_path, _from, _to)
185
- end
107
+ def timestamp=(time)
108
+ @timestamp = time.is_a?(Time) ? time.strftime('%Y-%m-%dT%H:%M:%S') : time
186
109
  end
187
110
 
188
- def list_gem_specs
189
- # Older versions of Rubygems don't support iterating over all specs
190
- Hash[Gem::Specification.map { |spec| [spec.name, spec.version.to_s] }] if Gem::Specification.respond_to?(:map)
111
+ def time_spent=(time)
112
+ @time_spent = time.is_a?(Float) ? (time * 1000).to_i : time
113
+ end
114
+
115
+ def level=(new_level) # needed to meet the Sentry spec
116
+ @level = new_level.to_s == "warn" ? :warning : new_level
191
117
  end
192
118
 
193
119
  def interface(name, value = nil, &block)
194
- int = Raven.find_interface(name)
195
- raise Error.new("Unknown interface: #{name}") unless int
196
- @interfaces[int.name] = int.new(value, &block) if value || block
197
- @interfaces[int.name]
120
+ int = Interface.registered[name]
121
+ raise(Error, "Unknown interface: #{name}") unless int
122
+
123
+ @interfaces[int.sentry_alias] = int.new(value, &block) if value || block
124
+ @interfaces[int.sentry_alias]
198
125
  end
199
126
 
200
127
  def [](key)
@@ -206,62 +133,123 @@ module Raven
206
133
  end
207
134
 
208
135
  def to_hash
209
- data = {
210
- :event_id => @id,
211
- :message => @message,
212
- :timestamp => @timestamp,
213
- :time_spent => @time_spent,
214
- :level => @level,
215
- :project => @project,
216
- :platform => PLATFORM,
217
- }
218
- data[:logger] = @logger if @logger
219
- data[:culprit] = @culprit if @culprit
220
- data[:server_name] = @server_name if @server_name
221
- data[:release] = @release if @release
222
- data[:fingerprint] = @fingerprint if @fingerprint
223
- data[:modules] = @modules if @modules
224
- data[:extra] = @extra if @extra
225
- data[:tags] = @tags if @tags
226
- data[:user] = @user if @user
227
- data[:checksum] = @checksum if @checksum
136
+ data = [:checksum, :environment, :event_id, :extra, :fingerprint, :level,
137
+ :logger, :message, :modules, :platform, :release, :sdk, :server_name,
138
+ :tags, :time_spent, :timestamp, :transaction, :user].each_with_object({}) do |att, memo|
139
+ memo[att] = public_send(att) if public_send(att)
140
+ end
141
+
142
+ data[:breadcrumbs] = @breadcrumbs.to_hash unless @breadcrumbs.empty?
143
+
228
144
  @interfaces.each_pair do |name, int_data|
229
145
  data[name.to_sym] = int_data.to_hash
230
146
  end
231
147
  data
232
148
  end
233
149
 
234
- def get_file_context(filename, lineno, context)
235
- return nil, nil, nil unless Raven::LineCache.is_valid_file(filename)
236
- lines = Array.new(2 * context + 1) do |i|
237
- Raven::LineCache.getline(filename, lineno - context + i)
150
+ def to_json_compatible
151
+ cleaned_hash = async_json_processors.reduce(to_hash) { |a, e| e.process(a) }
152
+ JSON.parse(JSON.generate(cleaned_hash))
153
+ end
154
+
155
+ def add_exception_interface(exc)
156
+ interface(:exception) do |exc_int|
157
+ exceptions = Raven::Utils::ExceptionCauseChain.exception_to_array(exc).reverse
158
+ backtraces = Set.new
159
+ exc_int.values = exceptions.map do |e|
160
+ SingleExceptionInterface.new do |int|
161
+ int.type = e.class.to_s
162
+ int.value = e.to_s
163
+ int.module = e.class.to_s.split('::')[0...-1].join('::')
164
+
165
+ int.stacktrace =
166
+ if e.backtrace && !backtraces.include?(e.backtrace.object_id)
167
+ backtraces << e.backtrace.object_id
168
+ StacktraceInterface.new do |stacktrace|
169
+ stacktrace.frames = stacktrace_interface_from(e.backtrace)
170
+ end
171
+ end
172
+ end
173
+ end
238
174
  end
239
- [lines[0..(context - 1)], lines[context], lines[(context + 1)..-1]]
240
175
  end
241
176
 
242
- def get_culprit(frames)
243
- lastframe = frames.reverse.find(&:in_app) || frames.last
244
- "#{lastframe.filename} in #{lastframe.function} at line #{lastframe.lineno}" if lastframe
177
+ def stacktrace_interface_from(backtrace)
178
+ Backtrace.parse(backtrace, { configuration: configuration }).lines.reverse.each_with_object([]) do |line, memo|
179
+ frame = StacktraceInterface::Frame.new
180
+ frame.abs_path = line.file if line.file
181
+ frame.function = line.method if line.method
182
+ frame.lineno = line.number
183
+ frame.in_app = line.in_app
184
+ frame.module = line.module_name if line.module_name
185
+
186
+ if configuration[:context_lines] && frame.abs_path
187
+ frame.pre_context, frame.context_line, frame.post_context = \
188
+ configuration.linecache.get_file_context(frame.abs_path, frame.lineno, configuration[:context_lines])
189
+ end
190
+
191
+ memo << frame if frame.filename
192
+ end
245
193
  end
246
194
 
247
195
  # For cross-language compat
248
196
  class << self
249
- alias :captureException :from_exception
250
- alias :captureMessage :from_message
251
- alias :capture_exception :from_exception
252
- alias :capture_message :from_message
197
+ alias captureException from_exception
198
+ alias captureMessage from_message
199
+ alias capture_exception from_exception
200
+ alias capture_message from_message
253
201
  end
254
202
 
255
203
  private
256
204
 
257
- def generate_event_id
258
- # generate a uuid. copy-pasted from SecureRandom, this method is not
259
- # available in <1.9.
260
- ary = SecureRandom.random_bytes(16).unpack("NnnnnN")
261
- ary[2] = (ary[2] & 0x0fff) | 0x4000
262
- ary[3] = (ary[3] & 0x3fff) | 0x8000
263
- uuid = "%08x-%04x-%04x-%04x-%04x%08x" % ary
264
- ::Digest::MD5.hexdigest(uuid)
205
+ def set_core_attributes_from_configuration
206
+ self.server_name ||= configuration.server_name
207
+ self.release ||= configuration.release
208
+ self.modules = list_gem_specs if configuration.send_modules
209
+ self.environment ||= configuration.current_environment
210
+ end
211
+
212
+ def set_core_attributes_from_context
213
+ self.transaction ||= context.transaction.last
214
+
215
+ # If this is a Rack event, merge Rack context
216
+ add_rack_context if !self[:http] && context.rack_env
217
+
218
+ # Merge contexts
219
+ self.user = context.user.merge(user) # TODO: contexts
220
+ self.extra = context.extra.merge(extra) # TODO: contexts
221
+ self.tags = configuration.tags.merge(context.tags).merge!(tags) # TODO: contexts
222
+ end
223
+
224
+ def add_rack_context
225
+ interface :http do |int|
226
+ int.from_rack(context.rack_env)
227
+ end
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
233
+ end
234
+
235
+ # When behind a proxy (or if the user is using a proxy), we can't use
236
+ # REMOTE_ADDR to determine the Event IP, and must use other headers instead.
237
+ def calculate_real_ip_from_rack
238
+ Utils::RealIp.new(
239
+ :remote_addr => context.rack_env["REMOTE_ADDR"],
240
+ :client_ip => context.rack_env["HTTP_CLIENT_IP"],
241
+ :real_ip => context.rack_env["HTTP_X_REAL_IP"],
242
+ :forwarded_for => context.rack_env["HTTP_X_FORWARDED_FOR"]
243
+ ).calculate_ip
244
+ end
245
+
246
+ def async_json_processors
247
+ configuration.processors.map { |v| v.new(self) }
248
+ end
249
+
250
+ def list_gem_specs
251
+ # Older versions of Rubygems don't support iterating over all specs
252
+ Hash[Gem::Specification.map { |spec| [spec.name, spec.version.to_s] }] if Gem::Specification.respond_to?(:map)
265
253
  end
266
254
  end
267
255
  end
@@ -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