sentry-ruby 0.1.1 → 4.0.0

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,11 +10,8 @@ module Sentry
10
10
  @buffer = Array.new(size)
11
11
  end
12
12
 
13
- def record(crumb = nil)
14
- if block_given?
15
- crumb = Breadcrumb.new if crumb.nil?
16
- yield(crumb)
17
- end
13
+ def record(crumb)
14
+ yield(crumb) if block_given?
18
15
  @buffer.slice!(0)
19
16
  @buffer << crumb
20
17
  end
@@ -1,5 +1,4 @@
1
1
  require "sentry/transport"
2
- require 'sentry/utils/deep_merge'
3
2
 
4
3
  module Sentry
5
4
  class Client
@@ -21,55 +20,51 @@ module Sentry
21
20
  end
22
21
  end
23
22
 
24
- def capture_exception(exception, scope:, **options, &block)
25
- event = event_from_exception(exception, **options)
26
- return unless event
27
-
28
- block.call(event) if block
29
- capture_event(event, scope)
30
- end
31
-
32
- def capture_message(message, scope:, **options, &block)
33
- event = event_from_message(message, **options)
34
- block.call(event) if block
35
- capture_event(event, scope)
36
- end
23
+ def capture_event(event, scope, hint = nil)
24
+ scope.apply_to_event(event, hint)
25
+
26
+ if configuration.async?
27
+ begin
28
+ # We have to convert to a JSON-like hash, because background job
29
+ # processors (esp ActiveJob) may not like weird types in the event hash
30
+ configuration.async.call(event.to_json_compatible)
31
+ rescue => e
32
+ configuration.logger.error(LOGGER_PROGNAME) { "async event sending failed: #{e.message}" }
33
+ send_event(event, hint)
34
+ end
35
+ else
36
+ send_event(event, hint)
37
+ end
37
38
 
38
- def capture_event(event, scope)
39
- scope.apply_to_event(event)
40
- send_event(event)
41
39
  event
42
40
  end
43
41
 
44
- def event_from_exception(exception, **options)
45
- exception_context =
46
- if exception.instance_variable_defined?(:@__sentry_context)
47
- exception.instance_variable_get(:@__sentry_context)
48
- elsif exception.respond_to?(:sentry_context)
49
- exception.sentry_context
50
- else
51
- {}
52
- end
53
-
54
- options = Utils::DeepMergeHash.deep_merge(exception_context, options)
55
-
42
+ def event_from_exception(exception)
56
43
  return unless @configuration.exception_class_allowed?(exception)
57
44
 
58
- options = Event::Options.new(**options)
59
-
60
- Event.new(configuration: configuration, options: options).tap do |event|
45
+ Event.new(configuration: configuration).tap do |event|
61
46
  event.add_exception_interface(exception)
62
47
  end
63
48
  end
64
49
 
65
- def event_from_message(message, **options)
66
- options.merge!(message: message)
67
- options = Event::Options.new(options)
68
- Event.new(configuration: configuration, options: options)
50
+ def event_from_message(message)
51
+ Event.new(configuration: configuration, message: message)
52
+ end
53
+
54
+ def event_from_transaction(transaction)
55
+ TransactionEvent.new(configuration: configuration).tap do |event|
56
+ event.transaction = transaction.name
57
+ event.contexts.merge!(trace: transaction.get_trace_context)
58
+ event.timestamp = transaction.timestamp
59
+ event.start_timestamp = transaction.start_timestamp
60
+
61
+ finished_spans = transaction.span_recorder.spans.select { |span| span.timestamp && span != transaction }
62
+ event.spans = finished_spans.map(&:to_hash)
63
+ end
69
64
  end
70
65
 
71
66
  def send_event(event, hint = nil)
72
- return false unless configuration.sending_allowed?(event)
67
+ return false unless configuration.sending_allowed?
73
68
 
74
69
  event = configuration.before_send.call(event, hint) if configuration.before_send
