sentry-ruby-core 4.7.2 → 5.0.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 (52) hide show
  1. checksums.yaml +4 -4
  2. data/.yardopts +2 -0
  3. data/Gemfile +6 -2
  4. data/README.md +9 -7
  5. data/bin/console +5 -1
  6. data/lib/sentry/background_worker.rb +33 -3
  7. data/lib/sentry/backtrace.rb +1 -3
  8. data/lib/sentry/breadcrumb/sentry_logger.rb +2 -0
  9. data/lib/sentry/breadcrumb.rb +24 -3
  10. data/lib/sentry/breadcrumb_buffer.rb +16 -0
  11. data/lib/sentry/client.rb +49 -3
  12. data/lib/sentry/configuration.rb +139 -114
  13. data/lib/sentry/core_ext/object/deep_dup.rb +2 -0
  14. data/lib/sentry/core_ext/object/duplicable.rb +1 -0
  15. data/lib/sentry/dsn.rb +2 -0
  16. data/lib/sentry/envelope.rb +26 -0
  17. data/lib/sentry/event.rb +58 -17
  18. data/lib/sentry/exceptions.rb +2 -0
  19. data/lib/sentry/hub.rb +16 -4
  20. data/lib/sentry/integrable.rb +2 -0
  21. data/lib/sentry/interface.rb +3 -10
  22. data/lib/sentry/interfaces/exception.rb +13 -3
  23. data/lib/sentry/interfaces/request.rb +34 -18
  24. data/lib/sentry/interfaces/single_exception.rb +31 -0
  25. data/lib/sentry/interfaces/stacktrace.rb +14 -0
  26. data/lib/sentry/interfaces/stacktrace_builder.rb +39 -10
  27. data/lib/sentry/interfaces/threads.rb +12 -2
  28. data/lib/sentry/linecache.rb +3 -0
  29. data/lib/sentry/net/http.rb +52 -64
  30. data/lib/sentry/rack/capture_exceptions.rb +2 -0
  31. data/lib/sentry/rack.rb +2 -0
  32. data/lib/sentry/rake.rb +16 -6
  33. data/lib/sentry/release_detector.rb +39 -0
  34. data/lib/sentry/scope.rb +75 -5
  35. data/lib/sentry/span.rb +84 -8
  36. data/lib/sentry/transaction.rb +48 -10
  37. data/lib/sentry/transaction_event.rb +8 -0
  38. data/lib/sentry/transport/configuration.rb +3 -2
  39. data/lib/sentry/transport/dummy_transport.rb +2 -0
  40. data/lib/sentry/transport/http_transport.rb +55 -42
  41. data/lib/sentry/transport.rb +80 -19
  42. data/lib/sentry/utils/argument_checking_helper.rb +2 -0
  43. data/lib/sentry/utils/custom_inspection.rb +14 -0
  44. data/lib/sentry/utils/exception_cause_chain.rb +10 -10
  45. data/lib/sentry/utils/logging_helper.rb +6 -4
  46. data/lib/sentry/utils/real_ip.rb +2 -0
  47. data/lib/sentry/utils/request_id.rb +2 -0
  48. data/lib/sentry/version.rb +3 -1
  49. data/lib/sentry-ruby.rb +142 -29
  50. data/sentry-ruby-core.gemspec +0 -1
  51. data/sentry-ruby.gemspec +0 -1
  52. metadata +6 -16
@@ -1,21 +1,31 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "concurrent/utility/processor_counter"
2
4
 
3
5
  require "sentry/utils/exception_cause_chain"
6
+ require 'sentry/utils/custom_inspection'
4
7
  require "sentry/dsn"
8
+ require "sentry/release_detector"
5
9
  require "sentry/transport/configuration"
6
10
  require "sentry/linecache"
7
11
  require "sentry/interfaces/stacktrace_builder"
8
12
 
9
13
  module Sentry
10
14
  class Configuration
15
+ include CustomInspection
11
16
  include LoggingHelper
12
17
  # Directories to be recognized as part of your app. e.g. if you
13
18
  # have an `engines` dir at the root of your project, you may want
