sentry-ruby 5.13.0 → 5.21.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (81) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +7 -18
  3. data/README.md +20 -10
  4. data/Rakefile +3 -1
  5. data/bin/console +2 -0
  6. data/lib/sentry/attachment.rb +40 -0
  7. data/lib/sentry/background_worker.rb +9 -2
  8. data/lib/sentry/backpressure_monitor.rb +45 -0
  9. data/lib/sentry/backtrace.rb +10 -8
  10. data/lib/sentry/baggage.rb +7 -7
  11. data/lib/sentry/breadcrumb/sentry_logger.rb +6 -6
  12. data/lib/sentry/check_in_event.rb +5 -5
  13. data/lib/sentry/client.rb +71 -18
  14. data/lib/sentry/configuration.rb +108 -32
  15. data/lib/sentry/core_ext/object/deep_dup.rb +1 -1
  16. data/lib/sentry/cron/configuration.rb +23 -0
  17. data/lib/sentry/cron/monitor_check_ins.rb +42 -26
  18. data/lib/sentry/cron/monitor_config.rb +1 -1
  19. data/lib/sentry/cron/monitor_schedule.rb +1 -1
  20. data/lib/sentry/dsn.rb +4 -4
  21. data/lib/sentry/envelope/item.rb +88 -0
  22. data/lib/sentry/envelope.rb +2 -68
  23. data/lib/sentry/error_event.rb +2 -2
  24. data/lib/sentry/event.rb +20 -46
  25. data/lib/sentry/faraday.rb +77 -0
  26. data/lib/sentry/graphql.rb +9 -0
  27. data/lib/sentry/hub.rb +25 -5
  28. data/lib/sentry/integrable.rb +4 -0
  29. data/lib/sentry/interface.rb +1 -0
  30. data/lib/sentry/interfaces/exception.rb +5 -3
  31. data/lib/sentry/interfaces/mechanism.rb +20 -0
  32. data/lib/sentry/interfaces/request.rb +7 -7
  33. data/lib/sentry/interfaces/single_exception.rb +10 -7
  34. data/lib/sentry/interfaces/stacktrace.rb +3 -1
  35. data/lib/sentry/interfaces/stacktrace_builder.rb +23 -2
  36. data/lib/sentry/logger.rb +1 -1
  37. data/lib/sentry/metrics/aggregator.rb +248 -0
  38. data/lib/sentry/metrics/configuration.rb +47 -0
  39. data/lib/sentry/metrics/counter_metric.rb +25 -0
  40. data/lib/sentry/metrics/distribution_metric.rb +25 -0
  41. data/lib/sentry/metrics/gauge_metric.rb +35 -0
  42. data/lib/sentry/metrics/local_aggregator.rb +53 -0
  43. data/lib/sentry/metrics/metric.rb +19 -0
  44. data/lib/sentry/metrics/set_metric.rb +28 -0
  45. data/lib/sentry/metrics/timing.rb +43 -0
  46. data/lib/sentry/metrics.rb +56 -0
  47. data/lib/sentry/net/http.rb +22 -39
  48. data/lib/sentry/profiler/helpers.rb +46 -0
  49. data/lib/sentry/profiler.rb +25 -56
  50. data/lib/sentry/propagation_context.rb +10 -9
  51. data/lib/sentry/puma.rb +1 -1
  52. data/lib/sentry/rack/capture_exceptions.rb +16 -4
  53. data/lib/sentry/rack.rb +2 -2
  54. data/lib/sentry/rake.rb +4 -15
  55. data/lib/sentry/redis.rb +2 -1
  56. data/lib/sentry/release_detector.rb +5 -5
  57. data/lib/sentry/scope.rb +48 -37
  58. data/lib/sentry/session.rb +2 -2
  59. data/lib/sentry/session_flusher.rb +7 -39
  60. data/lib/sentry/span.rb +46 -5
  61. data/lib/sentry/test_helper.rb +5 -2
  62. data/lib/sentry/threaded_periodic_worker.rb +39 -0
  63. data/lib/sentry/transaction.rb +27 -18
  64. data/lib/sentry/transaction_event.rb +6 -2
  65. data/lib/sentry/transport/configuration.rb +73 -1
  66. data/lib/sentry/transport/http_transport.rb +72 -41
  67. data/lib/sentry/transport/spotlight_transport.rb +50 -0
  68. data/lib/sentry/transport.rb +36 -41
  69. data/lib/sentry/utils/argument_checking_helper.rb +6 -0
  70. data/lib/sentry/utils/env_helper.rb +21 -0
  71. data/lib/sentry/utils/http_tracing.rb +41 -0
  72. data/lib/sentry/utils/logging_helper.rb +0 -4
  73. data/lib/sentry/utils/real_ip.rb +2 -2
  74. data/lib/sentry/utils/request_id.rb +1 -1
  75. data/lib/sentry/vernier/output.rb +89 -0
  76. data/lib/sentry/vernier/profiler.rb +125 -0
  77. data/lib/sentry/version.rb +1 -1
  78. data/lib/sentry-ruby.rb +61 -27
  79. data/sentry-ruby-core.gemspec +3 -1
  80. data/sentry-ruby.gemspec +15 -6
  81. metadata +47 -7