75
70
  if event.nil?
@@ -15,6 +15,28 @@ module Sentry
15
15
  attr_reader :async
16
16
  alias async? async
17
17
 
18
+ # a proc/lambda that takes an array of stack traces
19
+ # it'll be used to silence (reduce) backtrace of the exception
20
+ #
21
+ # for example:
22
+ #
23
+ # ```ruby
24
+ # Sentry.configuration.backtrace_cleanup_callback = lambda do |backtrace|
25
+ # Rails.backtrace_cleaner.clean(backtrace)
26
+ # end
27
+ # ```
28
+ #
29
+ attr_accessor :backtrace_cleanup_callback
30
+
31
+ # Optional Proc, called before sending an event to the server/
32
+ # E.g.: lambda { |event| event }
33
+ # E.g.: lambda { |event| nil }
34
+ # E.g.: lambda { |event|
35
+ # event[:message] = 'a'
36
+ # event
37
+ # }
38
+ attr_reader :before_send
39
+
18
40
  # An array of breadcrumbs loggers to be used. Available options are:
19
41
  # - :sentry_logger
20
42
  # - :active_support_logger
@@ -24,10 +46,13 @@ module Sentry
24
46
  attr_accessor :context_lines
25
47
 
26
48
  # RACK_ENV by default.
27
- attr_reader :current_environment
49
+ attr_reader :environment
28
50
 
29
- # Whitelist of environments that will send notifications to Sentry. Array of Strings.
30
- attr_accessor :environments
51
+ # the dsn value, whether it's set via `config.dsn=` or `ENV["SENTRY_DSN"]`
52
+ attr_reader :dsn
53
+
54
+ # Whitelist of enabled_environments that will send notifications to Sentry. Array of Strings.
55
+ attr_accessor :enabled_environments
31
56
 
32
57
  # Logger 'progname's to exclude from breadcrumbs
33
58
  attr_accessor :exclude_loggers
@@ -64,54 +89,36 @@ module Sentry
64
89
  # any events, and a value of 1.0 will send 100% of events.
65
90
  attr_accessor :sample_rate
66
91
 
67
- # a proc/lambda that takes an array of stack traces
68
- # it'll be used to silence (reduce) backtrace of the exception
69
- #
70
- # for example:
71
- #
72
- # ```ruby
73
- # Sentry.configuration.backtrace_cleanup_callback = lambda do |backtrace|
74
- # Rails.backtrace_cleaner.clean(backtrace)
75
- # end
76
- # ```
77
- #
78
- attr_accessor :backtrace_cleanup_callback
79
-
80
92
  # Include module versions in reports - boolean.
81
93
  attr_accessor :send_modules
82
94
 
95
+ # When send_default_pii's value is false (default), sensitive information like
96
+ # - user ip
97
+ # - user cookie
98
+ # - request body
99
+ # will not be sent to Sentry.
83
100
  attr_accessor :send_default_pii
84
101
 
85
102
  attr_accessor :server_name
86
103
 
87
- # Provide a configurable callback to determine event capture.
88
- # Note that the object passed into the block will be a String (messages) or
89
- # an exception.
90
- # e.g. lambda { |exc_or_msg| exc_or_msg.some_attr == false }
91
- attr_reader :should_capture
92
-
93
- # Silences ready message when true.
94
- attr_accessor :silence_ready
95
-
96
- # Default tags for events. Hash.
97
- attr_accessor :tags
98
-
104
+ # Return a Transport::Configuration object for transport-related configurations.
99
105
  attr_reader :transport
100
106
 
101
- # Optional Proc, called before sending an event to the server/
102
- # E.g.: lambda { |event, hint| event }
103
- # E.g.: lambda { |event, hint| nil }
104
- # E.g.: lambda { |event, hint|
105
- # event[:message] = 'a'
106
- # event
107
- # }
108
- attr_reader :before_send
107
+ # Take a float between 0.0 and 1.0 as the sample rate for tracing events (transactions).
108
+ attr_accessor :traces_sample_rate
109
109
 
