sentry-ruby 5.14.0 → 5.16.1
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.
- checksums.yaml +4 -4
- data/Gemfile +2 -19
- data/lib/sentry/background_worker.rb +8 -1
- data/lib/sentry/backpressure_monitor.rb +75 -0
- data/lib/sentry/client.rb +12 -9
- data/lib/sentry/configuration.rb +34 -4
- data/lib/sentry/cron/configuration.rb +23 -0
- data/lib/sentry/cron/monitor_check_ins.rb +29 -20
- data/lib/sentry/event.rb +0 -28
- data/lib/sentry/hub.rb +2 -2
- data/lib/sentry/rake.rb +0 -13
- data/lib/sentry/scope.rb +12 -11
- data/lib/sentry/transaction.rb +9 -2
- data/lib/sentry/transport/http_transport.rb +65 -40
- data/lib/sentry/transport/spotlight_transport.rb +50 -0
- data/lib/sentry/transport.rb +18 -15
- data/lib/sentry/version.rb +1 -1
- data/lib/sentry-ruby.rb +25 -23
- metadata +5 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 565289974a80625ef7ecac6926cfb7292569dfafd84f12584a8214619bdd5ed5
|
4
|
+
data.tar.gz: 005b3e5c27b7188058c412d36981779eb759f7be1032429eea17a2e4a7ffcb9f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d0e68db7961263d6aa070feaa131fae4c2039884e565cd248795298a99f08d66e7c921df1a6f6175e68ac0ed554185b4b94769f8b0fcba12d76e6aa042fc3359
|
7
|
+
data.tar.gz: 5e10f1406d7a7809aed02cef80e1d4355e61af54ed42ffd9a1b3a54a730229abfef4f0a690980c646757b93fffa6219d6500c5700b08a6eef61b6eb682d63620
|
data/Gemfile
CHANGED
@@ -12,28 +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
|
-
ruby_version = Gem::Version.new(RUBY_VERSION)
|
25
|
-
|
26
|
-
if ruby_version >= Gem::Version.new("2.6.0")
|
27
|
-
gem "debug", github: "ruby/debug", platform: :ruby
|
28
|
-
gem "irb"
|
29
|
-
|
30
|
-
if ruby_version >= Gem::Version.new("3.0.0")
|
31
|
-
gem "ruby-lsp-rspec"
|
32
|
-
end
|
33
|
-
end
|
34
|
-
|
35
|
-
gem "pry"
|
36
|
-
|
37
18
|
gem "benchmark-ips"
|
38
19
|
gem "benchmark_driver"
|
39
20
|
gem "benchmark-ipsa"
|
@@ -41,3 +22,5 @@ gem "benchmark-memory"
|
|
41
22
|
|
42
23
|
gem "yard", github: "lsegal/yard"
|
43
24
|
gem "webrick"
|
25
|
+
|
26
|
+
eval_gemfile File.expand_path("../Gemfile", __dir__)
|
@@ -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
|
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
|
-
|
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
|
-
|
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
|
@@ -150,7 +156,7 @@ module Sentry
|
|
150
156
|
event = configuration.before_send.call(event, hint)
|
151
157
|
|
152
158
|
if event.nil?
|
153
|
-
|
159
|
+
log_debug("Discarded event because before_send returned nil")
|
154
160
|
transport.record_lost_event(:before_send, 'event')
|
155
161
|
return
|
156
162
|
end
|
@@ -160,21 +166,18 @@ module Sentry
|
|
160
166
|
event = configuration.before_send_transaction.call(event, hint)
|
161
167
|
|
162
168
|
if event.nil?
|
163
|
-
|
169
|
+
log_debug("Discarded event because before_send_transaction returned nil")
|
164
170
|
transport.record_lost_event(:before_send, 'transaction')
|
165
171
|
return
|
166
172
|
end
|
167
173
|
end
|
168
174
|
|
169
175
|
transport.send_event(event)
|
176
|
+
spotlight_transport&.send_event(event)
|
170
177
|
|
171
178
|
event
|
172
179
|
rescue => e
|
173
|
-
|
174
|
-
log_error("#{loggable_event_type} sending failed", e, debug: configuration.debug)
|
175
|
-
|
176
|
-
event_info = Event.get_log_message(event.to_hash)
|
177
|
-
log_info("Unreported #{loggable_event_type}: #{event_info}")
|
180
|
+
log_error("Event sending failed", e, debug: configuration.debug)
|
178
181
|
transport.record_lost_event(:network_error, event_type)
|
179
182
|
raise
|
180
183
|
end
|
data/lib/sentry/configuration.rb
CHANGED
@@ -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
|
|
@@ -40,6 +41,13 @@ module Sentry
|
|
40
41
|
# @return [Integer]
|
41
42
|
attr_accessor :background_worker_threads
|
42
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
|
+
|
43
51
|
# a proc/lambda that takes an array of stack traces
|
44
52
|
# it'll be used to silence (reduce) backtrace of the exception
|
45
53
|
#
|
@@ -142,6 +150,14 @@ module Sentry
|
|
142
150
|
# @return [Boolean]
|
143
151
|
attr_accessor :include_local_variables
|
144
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
|
+
|
145
161
|
# @deprecated Use {#include_local_variables} instead.
|
146
162
|
alias_method :capture_exception_frame_locals, :include_local_variables
|
147
163
|
|
@@ -211,10 +227,14 @@ module Sentry
|
|
211
227
|
# @return [String]
|
212
228
|
attr_accessor :server_name
|
213
229
|
|
214
|
-
#
|
215
|
-
# @return [Transport]
|
230
|
+
# Transport related configuration.
|
231
|
+
# @return [Transport::Configuration]
|
216
232
|
attr_reader :transport
|
217
233
|
|
234
|
+
# Cron related configuration.
|
235
|
+
# @return [Cron::Configuration]
|
236
|
+
attr_reader :cron
|
237
|
+
|
218
238
|
# Take a float between 0.0 and 1.0 as the sample rate for tracing events (transactions).
|
219
239
|
# @return [Float, nil]
|
220
240
|
attr_reader :traces_sample_rate
|
@@ -243,6 +263,12 @@ module Sentry
|
|
243
263
|
# @return [Boolean]
|
244
264
|
attr_accessor :auto_session_tracking
|
245
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
|
+
|
246
272
|
# Allowlist of outgoing request targets to which sentry-trace and baggage headers are attached.
|
247
273
|
# Default is all (/.*/)
|
248
274
|
# @return [Array<String, Regexp>]
|
@@ -321,6 +347,7 @@ module Sentry
|
|
321
347
|
self.app_dirs_pattern = nil
|
322
348
|
self.debug = false
|
323
349
|
self.background_worker_threads = Concurrent.processor_count
|
350
|
+
self.background_worker_max_queue = BackgroundWorker::DEFAULT_MAX_QUEUE
|
324
351
|
self.backtrace_cleanup_callback = nil
|
325
352
|
self.max_breadcrumbs = BreadcrumbBuffer::DEFAULT_SIZE
|
326
353
|
self.breadcrumbs_logger = []
|
@@ -342,8 +369,10 @@ module Sentry
|
|
342
369
|
self.skip_rake_integration = false
|
343
370
|
self.send_client_reports = true
|
344
371
|
self.auto_session_tracking = true
|
372
|
+
self.enable_backpressure_handling = false
|
345
373
|
self.trusted_proxies = []
|
346
374
|
self.dsn = ENV['SENTRY_DSN']
|
375
|
+
self.spotlight = false
|
347
376
|
self.server_name = server_name_from_env
|
348
377
|
self.instrumenter = :sentry
|
349
378
|
self.trace_propagation_targets = [PROPAGATION_TARGETS_MATCH_ALL]
|
@@ -356,6 +385,7 @@ module Sentry
|
|
356
385
|
self.enable_tracing = nil
|
357
386
|
|
358
387
|
@transport = Transport::Configuration.new
|
388
|
+
@cron = Cron::Configuration.new
|
359
389
|
@gem_specs = Hash[Gem::Specification.map { |spec| [spec.name, spec.version.to_s] }] if Gem::Specification.respond_to?(:map)
|
360
390
|
|
361
391
|
run_post_initialization_callbacks
|
@@ -444,14 +474,14 @@ module Sentry
|
|
444
474
|
|
445
475
|
def profiles_sample_rate=(profiles_sample_rate)
|
446
476
|
raise ArgumentError, "profiles_sample_rate must be a Numeric or nil" unless is_numeric_or_nil?(profiles_sample_rate)
|
447
|
-
|
477
|
+
log_warn("Please make sure to include the 'stackprof' gem in your Gemfile to use Profiling with Sentry.") unless defined?(StackProf)
|
448
478
|
@profiles_sample_rate = profiles_sample_rate
|
449
479
|
end
|
450
480
|
|
451
481
|
def sending_allowed?
|
452
482
|
@errors = []
|
453
483
|
|
454
|
-
valid? && capture_in_environment?
|
484
|
+
spotlight || (valid? && capture_in_environment?)
|
455
485
|
end
|
456
486
|
|
457
487
|
def sample_allowed?
|
@@ -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
|
@@ -4,7 +4,7 @@ module Sentry
|
|
4
4
|
MAX_SLUG_LENGTH = 50
|
5
5
|
|
6
6
|
module Patch
|
7
|
-
def perform(*args)
|
7
|
+
def perform(*args, **opts)
|
8
8
|
slug = self.class.sentry_monitor_slug
|
9
9
|
monitor_config = self.class.sentry_monitor_config
|
10
10
|
|
@@ -13,38 +13,49 @@ module Sentry
|
|
13
13
|
monitor_config: monitor_config)
|
14
14
|
|
15
15
|
start = Sentry.utc_now.to_i
|
16
|
-
ret = super
|
17
|
-
duration = Sentry.utc_now.to_i - start
|
18
16
|
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
monitor_config: monitor_config)
|
17
|
+
begin
|
18
|
+
# need to do this on ruby <= 2.6 sadly
|
19
|
+
ret = method(:perform).super_method.arity == 0 ? super() : super
|
20
|
+
duration = Sentry.utc_now.to_i - start
|
24
21
|
|
25
|
-
|
26
|
-
|
27
|
-
|
22
|
+
Sentry.capture_check_in(slug,
|
23
|
+
:ok,
|
24
|
+
check_in_id: check_in_id,
|
25
|
+
duration: duration,
|
26
|
+
monitor_config: monitor_config)
|
28
27
|
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
duration: duration,
|
33
|
-
monitor_config: monitor_config)
|
28
|
+
ret
|
29
|
+
rescue Exception
|
30
|
+
duration = Sentry.utc_now.to_i - start
|
34
31
|
|
35
|
-
|
32
|
+
Sentry.capture_check_in(slug,
|
33
|
+
:error,
|
34
|
+
check_in_id: check_in_id,
|
35
|
+
duration: duration,
|
36
|
+
monitor_config: monitor_config)
|
37
|
+
|
38
|
+
raise
|
39
|
+
end
|
36
40
|
end
|
37
41
|
end
|
38
42
|
|
39
43
|
module ClassMethods
|
40
44
|
def sentry_monitor_check_ins(slug: nil, monitor_config: nil)
|
45
|
+
if monitor_config && Sentry.configuration
|
46
|
+
cron_config = Sentry.configuration.cron
|
47
|
+
monitor_config.checkin_margin ||= cron_config.default_checkin_margin
|
48
|
+
monitor_config.max_runtime ||= cron_config.default_max_runtime
|
49
|
+
monitor_config.timezone ||= cron_config.default_timezone
|
50
|
+
end
|
51
|
+
|
41
52
|
@sentry_monitor_slug = slug
|
42
53
|
@sentry_monitor_config = monitor_config
|
43
54
|
|
44
55
|
prepend Patch
|
45
56
|
end
|
46
57
|
|
47
|
-
def sentry_monitor_slug
|
58
|
+
def sentry_monitor_slug(name: self.name)
|
48
59
|
@sentry_monitor_slug ||= begin
|
49
60
|
slug = name.gsub('::', '-').downcase
|
50
61
|
slug[-MAX_SLUG_LENGTH..-1] || slug
|
@@ -56,8 +67,6 @@ module Sentry
|
|
56
67
|
end
|
57
68
|
end
|
58
69
|
|
59
|
-
extend ClassMethods
|
60
|
-
|
61
70
|
def self.included(base)
|
62
71
|
base.extend(ClassMethods)
|
63
72
|
end
|
data/lib/sentry/event.rb
CHANGED
@@ -76,34 +76,6 @@ module Sentry
|
|
76
76
|
@message = (message || "").byteslice(0..MAX_MESSAGE_SIZE_IN_BYTES)
|
77
77
|
end
|
78
78
|
|
79
|
-
class << self
|
80
|
-
# @!visibility private
|
81
|
-
def get_log_message(event_hash)
|
82
|
-
message = event_hash[:message] || event_hash['message']
|
83
|
-
|
84
|
-
return message unless message.nil? || message.empty?
|
85
|
-
|
86
|
-
message = get_message_from_exception(event_hash)
|
87
|
-
|
88
|
-
return message unless message.nil? || message.empty?
|
89
|
-
|
90
|
-
message = event_hash[:transaction] || event_hash["transaction"]
|
91
|
-
|
92
|
-
return message unless message.nil? || message.empty?
|
93
|
-
|
94
|
-
'<no message value>'
|
95
|
-
end
|
96
|
-
|
97
|
-
# @!visibility private
|
98
|
-
def get_message_from_exception(event_hash)
|
99
|
-
if exception = event_hash.dig(:exception, :values, 0)
|
100
|
-
"#{exception[:type]}: #{exception[:value]}"
|
101
|
-
elsif exception = event_hash.dig("exception", "values", 0)
|
102
|
-
"#{exception["type"]}: #{exception["value"]}"
|
103
|
-
end
|
104
|
-
end
|
105
|
-
end
|
106
|
-
|
107
79
|
# @deprecated This method will be removed in v5.0.0. Please just use Sentry.configuration
|
108
80
|
# @return [Configuration]
|
109
81
|
def configuration
|
data/lib/sentry/hub.rb
CHANGED
@@ -156,7 +156,7 @@ module Sentry
|
|
156
156
|
capture_event(event, **options, &block)
|
157
157
|
end
|
158
158
|
|
159
|
-
def capture_check_in(slug, status, **options
|
159
|
+
def capture_check_in(slug, status, **options)
|
160
160
|
check_argument_type!(slug, ::String)
|
161
161
|
check_argument_includes!(status, Sentry::CheckInEvent::VALID_STATUSES)
|
162
162
|
|
@@ -176,7 +176,7 @@ module Sentry
|
|
176
176
|
|
177
177
|
return unless event
|
178
178
|
|
179
|
-
capture_event(event, **options
|
179
|
+
capture_event(event, **options)
|
180
180
|
event.check_in_id
|
181
181
|
end
|
182
182
|
|
data/lib/sentry/rake.rb
CHANGED
@@ -17,15 +17,6 @@ module Sentry
|
|
17
17
|
super
|
18
18
|
end
|
19
19
|
end
|
20
|
-
|
21
|
-
module Task
|
22
|
-
# @api private
|
23
|
-
def execute(args=nil)
|
24
|
-
return super unless Sentry.initialized? && Sentry.get_current_hub
|
25
|
-
|
26
|
-
super
|
27
|
-
end
|
28
|
-
end
|
29
20
|
end
|
30
21
|
end
|
31
22
|
|
@@ -34,8 +25,4 @@ module Rake
|
|
34
25
|
class Application
|
35
26
|
prepend(Sentry::Rake::Application)
|
36
27
|
end
|
37
|
-
|
38
|
-
class Task
|
39
|
-
prepend(Sentry::Rake::Task)
|
40
|
-
end
|
41
28
|
end
|
data/lib/sentry/scope.rb
CHANGED
@@ -44,12 +44,18 @@ module Sentry
|
|
44
44
|
# @param hint [Hash] the hint data that'll be passed to event processors.
|
45
45
|
# @return [Event]
|
46
46
|
def apply_to_event(event, hint = nil)
|
47
|
-
event.
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
47
|
+
unless event.is_a?(CheckInEvent)
|
48
|
+
event.tags = tags.merge(event.tags)
|
49
|
+
event.user = user.merge(event.user)
|
50
|
+
event.extra = extra.merge(event.extra)
|
51
|
+
event.contexts = contexts.merge(event.contexts)
|
52
|
+
event.transaction = transaction_name if transaction_name
|
53
|
+
event.transaction_info = { source: transaction_source } if transaction_source
|
54
|
+
event.fingerprint = fingerprint
|
55
|
+
event.level = level
|
56
|
+
event.breadcrumbs = breadcrumbs
|
57
|
+
event.rack_env = rack_env if rack_env
|
58
|
+
end
|
53
59
|
|
54
60
|
if span
|
55
61
|
event.contexts[:trace] ||= span.get_trace_context
|
@@ -58,11 +64,6 @@ module Sentry
|
|
58
64
|
event.dynamic_sampling_context ||= propagation_context.get_dynamic_sampling_context
|
59
65
|
end
|
60
66
|
|
61
|
-
event.fingerprint = fingerprint
|
62
|
-
event.level = level
|
63
|
-
event.breadcrumbs = breadcrumbs
|
64
|
-
event.rack_env = rack_env if rack_env
|
65
|
-
|
66
67
|
all_event_processors = self.class.global_event_processors + @event_processors
|
67
68
|
|
68
69
|
unless all_event_processors.empty?
|
data/lib/sentry/transaction.rb
CHANGED
@@ -218,7 +218,12 @@ module Sentry
|
|
218
218
|
if sample_rate == true
|
219
219
|
@sampled = true
|
220
220
|
else
|
221
|
-
|
221
|
+
if Sentry.backpressure_monitor
|
222
|
+
factor = Sentry.backpressure_monitor.downsample_factor
|
223
|
+
@effective_sample_rate /= 2**factor
|
224
|
+
end
|
225
|
+
|
226
|
+
@sampled = Random.rand < @effective_sample_rate
|
222
227
|
end
|
223
228
|
|
224
229
|
if @sampled
|
@@ -257,7 +262,9 @@ module Sentry
|
|
257
262
|
event = hub.current_client.event_from_transaction(self)
|
258
263
|
hub.capture_event(event)
|
259
264
|
else
|
260
|
-
|
265
|
+
is_backpressure = Sentry.backpressure_monitor&.downsample_factor&.positive?
|
266
|
+
reason = is_backpressure ? :backpressure : :sample_rate
|
267
|
+
hub.current_client.transport.record_lost_event(reason, 'transaction')
|
261
268
|
end
|
262
269
|
end
|
263
270
|
|
@@ -14,11 +14,19 @@ module Sentry
|
|
14
14
|
RATE_LIMIT_HEADER = "x-sentry-rate-limits"
|
15
15
|
USER_AGENT = "sentry-ruby/#{Sentry::VERSION}"
|
16
16
|
|
17
|
+
# The list of errors ::Net::HTTP is known to raise
|
18
|
+
# See https://github.com/ruby/ruby/blob/b0c639f249165d759596f9579fa985cb30533de6/lib/bundler/fetcher.rb#L281-L286
|
19
|
+
HTTP_ERRORS = [
|
20
|
+
Timeout::Error, EOFError, SocketError, Errno::ENETDOWN, Errno::ENETUNREACH,
|
21
|
+
Errno::EINVAL, Errno::ECONNRESET, Errno::ETIMEDOUT, Errno::EAGAIN,
|
22
|
+
Net::HTTPBadResponse, Net::HTTPHeaderSyntaxError, Net::ProtocolError,
|
23
|
+
Zlib::BufError, Errno::EHOSTUNREACH, Errno::ECONNREFUSED
|
24
|
+
].freeze
|
25
|
+
|
26
|
+
|
17
27
|
def initialize(*args)
|
18
28
|
super
|
19
|
-
@
|
20
|
-
|
21
|
-
log_debug("Sentry HTTP Transport will connect to #{@dsn.server}")
|
29
|
+
log_debug("Sentry HTTP Transport will connect to #{@dsn.server}") if @dsn
|
22
30
|
end
|
23
31
|
|
24
32
|
def send_data(data)
|
@@ -32,34 +40,76 @@ module Sentry
|
|
32
40
|
headers = {
|
33
41
|
'Content-Type' => CONTENT_TYPE,
|
34
42
|
'Content-Encoding' => encoding,
|
35
|
-
'X-Sentry-Auth' => generate_auth_header,
|
36
43
|
'User-Agent' => USER_AGENT
|
37
44
|
}
|
38
45
|
|
46
|
+
auth_header = generate_auth_header
|
47
|
+
headers['X-Sentry-Auth'] = auth_header if auth_header
|
48
|
+
|
39
49
|
response = conn.start do |http|
|
40
|
-
request = ::Net::HTTP::Post.new(
|
50
|
+
request = ::Net::HTTP::Post.new(endpoint, headers)
|
41
51
|
request.body = data
|
42
52
|
http.request(request)
|
43
53
|
end
|
44
54
|
|
45
55
|
if response.code.match?(/\A2\d{2}/)
|
46
|
-
if has_rate_limited_header?(response)
|
47
|
-
|
48
|
-
|
56
|
+
handle_rate_limited_response(response) if has_rate_limited_header?(response)
|
57
|
+
elsif response.code == "429"
|
58
|
+
log_debug("the server responded with status 429")
|
59
|
+
handle_rate_limited_response(response)
|
49
60
|
else
|
50
61
|
error_info = "the server responded with status #{response.code}"
|
62
|
+
error_info += "\nbody: #{response.body}"
|
63
|
+
error_info += " Error in headers is: #{response['x-sentry-error']}" if response['x-sentry-error']
|
64
|
+
|
65
|
+
raise Sentry::ExternalError, error_info
|
66
|
+
end
|
67
|
+
rescue SocketError, *HTTP_ERRORS => e
|
68
|
+
on_error if respond_to?(:on_error)
|
69
|
+
raise Sentry::ExternalError.new(e&.message)
|
70
|
+
end
|
71
|
+
|
72
|
+
def endpoint
|
73
|
+
@dsn.envelope_endpoint
|
74
|
+
end
|
75
|
+
|
76
|
+
def generate_auth_header
|
77
|
+
return nil unless @dsn
|
78
|
+
|
79
|
+
now = Sentry.utc_now.to_i
|
80
|
+
fields = {
|
81
|
+
'sentry_version' => PROTOCOL_VERSION,
|
82
|
+
'sentry_client' => USER_AGENT,
|
83
|
+
'sentry_timestamp' => now,
|
84
|
+
'sentry_key' => @dsn.public_key
|
85
|
+
}
|
86
|
+
fields['sentry_secret'] = @dsn.secret_key if @dsn.secret_key
|
87
|
+
'Sentry ' + fields.map { |key, value| "#{key}=#{value}" }.join(', ')
|
88
|
+
end
|
51
89
|
|
52
|
-
|
53
|
-
|
90
|
+
def conn
|
91
|
+
server = URI(@dsn.server)
|
92
|
+
|
93
|
+
# connection respects proxy setting from @transport_configuration, or environment variables (HTTP_PROXY, HTTPS_PROXY, NO_PROXY)
|
94
|
+
# Net::HTTP will automatically read the env vars.
|
95
|
+
# See https://ruby-doc.org/3.2.2/stdlibs/net/Net/HTTP.html#class-Net::HTTP-label-Proxies
|
96
|
+
connection =
|
97
|
+
if proxy = normalize_proxy(@transport_configuration.proxy)
|
98
|
+
::Net::HTTP.new(server.hostname, server.port, proxy[:uri].hostname, proxy[:uri].port, proxy[:user], proxy[:password])
|
54
99
|
else
|
55
|
-
|
56
|
-
error_info += " Error in headers is: #{response['x-sentry-error']}" if response['x-sentry-error']
|
100
|
+
::Net::HTTP.new(server.hostname, server.port)
|
57
101
|
end
|
58
102
|
|
59
|
-
|
103
|
+
connection.use_ssl = server.scheme == "https"
|
104
|
+
connection.read_timeout = @transport_configuration.timeout
|
105
|
+
connection.write_timeout = @transport_configuration.timeout if connection.respond_to?(:write_timeout)
|
106
|
+
connection.open_timeout = @transport_configuration.open_timeout
|
107
|
+
|
108
|
+
ssl_configuration.each do |key, value|
|
109
|
+
connection.send("#{key}=", value)
|
60
110
|
end
|
61
|
-
|
62
|
-
|
111
|
+
|
112
|
+
connection
|
63
113
|
end
|
64
114
|
|
65
115
|
private
|
@@ -126,31 +176,6 @@ module Sentry
|
|
126
176
|
@transport_configuration.encoding == GZIP_ENCODING && data.bytesize >= GZIP_THRESHOLD
|
127
177
|
end
|
128
178
|
|
129
|
-
def conn
|
130
|
-
server = URI(@dsn.server)
|
131
|
-
|
132
|
-
# connection respects proxy setting from @transport_configuration, or environment variables (HTTP_PROXY, HTTPS_PROXY, NO_PROXY)
|
133
|
-
# Net::HTTP will automatically read the env vars.
|
134
|
-
# See https://ruby-doc.org/3.2.2/stdlibs/net/Net/HTTP.html#class-Net::HTTP-label-Proxies
|
135
|
-
connection =
|
136
|
-
if proxy = normalize_proxy(@transport_configuration.proxy)
|
137
|
-
::Net::HTTP.new(server.hostname, server.port, proxy[:uri].hostname, proxy[:uri].port, proxy[:user], proxy[:password])
|
138
|
-
else
|
139
|
-
::Net::HTTP.new(server.hostname, server.port)
|
140
|
-
end
|
141
|
-
|
142
|
-
connection.use_ssl = server.scheme == "https"
|
143
|
-
connection.read_timeout = @transport_configuration.timeout
|
144
|
-
connection.write_timeout = @transport_configuration.timeout if connection.respond_to?(:write_timeout)
|
145
|
-
connection.open_timeout = @transport_configuration.open_timeout
|
146
|
-
|
147
|
-
ssl_configuration.each do |key, value|
|
148
|
-
connection.send("#{key}=", value)
|
149
|
-
end
|
150
|
-
|
151
|
-
connection
|
152
|
-
end
|
153
|
-
|
154
179
|
# @param proxy [String, URI, Hash] Proxy config value passed into `config.transport`.
|
155
180
|
# Accepts either a URI formatted string, URI, or a hash with the `uri`, `user`, and `password` keys.
|
156
181
|
# @return [Hash] Normalized proxy config that will be passed into `Net::HTTP`
|
@@ -0,0 +1,50 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "net/http"
|
4
|
+
require "zlib"
|
5
|
+
|
6
|
+
module Sentry
|
7
|
+
# Designed to just report events to Spotlight in development.
|
8
|
+
class SpotlightTransport < HTTPTransport
|
9
|
+
DEFAULT_SIDECAR_URL = "http://localhost:8969/stream"
|
10
|
+
MAX_FAILED_REQUESTS = 3
|
11
|
+
|
12
|
+
def initialize(configuration)
|
13
|
+
super
|
14
|
+
@sidecar_url = configuration.spotlight.is_a?(String) ? configuration.spotlight : DEFAULT_SIDECAR_URL
|
15
|
+
@failed = 0
|
16
|
+
@logged = false
|
17
|
+
|
18
|
+
log_debug("[Spotlight] initialized for url #{@sidecar_url}")
|
19
|
+
end
|
20
|
+
|
21
|
+
def endpoint
|
22
|
+
"/stream"
|
23
|
+
end
|
24
|
+
|
25
|
+
def send_data(data)
|
26
|
+
if @failed >= MAX_FAILED_REQUESTS
|
27
|
+
unless @logged
|
28
|
+
log_debug("[Spotlight] disabling because of too many request failures")
|
29
|
+
@logged = true
|
30
|
+
end
|
31
|
+
|
32
|
+
return
|
33
|
+
end
|
34
|
+
|
35
|
+
super
|
36
|
+
end
|
37
|
+
|
38
|
+
def on_error
|
39
|
+
@failed += 1
|
40
|
+
end
|
41
|
+
|
42
|
+
# Similar to HTTPTransport connection, but does not support Proxy and SSL
|
43
|
+
def conn
|
44
|
+
sidecar = URI(@sidecar_url)
|
45
|
+
connection = ::Net::HTTP.new(sidecar.hostname, sidecar.port, nil)
|
46
|
+
connection.use_ssl = false
|
47
|
+
connection
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
data/lib/sentry/transport.rb
CHANGED
@@ -19,7 +19,8 @@ module Sentry
|
|
19
19
|
:sample_rate,
|
20
20
|
:before_send,
|
21
21
|
:event_processor,
|
22
|
-
:insufficient_data
|
22
|
+
:insufficient_data,
|
23
|
+
:backpressure
|
23
24
|
]
|
24
25
|
|
25
26
|
include LoggingHelper
|
@@ -74,7 +75,7 @@ module Sentry
|
|
74
75
|
result, oversized = item.serialize
|
75
76
|
|
76
77
|
if oversized
|
77
|
-
|
78
|
+
log_debug("Envelope item [#{item.type}] is still oversized after size reduction: {#{item.size_breakdown}}")
|
78
79
|
|
79
80
|
next
|
80
81
|
end
|
@@ -119,16 +120,8 @@ module Sentry
|
|
119
120
|
!!delay && delay > Time.now
|
120
121
|
end
|
121
122
|
|
122
|
-
def
|
123
|
-
|
124
|
-
fields = {
|
125
|
-
'sentry_version' => PROTOCOL_VERSION,
|
126
|
-
'sentry_client' => USER_AGENT,
|
127
|
-
'sentry_timestamp' => now,
|
128
|
-
'sentry_key' => @dsn.public_key
|
129
|
-
}
|
130
|
-
fields['sentry_secret'] = @dsn.secret_key if @dsn.secret_key
|
131
|
-
'Sentry ' + fields.map { |key, value| "#{key}=#{value}" }.join(', ')
|
123
|
+
def any_rate_limited?
|
124
|
+
@rate_limits.values.any? { |t| t && t > Time.now }
|
132
125
|
end
|
133
126
|
|
134
127
|
def envelope_from_event(event)
|
@@ -175,11 +168,20 @@ module Sentry
|
|
175
168
|
@discarded_events[[reason, item_type]] += 1
|
176
169
|
end
|
177
170
|
|
171
|
+
def flush
|
172
|
+
client_report_headers, client_report_payload = fetch_pending_client_report(force: true)
|
173
|
+
return unless client_report_headers
|
174
|
+
|
175
|
+
envelope = Envelope.new
|
176
|
+
envelope.add_item(client_report_headers, client_report_payload)
|
177
|
+
send_envelope(envelope)
|
178
|
+
end
|
179
|
+
|
178
180
|
private
|
179
181
|
|
180
|
-
def fetch_pending_client_report
|
182
|
+
def fetch_pending_client_report(force: false)
|
181
183
|
return nil unless @send_client_reports
|
182
|
-
return nil if @last_client_report_sent > Time.now - CLIENT_REPORT_INTERVAL
|
184
|
+
return nil if !force && @last_client_report_sent > Time.now - CLIENT_REPORT_INTERVAL
|
183
185
|
return nil if @discarded_events.empty?
|
184
186
|
|
185
187
|
discarded_events_hash = @discarded_events.map do |key, val|
|
@@ -206,7 +208,7 @@ module Sentry
|
|
206
208
|
def reject_rate_limited_items(envelope)
|
207
209
|
envelope.items.reject! do |item|
|
208
210
|
if is_rate_limited?(item.type)
|
209
|
-
|
211
|
+
log_debug("[Transport] Envelope item [#{item.type}] not sent: rate limiting")
|
210
212
|
record_lost_event(:ratelimit_backoff, item.type)
|
211
213
|
|
212
214
|
true
|
@@ -220,3 +222,4 @@ end
|
|
220
222
|
|
221
223
|
require "sentry/transport/dummy_transport"
|
222
224
|
require "sentry/transport/http_transport"
|
225
|
+
require "sentry/transport/spotlight_transport"
|
data/lib/sentry/version.rb
CHANGED
data/lib/sentry-ruby.rb
CHANGED
@@ -21,6 +21,7 @@ require "sentry/transaction"
|
|
21
21
|
require "sentry/hub"
|
22
22
|
require "sentry/background_worker"
|
23
23
|
require "sentry/session_flusher"
|
24
|
+
require "sentry/backpressure_monitor"
|
24
25
|
require "sentry/cron/monitor_check_ins"
|
25
26
|
|
26
27
|
[
|
@@ -65,13 +66,17 @@ module Sentry
|
|
65
66
|
end
|
66
67
|
|
67
68
|
# @!attribute [rw] background_worker
|
68
|
-
# @return [BackgroundWorker
|
69
|
+
# @return [BackgroundWorker]
|
69
70
|
attr_accessor :background_worker
|
70
71
|
|
71
72
|
# @!attribute [r] session_flusher
|
72
73
|
# @return [SessionFlusher, nil]
|
73
74
|
attr_reader :session_flusher
|
74
75
|
|
76
|
+
# @!attribute [r] backpressure_monitor
|
77
|
+
# @return [BackpressureMonitor, nil]
|
78
|
+
attr_reader :backpressure_monitor
|
79
|
+
|
75
80
|
##### Patch Registration #####
|
76
81
|
|
77
82
|
# @!visibility private
|
@@ -217,17 +222,9 @@ module Sentry
|
|
217
222
|
Thread.current.thread_variable_set(THREAD_LOCAL, hub)
|
218
223
|
@main_hub = hub
|
219
224
|
@background_worker = Sentry::BackgroundWorker.new(config)
|
220
|
-
|
221
|
-
@
|
222
|
-
|
223
|
-
else
|
224
|
-
nil
|
225
|
-
end
|
226
|
-
|
227
|
-
if config.include_local_variables
|
228
|
-
exception_locals_tp.enable
|
229
|
-
end
|
230
|
-
|
225
|
+
@session_flusher = config.auto_session_tracking ? Sentry::SessionFlusher.new(config, client) : nil
|
226
|
+
@backpressure_monitor = config.enable_backpressure_handling ? Sentry::BackpressureMonitor.new(config, client) : nil
|
227
|
+
exception_locals_tp.enable if config.include_local_variables
|
231
228
|
at_exit { close }
|
232
229
|
end
|
233
230
|
|
@@ -236,20 +233,27 @@ module Sentry
|
|
236
233
|
#
|
237
234
|
# @return [void]
|
238
235
|
def close
|
239
|
-
if @background_worker
|
240
|
-
@background_worker.shutdown
|
241
|
-
@background_worker = nil
|
242
|
-
end
|
243
|
-
|
244
236
|
if @session_flusher
|
237
|
+
@session_flusher.flush
|
245
238
|
@session_flusher.kill
|
246
239
|
@session_flusher = nil
|
247
240
|
end
|
248
241
|
|
249
|
-
if
|
250
|
-
|
242
|
+
if @backpressure_monitor
|
243
|
+
@backpressure_monitor.kill
|
244
|
+
@backpressure_monitor = nil
|
251
245
|
end
|
252
246
|
|
247
|
+
if client = get_current_client
|
248
|
+
client.transport.flush
|
249
|
+
|
250
|
+
if client.configuration.include_local_variables
|
251
|
+
exception_locals_tp.disable
|
252
|
+
end
|
253
|
+
end
|
254
|
+
|
255
|
+
@background_worker.shutdown
|
256
|
+
|
253
257
|
@main_hub = nil
|
254
258
|
Thread.current.thread_variable_set(THREAD_LOCAL, nil)
|
255
259
|
end
|
@@ -442,12 +446,10 @@ module Sentry
|
|
442
446
|
# @option options [Integer] duration seconds elapsed since this monitor started
|
443
447
|
# @option options [Cron::MonitorConfig] monitor_config configuration for this monitor
|
444
448
|
#
|
445
|
-
# @yieldparam scope [Scope]
|
446
|
-
#
|
447
449
|
# @return [String, nil] The {CheckInEvent#check_in_id} to use for later updates on the same slug
|
448
|
-
def capture_check_in(slug, status, **options
|
450
|
+
def capture_check_in(slug, status, **options)
|
449
451
|
return unless initialized?
|
450
|
-
get_current_hub.capture_check_in(slug, status, **options
|
452
|
+
get_current_hub.capture_check_in(slug, status, **options)
|
451
453
|
end
|
452
454
|
|
453
455
|
# Takes or initializes a new Sentry::Transaction and makes a sampling decision for it.
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: sentry-ruby
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 5.
|
4
|
+
version: 5.16.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Sentry Team
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2024-01-09 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: concurrent-ruby
|
@@ -51,6 +51,7 @@ files:
|
|
51
51
|
- bin/setup
|
52
52
|
- lib/sentry-ruby.rb
|
53
53
|
- lib/sentry/background_worker.rb
|
54
|
+
- lib/sentry/backpressure_monitor.rb
|
54
55
|
- lib/sentry/backtrace.rb
|
55
56
|
- lib/sentry/baggage.rb
|
56
57
|
- lib/sentry/breadcrumb.rb
|
@@ -61,6 +62,7 @@ files:
|
|
61
62
|
- lib/sentry/configuration.rb
|
62
63
|
- lib/sentry/core_ext/object/deep_dup.rb
|
63
64
|
- lib/sentry/core_ext/object/duplicable.rb
|
65
|
+
- lib/sentry/cron/configuration.rb
|
64
66
|
- lib/sentry/cron/monitor_check_ins.rb
|
65
67
|
- lib/sentry/cron/monitor_config.rb
|
66
68
|
- lib/sentry/cron/monitor_schedule.rb
|
@@ -100,6 +102,7 @@ files:
|
|
100
102
|
- lib/sentry/transport/configuration.rb
|
101
103
|
- lib/sentry/transport/dummy_transport.rb
|
102
104
|
- lib/sentry/transport/http_transport.rb
|
105
|
+
- lib/sentry/transport/spotlight_transport.rb
|
103
106
|
- lib/sentry/utils/argument_checking_helper.rb
|
104
107
|
- lib/sentry/utils/custom_inspection.rb
|
105
108
|
- lib/sentry/utils/encoding_helper.rb
|