@@ -3,10 +3,13 @@
3
3
  require "concurrent/utility/processor_counter"
4
4
 
5
5
  require "sentry/utils/exception_cause_chain"
6
- require 'sentry/utils/custom_inspection'
6
+ require "sentry/utils/custom_inspection"
7
+ require "sentry/utils/env_helper"
7
8
  require "sentry/dsn"
8
9
  require "sentry/release_detector"
9
10
  require "sentry/transport/configuration"
11
+ require "sentry/cron/configuration"
12
+ require "sentry/metrics/configuration"
10
13
  require "sentry/linecache"
11
14
  require "sentry/interfaces/stacktrace_builder"
12
15
 
@@ -20,6 +23,8 @@ module Sentry
20
23
  # have an `engines` dir at the root of your project, you may want
21
24
  # to set this to something like /(app|config|engines|lib)/
22
25
  #
26
+ # The default is value is /(bin|exe|app|config|lib|test|spec)/
27
+ #
23
28
  # @return [Regexp, nil]
24
29
  attr_accessor :app_dirs_pattern
25
30
 
@@ -40,6 +45,13 @@ module Sentry
40
45
  # @return [Integer]
41
46
  attr_accessor :background_worker_threads
42
47
 
48
+ # The maximum queue size for the background worker.
49
+ # Jobs will be rejected above this limit.
50
+ #
51
+ # Default is {BackgroundWorker::DEFAULT_MAX_QUEUE}.
52
+ # @return [Integer]
53
+ attr_accessor :background_worker_max_queue
54
+
43
55
  # a proc/lambda that takes an array of stack traces
44
56
  # it'll be used to silence (reduce) backtrace of the exception
45
57
  #
@@ -142,6 +154,14 @@ module Sentry
142
154
  # @return [Boolean]
143
155
  attr_accessor :include_local_variables
144
156
 
157
+ # Whether to capture events and traces into Spotlight. Default is false.
158
+ # If you set this to true, Sentry will send events and traces to the local
159
+ # Sidecar proxy at http://localhost:8969/stream.
160
+ # If you want to use a different Sidecar proxy address, set this to String
161
+ # with the proxy URL.
162
+ # @return [Boolean, String]
163
+ attr_accessor :spotlight
164
+
145
165
  # @deprecated Use {#include_local_variables} instead.
146
166
  alias_method :capture_exception_frame_locals, :include_local_variables
147
167
 
@@ -170,6 +190,11 @@ module Sentry
170
190
  # @return [String]
171
191
  attr_accessor :project_root
172
192
 
193
+ # Whether to strip the load path while constructing the backtrace frame filename.
194
+ # Defaults to true.
195
+ # @return [Boolean]
196
+ attr_accessor :strip_backtrace_load_path
197
+
173
198
  # Insert sentry-trace to outgoing requests' headers
174
199
  # @return [Boolean]
175
200
  attr_accessor :propagate_traces
@@ -211,10 +236,18 @@ module Sentry
211
236
  # @return [String]