110
- # Errors object - an Array that contains error messages. See #
111
- attr_reader :errors
110
+ # Take a Proc that controls the sample rate for every tracing event, e.g.
111
+ # ```
112
+ # lambda do |tracing_context|
113
+ # # tracing_context[:transaction_context] contains the information about the transaction
114
+ # # tracing_context[:parent_sampled] contains the transaction's parent's sample decision
115
+ # true # return value can be a boolean or a float between 0.0 and 1.0
116
+ # end
117
+ # ```
118
+ attr_accessor :traces_sampler
112
119
 
113
- # the dsn value, whether it's set via `config.dsn=` or `ENV["SENTRY_DSN"]`
114
- attr_reader :dsn
120
+ # these are not config options
121
+ attr_reader :errors, :gem_specs
115
122
 
116
123
  # Most of these errors generate 4XX responses. In general, Sentry clients
117
124
  # only automatically report 5xx responses.
@@ -141,8 +148,8 @@ module Sentry
141
148
  self.async = false
142
149
  self.breadcrumbs_logger = []
143
150
  self.context_lines = 3
144
- self.current_environment = current_environment_from_env
145
- self.environments = []
151
+ self.environment = environment_from_env
152
+ self.enabled_environments = []
146
153
  self.exclude_loggers = []
147
154
  self.excluded_exceptions = IGNORE_DEFAULT.dup
148
155
  self.inspect_exception_causes_for_exclusion = false
@@ -156,11 +163,12 @@ module Sentry
156
163
  self.send_default_pii = false
157
164
  self.dsn = ENV['SENTRY_DSN']
158
165
  self.server_name = server_name_from_env
159
- self.should_capture = false
160
- self.tags = {}
161
- @transport = Transport::Configuration.new
166
+
162
167
  self.before_send = false
163
168
  self.rack_env_whitelist = RACK_ENV_WHITELIST_DEFAULT
169
+
170
+ @transport = Transport::Configuration.new
171
+ @gem_specs = Hash[Gem::Specification.map { |spec| [spec.name, spec.version.to_s] }] if Gem::Specification.respond_to?(:map)
164
172
  post_initialization_callback
165
173
  end
166
174
 
@@ -198,14 +206,6 @@ module Sentry
198
206
  @breadcrumbs_logger = logger
199
207
  end
200
208
 
201
- def should_capture=(value)
202
- unless value == false || value.respond_to?(:call)
203
- raise ArgumentError, "should_capture must be callable (or false to disable)"
204
- end
205
-
206
- @should_capture = value
207
- end
208
-
209
209
  def before_send=(value)
210
210
  unless value == false || value.respond_to?(:call)
211
211
  raise ArgumentError, "before_send must be callable (or false to disable)"
@@ -214,31 +214,21 @@ module Sentry
214
214
  @before_send = value
215
215
  end
216
216
 
217
- # Allows config options to be read like a hash
218
- #
219
- # @param [Symbol] option Key for a given attribute
220
- def [](option)
221
- public_send(option)
217
+ def environment=(environment)
218
+ @environment = environment.to_s
222
219
  end
223
220
 
224
- def current_environment=(environment)
225
- @current_environment = environment.to_s
226
- end
227
-
228
- def capture_allowed?(message_or_exc = nil)
221
+ def sending_allowed?
229
222
  @errors = []
230
223
 
231
224
  valid? &&
232
- capture_in_current_environment? &&
233
- capture_allowed_by_callback?(message_or_exc) &&
225
+ capture_in_environment? &&
234
226
  sample_allowed?
235
227
  end
236
- # If we cannot capture, we cannot send.
237
- alias sending_allowed? capture_allowed?
238
228
 
239
229
  def error_messages