14
19
  # to set this to something like /(app|config|engines|lib)/
20
+ #
21
+ # @return [Regexp, nil]
15
22
  attr_accessor :app_dirs_pattern
16
23
 
17
24
  # Provide an object that responds to `call` to send events asynchronously.
18
25
  # E.g.: lambda { |event| Thread.new { Sentry.send_event(event) } }
26
+ #
27
+ # @deprecated It will be removed in the next major release. Please read https://github.com/getsentry/sentry-ruby/issues/1522 for more information
28
+ # @return [Proc, nil]
19
29
  attr_reader :async
20
30
 
21
31
  # to send events in a non-blocking way, sentry-ruby has its own background worker
@@ -25,136 +35,179 @@ module Sentry
25
35
  #
26
36
  # if you want to send events synchronously, set the value to 0
27
37
  # E.g.: config.background_worker_threads = 0
38
+ # @return [Integer]
28
39
  attr_accessor :background_worker_threads
29
40
 
30
41
  # a proc/lambda that takes an array of stack traces
31
42
  # it'll be used to silence (reduce) backtrace of the exception
32
43
  #
33
- # for example:
34
- #
35
- # ```ruby
36
- # Sentry.configuration.backtrace_cleanup_callback = lambda do |backtrace|
37
- # Rails.backtrace_cleaner.clean(backtrace)
38
- # end
39
- # ```
44
+ # @example
45
+ # config.backtrace_cleanup_callback = lambda do |backtrace|
46
+ # Rails.backtrace_cleaner.clean(backtrace)
47
+ # end
40
48
  #
49
+ # @return [Proc, nil]
41
50
  attr_accessor :backtrace_cleanup_callback
42
51
 
43
52
  # Optional Proc, called before adding the breadcrumb to the current scope
44
- # E.g.: lambda { |breadcrumb, hint| breadcrumb }
45
- # E.g.: lambda { |breadcrumb, hint| nil }
46
- # E.g.: lambda { |breadcrumb, hint|
47
- # breadcrumb.message = 'a'
48
- # breadcrumb
49
- # }
53
+ # @example
54
+ # config.before = lambda do |breadcrumb, hint|
55
+ # breadcrumb.message = 'a'
56
+ # breadcrumb
57
+ # end
58
+ # @return [Proc]
50
59
  attr_reader :before_breadcrumb
51
60
 
52
- # Optional Proc, called before sending an event to the server/
53
- # E.g.: lambda { |event, hint| event }
54
- # E.g.: lambda { |event, hint| nil }
55
- # E.g.: lambda { |event, hint|
56
- # event[:message] = 'a'
57
- # event
58
- # }
61
+ # Optional Proc, called before sending an event to the server
62
+ # @example
63
+ # config.before_send = lambda do |event, hint|
64
+ # # skip ZeroDivisionError exceptions
65
+ # # note: hint[:exception] would be a String if you use async callback
66
+ # if hint[:exception].is_a?(ZeroDivisionError)
67
+ # nil
68
+ # else
69
+ # event
70
+ # end
71
+ # end
72
+ # @return [Proc]
59
73
  attr_reader :before_send
60
74
 
61
75
  # An array of breadcrumbs loggers to be used. Available options are:
62
76
  # - :sentry_logger
77
+ # - :http_logger
78
+ #
79
+ # And if you also use sentry-rails:
63
80
  # - :active_support_logger
81
+ # - :monotonic_active_support_logger
82
+ #
83
+ # @return [Array<Symbol>]
64
84
  attr_reader :breadcrumbs_logger
65
85
 
86
+ # Whether to capture local variables from the raised exception's frame. Default is false.
87
+ # @return [Boolean]
88
+ attr_accessor :capture_exception_frame_locals
89
+
66
90
  # Max number of breadcrumbs a breadcrumb buffer can hold
91
+ # @return [Integer]
67
92
  attr_accessor :max_breadcrumbs
68
93
 
69
94
  # Number of lines of code context to capture, or nil for none
95
+ # @return [Integer, nil]
70
96
  attr_accessor :context_lines
71
97
 
72
98
  # RACK_ENV by default.