212
237
  attr_accessor :server_name
213
238
 
214
- # Return a Transport::Configuration object for transport-related configurations.
215
- # @return [Transport]
239
+ # Transport related configuration.
240
+ # @return [Transport::Configuration]
216
241
  attr_reader :transport
217
242
 
243
+ # Cron related configuration.
244
+ # @return [Cron::Configuration]
245
+ attr_reader :cron
246
+
247
+ # Metrics related configuration.
248
+ # @return [Metrics::Configuration]
249
+ attr_reader :metrics
250
+
218
251
  # Take a float between 0.0 and 1.0 as the sample rate for tracing events (transactions).
219
252
  # @return [Float, nil]
220
253
  attr_reader :traces_sample_rate
@@ -243,6 +276,12 @@ module Sentry
243
276
  # @return [Boolean]
244
277
  attr_accessor :auto_session_tracking
245
278
 
279
+ # Whether to downsample transactions automatically because of backpressure.
280
+ # Starts a new monitor thread to check health of the SDK every 10 seconds.
281
+ # Default is false
282
+ # @return [Boolean]
283
+ attr_accessor :enable_backpressure_handling
284
+
246
285
  # Allowlist of outgoing request targets to which sentry-trace and baggage headers are attached.
247
286
  # Default is all (/.*/)
248
287
  # @return [Array<String, Regexp>]
@@ -252,6 +291,10 @@ module Sentry
252
291
  # @return [Symbol]
253
292
  attr_reader :instrumenter
254
293
 
294
+ # The profiler class
295
+ # @return [Class]
296
+ attr_reader :profiler_class
297
+
255
298
  # Take a float between 0.0 and 1.0 as the sample rate for capturing profiles.
256
299
  # Note that this rate is relative to traces_sample_rate / traces_sampler,
257
300
  # i.e. the profile is sampled by this rate after the transaction is sampled.
@@ -271,38 +314,40 @@ module Sentry
271
314
  # But they are mostly considered as noise and should be ignored by default
272
315
  # Please see https://github.com/getsentry/sentry-ruby/pull/2026 for more information
273
316
  PUMA_IGNORE_DEFAULT = [
274
- 'Puma::MiniSSL::SSLError',
275
- 'Puma::HttpParserError',
276
- 'Puma::HttpParserError501'
317
+ "Puma::MiniSSL::SSLError",
318
+ "Puma::HttpParserError",
319
+ "Puma::HttpParserError501"
277
320
  ].freeze
278
321
 
279
322
  # Most of these errors generate 4XX responses. In general, Sentry clients
280
323
  # only automatically report 5xx responses.
281
324
  IGNORE_DEFAULT = [
282
- 'Mongoid::Errors::DocumentNotFound',
283
- 'Rack::QueryParser::InvalidParameterError',
284
- 'Rack::QueryParser::ParameterTypeError',
285
- 'Sinatra::NotFound'
325
+ "Mongoid::Errors::DocumentNotFound",
326
+ "Rack::QueryParser::InvalidParameterError",
327
+ "Rack::QueryParser::ParameterTypeError",
328
+ "Sinatra::NotFound"
286
329
  ].freeze
287
330
 
288
- RACK_ENV_WHITELIST_DEFAULT = %w(
331
+ RACK_ENV_WHITELIST_DEFAULT = %w[
289
332
  REMOTE_ADDR
290
333
  SERVER_NAME
291
334
  SERVER_PORT
292
- ).freeze
335
+ ].freeze
293
336
 
294
337
  HEROKU_DYNO_METADATA_MESSAGE = "You are running on Heroku but haven't enabled Dyno Metadata. For Sentry's "\
295
- "release detection to work correctly, please run `heroku labs:enable runtime-dyno-metadata`".freeze
338
+ "release detection to work correctly, please run `heroku labs:enable runtime-dyno-metadata`"
296
339
 
297
- LOG_PREFIX = "** [Sentry] ".freeze
298
- MODULE_SEPARATOR = "::".freeze
340
+ LOG_PREFIX = "** [Sentry] "
341
+ MODULE_SEPARATOR = "::"
299
342
  SKIP_INSPECTION_ATTRIBUTES = [:@linecache, :@stacktrace_builder]