240
- @errors = [errors[0]] + errors[1..-1].map(&:downcase) # fix case of all but first
241
- errors.join(", ")
230
+ @errors = [@errors[0]] + @errors[1..-1].map(&:downcase) # fix case of all but first
231
+ @errors.join(", ")
242
232
  end
243
233
 
244
234
  def project_root=(root_dir)
@@ -259,7 +249,11 @@ module Sentry
259
249
  end
260
250
 
261
251
  def enabled_in_current_env?
262
- environments.empty? || environments.include?(current_environment)
252
+ enabled_environments.empty? || enabled_environments.include?(environment)
253
+ end
254
+
255
+ def tracing_enabled?
256
+ !!((@traces_sample_rate && @traces_sample_rate > 0.0) || @traces_sampler)
263
257
  end
264
258
 
265
259
  private
@@ -282,13 +276,17 @@ module Sentry
282
276
  end
283
277
 
284
278
  def excluded_exception?(incoming_exception)
285
- excluded_exceptions.any? do |excluded_exception|
286
- matches_exception?(get_exception_class(excluded_exception), incoming_exception)
279
+ excluded_exception_classes.any? do |excluded_exception|
280
+ matches_exception?(excluded_exception, incoming_exception)
287
281
  end
288
282
  end
289
283
 
284
+ def excluded_exception_classes
285
+ @excluded_exception_classes ||= excluded_exceptions.map { |e| get_exception_class(e) }
286
+ end
287
+
290
288
  def get_exception_class(x)
291
- x.is_a?(Module) ? x : qualified_const_get(x)
289
+ x.is_a?(Module) ? x : safe_const_get(x)
292
290
  end
293
291
 
294
292
  def matches_exception?(excluded_exception_class, incoming_exception)
@@ -299,14 +297,9 @@ module Sentry
299
297
  end
300
298
  end
301
299
 
302
- # In Ruby <2.0 const_get can't lookup "SomeModule::SomeClass" in one go
303
- def qualified_const_get(x)
304
- x = x.to_s
305
- if !x.match(/::/)
306
- Object.const_get(x)
307
- else
308
- x.split(MODULE_SEPARATOR).reject(&:empty?).inject(Object) { |a, e| a.const_get(e) }
309
- end
300
+ def safe_const_get(x)
301
+ x = x.to_s unless x.is_a?(String)
302
+ Object.const_get(x)
310
303
  rescue NameError # There's no way to safely ask if a constant exist for an unknown string
311
304
  nil
312
305
  end
@@ -342,17 +335,10 @@ module Sentry
342
335
  ENV['SENTRY_RELEASE']
343
336
  end
344
337
 
345
- def capture_in_current_environment?
338
+ def capture_in_environment?
346
339
  return true if enabled_in_current_env?
347
340
 
348
- @errors << "Not configured to send/capture in environment '#{current_environment}'"
349
- false
350
- end
351
-
352
- def capture_allowed_by_callback?(message_or_exc)
353
- return true if !should_capture || message_or_exc.nil? || should_capture.call(message_or_exc)
354
-
355
- @errors << "should_capture returned false"
341
+ @errors << "Not configured to send/capture in environment '#{environment}'"
356
342
  false
357
343
  end
358
344
 
@@ -383,7 +369,7 @@ module Sentry
383
369
  Socket.gethostbyname(hostname).first rescue server_name
384
370
  end
385
371
 
386
- def current_environment_from_env
372
+ def environment_from_env
387
373
  ENV['SENTRY_CURRENT_ENV'] || ENV['SENTRY_ENVIRONMENT'] || ENV['RAILS_ENV'] || ENV['RACK_ENV'] || 'default'
388
374
  end
389
375
 
@@ -2,7 +2,10 @@ require "uri"
2
2
 
3
3
  module Sentry
4
4
  class DSN