99
+ # @return [String]
73
100
  attr_reader :environment
74
101
 
75
102
  # Whether the SDK should run in the debugging mode. Default is false.
76
103
  # If set to true, SDK errors will be logged with backtrace
104
+ # @return [Boolean]
77
105
  attr_accessor :debug
78
106
 
79
107
  # the dsn value, whether it's set via `config.dsn=` or `ENV["SENTRY_DSN"]`
108
+ # @return [String]
80
109
  attr_reader :dsn
81
110
 
82
111
  # Whitelist of enabled_environments that will send notifications to Sentry. Array of Strings.
112
+ # @return [Array<String>]
83
113
  attr_accessor :enabled_environments
84
114
 
85
115
  # Logger 'progname's to exclude from breadcrumbs
116
+ # @return [Array<String>]
86
117
  attr_accessor :exclude_loggers
87
118
 
88
119
  # Array of exception classes that should never be sent. See IGNORE_DEFAULT.
89
120
  # You should probably append to this rather than overwrite it.
121
+ # @return [Array<String>]
90
122
  attr_accessor :excluded_exceptions
91
123
 
92
124
  # Boolean to check nested exceptions when deciding if to exclude. Defaults to true
125
+ # @return [Boolean]
93
126
  attr_accessor :inspect_exception_causes_for_exclusion
94
127
  alias inspect_exception_causes_for_exclusion? inspect_exception_causes_for_exclusion
95
128
 
96
129
  # You may provide your own LineCache for matching paths with source files.
97
- # This may be useful if you need to get source code from places other than
98
- # the disk. See Sentry::LineCache for the required interface you must implement.
130
+ # This may be useful if you need to get source code from places other than the disk.
131
+ # @see LineCache
132
+ # @return [LineCache]
99
133
  attr_accessor :linecache
100
134
 
101
135
  # Logger used by Sentry. In Rails, this is the Rails logger, otherwise
102
136
  # Sentry provides its own Sentry::Logger.
137
+ # @return [Logger]
103
138
  attr_accessor :logger
104
139
 
105
140
  # Project directory root for in_app detection. Could be Rails root, etc.
106
141
  # Set automatically for Rails.
107
- attr_reader :project_root
142
+ # @return [String]
143
+ attr_accessor :project_root
108
144
 
109
145
  # Insert sentry-trace to outgoing requests' headers
146
+ # @return [Boolean]
110
147
  attr_accessor :propagate_traces
111
148
 
112
149
  # Array of rack env parameters to be included in the event sent to sentry.
150
+ # @return [Array<String>]
113
151
  attr_accessor :rack_env_whitelist
114
152
 
115
153
  # Release tag to be passed with every event sent to Sentry.
116
154
  # We automatically try to set this to a git SHA or Capistrano release.
155
+ # @return [String]
117
156
  attr_accessor :release
118
157
 
119
158
  # The sampling factor to apply to events. A value of 0.0 will not send
120
159
  # any events, and a value of 1.0 will send 100% of events.
160
+ # @return [Float]
121
161
  attr_accessor :sample_rate
122
162
 
123
163
  # Include module versions in reports - boolean.
164
+ # @return [Boolean]
124
165
  attr_accessor :send_modules
125
166
 
126
167
  # When send_default_pii's value is false (default), sensitive information like
127
168
  # - user ip
128
169
  # - user cookie
129
170
  # - request body
171
+ # - query string
130
172
  # will not be sent to Sentry.
173
+ # @return [Boolean]
131
174
  attr_accessor :send_default_pii
132
175
 
133
176
  # Allow to skip Sentry emails within rake tasks
177
+ # @return [Boolean]
134
178
  attr_accessor :skip_rake_integration
135
179
 
136
180
  # IP ranges for trusted proxies that will be skipped when calculating IP address.
137
181
  attr_accessor :trusted_proxies
138
182
 
183
+ # @return [String]
139
184
  attr_accessor :server_name
140
185
 
141
186
  # Return a Transport::Configuration object for transport-related configurations.
187
+ # @return [Transport]
142
188
  attr_reader :transport
143
189
 
