sentry-raven 2.6.3 → 2.7.1

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.
@@ -10,7 +10,7 @@ module Raven
10
10
  Thread.current[:sentry_context] = nil
11
11
  end
12
12
 
13
- attr_accessor :extra, :server_os, :rack_env, :runtime, :tags, :user
13
+ attr_accessor :transaction, :extra, :server_os, :rack_env, :runtime, :tags, :user
14
14
 
15
15
  def initialize
16
16
  self.server_os = self.class.os_context
@@ -19,6 +19,7 @@ module Raven
19
19
  self.rack_env = nil
20
20
  self.tags = {}
21
21
  self.user = {}
22
+ self.transaction = []
22
23
  end
23
24
 
24
25
  class << self
@@ -1,227 +1,117 @@
1
1
  # frozen_string_literal: true
2
- require 'rubygems'
3
2
  require 'socket'
4
3
  require 'securerandom'
5
- require 'digest/md5'
6
-
7
- require 'raven/error'
8
- require 'raven/linecache'
9
4
 
10
5
  module Raven
11
6
  class Event
12
- LOG_LEVELS = {
13
- "debug" => 10,
14
- "info" => 20,
15
- "warn" => 30,
16
- "warning" => 30,
17
- "error" => 40,
18
- "fatal" => 50
19
- }.freeze
20
-
21
- BACKTRACE_RE = /^(.+?):(\d+)(?::in `(.+?)')?$/
22
7
  # See Sentry server default limits at
23
8
  # https://github.com/getsentry/sentry/blob/master/src/sentry/conf/server.py
24
9
  MAX_MESSAGE_SIZE_IN_BYTES = 1024 * 8
25
10
 
26
- PLATFORM = "ruby".freeze
27
11
  SDK = { "name" => "raven-ruby", "version" => Raven::VERSION }.freeze
28
12
 
29
13
  attr_accessor :id, :timestamp, :time_spent, :level, :logger,
30
- :culprit, :server_name, :release, :modules, :extra, :tags,
14
+ :transaction, :server_name, :release, :modules, :extra, :tags,
31
15
  :context, :configuration, :checksum, :fingerprint, :environment,
32
- :server_os, :runtime, :breadcrumbs, :user, :backtrace, :linecache
16
+ :server_os, :runtime, :breadcrumbs, :user, :backtrace, :platform,
17
+ :sdk
18
+ alias event_id id
33
19
 
34
20
  def initialize(init = {})
35
- @configuration = init[:configuration] || Raven.configuration
36
- @interfaces = {}
37
- @breadcrumbs = init[:breadcrumbs] || Raven.breadcrumbs
38
- @context = init[:context] || Raven.context
39
- @linecache = @configuration.linecache
40
- @id = SecureRandom.uuid.delete("-")
41
- @timestamp = Time.now.utc
42
- @time_spent = nil
43
- @level = :error
44
- @logger = 'ruby'
45
- @culprit = nil
46
- @server_name = @configuration.server_name
47
- @release = @configuration.release
48
- @modules = list_gem_specs if @configuration.send_modules
49
- @user = {} # TODO: contexts
50
- @extra = {} # TODO: contexts
51
- @server_os = {} # TODO: contexts
52
- @runtime = {} # TODO: contexts
53
- @tags = {} # TODO: contexts
54
- @checksum = nil
55
- @fingerprint = nil
56
- @environment = @configuration.current_environment
21
+ self.configuration = Raven.configuration
22
+ self.breadcrumbs = Raven.breadcrumbs
23
+ self.context = Raven.context
24
+ self.id = SecureRandom.uuid.delete("-")
25
+ self.timestamp = Time.now.utc
26
+ self.level = :error
27
+ self.logger = :ruby
28
+ self.platform = :ruby
29
+ self.sdk = SDK
30
+ @interfaces = {}
31
+ self.user = {} # TODO: contexts
32
+ self.extra = {} # TODO: contexts
33
+ self.server_os = {} # TODO: contexts
34
+ self.runtime = {} # TODO: contexts
35
+ self.tags = {} # TODO: contexts
57
36
 
58
37
  yield self if block_given?
59
38
 
60
- if !self[:http] && @context.rack_env
39
+ init.each_pair { |key, val| public_send("#{key}=", val) }
40
+
41
+ self.transaction ||= context.transaction.last
42
+ self.server_name ||= configuration.server_name
43
+ self.release ||= configuration.release
44
+ self.modules = list_gem_specs if configuration.send_modules
45
+ self.environment ||= configuration.current_environment
46
+
47
+ if !self[:http] && context.rack_env
61
48
  interface :http do |int|
62
- int.from_rack(@context.rack_env)
49
+ int.from_rack(context.rack_env)
63
50
  end
64
- end
65
51
 
66
- if @context.rack_env # TODO: contexts
67
- @context.user[:ip_address] = calculate_real_ip_from_rack
52
+ context.user[:ip_address] = calculate_real_ip_from_rack
68
53
  end
69
54
 
70
- init.each_pair { |key, val| public_send(key.to_s + "=", val) }
71
-
72
- @user = @context.user.merge(@user) # TODO: contexts
73
- @extra = @context.extra.merge(@extra) # TODO: contexts
74
- @tags = @configuration.tags.merge(@context.tags).merge(@tags) # TODO: contexts
75
-
76
- # Some type coercion
77
- @timestamp = @timestamp.strftime('%Y-%m-%dT%H:%M:%S') if @timestamp.is_a?(Time)
78
- @time_spent = (@time_spent * 1000).to_i if @time_spent.is_a?(Float)
79
- @level = LOG_LEVELS[@level.to_s.downcase] if @level.is_a?(String) || @level.is_a?(Symbol)
55
+ self.user = context.user.merge(user) # TODO: contexts
56
+ self.extra = context.extra.merge(extra) # TODO: contexts
57
+ self.tags = configuration.tags.merge(context.tags).merge(tags) # TODO: contexts
80
58
  end
81
59
 
82
- def message
83
- @interfaces[:logentry] && @interfaces[:logentry].unformatted_message
84
- end
60
+ def self.from_exception(exc, options = {}, &block)
61
+ exception_context = if exc.instance_variable_defined?(:@__raven_context)
62
+ exc.instance_variable_get(:@__raven_context)
63
+ elsif exc.respond_to?(:raven_context)
64
+ exc.raven_context
65
+ else
66
+ {}
67
+ end
68
+ options = Raven::Utils::DeepMergeHash.deep_merge(exception_context, options)
85
69
 
86
- def message=(args)
87
- message, params = *args
88
- interface(:message) do |int|
89
- int.message = message
90
- int.params = params
91
- end
92
- end
93
-
94
- class << self
95
- def from_exception(exc, options = {}, &block)
96
- exception_context = get_exception_context(exc) || {}
97
- options = Raven::Utils::DeepMergeHash.deep_merge(exception_context, options)
98
-
99
- configuration = options[:configuration] || Raven.configuration
100
- if exc.is_a?(Raven::Error)
101
- # Try to prevent error reporting loops
102
- configuration.logger.debug "Refusing to capture Raven error: #{exc.inspect}"
103
- return nil
104
- end
105
- if configuration[:excluded_exceptions].any? { |x| get_exception_class(x) === exc }
106
- configuration.logger.debug "User excluded error: #{exc.inspect}"
107
- return nil
108
- end
70
+ configuration = options[:configuration] || Raven.configuration
71
+ return unless configuration.exception_class_allowed?(exc)
109
72
 
110
- new(options) do |evt|
111
- evt.configuration = configuration
112
- evt.message = "#{exc.class}: #{exc.message}".byteslice(0...MAX_MESSAGE_SIZE_IN_BYTES) # Messages limited to 10kb
113
- evt.level = options[:level] || :error
73
+ new(options) do |evt|
74
+ evt.message = "#{exc.class}: #{exc.message}"
114
75
 
115
- add_exception_interface(evt, exc)
76
+ evt.add_exception_interface(exc)
116
77
 
117
- yield evt if block
118
- end
78
+ yield evt if block
119
79
  end
80
+ end
120
81
 
121
- def from_message(message, options = {})
122
- message = message.byteslice(0...MAX_MESSAGE_SIZE_IN_BYTES)
123
- configuration = options[:configuration] || Raven.configuration
124
-
125
- new(options) do |evt|
126
- evt.configuration = configuration
127
- evt.level = options[:level] || :error
128
- evt.message = message, options[:message_params] || []
129
- if options[:backtrace]
130
- evt.interface(:stacktrace) do |int|
131
- stacktrace_interface_from(int, evt, options[:backtrace])
132
- end
82
+ def self.from_message(message, options = {})
83
+ new(options) do |evt|
84
+ evt.message = message, options[:message_params] || []
85
+ if options[:backtrace]
86
+ evt.interface(:stacktrace) do |int|
87
+ int.frames = evt.stacktrace_interface_from(options[:backtrace])
133
88
  end
134
89
  end
135
90
  end
91
+ end
136
92
 
137
- private
138
-
139
- def get_exception_class(x)
140
- x.is_a?(Module) ? x : qualified_const_get(x)
141
- end
142
-
143
- # In Ruby <2.0 const_get can't lookup "SomeModule::SomeClass" in one go
144
- def qualified_const_get(x)
145
- x = x.to_s
146
- parts = x.split("::")
147
- parts.reject!(&:empty?)
148
-
149
- if parts.size < 2
150
- Object.const_get(x)
151
- else
152
- parts.inject(Object) { |a, e| a.const_get(e) }
153
- end
154
- rescue NameError # There's no way to safely ask if a constant exist for an unknown string
155
- nil
156
- end
157
-
158
- def get_exception_context(exc)
159
- if exc.instance_variable_defined?(:@__raven_context)
160
- exc.instance_variable_get(:@__raven_context)
161
- elsif exc.respond_to?(:raven_context)
162
- exc.raven_context
163
- end
164
- end
93
+ def message
94
+ @interfaces[:logentry] && @interfaces[:logentry].unformatted_message
95
+ end
165
96
 
166
- def add_exception_interface(evt, exc)
167
- evt.interface(:exception) do |exc_int|
168
- exceptions = [exc]
169
- context = Set.new [exc.object_id]
170
- backtraces = Set.new
171
-
172
- while exc.respond_to?(:cause) && exc.cause
173
- exc = exc.cause
174
- break if context.include?(exc.object_id)
175
- exceptions << exc
176
- context.add(exc.object_id)
177
- end
178
- exceptions.reverse!
179
-
180
- exc_int.values = exceptions.map do |e|
181
- SingleExceptionInterface.new do |int|
182
- int.type = e.class.to_s
183
- int.value = e.to_s
184
- int.module = e.class.to_s.split('::')[0...-1].join('::')
185
-
186
- int.stacktrace =
187
- if e.backtrace && !backtraces.include?(e.backtrace.object_id)
188
- backtraces << e.backtrace.object_id
189
- StacktraceInterface.new do |stacktrace|
190
- stacktrace_interface_from(stacktrace, evt, e.backtrace)
191
- end
192
- end
193
- end
194
- end
195
- end
97
+ def message=(args)
98
+ message, params = *args
99
+ interface(:message) do |int|
100
+ int.message = message.byteslice(0...MAX_MESSAGE_SIZE_IN_BYTES) # Messages limited to 10kb
101
+ int.params = params
196
102
  end
103
+ end
197
104
 
198
- def stacktrace_interface_from(int, evt, backtrace)
199
- backtrace = Backtrace.parse(backtrace)
200
-
201
- int.frames = []
202
- backtrace.lines.reverse_each do |line|
203
- frame = StacktraceInterface::Frame.new
204
- frame.abs_path = line.file if line.file
205
- frame.function = line.method if line.method
206
- frame.lineno = line.number
207
- frame.in_app = line.in_app
208
- frame.module = line.module_name if line.module_name
209
-
210
- if evt.configuration[:context_lines] && frame.abs_path
211
- frame.pre_context, frame.context_line, frame.post_context = \
212
- evt.get_file_context(frame.abs_path, frame.lineno, evt.configuration[:context_lines])
213
- end
214
-
215
- int.frames << frame if frame.filename
216
- end
105
+ def timestamp=(time)
106
+ @timestamp = time.is_a?(Time) ? time.strftime('%Y-%m-%dT%H:%M:%S') : time
107
+ end
217
108
 
218
- evt.culprit = evt.get_culprit(int.frames)
219
- end
109
+ def time_spent=(time)
110
+ @time_spent = time.is_a?(Float) ? (time * 1000).to_i : time
220
111
  end
221
112
 
222
- def list_gem_specs
223
- # Older versions of Rubygems don't support iterating over all specs
224
- Hash[Gem::Specification.map { |spec| [spec.name, spec.version.to_s] }] if Gem::Specification.respond_to?(:map)
113
+ def level=(new_level) # needed to meet the Sentry spec
114
+ @level = new_level == "warn" || new_level == :warn ? :warning : new_level
225
115
  end
226
116
 
227
117
  def interface(name, value = nil, &block)
@@ -240,47 +130,63 @@ module Raven
240
130
  end
241
131
 
242
132
  def to_hash
243
- data = {
244
- :event_id => @id,
245
- :timestamp => @timestamp,
246
- :time_spent => @time_spent,
247
- :level => @level,
248
- :platform => PLATFORM,
249
- :sdk => SDK
250
- }
251
-
252
- data[:logger] = @logger if @logger
253
- data[:culprit] = @culprit if @culprit
254
- data[:server_name] = @server_name if @server_name
255
- data[:release] = @release if @release
256
- data[:environment] = @environment if @environment
257
- data[:fingerprint] = @fingerprint if @fingerprint
258
- data[:modules] = @modules if @modules
259
- data[:extra] = @extra if @extra
260
- data[:tags] = @tags if @tags
261
- data[:user] = @user if @user
133
+ data = [:checksum, :environment, :event_id, :extra, :fingerprint, :level,
134
+ :logger, :message, :modules, :platform, :release, :sdk, :server_name,
135
+ :tags, :time_spent, :timestamp, :transaction, :user].each_with_object({}) do |att, memo|
136
+ memo[att] = public_send(att) if public_send(att)
137
+ end
138
+
262
139
  data[:breadcrumbs] = @breadcrumbs.to_hash unless @breadcrumbs.empty?
263
- data[:checksum] = @checksum if @checksum
264
140
 
265
141
  @interfaces.each_pair do |name, int_data|
266
142
  data[name.to_sym] = int_data.to_hash
267
143
  end
268
- data[:message] = message
269
144
  data
270
145
  end
271
146
 
272
- def get_file_context(filename, lineno, context)
273
- linecache.get_file_context(filename, lineno, context)
147
+ def to_json_compatible
148
+ cleaned_hash = async_json_processors.reduce(to_hash) { |a, e| e.process(a) }
149
+ JSON.parse(JSON.generate(cleaned_hash))
274
150
  end
275
151
 
276
- def get_culprit(frames)
277
- lastframe = frames.reverse.find(&:in_app) || frames.last
278
- "#{lastframe.filename} in #{lastframe.function} at line #{lastframe.lineno}" if lastframe
152
+ def add_exception_interface(exc)
153
+ interface(:exception) do |exc_int|
154
+ exceptions = exception_chain_to_array(exc)
155
+ backtraces = Set.new
156
+ exc_int.values = exceptions.map do |e|
157
+ SingleExceptionInterface.new do |int|
158
+ int.type = e.class.to_s
159
+ int.value = e.to_s
160
+ int.module = e.class.to_s.split('::')[0...-1].join('::')
161
+
162
+ int.stacktrace =
163
+ if e.backtrace && !backtraces.include?(e.backtrace.object_id)
164
+ backtraces << e.backtrace.object_id
165
+ StacktraceInterface.new do |stacktrace|
166
+ stacktrace.frames = stacktrace_interface_from(e.backtrace)
167
+ end
168
+ end
169
+ end
170
+ end
171
+ end
279
172
  end
280
173
 
281
- def to_json_compatible
282
- cleaned_hash = async_json_processors.reduce(to_hash) { |a, e| e.process(a) }
283
- JSON.parse(JSON.generate(cleaned_hash))
174
+ def stacktrace_interface_from(backtrace)
175
+ Backtrace.parse(backtrace).lines.reverse.each_with_object([]) do |line, memo|
176
+ frame = StacktraceInterface::Frame.new
177
+ frame.abs_path = line.file if line.file
178
+ frame.function = line.method if line.method
179
+ frame.lineno = line.number
180
+ frame.in_app = line.in_app
181
+ frame.module = line.module_name if line.module_name
182
+
183
+ if configuration[:context_lines] && frame.abs_path
184
+ frame.pre_context, frame.context_line, frame.post_context = \
185
+ configuration.linecache.get_file_context(frame.abs_path, frame.lineno, configuration[:context_lines])
186
+ end
187
+
188
+ memo << frame if frame.filename
189
+ end
284
190
  end
285
191
 
286
192
  # For cross-language compat
@@ -310,5 +216,24 @@ module Raven
310
216
  Raven::Processor::UTF8Conversion
311
217
  ].map { |v| v.new(self) }
312
218
  end
219
+
220
+ def exception_chain_to_array(exc)
221
+ if exc.respond_to?(:cause) && exc.cause
222
+ exceptions = [exc]
223
+ while exc.cause
224
+ exc = exc.cause
225
+ break if exceptions.any? { |e| e.object_id == exc.object_id }
226
+ exceptions << exc
227
+ end
228
+ exceptions.reverse!
229
+ else
230
+ [exc]
231
+ end
232
+ end
233
+
234
+ def list_gem_specs
235
+ # Older versions of Rubygems don't support iterating over all specs
236
+ Hash[Gem::Specification.map { |spec| [spec.name, spec.version.to_s] }] if Gem::Specification.respond_to?(:map)
237
+ end
313
238
  end
314
239
  end
@@ -45,6 +45,7 @@ module Raven
45
45
  # callers
46
46
  env['raven.requested_at'] = Time.now
47
47
  Raven.rack_context(env)
48
+ Raven.context.transaction.push(env["PATH_INFO"]) if env["PATH_INFO"]
48
49
 
49
50
  begin
50
51
  response = @app.call(env)
@@ -4,6 +4,7 @@ module Raven
4
4
  class Rails < ::Rails::Railtie
5
5
  require 'raven/integrations/rails/overrides/streaming_reporter'
6
6
  require 'raven/integrations/rails/controller_methods'
7
+ require 'raven/integrations/rails/controller_transaction'
7
8
 
8
9
  initializer "raven.use_rack_middleware" do |app|
9
10
  app.config.middleware.insert 0, Raven::Rack
@@ -12,6 +13,7 @@ module Raven
12
13
  initializer 'raven.action_controller' do
13
14
  ActiveSupport.on_load :action_controller do
14
15
  include Raven::Rails::ControllerMethods
16
+ include Raven::Rails::ControllerTransaction
15
17
  if ::Rails::VERSION::STRING >= "4.0.0"
16
18
  Raven.safely_prepend(
17
19
  "StreamingReporter",