300
343
 
301
344
  INSTRUMENTERS = [:sentry, :otel]
302
345
 
303
- PROPAGATION_TARGETS_MATCH_ALL = /.*/.freeze
346
+ PROPAGATION_TARGETS_MATCH_ALL = /.*/
347
+
348
+ DEFAULT_PATCHES = %i[redis puma http].freeze
304
349
 
305
- DEFAULT_PATCHES = %i(redis puma http).freeze
350
+ APP_DIRS_PATTERN = /(bin|exe|app|config|lib|test|spec)/
306
351
 
307
352
  class << self
308
353
  # Post initialization callbacks are called at the end of initialization process
@@ -311,17 +356,19 @@ module Sentry
311
356
  @post_initialization_callbacks ||= []
312
357
  end
313
358
 
314
- # allow extensions to add their hooks to the Configuration class
359
+ # allow extensions to add their hooks to the Configuration class
315
360
  def add_post_initialization_callback(&block)
316
361
  post_initialization_callbacks << block
317
362
  end
318
363
  end
319
364
 
320
365
  def initialize
321
- self.app_dirs_pattern = nil
322
- self.debug = false
323
- self.background_worker_threads = Concurrent.processor_count
366
+ self.app_dirs_pattern = APP_DIRS_PATTERN
367
+ self.debug = Sentry::Utils::EnvHelper.env_to_bool(ENV["SENTRY_DEBUG"])
368
+ self.background_worker_threads = (processor_count / 2.0).ceil
369
+ self.background_worker_max_queue = BackgroundWorker::DEFAULT_MAX_QUEUE
324
370
  self.backtrace_cleanup_callback = nil
371
+ self.strip_backtrace_load_path = true
325
372
  self.max_breadcrumbs = BreadcrumbBuffer::DEFAULT_SIZE
326
373
  self.breadcrumbs_logger = []
327
374
  self.context_lines = 3
@@ -342,8 +389,13 @@ module Sentry
342
389
  self.skip_rake_integration = false
343
390
  self.send_client_reports = true
344
391
  self.auto_session_tracking = true
392
+ self.enable_backpressure_handling = false
345
393
  self.trusted_proxies = []
346
- self.dsn = ENV['SENTRY_DSN']
394
+ self.dsn = ENV["SENTRY_DSN"]
395
+
396
+ spotlight_env = ENV["SENTRY_SPOTLIGHT"]
397
+ spotlight_bool = Sentry::Utils::EnvHelper.env_to_bool(spotlight_env, strict: true)
398
+ self.spotlight = spotlight_bool.nil? ? (spotlight_env || false) : spotlight_bool
347
399
  self.server_name = server_name_from_env
348
400
  self.instrumenter = :sentry
349
401
  self.trace_propagation_targets = [PROPAGATION_TARGETS_MATCH_ALL]
@@ -355,7 +407,11 @@ module Sentry
355
407
  self.traces_sampler = nil
356
408
  self.enable_tracing = nil
357
409
 
410
+ self.profiler_class = Sentry::Profiler
411
+
358
412
  @transport = Transport::Configuration.new
413
+ @cron = Cron::Configuration.new
414
+ @metrics = Metrics::Configuration.new
359
415
  @gem_specs = Hash[Gem::Specification.map { |spec| [spec.name, spec.version.to_s] }] if Gem::Specification.respond_to?(:map)
360
416
 
361
417
  run_post_initialization_callbacks
@@ -444,11 +500,27 @@ module Sentry
444
500
 
445
501
  def profiles_sample_rate=(profiles_sample_rate)
446
502
  raise ArgumentError, "profiles_sample_rate must be a Numeric or nil" unless is_numeric_or_nil?(profiles_sample_rate)
447
- log_info("Please make sure to include the 'stackprof' gem in your Gemfile to use Profiling with Sentry.") unless defined?(StackProf)
503
+ log_warn("Please make sure to include the 'stackprof' gem in your Gemfile to use Profiling with Sentry.") unless defined?(StackProf)
448
504
  @profiles_sample_rate = profiles_sample_rate