5
- attr_reader :scheme, :project_id, :public_key, :secret_key, :host, :port, :path
5
+ PORT_MAP = { 'http' => 80, 'https' => 443 }.freeze
6
+ REQUIRED_ATTRIBUTES = %w(host path public_key project_id).freeze
7
+
8
+ attr_reader :scheme, :secret_key, :port, *REQUIRED_ATTRIBUTES
6
9
 
7
10
  def initialize(dsn_string)
8
11
  @raw_value = dsn_string
@@ -24,7 +27,7 @@ module Sentry
24
27
  end
25
28
 
26
29
  def valid?
27
- %w(host path public_key project_id).all? { |k| public_send(k) }
30
+ REQUIRED_ATTRIBUTES.all? { |k| public_send(k) }
28
31
  end
29
32
 
30
33
  def to_s
@@ -33,7 +36,7 @@ module Sentry
33
36
 
34
37
  def server
35
38
  server = "#{scheme}://#{host}"
36
- server += ":#{port}" unless port == { 'http' => 80, 'https' => 443 }[scheme]
39
+ server += ":#{port}" unless port == PORT_MAP[scheme]
37
40
  server += path
38
41
  server
39
42
  end
@@ -2,11 +2,10 @@
2
2
 
3
3
  require 'socket'
4
4
  require 'securerandom'
5
- require 'sentry/event/options'
6
5
  require 'sentry/interface'
7
6
  require 'sentry/backtrace'
8
- require 'sentry/utils/deep_merge'
9
7
  require 'sentry/utils/real_ip'
8
+ require 'sentry/utils/request_id'
10
9
 
11
10
  module Sentry
12
11
  class Event
@@ -15,52 +14,44 @@ module Sentry
15
14
  release environment server_name modules
16
15
  message user tags contexts extra
17
16
  fingerprint breadcrumbs backtrace transaction
18
- platform sdk
17
+ platform sdk type
19
18
  )
20
19
 
21
20
  attr_accessor(*ATTRIBUTES)
22
- attr_reader :id, :configuration
21
+ attr_reader :configuration
23
22
 
24
- alias event_id id
25
-
26
- def initialize(options:, configuration:)
23
+ def initialize(configuration:, message: nil)
27
24
  # this needs to go first because some setters rely on configuration
28
25
  @configuration = configuration
29
26
 
30
27
  # Set some simple default values
31
- @id = SecureRandom.uuid.delete("-")
32
- @timestamp = Time.now.utc
28
+ @event_id = SecureRandom.uuid.delete("-")
29
+ @timestamp = Sentry.utc_now.iso8601
33
30
  @platform = :ruby
34
31
  @sdk = Sentry.sdk_meta
35
32
 
36
- @user = options.user
37
- @extra = options.extra
38
- @contexts = options.contexts
39
- @tags = configuration.tags.merge(options.tags)
40
-
41
- @fingerprint = options.fingerprint
33
+ @user = {}
34
+ @extra = {}
35
+ @contexts = {}
36
+ @tags = {}
42
37
 
43
- @server_name = options.server_name || configuration.server_name
44
- @environment = options.environment || configuration.current_environment
45
- @release = options.release || configuration.release
46
- @modules = list_gem_specs if configuration.send_modules
38
+ @fingerprint = []
47
39
 
48
- @message = options.message if options.message
40
+ @server_name = configuration.server_name
41
+ @environment = configuration.environment
42
+ @release = configuration.release
43
+ @modules = configuration.gem_specs if configuration.send_modules
49
44
 
50
- self.level = options.level
45
+ @message = message || ""
51
46
 
52
- if !options.backtrace.empty?
53
- @stacktrace = Sentry::StacktraceInterface.new.tap do |int|
54
- int.frames = stacktrace_interface_from(options.backtrace)
55
- end
56
- end
47
+ self.level = :error
57
48
  end
58
49
 
59
50
  class << self
60
51
  def get_log_message(event_hash)
61
52
  message = event_hash[:message] || event_hash['message']