144
190
  # Take a float between 0.0 and 1.0 as the sample rate for tracing events (transactions).
191
+ # @return [Float]
145
192
  attr_accessor :traces_sample_rate
146
193
 
147
194
  # Take a Proc that controls the sample rate for every tracing event, e.g.
148
- # ```
149
- # lambda do |tracing_context|
150
- # # tracing_context[:transaction_context] contains the information about the transaction
151
- # # tracing_context[:parent_sampled] contains the transaction's parent's sample decision
152
- # true # return value can be a boolean or a float between 0.0 and 1.0
153
- # end
154
- # ```
195
+ # @example
196
+ # config.traces_sampler = lambda do |tracing_context|
197
+ # # tracing_context[:transaction_context] contains the information about the transaction
198
+ # # tracing_context[:parent_sampled] contains the transaction's parent's sample decision
199
+ # true # return value can be a boolean or a float between 0.0 and 1.0
200
+ # end
201
+ # @return [Proc]
155
202
  attr_accessor :traces_sampler
156
203
 
204
+ # Send diagnostic client reports about dropped events, true by default
205
+ # tries to attach to an existing envelope max once every 30s
206
+ # @return [Boolean]
207
+ attr_accessor :send_client_reports
208
+
157
209
  # these are not config options
210
+ # @!visibility private
158
211
  attr_reader :errors, :gem_specs
159
212
 
160
213
  # Most of these errors generate 4XX responses. In general, Sentry clients
@@ -177,17 +230,21 @@ module Sentry
177
230
 
178
231
  LOG_PREFIX = "** [Sentry] ".freeze
179
232
  MODULE_SEPARATOR = "::".freeze
233
+ SKIP_INSPECTION_ATTRIBUTES = [:@linecache, :@stacktrace_builder]
180
234
 
181
235
  # Post initialization callbacks are called at the end of initialization process
182
236
  # allowing extending the configuration of sentry-ruby by multiple extensions
183
237
  @@post_initialization_callbacks = []
184
238
 
185
239
  def initialize
240
+ self.app_dirs_pattern = nil
186
241
  self.debug = false
187
242
  self.background_worker_threads = Concurrent.processor_count
243
+ self.backtrace_cleanup_callback = nil
188
244
  self.max_breadcrumbs = BreadcrumbBuffer::DEFAULT_SIZE
189
245
  self.breadcrumbs_logger = []
190
246
  self.context_lines = 3
247
+ self.capture_exception_frame_locals = false
191
248
  self.environment = environment_from_env
192
249
  self.enabled_environments = []
193
250
  self.exclude_loggers = []
@@ -202,12 +259,15 @@ module Sentry
202
259
  self.send_modules = true
203
260
  self.send_default_pii = false
204
261
  self.skip_rake_integration = false
262
+ self.send_client_reports = true
205
263
  self.trusted_proxies = []
206
264
  self.dsn = ENV['SENTRY_DSN']
207
265
  self.server_name = server_name_from_env
208
266
 
209
- self.before_send = false
267
+ self.before_send = nil
210
268
  self.rack_env_whitelist = RACK_ENV_WHITELIST_DEFAULT
269
+ self.traces_sample_rate = nil
270
+ self.traces_sampler = nil
211
271
 
212
272
  @transport = Transport::Configuration.new
213
273
  @gem_specs = Hash[Gem::Specification.map { |spec| [spec.name, spec.version.to_s] }] if Gem::Specification.respond_to?(:map)
@@ -216,18 +276,13 @@ module Sentry
216
276
  end
217
277
 
218
278
  def dsn=(value)
219
- return if value.nil? || value.empty?
220
-
221
- @dsn = DSN.new(value)
279
+ @dsn = init_dsn(value)
222
280
  end
223
281
 
224
282
  alias server= dsn=
225
283
 
226
-
227
284
  def async=(value)
228
- if value && !value.respond_to?(:call)
229
- raise(ArgumentError, "async must be callable")
230
- end
285
+ check_callable!("async", value)
231
286
 
232
287
  @async = value
233
288
  end
@@ -246,17 +301,13 @@ module Sentry
246
301
  end