449
505
  end
450
506
 
507
+ def profiler_class=(profiler_class)
508
+ if profiler_class == Sentry::Vernier::Profiler
509
+ begin
510
+ require "vernier"
511
+ rescue LoadError
512
+ raise ArgumentError, "Please add the 'vernier' gem to your Gemfile to use the Vernier profiler with Sentry."
513
+ end
514
+ end
515
+
516
+ @profiler_class = profiler_class
517
+ end
518
+
451
519
  def sending_allowed?
520
+ spotlight || sending_to_dsn_allowed?
521
+ end
522
+
523
+ def sending_to_dsn_allowed?
452
524
  @errors = []
453
525
 
454
526
  valid? && capture_in_environment?
@@ -460,6 +532,10 @@ module Sentry
460
532
  Random.rand < sample_rate
461
533
  end
462
534
 
535
+ def session_tracking?
536
+ auto_session_tracking && enabled_in_current_env?
537
+ end
538
+
463
539
  def exception_class_allowed?(exc)
464
540
  if exc.is_a?(Sentry::Error)
465
541
  # Try to prevent error reporting loops
@@ -511,7 +587,8 @@ module Sentry
511
587
  app_dirs_pattern: @app_dirs_pattern,
512
588
  linecache: @linecache,
513
589
  context_lines: @context_lines,
514
- backtrace_cleanup_callback: @backtrace_cleanup_callback
590
+ backtrace_cleanup_callback: @backtrace_cleanup_callback,
591
+ strip_backtrace_load_path: @strip_backtrace_load_path
515
592
  )
516
593
  end
517
594
 
@@ -536,12 +613,6 @@ module Sentry
536
613
 
537
614
  private
538
615
 
539
- def check_callable!(name, value)
540
- unless value == nil || value.respond_to?(:call)
541
- raise ArgumentError, "#{name} must be callable (or nil to disable)"
542
- end
543
- end
544
-
545
616
  def init_dsn(dsn_string)
546
617
  return if dsn_string.nil? || dsn_string.empty?
547
618
 
@@ -594,12 +665,12 @@ module Sentry
594
665
  end
595
666
 
596
667
  def environment_from_env
597
- ENV['SENTRY_CURRENT_ENV'] || ENV['SENTRY_ENVIRONMENT'] || ENV['RAILS_ENV'] || ENV['RACK_ENV'] || 'development'
668
+ ENV["SENTRY_CURRENT_ENV"] || ENV["SENTRY_ENVIRONMENT"] || ENV["RAILS_ENV"] || ENV["RACK_ENV"] || "development"
598
669
  end
599
670
 
600
671
  def server_name_from_env
601
672
  if running_on_heroku?
602
- ENV['DYNO']
673
+ ENV["DYNO"]
603
674
  else
604
675
  # Try to resolve the hostname to an FQDN, but fall back to whatever
605
676
  # the load name is.
@@ -616,5 +687,10 @@ module Sentry
616
687
  instance_eval(&hook)
617
688
  end
618
689
  end
690
+
691
+ def processor_count
692
+ available_processor_count = Concurrent.available_processor_count if Concurrent.respond_to?(:available_processor_count)
693
+ available_processor_count || Concurrent.processor_count
694
+ end
619
695
  end
620
696
  end
@@ -2,7 +2,7 @@
2
2
 
3
3
  return if Object.method_defined?(:deep_dup)
4
4
 
5
- require 'sentry/core_ext/object/duplicable'
5
+ require "sentry/core_ext/object/duplicable"
6
6
 
7
7
  #########################################
8
8
  # This file was copied from Rails 5.2 #
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sentry
4
+ module Cron
5
+ class Configuration
6
+ # Defaults set here will apply to all {Cron::MonitorConfig} objects unless overwritten.
7
+
8
+ # How long (in minutes) after the expected checkin time will we wait
9
+ # until we consider the checkin to have been missed.
10
+ # @return [Integer, nil]
11
+ attr_accessor :default_checkin_margin
12
+
13
+ # How long (in minutes) is the checkin allowed to run for in in_progress
14
+ # before it is considered failed.
15
+ # @return [Integer, nil]
16
+ attr_accessor :default_max_runtime
17
+
18
+ # tz database style timezone string
19
+ # @return [String, nil]
20
+ attr_accessor :default_timezone
21
+ end
22
+ end
23
+ end
@@ -1,9 +1,13 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Sentry
2
4
  module Cron
