sentry-ruby 5.9.0 → 5.16.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (40) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +2 -10
  3. data/README.md +9 -9
  4. data/lib/sentry/background_worker.rb +8 -1
  5. data/lib/sentry/backpressure_monitor.rb +75 -0
  6. data/lib/sentry/breadcrumb.rb +8 -2
  7. data/lib/sentry/check_in_event.rb +60 -0
  8. data/lib/sentry/client.rb +48 -10
  9. data/lib/sentry/configuration.rb +89 -17
  10. data/lib/sentry/cron/configuration.rb +23 -0
  11. data/lib/sentry/cron/monitor_check_ins.rb +75 -0
  12. data/lib/sentry/cron/monitor_config.rb +53 -0
  13. data/lib/sentry/cron/monitor_schedule.rb +42 -0
  14. data/lib/sentry/envelope.rb +1 -1
  15. data/lib/sentry/event.rb +6 -28
  16. data/lib/sentry/hub.rb +74 -2
  17. data/lib/sentry/integrable.rb +6 -0
  18. data/lib/sentry/interfaces/single_exception.rb +5 -3
  19. data/lib/sentry/net/http.rb +26 -20
  20. data/lib/sentry/profiler.rb +18 -7
  21. data/lib/sentry/propagation_context.rb +134 -0
  22. data/lib/sentry/puma.rb +11 -4
  23. data/lib/sentry/rack/capture_exceptions.rb +1 -4
  24. data/lib/sentry/rake.rb +0 -13
  25. data/lib/sentry/redis.rb +9 -3
  26. data/lib/sentry/release_detector.rb +1 -1
  27. data/lib/sentry/scope.rb +29 -13
  28. data/lib/sentry/span.rb +39 -2
  29. data/lib/sentry/test_helper.rb +18 -12
  30. data/lib/sentry/transaction.rb +18 -19
  31. data/lib/sentry/transaction_event.rb +0 -3
  32. data/lib/sentry/transport/configuration.rb +74 -1
  33. data/lib/sentry/transport/http_transport.rb +68 -37
  34. data/lib/sentry/transport/spotlight_transport.rb +50 -0
  35. data/lib/sentry/transport.rb +21 -17
  36. data/lib/sentry/utils/argument_checking_helper.rb +9 -3
  37. data/lib/sentry/version.rb +1 -1
  38. data/lib/sentry-ruby.rb +83 -25
  39. metadata +10 -3
  40. data/CODE_OF_CONDUCT.md +0 -74
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7a114a391fe058601b40369376152bee8ba9b1b96ab94710344283cfe1a4490b
4
- data.tar.gz: 7a17648c7a5d06f22d2d643f6ff1cb25d3fed2348609e372fc5762043ebd538a
3
+ metadata.gz: 565289974a80625ef7ecac6926cfb7292569dfafd84f12584a8214619bdd5ed5
4
+ data.tar.gz: 005b3e5c27b7188058c412d36981779eb759f7be1032429eea17a2e4a7ffcb9f
5
5
  SHA512:
6
- metadata.gz: dab19469a8e68201380e2be405332bb7c8caef7bf46fd4dae70f4dc3beaac2902ce51b2b2c902b39ca048e1bc5ff47f85b1e7d3a824b5f20ff002dcbdca2c354
7
- data.tar.gz: 6bf0cba7b42dd9bfd5078e4353ad7f92d932b959a87c748cb30e505ffd8d205899c819ea4f72e5f3099f0b861139864062b870219678abdeda0a0629604963d3
6
+ metadata.gz: d0e68db7961263d6aa070feaa131fae4c2039884e565cd248795298a99f08d66e7c921df1a6f6175e68ac0ed554185b4b94769f8b0fcba12d76e6aa042fc3359
7
+ data.tar.gz: 5e10f1406d7a7809aed02cef80e1d4355e61af54ed42ffd9a1b3a54a730229abfef4f0a690980c646757b93fffa6219d6500c5700b08a6eef61b6eb682d63620
data/Gemfile CHANGED
@@ -12,19 +12,9 @@ gem "redis", "~> #{redis_rb_version}"
12
12
 
13
13
  gem "puma"
14
14
 
15
- gem "rake", "~> 12.0"
16
- gem "rspec", "~> 3.0"
17
- gem "rspec-retry"
18
15
  gem "timecop"
19
- gem "simplecov"
20
- gem "simplecov-cobertura", "~> 1.4"
21
- gem "rexml"
22
16
  gem "stackprof" unless RUBY_PLATFORM == "java"
23
17
 
24
- gem "object_tracer"
25
- gem "debug", github: "ruby/debug", platform: :ruby if RUBY_VERSION.to_f >= 2.6
26
- gem "pry"
27
-
28
18
  gem "benchmark-ips"
29
19
  gem "benchmark_driver"
30
20
  gem "benchmark-ipsa"
