sentry-ruby 5.13.0 → 5.21.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.
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