sentry-ruby 5.14.0 → 5.16.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|