3
5
  module MonitorCheckIns
6
+ MAX_SLUG_LENGTH = 50
7
+
4
8
  module Patch
5
- def perform(*args)
6
- slug = self.class.sentry_monitor_slug || self.class.name
9
+ def perform(*args, **opts)
10
+ slug = self.class.sentry_monitor_slug
7
11
  monitor_config = self.class.sentry_monitor_config
8
12
 
9
13
  check_in_id = Sentry.capture_check_in(slug,
@@ -11,39 +15,53 @@ module Sentry
11
15
  monitor_config: monitor_config)
12
16
 
13
17
  start = Sentry.utc_now.to_i
14
- ret = super
15
- duration = Sentry.utc_now.to_i - start
16
-
17
- Sentry.capture_check_in(slug,
18
- :ok,
19
- check_in_id: check_in_id,
20
- duration: duration,
21
- monitor_config: monitor_config)
22
-
23
- ret
24
- rescue Exception
25
- duration = Sentry.utc_now.to_i - start
26
-
27
- Sentry.capture_check_in(slug,
28
- :error,
29
- check_in_id: check_in_id,
30
- duration: duration,
31
- monitor_config: monitor_config)
32
-
33
- raise
18
+
19
+ begin
20
+ # need to do this on ruby <= 2.6 sadly
21
+ ret = method(:perform).super_method.arity == 0 ? super() : super
22
+ duration = Sentry.utc_now.to_i - start
23
+
24
+ Sentry.capture_check_in(slug,
25
+ :ok,
26
+ check_in_id: check_in_id,
27
+ duration: duration,
28
+ monitor_config: monitor_config)
29
+
30
+ ret
31
+ rescue Exception
32
+ duration = Sentry.utc_now.to_i - start
33
+
34
+ Sentry.capture_check_in(slug,
35
+ :error,
36
+ check_in_id: check_in_id,
37
+ duration: duration,
38
+ monitor_config: monitor_config)
39
+
40
+ raise
41
+ end
34
42
  end
35
43
  end
36
44
 
37
45
  module ClassMethods
38
46
  def sentry_monitor_check_ins(slug: nil, monitor_config: nil)
47
+ if monitor_config && Sentry.configuration
48
+ cron_config = Sentry.configuration.cron
49
+ monitor_config.checkin_margin ||= cron_config.default_checkin_margin
50
+ monitor_config.max_runtime ||= cron_config.default_max_runtime
51
+ monitor_config.timezone ||= cron_config.default_timezone
52
+ end
53
+
39
54
  @sentry_monitor_slug = slug
40
55
  @sentry_monitor_config = monitor_config
41
56
 
42
57
  prepend Patch
43
58
  end
44
59
 
45
- def sentry_monitor_slug
46
- @sentry_monitor_slug
60
+ def sentry_monitor_slug(name: self.name)
61
+ @sentry_monitor_slug ||= begin
62
+ slug = name.gsub("::", "-").downcase
63
+ slug[-MAX_SLUG_LENGTH..-1] || slug
64
+ end
47
65
  end
48
66
 
49
67
  def sentry_monitor_config
@@ -51,8 +69,6 @@ module Sentry
51
69
  end
52
70
  end
53
71
 
54
- extend ClassMethods
55
-
56
72
  def self.included(base)
57
73
  base.extend(ClassMethods)
58
74
  end
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'sentry/cron/monitor_schedule'
3
+ require "sentry/cron/monitor_schedule"
4
4
 
5
5
  module Sentry
6
6
  module Cron
@@ -26,7 +26,7 @@ module Sentry
26
26
  # @return [Symbol]
27
27
  attr_accessor :unit
28
28
 
29
- VALID_UNITS = %i(year month week day hour minute)
29
+ VALID_UNITS = %i[year month week day hour minute]
30
30
 