@@ -32,3 +22,5 @@ gem "benchmark-memory"
32
22
 
33
23
  gem "yard", github: "lsegal/yard"
34
24
  gem "webrick"
25
+
26
+ eval_gemfile File.expand_path("../Gemfile", __dir__)
data/README.md CHANGED
@@ -13,14 +13,14 @@ _Bad software is everywhere, and we're tired of it. Sentry is on a mission to he
13
13
  Sentry SDK for Ruby
14
14
  ===========
15
15
 
16
- | current version | build | coverage | downloads |
17
- | --- | ----- | -------- | --------- |
18
- | [![Gem Version](https://img.shields.io/gem/v/sentry-ruby?label=sentry-ruby)](https://rubygems.org/gems/sentry-ruby) | [![Build Status](https://github.com/getsentry/sentry-ruby/workflows/sentry-ruby%20Test/badge.svg)](https://github.com/getsentry/sentry-ruby/actions/workflows/sentry_ruby_test.yml) | [![Coverage Status](https://img.shields.io/codecov/c/github/getsentry/sentry-ruby/master?logo=codecov)](https://codecov.io/gh/getsentry/sentry-ruby/branch/master) | [![Downloads](https://img.shields.io/gem/dt/sentry-ruby.svg)](https://rubygems.org/gems/sentry-ruby/) |
19
- | [![Gem Version](https://img.shields.io/gem/v/sentry-rails?label=sentry-rails)](https://rubygems.org/gems/sentry-rails) | [![Build Status](https://github.com/getsentry/sentry-ruby/workflows/sentry-rails%20Test/badge.svg)](https://github.com/getsentry/sentry-ruby/actions/workflows/sentry_rails_test.yml) | [![Coverage Status](https://img.shields.io/codecov/c/github/getsentry/sentry-ruby/master?logo=codecov)](https://codecov.io/gh/getsentry/sentry-ruby/branch/master) | [![Downloads](https://img.shields.io/gem/dt/sentry-rails.svg)](https://rubygems.org/gems/sentry-rails/) |
20
- | [![Gem Version](https://img.shields.io/gem/v/sentry-sidekiq?label=sentry-sidekiq)](https://rubygems.org/gems/sentry-sidekiq) | [![Build Status](https://github.com/getsentry/sentry-ruby/workflows/sentry-sidekiq%20Test/badge.svg)](https://github.com/getsentry/sentry-ruby/actions/workflows/sentry_sidekiq_test.yml) | [![Coverage Status](https://img.shields.io/codecov/c/github/getsentry/sentry-ruby/master?logo=codecov)](https://codecov.io/gh/getsentry/sentry-ruby/branch/master) | [![Downloads](https://img.shields.io/gem/dt/sentry-sidekiq.svg)](https://rubygems.org/gems/sentry-sidekiq/) |
21
- | [![Gem Version](https://img.shields.io/gem/v/sentry-delayed_job?label=sentry-delayed_job)](https://rubygems.org/gems/sentry-delayed_job) | [![Build Status](https://github.com/getsentry/sentry-ruby/workflows/sentry-delayed_job%20Test/badge.svg)](https://github.com/getsentry/sentry-ruby/actions/workflows/sentry_delayed_job_test.yml) | [![Coverage Status](https://img.shields.io/codecov/c/github/getsentry/sentry-ruby/master?logo=codecov)](https://codecov.io/gh/getsentry/sentry-ruby/branch/master) | [![Downloads](https://img.shields.io/gem/dt/sentry-delayed_job.svg)](https://rubygems.org/gems/sentry-delayed_job/) |
22
- | [![Gem Version](https://img.shields.io/gem/v/sentry-resque?label=sentry-resque)](https://rubygems.org/gems/sentry-resque) | [![Build Status](https://github.com/getsentry/sentry-ruby/workflows/sentry-resque%20Test/badge.svg)](https://github.com/getsentry/sentry-ruby/actions/workflows/sentry_resque_test.yml) | [![Coverage Status](https://img.shields.io/codecov/c/github/getsentry/sentry-ruby/master?logo=codecov)](https://codecov.io/gh/getsentry/sentry-ruby/branch/master) | [![Downloads](https://img.shields.io/gem/dt/sentry-resque.svg)](https://rubygems.org/gems/sentry-resque/) |
23
- | [![Gem Version](https://img.shields.io/gem/v/sentry-opentelemetry?label=sentry-opentelemetry)](https://rubygems.org/gems/sentry-opentelemetry) | [![Build Status](https://github.com/getsentry/sentry-ruby/workflows/sentry-opentelemetry%20Test/badge.svg)](https://github.com/getsentry/sentry-ruby/actions/workflows/sentry_opentelemetry_test.yml) | [![Coverage Status](https://img.shields.io/codecov/c/github/getsentry/sentry-ruby/master?logo=codecov)](https://codecov.io/gh/getsentry/sentry-ruby/branch/master) | [![Downloads](https://img.shields.io/gem/dt/sentry-opentelemetry.svg)](https://rubygems.org/gems/sentry-opentelemetry/) |
16
+ | current version | build | coverage | downloads |
17
+ | --- | ----- | -------- | --------- |
18
+ | [![Gem Version](https://img.shields.io/gem/v/sentry-ruby?label=sentry-ruby)](https://rubygems.org/gems/sentry-ruby) | [![Build Status](https://github.com/getsentry/sentry-ruby/actions/workflows/sentry_ruby_test.yml/badge.svg)](https://github.com/getsentry/sentry-ruby/actions/workflows/sentry_ruby_test.yml) | [![Coverage Status](https://img.shields.io/codecov/c/github/getsentry/sentry-ruby/master?logo=codecov)](https://codecov.io/gh/getsentry/sentry-ruby/branch/master) | [![Downloads](https://img.shields.io/gem/dt/sentry-ruby.svg)](https://rubygems.org/gems/sentry-ruby/) |
19
+ | [![Gem Version](https://img.shields.io/gem/v/sentry-rails?label=sentry-rails)](https://rubygems.org/gems/sentry-rails) | [![Build Status](https://github.com/getsentry/sentry-ruby/actions/workflows/sentry_rails_test.yml/badge.svg)](https://github.com/getsentry/sentry-ruby/actions/workflows/sentry_rails_test.yml) | [![Coverage Status](https://img.shields.io/codecov/c/github/getsentry/sentry-ruby/master?logo=codecov)](https://codecov.io/gh/getsentry/sentry-ruby/branch/master) | [![Downloads](https://img.shields.io/gem/dt/sentry-rails.svg)](https://rubygems.org/gems/sentry-rails/) |
20
+ | [![Gem Version](https://img.shields.io/gem/v/sentry-sidekiq?label=sentry-sidekiq)](https://rubygems.org/gems/sentry-sidekiq) | [![Build Status](https://github.com/getsentry/sentry-ruby/actions/workflows/sentry_sidekiq_test.yml/badge.svg)](https://github.com/getsentry/sentry-ruby/actions/workflows/sentry_sidekiq_test.yml) | [![Coverage Status](https://img.shields.io/codecov/c/github/getsentry/sentry-ruby/master?logo=codecov)](https://codecov.io/gh/getsentry/sentry-ruby/branch/master) | [![Downloads](https://img.shields.io/gem/dt/sentry-sidekiq.svg)](https://rubygems.org/gems/sentry-sidekiq/) |
21
+ | [![Gem Version](https://img.shields.io/gem/v/sentry-delayed_job?label=sentry-delayed_job)](https://rubygems.org/gems/sentry-delayed_job) | [![Build Status](https://github.com/getsentry/sentry-ruby/actions/workflows/sentry_delayed_job_test.yml/badge.svg)](https://github.com/getsentry/sentry-ruby/actions/workflows/sentry_delayed_job_test.yml) | [![Coverage Status](https://img.shields.io/codecov/c/github/getsentry/sentry-ruby/master?logo=codecov)](https://codecov.io/gh/getsentry/sentry-ruby/branch/master) | [![Downloads](https://img.shields.io/gem/dt/sentry-delayed_job.svg)](https://rubygems.org/gems/sentry-delayed_job/) |
22
+ | [![Gem Version](https://img.shields.io/gem/v/sentry-resque?label=sentry-resque)](https://rubygems.org/gems/sentry-resque) | [![Build Status](https://github.com/getsentry/sentry-ruby/actions/workflows/sentry_resque_test.yml/badge.svg)](https://github.com/getsentry/sentry-ruby/actions/workflows/sentry_resque_test.yml) | [![Coverage Status](https://img.shields.io/codecov/c/github/getsentry/sentry-ruby/master?logo=codecov)](https://codecov.io/gh/getsentry/sentry-ruby/branch/master) | [![Downloads](https://img.shields.io/gem/dt/sentry-resque.svg)](https://rubygems.org/gems/sentry-resque/) |
23
+ | [![Gem Version](https://img.shields.io/gem/v/sentry-opentelemetry?label=sentry-opentelemetry)](https://rubygems.org/gems/sentry-opentelemetry) | [![Build Status](https://github.com/getsentry/sentry-ruby/actions/workflows/sentry_opentelemetry_test.yml/badge.svg)](https://github.com/getsentry/sentry-ruby/actions/workflows/sentry_opentelemetry_test.yml) | [![Coverage Status](https://img.shields.io/codecov/c/github/getsentry/sentry-ruby/master?logo=codecov)](https://codecov.io/gh/getsentry/sentry-ruby/branch/master) | [![Downloads](https://img.shields.io/gem/dt/sentry-opentelemetry.svg)](https://rubygems.org/gems/sentry-opentelemetry/) |
24
24
 
25
25
 
26
26
 
@@ -33,7 +33,7 @@ If you're using `sentry-raven`, we recommend you to migrate to this new SDK. You
33
33
 
34
34
  ## Requirements
35
35
 
36
- We test on Ruby 2.4, 2.5, 2.6, 2.7, 3.0, and 3.1 at the latest patchlevel/teeny version. We also support JRuby 9.0.
36
+ We test from Ruby 2.4 to Ruby 3.2 at the latest patchlevel/teeny version. We also support JRuby 9.0.
37
37
 
38
38
  If you use self-hosted Sentry, please also make sure its version is above `20.6.0`.
39
39
 
@@ -13,10 +13,12 @@ module Sentry
13
13
  attr_reader :logger
14
14
  attr_accessor :shutdown_timeout
15
15
 
16
+ DEFAULT_MAX_QUEUE = 30
17
+
16
18
  def initialize(configuration)
17
- @max_queue = 30
18
19
  @shutdown_timeout = 1
19
20
  @number_of_threads = configuration.background_worker_threads
21
+ @max_queue = configuration.background_worker_max_queue
20
22
  @logger = configuration.logger
21
23
  @debug = configuration.debug
22
24
  @shutdown_callback = nil
@@ -63,6 +65,11 @@ module Sentry
63
65
  @shutdown_callback&.call
64
66
  end
65
67
 
68
+ def full?
69
+ @executor.is_a?(Concurrent::ThreadPoolExecutor) &&
70
+ @executor.remaining_capacity == 0
71
+ end
72
+
66
73
  private
67
74
 
68
75
  def _perform(&block)
@@ -0,0 +1,75 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sentry
4
+ class BackpressureMonitor
5
+ include LoggingHelper
6
+
7
+ DEFAULT_INTERVAL = 10
8
+ MAX_DOWNSAMPLE_FACTOR = 10
9
+
10
+ def initialize(configuration, client, interval: DEFAULT_INTERVAL)
11
+ @interval = interval
12
+ @client = client
13
+ @logger = configuration.logger
14
+
15
+ @thread = nil
16
+ @exited = false
17
+
18
+ @healthy = true
19
+ @downsample_factor = 0
20
+ end
21
+
22
+ def healthy?
23
+ ensure_thread
24
+ @healthy
25
+ end
26
+
27
+ def downsample_factor
28
+ ensure_thread
29
+ @downsample_factor
30
+ end
31
+
32
+ def run
33
+ check_health
34
+ set_downsample_factor
35
+ end
36
+
37
+ def check_health
38
+ @healthy = !(@client.transport.any_rate_limited? || Sentry.background_worker&.full?)
39
+ end
40
+
41
+ def set_downsample_factor
42
+ if @healthy
43
+ log_debug("[BackpressureMonitor] health check positive, reverting to normal sampling") if @downsample_factor.positive?
44
+ @downsample_factor = 0
45
+ else
46
+ @downsample_factor += 1 if @downsample_factor < MAX_DOWNSAMPLE_FACTOR
47
+ log_debug("[BackpressureMonitor] health check negative, downsampling with a factor of #{@downsample_factor}")
48
+ end
49
+ end
50
+
51
+ def kill
52
+ log_debug("[BackpressureMonitor] killing monitor")
53
+
54
+ @exited = true
55
+ @thread&.kill
56
+ end
57
+
58
+ private
59
+
60
+ def ensure_thread
61
+ return if @exited
62
+ return if @thread&.alive?
63
+
64
+ @thread = Thread.new do
65
+ loop do
66
+ sleep(@interval)
67
+ run
68
+ end
69
+ end
70
+ rescue ThreadError
71
+ log_debug("[BackpressureMonitor] Thread creation failed")
72
+ @exited = true
73
+ end
74
+ end
75
+ end
@@ -9,7 +9,7 @@ module Sentry
9
9
  # @return [Hash, nil]
10
10
  attr_accessor :data
11
11
  # @return [String, nil]
12
- attr_accessor :level
12
+ attr_reader :level
13
13
  # @return [Time, Integer, nil]
14
14
  attr_accessor :timestamp
15
15
  # @return [String, nil]
@@ -26,10 +26,10 @@ module Sentry
26
26
  def initialize(category: nil, data: nil, message: nil, timestamp: nil, level: nil, type: nil)
27
27
  @category = category
28
28
  @data = data || {}
29
- @level = level
30
29
  @timestamp = timestamp || Sentry.utc_now.to_i
31
30
  @type = type
32
31
  self.message = message
32
+ self.level = level
33
33
  end
34
34
 
35
35
  # @return [Hash]
@@ -50,6 +50,12 @@ module Sentry
50
50
  @message = (message || "").byteslice(0..Event::MAX_MESSAGE_SIZE_IN_BYTES)
51
51
  end
52
52
 
53
+ # @param level [String]
54
+ # @return [void]
55
+ def level=(level) # needed to meet the Sentry spec
56
+ @level = level == "warn" ? "warning" : level
57
+ end
58
+
53
59
  private
54
60
 
55
61
  def serialized_data
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'securerandom'
4
+ require 'sentry/cron/monitor_config'
5
+
6
+ module Sentry
7
+ class CheckInEvent < Event
8
+ TYPE = 'check_in'
9
+
10
+ # uuid to identify this check-in.
11
+ # @return [String]
12
+ attr_accessor :check_in_id
13
+
14
+ # Identifier of the monitor for this check-in.
15
+ # @return [String]
16
+ attr_accessor :monitor_slug
17
+
18
+ # Duration of this check since it has started in seconds.
19
+ # @return [Integer, nil]
20
+ attr_accessor :duration
21
+
22
+ # Monitor configuration to support upserts.
23
+ # @return [Cron::MonitorConfig, nil]
24
+ attr_accessor :monitor_config
25
+
26
+ # Status of this check-in.
27
+ # @return [Symbol]
28
+ attr_accessor :status
29
+
30
+ VALID_STATUSES = %i(ok in_progress error)
31
+
32
+ def initialize(
33
+ slug:,
34
+ status:,
35
+ duration: nil,
36
+ monitor_config: nil,
37
+ check_in_id: nil,
38
+ **options
39
+ )
40
+ super(**options)
41
+
42
+ self.monitor_slug = slug
43
+ self.status = status
44
+ self.duration = duration
45
+ self.monitor_config = monitor_config
46
+ self.check_in_id = check_in_id || SecureRandom.uuid.delete('-')
47
+ end
48
+
49
+ # @return [Hash]
50
+ def to_hash
51
+ data = super
52
+ data[:check_in_id] = check_in_id
53
+ data[:monitor_slug] = monitor_slug
54
+ data[:status] = status
55
+ data[:duration] = duration if duration
56
+ data[:monitor_config] = monitor_config.to_hash if monitor_config
57
+ data
58
+ end
59
+ end
60
+ end
data/lib/sentry/client.rb CHANGED
@@ -10,6 +10,10 @@ module Sentry
10
10
  # @return [Transport]
11
11
  attr_reader :transport
12
12
 
13
+ # The Transport object that'll send events for the client.
14
+ # @return [SpotlightTransport, nil]
15
+ attr_reader :spotlight_transport
16
+
13
17
  # @!macro configuration
14
18
  attr_reader :configuration
15
19
 
@@ -32,6 +36,8 @@ module Sentry
32
36
  DummyTransport.new(configuration)
33
37
  end
34
38
  end
39
+
40
+ @spotlight_transport = SpotlightTransport.new(configuration) if configuration.spotlight
35
41
  end
36
42
 
37
43
  # Applies the given scope's data to the event and sends it to Sentry.
@@ -42,7 +48,7 @@ module Sentry
42
48
  def capture_event(event, scope, hint = {})
43
49
  return unless configuration.sending_allowed?
44
50
 
45
- unless event.is_a?(TransactionEvent) || configuration.sample_allowed?
51
+ if event.is_a?(ErrorEvent) && !configuration.sample_allowed?
46
52
  transport.record_lost_event(:sample_rate, 'event')
47
53
  return
48
54
  end
@@ -51,7 +57,7 @@ module Sentry
51
57
  event = scope.apply_to_event(event, hint)
52
58
 
53
59
  if event.nil?
54
- log_info("Discarded event because one of the event processors returned nil")
60
+ log_debug("Discarded event because one of the event processors returned nil")
55
61
  transport.record_lost_event(:event_processor, event_type)
56
62
  return
57
63
  end
@@ -104,6 +110,37 @@ module Sentry
104
110
  event
105
111
  end
106
112
 
113
+ # Initializes a CheckInEvent object with the given options.
114
+ #
115
+ # @param slug [String] identifier of this monitor
116
+ # @param status [Symbol] status of this check-in, one of {CheckInEvent::VALID_STATUSES}
117
+ # @param hint [Hash] the hint data that'll be passed to `before_send` callback and the scope's event processors.
118
+ # @param duration [Integer, nil] seconds elapsed since this monitor started
119
+ # @param monitor_config [Cron::MonitorConfig, nil] configuration for this monitor
120
+ # @param check_in_id [String, nil] for updating the status of an existing monitor
121
+ #
122
+ # @return [Event]
123
+ def event_from_check_in(
124
+ slug,
125
+ status,
126
+ hint = {},
127
+ duration: nil,
128
+ monitor_config: nil,
129
+ check_in_id: nil
130
+ )
131
+ return unless configuration.sending_allowed?
132
+
133
+ CheckInEvent.new(
134
+ configuration: configuration,
135
+ integration_meta: Sentry.integrations[hint[:integration]],
136
+ slug: slug,
137
+ status: status,
138
+ duration: duration,
139
+ monitor_config: monitor_config,
140
+ check_in_id: check_in_id
141
+ )
142
+ end
143
+
107
144
  # Initializes an Event object with the given Transaction object.
108
145
  # @param transaction [Transaction] the transaction to be recorded.
109
146
  # @return [TransactionEvent]
@@ -119,7 +156,7 @@ module Sentry
119
156
  event = configuration.before_send.call(event, hint)
120
157
 
121
158
  if event.nil?
122
- log_info("Discarded event because before_send returned nil")
159
+ log_debug("Discarded event because before_send returned nil")
123
160
  transport.record_lost_event(:before_send, 'event')
124
161
  return
125
162
  end
@@ -129,25 +166,24 @@ module Sentry
129
166
  event = configuration.before_send_transaction.call(event, hint)
130
167
 
131
168
  if event.nil?
132
- log_info("Discarded event because before_send_transaction returned nil")
169
+ log_debug("Discarded event because before_send_transaction returned nil")
133
170
  transport.record_lost_event(:before_send, 'transaction')
134
171
  return
135
172
  end
136
173
  end
137
174
 
138
175
  transport.send_event(event)
176
+ spotlight_transport&.send_event(event)
139
177
 
140
178
  event
141
179
  rescue => e
142
- loggable_event_type = event_type.capitalize
143
- log_error("#{loggable_event_type} sending failed", e, debug: configuration.debug)
144
-
145
- event_info = Event.get_log_message(event.to_hash)
146
- log_info("Unreported #{loggable_event_type}: #{event_info}")
180
+ log_error("Event sending failed", e, debug: configuration.debug)
147
181
  transport.record_lost_event(:network_error, event_type)
148
182
  raise
149
183
  end
150
184
 
185
+ # @deprecated use Sentry.get_traceparent instead.
186
+ #
151
187
  # Generates a Sentry trace for distribted tracing from the given Span.
152
188
  # Returns `nil` if `config.propagate_traces` is `false`.
153
189
  # @param span [Span] the span to generate trace from.
@@ -160,7 +196,9 @@ module Sentry
160
196
  trace
161
197
  end
162
198
 
163
- # Generates a W3C Baggage header for distribted tracing from the given Span.
199
+ # @deprecated Use Sentry.get_baggage instead.
200
+ #
201
+ # Generates a W3C Baggage header for distributed tracing from the given Span.
164
202
  # Returns `nil` if `config.propagate_traces` is `false`.
165
203
  # @param span [Span] the span to generate trace from.
166
204
  # @return [String, nil]
@@ -7,6 +7,7 @@ require 'sentry/utils/custom_inspection'
7
7
  require "sentry/dsn"
8
8
  require "sentry/release_detector"
9
9
  require "sentry/transport/configuration"
10
+ require "sentry/cron/configuration"
10
11
  require "sentry/linecache"
11
12
  require "sentry/interfaces/stacktrace_builder"
12
13
 
@@ -14,6 +15,8 @@ module Sentry
14
15
  class Configuration
15
16
  include CustomInspection
16
17
  include LoggingHelper
18
+ include ArgumentCheckingHelper
19
+
17
20
  # Directories to be recognized as part of your app. e.g. if you
18
21
  # have an `engines` dir at the root of your project, you may want
19
22
  # to set this to something like /(app|config|engines|lib)/
@@ -38,6 +41,13 @@ module Sentry
38
41
  # @return [Integer]
39
42
  attr_accessor :background_worker_threads
40
43
 
44
+ # The maximum queue size for the background worker.
45
+ # Jobs will be rejected above this limit.
46
+ #
47
+ # Default is {BackgroundWorker::DEFAULT_MAX_QUEUE}.
48
+ # @return [Integer]
49
+ attr_accessor :background_worker_max_queue
50
+
41
51
  # a proc/lambda that takes an array of stack traces
42
52
  # it'll be used to silence (reduce) backtrace of the exception
43
53
  #
@@ -140,6 +150,14 @@ module Sentry
140
150
  # @return [Boolean]
141
151
  attr_accessor :include_local_variables
142
152
 
153
+ # Whether to capture events and traces into Spotlight. Default is false.
154
+ # If you set this to true, Sentry will send events and traces to the local
155
+ # Sidecar proxy at http://localhost:8969/stream.
156
+ # If you want to use a different Sidecar proxy address, set this to String
157
+ # with the proxy URL.
158
+ # @return [Boolean, String]
159
+ attr_accessor :spotlight
160
+
143
161
  # @deprecated Use {#include_local_variables} instead.
144
162
  alias_method :capture_exception_frame_locals, :include_local_variables
145
163
 
@@ -179,7 +197,7 @@ module Sentry
179
197
  # Release tag to be passed with every event sent to Sentry.
180
198
  # We automatically try to set this to a git SHA or Capistrano release.
181
199
  # @return [String]
182
- attr_accessor :release
200
+ attr_reader :release
183
201
 
184
202
  # The sampling factor to apply to events. A value of 0.0 will not send
185
203
  # any events, and a value of 1.0 will send 100% of events.
@@ -209,13 +227,17 @@ module Sentry
209
227
  # @return [String]
210
228
  attr_accessor :server_name
211
229
 
212
- # Return a Transport::Configuration object for transport-related configurations.
213
- # @return [Transport]
230
+ # Transport related configuration.
231
+ # @return [Transport::Configuration]
214
232
  attr_reader :transport
215
233
 
234
+ # Cron related configuration.
235
+ # @return [Cron::Configuration]
236
+ attr_reader :cron
237
+
216
238
  # Take a float between 0.0 and 1.0 as the sample rate for tracing events (transactions).
217
- # @return [Float]
218
- attr_accessor :traces_sample_rate
239
+ # @return [Float, nil]
240
+ attr_reader :traces_sample_rate
219
241
 
220
242
  # Take a Proc that controls the sample rate for every tracing event, e.g.
221
243
  # @example
@@ -241,6 +263,17 @@ module Sentry
241
263
  # @return [Boolean]
242
264
  attr_accessor :auto_session_tracking
243
265
 
266
+ # Whether to downsample transactions automatically because of backpressure.
267
+ # Starts a new monitor thread to check health of the SDK every 10 seconds.
268
+ # Default is false
269
+ # @return [Boolean]
270
+ attr_accessor :enable_backpressure_handling
271
+
272
+ # Allowlist of outgoing request targets to which sentry-trace and baggage headers are attached.
273
+ # Default is all (/.*/)
274
+ # @return [Array<String, Regexp>]
275
+ attr_accessor :trace_propagation_targets
276
+
244
277
  # The instrumenter to use, :sentry or :otel
245
278
  # @return [Symbol]
246
279
  attr_reader :instrumenter
@@ -251,10 +284,24 @@ module Sentry
251
284
  # @return [Float, nil]
252
285
  attr_reader :profiles_sample_rate
253
286
 
287
+ # Array of patches to apply.
288
+ # Default is {DEFAULT_PATCHES}
289
+ # @return [Array<Symbol>]
290
+ attr_accessor :enabled_patches
291
+
254
292
  # these are not config options
255
293
  # @!visibility private
256
294
  attr_reader :errors, :gem_specs
257
295
 
296
+ # These exceptions could enter Puma's `lowlevel_error_handler` callback and the SDK's Puma integration
297
+ # But they are mostly considered as noise and should be ignored by default
298
+ # Please see https://github.com/getsentry/sentry-ruby/pull/2026 for more information
299
+ PUMA_IGNORE_DEFAULT = [
300
+ 'Puma::MiniSSL::SSLError',
301
+ 'Puma::HttpParserError',
302
+ 'Puma::HttpParserError501'
303
+ ].freeze
304
+
258
305
  # Most of these errors generate 4XX responses. In general, Sentry clients
259
306
  # only automatically report 5xx responses.
260
307
  IGNORE_DEFAULT = [
@@ -279,6 +326,10 @@ module Sentry
279
326
 
280
327
  INSTRUMENTERS = [:sentry, :otel]
281
328
 
329
+ PROPAGATION_TARGETS_MATCH_ALL = /.*/.freeze
330
+
331
+ DEFAULT_PATCHES = %i(redis puma http).freeze
332
+
282
333
  class << self
283
334
  # Post initialization callbacks are called at the end of initialization process
284
335
  # allowing extending the configuration of sentry-ruby by multiple extensions
@@ -296,6 +347,7 @@ module Sentry
296
347
  self.app_dirs_pattern = nil
297
348
  self.debug = false
298
349
  self.background_worker_threads = Concurrent.processor_count
350
+ self.background_worker_max_queue = BackgroundWorker::DEFAULT_MAX_QUEUE
299
351
  self.backtrace_cleanup_callback = nil
300
352
  self.max_breadcrumbs = BreadcrumbBuffer::DEFAULT_SIZE
301
353
  self.breadcrumbs_logger = []
@@ -304,7 +356,7 @@ module Sentry
304
356
  self.environment = environment_from_env
305
357
  self.enabled_environments = []
306
358
  self.exclude_loggers = []
307
- self.excluded_exceptions = IGNORE_DEFAULT.dup
359
+ self.excluded_exceptions = IGNORE_DEFAULT + PUMA_IGNORE_DEFAULT
308
360
  self.inspect_exception_causes_for_exclusion = true
309
361
  self.linecache = ::Sentry::LineCache.new
310
362
  self.logger = ::Sentry::Logger.new(STDOUT)
@@ -317,19 +369,23 @@ module Sentry
317
369
  self.skip_rake_integration = false
318
370
  self.send_client_reports = true
319
371
  self.auto_session_tracking = true
372
+ self.enable_backpressure_handling = false
320
373
  self.trusted_proxies = []
321
374
  self.dsn = ENV['SENTRY_DSN']
375
+ self.spotlight = false
322
376
  self.server_name = server_name_from_env
323
377
  self.instrumenter = :sentry
378
+ self.trace_propagation_targets = [PROPAGATION_TARGETS_MATCH_ALL]
379
+ self.enabled_patches = DEFAULT_PATCHES.dup
324
380
 
325
381
  self.before_send = nil
326
382
  self.before_send_transaction = nil
327
383
  self.rack_env_whitelist = RACK_ENV_WHITELIST_DEFAULT
328
- self.traces_sample_rate = nil
329
384
  self.traces_sampler = nil
330
385
  self.enable_tracing = nil
331
386
 
332
387
  @transport = Transport::Configuration.new
388
+ @cron = Cron::Configuration.new
333
389
  @gem_specs = Hash[Gem::Specification.map { |spec| [spec.name, spec.version.to_s] }] if Gem::Specification.respond_to?(:map)
334
390
 
335
391
  run_post_initialization_callbacks
@@ -341,6 +397,12 @@ module Sentry
341
397
 
342
398
  alias server= dsn=
343
399
 
400
+ def release=(value)
401
+ check_argument_type!(value, String, NilClass)
402
+
403
+ @release = value
404
+ end
405
+
344
406
  def async=(value)
345
407
  check_callable!("async", value)
346
408
 
@@ -401,15 +463,25 @@ module Sentry
401
463
  @traces_sample_rate ||= 1.0 if enable_tracing
402
464
  end
403
465
 
466
+ def is_numeric_or_nil?(value)
467
+ value.is_a?(Numeric) || value.nil?
468
+ end
469
+
470
+ def traces_sample_rate=(traces_sample_rate)
471
+ raise ArgumentError, "traces_sample_rate must be a Numeric or nil" unless is_numeric_or_nil?(traces_sample_rate)
472
+ @traces_sample_rate = traces_sample_rate
473
+ end
474
+
404
475
  def profiles_sample_rate=(profiles_sample_rate)
405
- log_info("Please make sure to include the 'stackprof' gem in your Gemfile to use Profiling with Sentry.") unless defined?(StackProf)
476
+ raise ArgumentError, "profiles_sample_rate must be a Numeric or nil" unless is_numeric_or_nil?(profiles_sample_rate)
477
+ log_warn("Please make sure to include the 'stackprof' gem in your Gemfile to use Profiling with Sentry.") unless defined?(StackProf)
406
478
  @profiles_sample_rate = profiles_sample_rate
407
479
  end
408
480
 
409
481
  def sending_allowed?
410
482
  @errors = []
411
483
 
412
- valid? && capture_in_environment?
484
+ spotlight || (valid? && capture_in_environment?)
413
485
  end
414
486
 
415
487
  def sample_allowed?
@@ -435,19 +507,19 @@ module Sentry
435
507
  enabled_environments.empty? || enabled_environments.include?(environment)
436
508
  end
437
509
 
510
+ def valid_sample_rate?(sample_rate)
511
+ return false unless sample_rate.is_a?(Numeric)
512
+ sample_rate >= 0.0 && sample_rate <= 1.0
513
+ end
514
+
438
515
  def tracing_enabled?
439
- valid_sampler = !!((@traces_sample_rate &&
440
- @traces_sample_rate >= 0.0 &&
441
- @traces_sample_rate <= 1.0) ||
442
- @traces_sampler)
516
+ valid_sampler = !!((valid_sample_rate?(@traces_sample_rate)) || @traces_sampler)
443
517
 
444
518
  (@enable_tracing != false) && valid_sampler && sending_allowed?
445
519
  end
446
520
 
447
521
  def profiling_enabled?
448
- valid_sampler = !!(@profiles_sample_rate &&
449
- @profiles_sample_rate >= 0.0 &&
450
- @profiles_sample_rate <= 1.0)
522
+ valid_sampler = !!(valid_sample_rate?(@profiles_sample_rate))
451
523
 
452
524
  tracing_enabled? && valid_sampler && sending_allowed?
453
525
  end
@@ -477,7 +549,7 @@ module Sentry
477
549
  def detect_release
478
550
  return unless sending_allowed?
479
551
 
480
- self.release ||= ReleaseDetector.detect_release(project_root: project_root, running_on_heroku: running_on_heroku?)
552
+ @release ||= ReleaseDetector.detect_release(project_root: project_root, running_on_heroku: running_on_heroku?)
481
553
 
482
554
  if running_on_heroku? && release.nil?
483
555
  log_warn(HEROKU_DYNO_METADATA_MESSAGE)
@@ -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