sentry-raven 2.6.3 → 2.7.1

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