31
31
  def initialize(value, unit)
32
32
  @value = value
data/lib/sentry/dsn.rb CHANGED
@@ -4,8 +4,8 @@ require "uri"
4
4
 
5
5
  module Sentry
6
6
  class DSN
7
- PORT_MAP = { 'http' => 80, 'https' => 443 }.freeze
8
- REQUIRED_ATTRIBUTES = %w(host path public_key project_id).freeze
7
+ PORT_MAP = { "http" => 80, "https" => 443 }.freeze
8
+ REQUIRED_ATTRIBUTES = %w[host path public_key project_id].freeze
9
9
 
10
10
  attr_reader :scheme, :secret_key, :port, *REQUIRED_ATTRIBUTES
11
11
 
@@ -13,7 +13,7 @@ module Sentry
13
13
  @raw_value = dsn_string
14
14
 
15
15
  uri = URI.parse(dsn_string)
16
- uri_path = uri.path.split('/')
16
+ uri_path = uri.path.split("/")
17
17
 
18
18
  if uri.user
19
19
  # DSN-style string
@@ -25,7 +25,7 @@ module Sentry
25
25
  @scheme = uri.scheme
26
26
  @host = uri.host
27
27
  @port = uri.port if uri.port
28
- @path = uri_path.join('/')
28
+ @path = uri_path.join("/")
29
29
  end
30
30
 
31
31
  def valid?
@@ -0,0 +1,88 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sentry
4
+ # @api private
5
+ class Envelope::Item
6
+ STACKTRACE_FRAME_LIMIT_ON_OVERSIZED_PAYLOAD = 500
7
+ MAX_SERIALIZED_PAYLOAD_SIZE = 1024 * 1000
8
+
9
+ SIZE_LIMITS = Hash.new(MAX_SERIALIZED_PAYLOAD_SIZE).update(
10
+ "profile" => 1024 * 1000 * 50
11
+ )
12
+
13
+ attr_reader :size_limit, :headers, :payload, :type, :data_category
14
+
15
+ # rate limits and client reports use the data_category rather than envelope item type
16
+ def self.data_category(type)
17
+ case type
18
+ when "session", "attachment", "transaction", "profile", "span" then type
19
+ when "sessions" then "session"
20
+ when "check_in" then "monitor"
21
+ when "statsd", "metric_meta" then "metric_bucket"
22
+ when "event" then "error"
23
+ when "client_report" then "internal"
24
+ else "default"
25
+ end
26
+ end
27
+
28
+ def initialize(headers, payload)
29
+ @headers = headers
30
+ @payload = payload
31
+ @type = headers[:type] || "event"
32
+ @data_category = self.class.data_category(type)
33
+ @size_limit = SIZE_LIMITS[type]
34
+ end
35
+
36
+ def to_s
37
+ [JSON.generate(@headers), @payload.is_a?(String) ? @payload : JSON.generate(@payload)].join("\n")
38
+ end
39
+
40
+ def serialize
41
+ result = to_s
42
+
43
+ if result.bytesize > size_limit
44
+ remove_breadcrumbs!
45
+ result = to_s
46
+ end
47
+
48
+ if result.bytesize > size_limit
49
+ reduce_stacktrace!
50
+ result = to_s
51
+ end
52
+
53
+ [result, result.bytesize > size_limit]
54
+ end
55
+
56
+ def size_breakdown
57
+ payload.map do |key, value|
58
+ "#{key}: #{JSON.generate(value).bytesize}"
59
+ end.join(", ")
60
+ end
61
+
62
+ private
63
+
64
+ def remove_breadcrumbs!
65
+ if payload.key?(:breadcrumbs)
66
+ payload.delete(:breadcrumbs)
67
+ elsif payload.key?("breadcrumbs")
68
+ payload.delete("breadcrumbs")
69
+ end
70
+ end
71
+
72
+ def reduce_stacktrace!
73
+ if exceptions = payload.dig(:exception, :values) || payload.dig("exception", "values")
74
+ exceptions.each do |exception|
75
+ # in most cases there is only one exception (2 or 3 when have multiple causes), so we won't loop through this double condition much
76
+ traces = exception.dig(:stacktrace, :frames) || exception.dig("stacktrace", "frames")
77
+
78
+ if traces && traces.size > STACKTRACE_FRAME_LIMIT_ON_OVERSIZED_PAYLOAD
79
+ size_on_both_ends = STACKTRACE_FRAME_LIMIT_ON_OVERSIZED_PAYLOAD / 2
80
+ traces.replace(
81
+ traces[0..(size_on_both_ends - 1)] + traces[-size_on_both_ends..-1],
82
+ )
83
+ end
84
+ end
85
+ end
86
+ end
87
+ end
88
+ end
@@ -3,74 +3,6 @@
3
3
  module Sentry
