sentry-ruby 7.0.0 → 7.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/Gemfile +2 -0
- data/gemfiles/ruby-2.7_rack-2_redis-4.gemfile.lock +21 -9
- data/gemfiles/ruby-2.7_rack-3.1_redis-4.gemfile.lock +21 -9
- data/gemfiles/ruby-2.7_rack-3_redis-4.gemfile.lock +21 -9
- data/gemfiles/ruby-3.0_rack-2_redis-4.gemfile.lock +30 -14
- data/gemfiles/ruby-3.0_rack-3.1_redis-4.gemfile.lock +30 -14
- data/gemfiles/ruby-3.0_rack-3_redis-4.gemfile.lock +30 -14
- data/gemfiles/ruby-3.1_rack-2_redis-4.gemfile.lock +50 -27
- data/gemfiles/ruby-3.1_rack-3.1_redis-4.gemfile.lock +50 -27
- data/gemfiles/ruby-3.1_rack-3_redis-4.gemfile.lock +50 -27
- data/gemfiles/ruby-3.2_rack-0_redis-5.gemfile.lock +54 -31
- data/gemfiles/ruby-3.2_rack-2_redis-4.gemfile.lock +52 -29
- data/gemfiles/ruby-3.2_rack-2_redis-5.gemfile.lock +54 -31
- data/gemfiles/ruby-3.2_rack-3.1_redis-4.gemfile.lock +52 -29
- data/gemfiles/ruby-3.2_rack-3_redis-4.gemfile.lock +52 -29
- data/gemfiles/ruby-3.2_rack-3_redis-5.gemfile.lock +54 -31
- data/gemfiles/ruby-3.3_rack-2_redis-4.gemfile.lock +56 -33
- data/gemfiles/ruby-3.3_rack-3.1_redis-4.gemfile.lock +56 -33
- data/gemfiles/ruby-3.3_rack-3.1_redis-5.3.gemfile.lock +58 -35
- data/gemfiles/ruby-3.3_rack-3_redis-4.gemfile.lock +56 -33
- data/gemfiles/ruby-3.4_rack-2_redis-4.gemfile.lock +56 -33
- data/gemfiles/ruby-3.4_rack-3.1_redis-4.gemfile.lock +56 -33
- data/gemfiles/ruby-3.4_rack-3.1_redis-5.3.gemfile.lock +58 -35
- data/gemfiles/ruby-3.4_rack-3_redis-4.gemfile.lock +56 -33
- data/gemfiles/ruby-4.0_rack-2_redis-4.gemfile.lock +56 -33
- data/gemfiles/ruby-4.0_rack-3.1_redis-4.gemfile.lock +56 -33
- data/gemfiles/ruby-4.0_rack-3_redis-4.gemfile.lock +56 -33
- data/gemfiles/ruby-jruby-9.4.14.0_rack-2_redis-4.gemfile.lock +59 -41
- data/gemfiles/ruby-jruby-9.4.14.0_rack-3.1_redis-4.gemfile.lock +59 -41
- data/gemfiles/ruby-jruby-9.4.14.0_rack-3_redis-4.gemfile.lock +59 -41
- data/lib/sentry/backtrace.rb +10 -1
- data/lib/sentry/client.rb +19 -6
- data/lib/sentry/configuration.rb +8 -3
- data/lib/sentry/cron/fugit_schedule.rb +117 -0
- data/lib/sentry/cron/monitor_check_ins.rb +33 -27
- data/lib/sentry/cron/slug.rb +18 -0
- data/lib/sentry/data_collection.rb +6 -1
- data/lib/sentry/faraday.rb +4 -1
- data/lib/sentry/hub.rb +9 -2
- data/lib/sentry/integrable.rb +12 -0
- data/lib/sentry/log_event_buffer.rb +1 -1
- data/lib/sentry/metric_event.rb +4 -2
- data/lib/sentry/metric_event_buffer.rb +1 -1
- data/lib/sentry/metrics.rb +12 -6
- data/lib/sentry/rufus_scheduler.rb +120 -0
- data/lib/sentry/scope.rb +7 -3
- data/lib/sentry/session_flusher.rb +7 -0
- data/lib/sentry/std_lib_logger.rb +8 -2
- data/lib/sentry/telemetry_event_buffer.rb +57 -31
- data/lib/sentry/test_helper.rb +4 -0
- data/lib/sentry/threaded_periodic_worker.rb +90 -13
- data/lib/sentry/transaction.rb +7 -1
- data/lib/sentry/transport/debug_transport.rb +11 -2
- data/lib/sentry/transport/dummy_transport.rb +7 -4
- data/lib/sentry/transport/spotlight_transport.rb +2 -2
- data/lib/sentry/utils/callback_helper.rb +27 -0
- data/lib/sentry/version.rb +1 -1
- data/lib/sentry-ruby.rb +6 -0
- metadata +10 -6
data/lib/sentry/integrable.rb
CHANGED
|
@@ -32,5 +32,17 @@ module Sentry
|
|
|
32
32
|
options[:hint][:integration] = integration_name
|
|
33
33
|
Sentry.capture_check_in(slug, status, **options, &block)
|
|
34
34
|
end
|
|
35
|
+
|
|
36
|
+
def count(name, value: 1, attributes: nil)
|
|
37
|
+
Sentry.metrics.count(name, value: value, attributes: attributes, integration: integration_name)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def gauge(name, value, unit: nil, attributes: nil)
|
|
41
|
+
Sentry.metrics.gauge(name, value, unit: unit, attributes: attributes, integration: integration_name)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def distribution(name, value, unit: nil, attributes: nil)
|
|
45
|
+
Sentry.metrics.distribution(name, value, unit: unit, attributes: attributes, integration: integration_name)
|
|
46
|
+
end
|
|
35
47
|
end
|
|
36
48
|
end
|
data/lib/sentry/metric_event.rb
CHANGED
|
@@ -6,7 +6,7 @@ module Sentry
|
|
|
6
6
|
class MetricEvent
|
|
7
7
|
include Sentry::Utils::TelemetryAttributes
|
|
8
8
|
|
|
9
|
-
attr_reader :name, :type, :value, :unit, :timestamp, :trace_id, :span_id, :attributes
|
|
9
|
+
attr_reader :name, :type, :value, :unit, :timestamp, :trace_id, :span_id, :attributes, :integration_meta
|
|
10
10
|
attr_writer :trace_id, :span_id, :attributes
|
|
11
11
|
|
|
12
12
|
def initialize(
|
|
@@ -14,13 +14,15 @@ module Sentry
|
|
|
14
14
|
type:,
|
|
15
15
|
value:,
|
|
16
16
|
unit: nil,
|
|
17
|
-
attributes: nil
|
|
17
|
+
attributes: nil,
|
|
18
|
+
integration_meta: nil
|
|
18
19
|
)
|
|
19
20
|
@name = name
|
|
20
21
|
@type = type
|
|
21
22
|
@value = value
|
|
22
23
|
@unit = unit
|
|
23
24
|
@attributes = attributes || {}
|
|
25
|
+
@integration_meta = integration_meta
|
|
24
26
|
|
|
25
27
|
@timestamp = Sentry.utc_now
|
|
26
28
|
@trace_id = nil
|
|
@@ -21,7 +21,7 @@ module Sentry
|
|
|
21
21
|
max_items_before_drop: MAX_METRICS_BEFORE_DROP,
|
|
22
22
|
envelope_type: "trace_metric",
|
|
23
23
|
envelope_content_type: "application/vnd.sentry.items.trace-metric+json",
|
|
24
|
-
before_send:
|
|
24
|
+
before_send: :before_send_metric
|
|
25
25
|
)
|
|
26
26
|
end
|
|
27
27
|
end
|
data/lib/sentry/metrics.rb
CHANGED
|
@@ -9,15 +9,17 @@ module Sentry
|
|
|
9
9
|
# @param name [String] the metric name
|
|
10
10
|
# @param value [Numeric] the value to increment by (default: 1)
|
|
11
11
|
# @param attributes [Hash, nil] additional attributes for the metric (optional)
|
|
12
|
+
# @param integration [String, Symbol, nil] registered integration emitting the metric (optional)
|
|
12
13
|
# @return [void]
|
|
13
|
-
def count(name, value: 1, attributes: nil)
|
|
14
|
+
def count(name, value: 1, attributes: nil, integration: nil)
|
|
14
15
|
return unless Sentry.initialized?
|
|
15
16
|
|
|
16
17
|
Sentry.get_current_hub.capture_metric(
|
|
17
18
|
name: name,
|
|
18
19
|
type: :counter,
|
|
19
20
|
value: value,
|
|
20
|
-
attributes: attributes
|
|
21
|
+
attributes: attributes,
|
|
22
|
+
integration: integration
|
|
21
23
|
)
|
|
22
24
|
end
|
|
23
25
|
|
|
@@ -26,8 +28,9 @@ module Sentry
|
|
|
26
28
|
# @param value [Numeric] the gauge value
|
|
27
29
|
# @param unit [String, nil] the metric unit (optional)
|
|
28
30
|
# @param attributes [Hash, nil] additional attributes for the metric (optional)
|
|
31
|
+
# @param integration [String, Symbol, nil] registered integration emitting the metric (optional)
|
|
29
32
|
# @return [void]
|
|
30
|
-
def gauge(name, value, unit: nil, attributes: nil)
|
|
33
|
+
def gauge(name, value, unit: nil, attributes: nil, integration: nil)
|
|
31
34
|
return unless Sentry.initialized?
|
|
32
35
|
|
|
33
36
|
Sentry.get_current_hub.capture_metric(
|
|
@@ -35,7 +38,8 @@ module Sentry
|
|
|
35
38
|
type: :gauge,
|
|
36
39
|
value: value,
|
|
37
40
|
unit: unit,
|
|
38
|
-
attributes: attributes
|
|
41
|
+
attributes: attributes,
|
|
42
|
+
integration: integration
|
|
39
43
|
)
|
|
40
44
|
end
|
|
41
45
|
|
|
@@ -44,8 +48,9 @@ module Sentry
|
|
|
44
48
|
# @param value [Numeric] the distribution value
|
|
45
49
|
# @param unit [String, nil] the metric unit (optional)
|
|
46
50
|
# @param attributes [Hash, nil] additional attributes for the metric (optional)
|
|
51
|
+
# @param integration [String, Symbol, nil] registered integration emitting the metric (optional)
|
|
47
52
|
# @return [void]
|
|
48
|
-
def distribution(name, value, unit: nil, attributes: nil)
|
|
53
|
+
def distribution(name, value, unit: nil, attributes: nil, integration: nil)
|
|
49
54
|
return unless Sentry.initialized?
|
|
50
55
|
|
|
51
56
|
Sentry.get_current_hub.capture_metric(
|
|
@@ -53,7 +58,8 @@ module Sentry
|
|
|
53
58
|
type: :distribution,
|
|
54
59
|
value: value,
|
|
55
60
|
unit: unit,
|
|
56
|
-
attributes: attributes
|
|
61
|
+
attributes: attributes,
|
|
62
|
+
integration: integration
|
|
57
63
|
)
|
|
58
64
|
end
|
|
59
65
|
end
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Sentry
|
|
4
|
+
# Sends Crons check-ins for rufus-scheduler `cron`, `every` and `interval` jobs.
|
|
5
|
+
# Enable with `config.enabled_patches << :rufus_scheduler`.
|
|
6
|
+
module RufusScheduler
|
|
7
|
+
module Job
|
|
8
|
+
protected
|
|
9
|
+
|
|
10
|
+
def do_call(time, do_rescue)
|
|
11
|
+
return super unless Sentry.initialized? && (monitor = sentry_monitor)
|
|
12
|
+
|
|
13
|
+
slug, monitor_config = monitor
|
|
14
|
+
check_in_id = Sentry.capture_check_in(slug, :in_progress, monitor_config: monitor_config)
|
|
15
|
+
start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
16
|
+
|
|
17
|
+
begin
|
|
18
|
+
# Run without rufus's rescue so the error is seen, then handle it like Job#do_call.
|
|
19
|
+
ret = super(time, false)
|
|
20
|
+
rescue Exception => e
|
|
21
|
+
duration = Process.clock_gettime(Process::CLOCK_MONOTONIC) - start
|
|
22
|
+
Sentry.capture_check_in(slug, :error, check_in_id: check_in_id, duration: duration, monitor_config: monitor_config)
|
|
23
|
+
|
|
24
|
+
raise unless do_rescue && e.is_a?(StandardError)
|
|
25
|
+
return if e.is_a?(::Rufus::Scheduler::Job::KillSignal)
|
|
26
|
+
|
|
27
|
+
return @scheduler.on_error(self, e)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
duration = Process.clock_gettime(Process::CLOCK_MONOTONIC) - start
|
|
31
|
+
Sentry.capture_check_in(slug, :ok, check_in_id: check_in_id, duration: duration, monitor_config: monitor_config)
|
|
32
|
+
|
|
33
|
+
ret
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
private
|
|
37
|
+
|
|
38
|
+
def sentry_monitor
|
|
39
|
+
return @sentry_monitor if defined?(@sentry_monitor)
|
|
40
|
+
|
|
41
|
+
@sentry_monitor = Sentry::RufusScheduler.monitor_for(self)
|
|
42
|
+
rescue StandardError => e
|
|
43
|
+
Sentry.sdk_logger.error(Sentry::LOGGER_PROGNAME) { "Not monitoring rufus-scheduler job #{id}: #{e.class}: #{e.message}" }
|
|
44
|
+
@sentry_monitor = nil
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
class << self
|
|
49
|
+
# Returns [slug, monitor_config], or nil for jobs that aren't monitored (like `at`/`in`).
|
|
50
|
+
def monitor_for(job)
|
|
51
|
+
return unless job.is_a?(::Rufus::Scheduler::RepeatJob)
|
|
52
|
+
return if job.opts[:sentry_monitor] == false
|
|
53
|
+
return if sidekiq_scheduler_job?(job)
|
|
54
|
+
|
|
55
|
+
slug = slug_for(job)
|
|
56
|
+
unless slug
|
|
57
|
+
Sentry.sdk_logger.warn(Sentry::LOGGER_PROGNAME) do
|
|
58
|
+
"Not monitoring rufus-scheduler job #{job.id} at #{job.source_location&.join(":")}: " \
|
|
59
|
+
"pass `name:` to give it a stable monitor slug."
|
|
60
|
+
end
|
|
61
|
+
return
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
[slug, monitor_config_for(job)]
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
private
|
|
68
|
+
|
|
69
|
+
# sidekiq-scheduler jobs are monitored by the :sidekiq_scheduler patch.
|
|
70
|
+
def sidekiq_scheduler_job?(job)
|
|
71
|
+
file, _ = job.callable.source_location if job.callable.respond_to?(:source_location)
|
|
72
|
+
file&.end_with?("sidekiq-scheduler/scheduler.rb")
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# Job ids are random per process and block paths change between deploys,
|
|
76
|
+
# so the slug comes from `name:`, then the handler method or class.
|
|
77
|
+
def slug_for(job)
|
|
78
|
+
handler = job.handler
|
|
79
|
+
source =
|
|
80
|
+
if job.name
|
|
81
|
+
job.name.to_s
|
|
82
|
+
elsif handler.is_a?(Method)
|
|
83
|
+
# The receiver's class, not the method owner, so inherited and mixin methods get distinct slugs.
|
|
84
|
+
namespace = handler.receiver.is_a?(Module) ? handler.receiver : handler.receiver.class
|
|
85
|
+
[namespace == Object ? nil : namespace.name, handler.name].compact.join("::")
|
|
86
|
+
elsif !handler.is_a?(Proc)
|
|
87
|
+
handler.class.name
|
|
88
|
+
end
|
|
89
|
+
return unless source
|
|
90
|
+
|
|
91
|
+
Sentry::Cron::Slug.from(source)
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def monitor_config_for(job)
|
|
95
|
+
case job
|
|
96
|
+
when ::Rufus::Scheduler::CronJob
|
|
97
|
+
Sentry::Cron::FugitSchedule.from_cron(job.cron_line)
|
|
98
|
+
when ::Rufus::Scheduler::EveryJob
|
|
99
|
+
Sentry::Cron::FugitSchedule.from_seconds(job.frequency)
|
|
100
|
+
end
|
|
101
|
+
# IntervalJob waits `interval` after each run ends, so it has no fixed
|
|
102
|
+
# schedule to monitor against.
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
Sentry.register_patch(:rufus_scheduler) do |config|
|
|
109
|
+
# Load rufus-scheduler in case it's required after Sentry.init.
|
|
110
|
+
begin
|
|
111
|
+
require "rufus-scheduler"
|
|
112
|
+
rescue LoadError
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
if defined?(::Rufus::Scheduler::RepeatJob)
|
|
116
|
+
::Rufus::Scheduler::Job.prepend(Sentry::RufusScheduler::Job) unless ::Rufus::Scheduler::Job.ancestors.include?(Sentry::RufusScheduler::Job)
|
|
117
|
+
else
|
|
118
|
+
config.sdk_logger.warn(Sentry::LOGGER_PROGNAME) { "You tried to enable the rufus-scheduler integration but the `rufus-scheduler` gem was not detected." }
|
|
119
|
+
end
|
|
120
|
+
end
|
data/lib/sentry/scope.rb
CHANGED
|
@@ -9,6 +9,7 @@ require "etc"
|
|
|
9
9
|
module Sentry
|
|
10
10
|
class Scope
|
|
11
11
|
include ArgumentCheckingHelper
|
|
12
|
+
include CallbackHelper
|
|
12
13
|
|
|
13
14
|
ATTRIBUTES = [
|
|
14
15
|
:transaction_name,
|
|
@@ -71,7 +72,8 @@ module Sentry
|
|
|
71
72
|
|
|
72
73
|
unless all_event_processors.empty?
|
|
73
74
|
all_event_processors.each do |processor_block|
|
|
74
|
-
event = processor_block
|
|
75
|
+
event = safe_dispatch_callback("event_processor", processor_block, [event, hint])
|
|
76
|
+
return unless event
|
|
75
77
|
end
|
|
76
78
|
end
|
|
77
79
|
|
|
@@ -102,8 +104,10 @@ module Sentry
|
|
|
102
104
|
configuration = Sentry.configuration
|
|
103
105
|
return telemetry unless configuration
|
|
104
106
|
|
|
105
|
-
telemetry.
|
|
106
|
-
|
|
107
|
+
sdk_meta = telemetry.respond_to?(:integration_meta) ? telemetry.integration_meta : nil
|
|
108
|
+
sdk_meta ||= Sentry.sdk_meta
|
|
109
|
+
telemetry.attributes["sentry.sdk.name"] ||= sdk_meta[:name] || sdk_meta["name"]
|
|
110
|
+
telemetry.attributes["sentry.sdk.version"] ||= sdk_meta[:version] || sdk_meta["version"]
|
|
107
111
|
telemetry.attributes["sentry.environment"] ||= configuration.environment if configuration.environment
|
|
108
112
|
telemetry.attributes["sentry.release"] ||= configuration.release if configuration.release
|
|
109
113
|
telemetry.attributes["server.address"] ||= configuration.server_name if configuration.server_name
|
|
@@ -35,6 +35,13 @@ module Sentry
|
|
|
35
35
|
|
|
36
36
|
private
|
|
37
37
|
|
|
38
|
+
def reset_if_forked
|
|
39
|
+
return unless super
|
|
40
|
+
|
|
41
|
+
@mutex = Mutex.new
|
|
42
|
+
@pending_aggregates = {}
|
|
43
|
+
end
|
|
44
|
+
|
|
38
45
|
def init_aggregates(aggregation_key)
|
|
39
46
|
aggregates = { started: aggregation_key.iso8601 }
|
|
40
47
|
Session::AGGREGATE_STATUSES.each { |k| aggregates[k] = 0 }
|
|
@@ -4,6 +4,8 @@ module Sentry
|
|
|
4
4
|
# Ruby Logger support Add commentMore actions
|
|
5
5
|
# intercepts any logger instance and send the log to Sentry too.
|
|
6
6
|
module StdLibLogger
|
|
7
|
+
include CallbackHelper
|
|
8
|
+
|
|
7
9
|
SEVERITY_MAP = {
|
|
8
10
|
0 => :debug,
|
|
9
11
|
1 => :info,
|
|
@@ -37,8 +39,12 @@ module Sentry
|
|
|
37
39
|
message = message.to_s.strip
|
|
38
40
|
|
|
39
41
|
if !message.nil? && message != Sentry::Logger::PROGNAME && method = SEVERITY_MAP[severity]
|
|
40
|
-
if
|
|
41
|
-
return result
|
|
42
|
+
if filter = Sentry.configuration.std_lib_logger_filter
|
|
43
|
+
return result unless safe_dispatch_callback(
|
|
44
|
+
"std_lib_logger_filter",
|
|
45
|
+
filter,
|
|
46
|
+
[self, message, method]
|
|
47
|
+
)
|
|
42
48
|
end
|
|
43
49
|
|
|
44
50
|
Sentry.logger.send(method, message, origin: ORIGIN)
|
|
@@ -12,7 +12,10 @@ module Sentry
|
|
|
12
12
|
#
|
|
13
13
|
# @!visibility private
|
|
14
14
|
class TelemetryEventBuffer < ThreadedPeriodicWorker
|
|
15
|
+
include CallbackHelper
|
|
16
|
+
|
|
15
17
|
FLUSH_INTERVAL = 5 # seconds
|
|
18
|
+
FLUSH_TIMEOUT = 2 # seconds
|
|
16
19
|
|
|
17
20
|
# @!visibility private
|
|
18
21
|
attr_reader :pending_items, :envelope_type, :data_category, :thread
|
|
@@ -21,6 +24,7 @@ module Sentry
|
|
|
21
24
|
super(configuration.sdk_logger, FLUSH_INTERVAL)
|
|
22
25
|
|
|
23
26
|
@client = client
|
|
27
|
+
@configuration = configuration
|
|
24
28
|
@dsn = configuration.dsn
|
|
25
29
|
@debug = configuration.debug
|
|
26
30
|
@event_class = event_class
|
|
@@ -38,39 +42,44 @@ module Sentry
|
|
|
38
42
|
end
|
|
39
43
|
|
|
40
44
|
def flush
|
|
41
|
-
|
|
42
|
-
return if empty?
|
|
43
|
-
|
|
44
|
-
log_debug("[#{self.class}] flushing #{size} #{@event_class}")
|
|
45
|
+
return unless thread&.alive? && wake
|
|
45
46
|
|
|
46
|
-
|
|
47
|
-
|
|
47
|
+
wait_until_idle(FLUSH_TIMEOUT)
|
|
48
|
+
end
|
|
48
49
|
|
|
49
|
-
|
|
50
|
+
def run
|
|
51
|
+
flush_pending_items
|
|
50
52
|
end
|
|
51
|
-
alias_method :run, :flush
|
|
52
53
|
|
|
53
54
|
def add_item(item)
|
|
55
|
+
# the buffer thread can never add telemetry itself to prevent recursion
|
|
56
|
+
return self if Thread.current == thread
|
|
57
|
+
return unless ensure_thread
|
|
58
|
+
|
|
54
59
|
# Prevent ThreadError from re-entrant locking (e.g. transport instrumentation calling Sentry.metrics.*)
|
|
55
60
|
return self if @mutex.owned?
|
|
56
61
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
62
|
+
dropped = false
|
|
63
|
+
size_exceeded = @mutex.synchronize do
|
|
60
64
|
if size >= @max_items_before_drop
|
|
61
|
-
|
|
62
|
-
@client.transport.record_lost_event(
|
|
63
|
-
:queue_overflow,
|
|
64
|
-
@data_category,
|
|
65
|
-
num_bytes: JSON.generate(item.to_h).bytesize
|
|
66
|
-
)
|
|
65
|
+
dropped = true
|
|
67
66
|
else
|
|
68
67
|
@pending_items << item
|
|
69
68
|
end
|
|
70
69
|
|
|
71
|
-
|
|
70
|
+
size >= @max_items
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
if dropped
|
|
74
|
+
log_debug("[#{self.class}] exceeded max capacity, dropping event")
|
|
75
|
+
@client.transport.record_lost_event(
|
|
76
|
+
:queue_overflow,
|
|
77
|
+
@data_category,
|
|
78
|
+
num_bytes: JSON.generate(item.to_h).bytesize
|
|
79
|
+
)
|
|
72
80
|
end
|
|
73
81
|
|
|
82
|
+
wake if size_exceeded
|
|
74
83
|
self
|
|
75
84
|
end
|
|
76
85
|
|
|
@@ -88,21 +97,40 @@ module Sentry
|
|
|
88
97
|
|
|
89
98
|
private
|
|
90
99
|
|
|
91
|
-
def
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
100
|
+
def reset_if_forked
|
|
101
|
+
return unless super
|
|
102
|
+
|
|
103
|
+
# Discard items inherited from the parent to avoid duplicate delivery.
|
|
104
|
+
@mutex = Mutex.new
|
|
105
|
+
@pending_items = []
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def flush_pending_items
|
|
109
|
+
pending_items = @mutex.synchronize do
|
|
110
|
+
next if @pending_items.empty?
|
|
111
|
+
|
|
112
|
+
items = @pending_items
|
|
113
|
+
@pending_items = []
|
|
114
|
+
items
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
return unless pending_items
|
|
118
|
+
|
|
119
|
+
log_debug("[#{self.class}] flushing #{pending_items.size} #{@event_class}")
|
|
120
|
+
send_items(pending_items)
|
|
121
|
+
self
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def send_items(pending_items)
|
|
125
|
+
envelope = Envelope.new(sent_at: Sentry.utc_now.iso8601)
|
|
98
126
|
|
|
99
127
|
discarded_count = 0
|
|
100
128
|
discarded_bytes = 0
|
|
101
129
|
envelope_items = []
|
|
102
130
|
|
|
103
|
-
if @before_send
|
|
104
|
-
|
|
105
|
-
processed_item = @before_send.
|
|
131
|
+
if callback = @configuration.send(@before_send)
|
|
132
|
+
pending_items.each do |item|
|
|
133
|
+
processed_item = safe_dispatch_callback(@before_send.to_s, callback, [item])
|
|
106
134
|
|
|
107
135
|
if processed_item
|
|
108
136
|
envelope_items << processed_item.to_h
|
|
@@ -112,7 +140,7 @@ module Sentry
|
|
|
112
140
|
end
|
|
113
141
|
end
|
|
114
142
|
else
|
|
115
|
-
envelope_items =
|
|
143
|
+
envelope_items = pending_items.map(&:to_h)
|
|
116
144
|
end
|
|
117
145
|
|
|
118
146
|
unless discarded_count.zero?
|
|
@@ -133,8 +161,6 @@ module Sentry
|
|
|
133
161
|
@client.send_envelope(envelope)
|
|
134
162
|
rescue => e
|
|
135
163
|
log_error("[#{self.class}] Failed to send #{@event_class}", e, debug: @debug)
|
|
136
|
-
ensure
|
|
137
|
-
clear!
|
|
138
164
|
end
|
|
139
165
|
end
|
|
140
166
|
end
|
data/lib/sentry/test_helper.rb
CHANGED
|
@@ -127,6 +127,8 @@ module Sentry
|
|
|
127
127
|
end
|
|
128
128
|
|
|
129
129
|
def sentry_logs
|
|
130
|
+
Sentry.get_current_client&.log_event_buffer&.flush
|
|
131
|
+
|
|
130
132
|
sentry_envelopes
|
|
131
133
|
.flat_map(&:items)
|
|
132
134
|
.select { |item| item.headers[:type] == "log" }
|
|
@@ -134,6 +136,8 @@ module Sentry
|
|
|
134
136
|
end
|
|
135
137
|
|
|
136
138
|
def sentry_metrics
|
|
139
|
+
Sentry.get_current_client&.metric_event_buffer&.flush
|
|
140
|
+
|
|
137
141
|
sentry_envelopes
|
|
138
142
|
.flat_map(&:items)
|
|
139
143
|
.select { |item| item.headers[:type] == "trace_metric" }
|
|
@@ -4,41 +4,118 @@ module Sentry
|
|
|
4
4
|
class ThreadedPeriodicWorker
|
|
5
5
|
include LoggingHelper
|
|
6
6
|
|
|
7
|
+
# @!visibility private
|
|
8
|
+
attr_reader :thread, :thread_mutex, :idle_condition
|
|
9
|
+
|
|
7
10
|
def initialize(sdk_logger, interval)
|
|
11
|
+
@process_id = Process.pid
|
|
8
12
|
@thread = nil
|
|
9
13
|
@exited = false
|
|
10
14
|
@interval = interval
|
|
11
15
|
@sdk_logger = sdk_logger
|
|
16
|
+
|
|
17
|
+
@thread_mutex = Mutex.new
|
|
18
|
+
@wake_condition = ConditionVariable.new
|
|
19
|
+
@idle_condition = ConditionVariable.new
|
|
20
|
+
|
|
21
|
+
@woken = false
|
|
22
|
+
@running = false
|
|
12
23
|
end
|
|
13
24
|
|
|
14
25
|
def ensure_thread
|
|
15
|
-
|
|
16
|
-
return true if @thread&.alive?
|
|
26
|
+
reset_if_forked
|
|
17
27
|
|
|
18
|
-
@
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
run
|
|
22
|
-
end
|
|
23
|
-
end
|
|
28
|
+
@thread_mutex.synchronize do
|
|
29
|
+
return false if @exited
|
|
30
|
+
return true if @thread&.alive?
|
|
24
31
|
|
|
25
|
-
|
|
32
|
+
@thread = Thread.new { worker_loop }
|
|
33
|
+
|
|
34
|
+
true
|
|
35
|
+
end
|
|
26
36
|
rescue ThreadError
|
|
37
|
+
@thread_mutex.synchronize { @exited = true }
|
|
27
38
|
log_debug("[#{self.class.name}] thread creation failed")
|
|
28
|
-
@exited = true
|
|
29
39
|
false
|
|
30
40
|
end
|
|
31
41
|
|
|
42
|
+
def wake
|
|
43
|
+
@thread_mutex.synchronize do
|
|
44
|
+
next false if @exited
|
|
45
|
+
|
|
46
|
+
@woken = true
|
|
47
|
+
@wake_condition.signal
|
|
48
|
+
true
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def wait_until_idle(timeout = nil)
|
|
53
|
+
deadline = Process.clock_gettime(Process::CLOCK_MONOTONIC) + timeout if timeout
|
|
54
|
+
|
|
55
|
+
@thread_mutex.synchronize do
|
|
56
|
+
while !@exited && (@running || @woken)
|
|
57
|
+
remaining = deadline - Process.clock_gettime(Process::CLOCK_MONOTONIC) if deadline
|
|
58
|
+
return false if remaining && remaining <= 0
|
|
59
|
+
|
|
60
|
+
@idle_condition.wait(@thread_mutex, remaining)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
true
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
32
67
|
def kill
|
|
33
|
-
|
|
68
|
+
thread = @thread_mutex.synchronize do
|
|
69
|
+
@exited = true
|
|
70
|
+
@woken = false
|
|
71
|
+
@idle_condition.broadcast
|
|
72
|
+
@thread
|
|
73
|
+
end
|
|
34
74
|
|
|
35
75
|
# Only a started worker has a thread to kill (and to log about).
|
|
36
76
|
# Guarding here keeps a never-started worker's teardown silent, so
|
|
37
77
|
# killing one during test reset can't emit a stray debug line.
|
|
38
|
-
return unless
|
|
78
|
+
return unless thread
|
|
39
79
|
|
|
40
80
|
log_debug("[#{self.class.name}] thread killed")
|
|
41
|
-
|
|
81
|
+
thread.kill
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
private
|
|
85
|
+
|
|
86
|
+
def reset_if_forked
|
|
87
|
+
return false if @process_id == Process.pid
|
|
88
|
+
|
|
89
|
+
@process_id = Process.pid
|
|
90
|
+
@thread = nil
|
|
91
|
+
@exited = false
|
|
92
|
+
@woken = false
|
|
93
|
+
@running = false
|
|
94
|
+
@thread_mutex = Mutex.new
|
|
95
|
+
@wake_condition = ConditionVariable.new
|
|
96
|
+
@idle_condition = ConditionVariable.new
|
|
97
|
+
true
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def worker_loop
|
|
101
|
+
loop do
|
|
102
|
+
@thread_mutex.synchronize do
|
|
103
|
+
@wake_condition.wait(@thread_mutex, @interval) unless @woken
|
|
104
|
+
@woken = false
|
|
105
|
+
@running = true
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
begin
|
|
109
|
+
run
|
|
110
|
+
rescue Exception => e
|
|
111
|
+
log_error("[#{self.class.name}] run failed", e)
|
|
112
|
+
ensure
|
|
113
|
+
@thread_mutex.synchronize do
|
|
114
|
+
@running = false
|
|
115
|
+
@idle_condition.broadcast
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
end
|
|
42
119
|
end
|
|
43
120
|
end
|
|
44
121
|
end
|
data/lib/sentry/transaction.rb
CHANGED
|
@@ -14,6 +14,7 @@ module Sentry
|
|
|
14
14
|
SOURCES = %i[custom url route view component task]
|
|
15
15
|
|
|
16
16
|
include LoggingHelper
|
|
17
|
+
include CallbackHelper
|
|
17
18
|
|
|
18
19
|
# The name of the transaction.
|
|
19
20
|
# @return [String]
|
|
@@ -142,7 +143,12 @@ module Sentry
|
|
|
142
143
|
|
|
143
144
|
sample_rate =
|
|
144
145
|
if configuration.traces_sampler.is_a?(Proc)
|
|
145
|
-
|
|
146
|
+
safe_dispatch_callback(
|
|
147
|
+
"traces_sampler",
|
|
148
|
+
configuration.traces_sampler,
|
|
149
|
+
[sampling_context],
|
|
150
|
+
fallback: configuration.traces_sample_rate
|
|
151
|
+
)
|
|
146
152
|
elsif !sampling_context[:parent_sampled].nil?
|
|
147
153
|
sampling_context[:parent_sampled]
|
|
148
154
|
else
|
|
@@ -27,6 +27,11 @@ module Sentry
|
|
|
27
27
|
backend.send_event(event)
|
|
28
28
|
end
|
|
29
29
|
|
|
30
|
+
def send_envelope(envelope)
|
|
31
|
+
log_envelope(envelope)
|
|
32
|
+
backend.send_envelope(envelope)
|
|
33
|
+
end
|
|
34
|
+
|
|
30
35
|
def log_envelope(envelope)
|
|
31
36
|
envelope_json = {
|
|
32
37
|
timestamp: Time.now.utc.iso8601,
|
|
@@ -36,13 +41,13 @@ module Sentry
|
|
|
36
41
|
end
|
|
37
42
|
}
|
|
38
43
|
|
|
39
|
-
File.open(log_file, "a") { |file| file
|
|
44
|
+
File.open(log_file, "a") { |file| file.write("#{JSON.dump(envelope_json)}\n") }
|
|
40
45
|
end
|
|
41
46
|
|
|
42
47
|
def logged_envelopes
|
|
43
48
|
return [] unless File.exist?(log_file)
|
|
44
49
|
|
|
45
|
-
File.readlines(log_file).map do |line|
|
|
50
|
+
File.readlines(log_file).select { |line| fully_written?(line) }.map do |line|
|
|
46
51
|
JSON.parse(line)
|
|
47
52
|
end
|
|
48
53
|
end
|
|
@@ -54,6 +59,10 @@ module Sentry
|
|
|
54
59
|
|
|
55
60
|
private
|
|
56
61
|
|
|
62
|
+
def fully_written?(line)
|
|
63
|
+
line.end_with?("\n")
|
|
64
|
+
end
|
|
65
|
+
|
|
57
66
|
def initialize_backend(configuration)
|
|
58
67
|
backend = configuration.dsn.local? ? DummyTransport : HTTPTransport
|
|
59
68
|
backend.new(configuration)
|