62
- message = get_message_from_exception(event_hash) if message.empty?
63
- message = '<no message value>' if message.empty?
53
+ message = get_message_from_exception(event_hash) if message.nil? || message.empty?
54
+ message = '<no message value>' if message.nil? || message.empty?
64
55
  message
65
56
  end
66
57
 
@@ -76,7 +67,7 @@ module Sentry
76
67
  end
77
68
 
78
69
  def timestamp=(time)
79
- @timestamp = time.is_a?(Time) ? time.strftime('%Y-%m-%dT%H:%M:%S') : time
70
+ @timestamp = time.is_a?(Time) ? time.to_f : time
80
71
  end
81
72
 
82
73
  def level=(new_level) # needed to meet the Sentry spec
@@ -92,14 +83,18 @@ module Sentry
92
83
  if configuration.send_default_pii && ip = calculate_real_ip_from_rack(env.dup)
93
84
  user[:ip_address] = ip
94
85
  end
86
+ if request_id = Utils::RequestId.read_from(env)
87
+ tags[:request_id] = request_id
88
+ end
95
89
  end
96
90
  end
97
91
 
98
- def to_hash
99
- data = ATTRIBUTES.each_with_object({}) do |att, memo|
100
- memo[att] = public_send(att) if public_send(att)
101
- end
92
+ def type
93
+ "event"
94
+ end
102
95
 
96
+ def to_hash
97
+ data = serialize_attributes
103
98
  data[:breadcrumbs] = breadcrumbs.to_hash if breadcrumbs
104
99
  data[:stacktrace] = @stacktrace.to_hash if @stacktrace
105
100
  data[:request] = @request.to_hash if @request
@@ -113,6 +108,10 @@ module Sentry
113
108
  end
114
109
 
115
110
  def add_exception_interface(exc)
111
+ if exc.respond_to?(:sentry_context)
112
+ @extra.merge!(exc.sentry_context)
113
+ end
114
+
116
115
  @exception = Sentry::ExceptionInterface.new.tap do |exc_int|
117
116
  exceptions = Sentry::Utils::ExceptionCauseChain.exception_to_array(exc).reverse
118
117
  backtraces = Set.new
@@ -145,9 +144,9 @@ module Sentry
145
144
  frame.in_app = line.in_app
146
145
  frame.module = line.module_name if line.module_name
147
146
 
148
- if configuration[:context_lines] && frame.abs_path
147
+ if configuration.context_lines && frame.abs_path
149
148
  frame.pre_context, frame.context_line, frame.post_context = \
150
- configuration.linecache.get_file_context(frame.abs_path, frame.lineno, configuration[:context_lines])
149
+ configuration.linecache.get_file_context(frame.abs_path, frame.lineno, configuration.context_lines)
151
150
  end
152
151
 
153
152
  memo << frame if frame.filename
@@ -156,6 +155,14 @@ module Sentry
156
155
 
157
156
  private
158
157
 
158
+ def serialize_attributes
159
+ self.class::ATTRIBUTES.each_with_object({}) do |att, memo|
160
+ if value = public_send(att)
161
+ memo[att] = value
162
+ end
163
+ end
164
+ end
165
+
159
166
  # When behind a proxy (or if the user is using a proxy), we can't use
160
167
  # REMOTE_ADDR to determine the Event IP, and must use other headers instead.
161
168
  def calculate_real_ip_from_rack(env)
@@ -166,10 +173,5 @@ module Sentry
166
173
  :forwarded_for => env["HTTP_X_FORWARDED_FOR"]
167
174
  ).calculate_ip
168
175
  end
169
-
170
- def list_gem_specs
171
- # Older versions of Rubygems don't support iterating over all specs
172
- Hash[Gem::Specification.map { |spec| [spec.name, spec.version.to_s] }] if Gem::Specification.respond_to?(:map)
173
- end
174
176
  end
175
177
  end