4
4
  # @api private
5
5
  class Envelope
6
- class Item
7
- STACKTRACE_FRAME_LIMIT_ON_OVERSIZED_PAYLOAD = 500
8
- MAX_SERIALIZED_PAYLOAD_SIZE = 1024 * 1000
9
-
10
- attr_accessor :headers, :payload
11
-
12
- def initialize(headers, payload)
13
- @headers = headers
14
- @payload = payload
15
- end
16
-
17
- def type
18
- @headers[:type] || 'event'
19
- end
20
-
21
- def to_s
22
- [JSON.generate(@headers), JSON.generate(@payload)].join("\n")
23
- end
24
-
25
- def serialize
26
- result = to_s
27
-
28
- if result.bytesize > MAX_SERIALIZED_PAYLOAD_SIZE
29
- remove_breadcrumbs!
30
- result = to_s
31
- end
32
-
33
- if result.bytesize > MAX_SERIALIZED_PAYLOAD_SIZE
34
- reduce_stacktrace!
35
- result = to_s
36
- end
37
-
38
- [result, result.bytesize > MAX_SERIALIZED_PAYLOAD_SIZE]
39
- end
40
-
41
- def size_breakdown
42
- payload.map do |key, value|
43
- "#{key}: #{JSON.generate(value).bytesize}"
44
- end.join(", ")
45
- end
46
-
47
- private
48
-
49
- def remove_breadcrumbs!
50
- if payload.key?(:breadcrumbs)
51
- payload.delete(:breadcrumbs)
52
- elsif payload.key?("breadcrumbs")
53
- payload.delete("breadcrumbs")
54
- end
55
- end
56
-
57
- def reduce_stacktrace!
58
- if exceptions = payload.dig(:exception, :values) || payload.dig("exception", "values")
59
- exceptions.each do |exception|
60
- # in most cases there is only one exception (2 or 3 when have multiple causes), so we won't loop through this double condition much
61
- traces = exception.dig(:stacktrace, :frames) || exception.dig("stacktrace", "frames")
62
-
63
- if traces && traces.size > STACKTRACE_FRAME_LIMIT_ON_OVERSIZED_PAYLOAD
64
- size_on_both_ends = STACKTRACE_FRAME_LIMIT_ON_OVERSIZED_PAYLOAD / 2
65
- traces.replace(
66
- traces[0..(size_on_both_ends - 1)] + traces[-size_on_both_ends..-1],
67
- )
68
- end
69
- end
70
- end
71
- end
72
- end
73
-
74
6
  attr_accessor :headers, :items
75
7
 
76
8
  def initialize(headers = {})
@@ -91,3 +23,5 @@ module Sentry
91
23
  end
92
24
  end
93
25
  end
26
+
27
+ require_relative "envelope/item"
@@ -27,12 +27,12 @@ module Sentry
27
27
  end
28
28
 
29
29
  # @!visibility private
30
- def add_exception_interface(exception)
30
+ def add_exception_interface(exception, mechanism:)
31
31
  if exception.respond_to?(:sentry_context)
32
32
  @extra.merge!(exception.sentry_context)
33
33
  end
34
34
 
35
- @exception = Sentry::ExceptionInterface.build(exception: exception, stacktrace_builder: @stacktrace_builder)
35
+ @exception = Sentry::ExceptionInterface.build(exception: exception, stacktrace_builder: @stacktrace_builder, mechanism: mechanism)
36
36
  end
37
37
  end
38
38
  end