247
302
 
248
303
  def before_send=(value)
249
- unless value == false || value.respond_to?(:call)
250
- raise ArgumentError, "before_send must be callable (or false to disable)"
251
- end
304
+ check_callable!("before_send", value)
252
305
 
253
306
  @before_send = value
254
307
  end
255
308
 
256
309
  def before_breadcrumb=(value)
257
- unless value.nil? || value.respond_to?(:call)
258
- raise ArgumentError, "before_breadcrumb must be callable (or nil to disable)"
259
- end
310
+ check_callable!("before_breadcrumb", value)
260
311
 
261
312
  @before_breadcrumb = value
262
313
  end
@@ -268,18 +319,13 @@ module Sentry
268
319
  def sending_allowed?
269
320
  @errors = []
270
321
 
271
- valid? &&
272
- capture_in_environment? &&
273
- sample_allowed?
322
+ valid? && capture_in_environment?
274
323
  end
275
324
 
276
- def error_messages
277
- @errors = [@errors[0]] + @errors[1..-1].map(&:downcase) # fix case of all but first
278
- @errors.join(", ")
279
- end
325
+ def sample_allowed?
326
+ return true if sample_rate == 1.0
280
327
 
281
- def project_root=(root_dir)
282
- @project_root = root_dir
328
+ Random.rand < sample_rate
283
329
  end
284
330
 
285
331
  def exception_class_allowed?(exc)
@@ -303,6 +349,17 @@ module Sentry
303
349
  !!((@traces_sample_rate && @traces_sample_rate >= 0.0 && @traces_sample_rate <= 1.0) || @traces_sampler) && sending_allowed?
304
350
  end
305
351
 
352
+ # @return [String, nil]
353
+ def csp_report_uri
354
+ if dsn && dsn.valid?
355
+ uri = dsn.csp_report_uri
356
+ uri += "&sentry_release=#{CGI.escape(release)}" if release && !release.empty?
357
+ uri += "&sentry_environment=#{CGI.escape(environment)}" if environment && !environment.empty?
358
+ uri
359
+ end
360
+ end
361
+
362
+ # @api private
306
363
  def stacktrace_builder
307
364
  @stacktrace_builder ||= StacktraceBuilder.new(
308
365
  project_root: @project_root.to_s,
@@ -313,28 +370,39 @@ module Sentry
313
370
  )
314
371
  end
315
372
 
373
+ # @api private
316
374
  def detect_release
317
375
  return unless sending_allowed?
318
376
 
319
- self.release ||= detect_release_from_env ||
320
- detect_release_from_git ||
321
- detect_release_from_capistrano ||
322
- detect_release_from_heroku
377
+ self.release ||= ReleaseDetector.detect_release(project_root: project_root, running_on_heroku: running_on_heroku?)
378
+
379
+ if running_on_heroku? && release.nil?
380
+ log_warn(HEROKU_DYNO_METADATA_MESSAGE)
381
+ end
323
382
  rescue => e
324
383
  log_error("Error detecting release", e, debug: debug)
325
384
  end
326
385
 
327
- def csp_report_uri
328
- if dsn && dsn.valid?
329
- uri = dsn.csp_report_uri
330
- uri += "&sentry_release=#{CGI.escape(release)}" if release && !release.empty?
331
- uri += "&sentry_environment=#{CGI.escape(environment)}" if environment && !environment.empty?
332
- uri
333
- end
386
+ # @api private
387
+ def error_messages
388
+ @errors = [@errors[0]] + @errors[1..-1].map(&:downcase) # fix case of all but first
389
+ @errors.join(", ")
334
390
  end
335
391
 
336
392
  private
337
393
 
394
+ def check_callable!(name, value)
395
+ unless value == nil || value.respond_to?(:call)
396
+ raise ArgumentError, "#{name} must be callable (or nil to disable)"
397
+ end
398
+ end
399
+
400
+ def init_dsn(dsn_string)
401
+ return if dsn_string.nil? || dsn_string.empty?
402
+
403
+ DSN.new(dsn_string)
404
+ end
405
+
338
406
  def excluded_exception?(incoming_exception)
339
407
  excluded_exception_classes.any? do |excluded_exception|
340
408
  matches_exception?(excluded_exception, incoming_exception)
@@ -364,37 +432,6 @@ module Sentry
364
432
  nil
365
433
  end
366
434
 
367
- def detect_release_from_heroku
368
- return unless running_on_heroku?
369
- return if ENV['CI']
370
- log_warn(HEROKU_DYNO_METADATA_MESSAGE) && return unless ENV['HEROKU_SLUG_COMMIT']
371
-
372
- ENV['HEROKU_SLUG_COMMIT']
373
- end
374
-
375
- def running_on_heroku?
376
- File.directory?("/etc/heroku")
377
- end
378
-
379
- def detect_release_from_capistrano
380
- revision_file = File.join(project_root, 'REVISION')
381
- revision_log = File.join(project_root, '..', 'revisions.log')
382
-
383
- if File.exist?(revision_file)
384
- File.read(revision_file).strip
385
- elsif File.exist?(revision_log)
386
- File.open(revision_log).to_a.last.strip.sub(/.*as release ([0-9]+).*/, '\1')
387
- end
388
- end
389
-
390
- def detect_release_from_git
391
- Sentry.sys_command("git rev-parse --short HEAD") if File.directory?(".git")
392
- end
393
-
394
- def detect_release_from_env
395
- ENV['SENTRY_RELEASE']
396
- end
397
-
398
435
  def capture_in_environment?
399
436
  return true if enabled_in_current_env?
400
437
 
@@ -411,24 +448,6 @@ module Sentry
411
448
  end
412
449
  end
413
450
 
414
- def sample_allowed?
415
- return true if sample_rate == 1.0
416
-
417
- if Random.rand >= sample_rate
418
- @errors << "Excluded by random sample"
419
- false
420
- else
421
- true
422
- end
423
- end
424
-
425
- # Try to resolve the hostname to an FQDN, but fall back to whatever
426
- # the load name is.
427
- def resolve_hostname
428
- Socket.gethostname ||
429
- Socket.gethostbyname(hostname).first rescue server_name
430
- end
431
-
432
451
  def environment_from_env
433
452
  ENV['SENTRY_CURRENT_ENV'] || ENV['SENTRY_ENVIRONMENT'] || ENV['RAILS_ENV'] || ENV['RACK_ENV'] || 'development'
434
453
  end
@@ -437,10 +456,16 @@ module Sentry
437
456
  if running_on_heroku?
438
457
  ENV['DYNO']
439
458
  else
440
- resolve_hostname
459
+ # Try to resolve the hostname to an FQDN, but fall back to whatever
460
+ # the load name is.
461
+ Socket.gethostname || Socket.gethostbyname(hostname).first rescue server_name
441
462
  end
442
463
  end
443
464
 
465
+ def running_on_heroku?
466
+ File.directory?("/etc/heroku") && !ENV["CI"]
467
+ end
468
+
444
469
  def run_post_initialization_callbacks
445
470
  self.class.post_initialization_callbacks.each do |hook|
446
471
  instance_eval(&hook)
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  return if Object.method_defined?(:deep_dup)
2
4
 
3
5
  require 'sentry/core_ext/object/duplicable'
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  return if Object.method_defined?(:duplicable?)
3
4
 
4
5
  #########################################
data/lib/sentry/dsn.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "uri"
2
4
 
3
5
  module Sentry
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sentry
4
+ # @api private
5
+ class Envelope
6
+ def initialize(headers)
7
+ @headers = headers
8
+ @items = []
9
+ end
10
+
11
+ def add_item(headers, payload)
12
+ @items << [headers, payload]
13
+ end
14
+
15
+ def to_s
16
+ payload = @items.map do |item_headers, item_payload|
17
+ <<~ENVELOPE
18
+ #{JSON.generate(item_headers)}
19
+ #{JSON.generate(item_payload)}
20
+ ENVELOPE
21
+ end.join("\n")
22
+
23
+ "#{JSON.generate(@headers)}\n#{payload}"
24
+ end
25
+ end
26
+ end