sentry-ruby 5.18.2 → 5.28.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/.rspec +3 -1
- data/Gemfile +9 -3
- data/README.md +12 -7
- data/Rakefile +9 -11
- data/bin/console +1 -0
- data/lib/sentry/attachment.rb +40 -0
- data/lib/sentry/background_worker.rb +2 -3
- data/lib/sentry/backpressure_monitor.rb +1 -1
- data/lib/sentry/backtrace.rb +7 -8
- data/lib/sentry/baggage.rb +7 -7
- data/lib/sentry/breadcrumb/sentry_logger.rb +6 -6
- data/lib/sentry/breadcrumb.rb +5 -4
- data/lib/sentry/check_in_event.rb +5 -4
- data/lib/sentry/client.rb +121 -19
- data/lib/sentry/configuration.rb +207 -38
- data/lib/sentry/core_ext/object/deep_dup.rb +1 -1
- data/lib/sentry/cron/monitor_check_ins.rb +6 -4
- data/lib/sentry/cron/monitor_config.rb +1 -1
- data/lib/sentry/debug_structured_logger.rb +94 -0
- data/lib/sentry/dsn.rb +35 -3
- data/lib/sentry/envelope/item.rb +88 -0
- data/lib/sentry/envelope.rb +2 -85
- data/lib/sentry/event.rb +13 -8
- data/lib/sentry/excon/middleware.rb +77 -0
- data/lib/sentry/excon.rb +10 -0
- data/lib/sentry/faraday.rb +77 -0
- data/lib/sentry/graphql.rb +1 -1
- data/lib/sentry/hub.rb +57 -4
- data/lib/sentry/interfaces/mechanism.rb +1 -1
- data/lib/sentry/interfaces/request.rb +6 -6
- data/lib/sentry/interfaces/single_exception.rb +3 -3
- data/lib/sentry/interfaces/stacktrace.rb +3 -1
- data/lib/sentry/interfaces/stacktrace_builder.rb +15 -2
- data/lib/sentry/linecache.rb +3 -3
- data/lib/sentry/log_event.rb +219 -0
- data/lib/sentry/log_event_buffer.rb +75 -0
- data/lib/sentry/logger.rb +1 -1
- data/lib/sentry/metrics/aggregator.rb +13 -13
- data/lib/sentry/metrics/configuration.rb +12 -2
- data/lib/sentry/metrics/set_metric.rb +2 -2
- data/lib/sentry/metrics/timing.rb +8 -0
- data/lib/sentry/metrics.rb +27 -15
- data/lib/sentry/net/http.rb +17 -38
- data/lib/sentry/profiler/helpers.rb +46 -0
- data/lib/sentry/profiler.rb +27 -57
- data/lib/sentry/propagation_context.rb +60 -23
- data/lib/sentry/rack/capture_exceptions.rb +2 -2
- data/lib/sentry/rack.rb +2 -2
- data/lib/sentry/rake.rb +2 -2
- data/lib/sentry/release_detector.rb +4 -4
- data/lib/sentry/rspec.rb +91 -0
- data/lib/sentry/scope.rb +27 -2
- data/lib/sentry/session_flusher.rb +10 -6
- data/lib/sentry/span.rb +17 -4
- data/lib/sentry/std_lib_logger.rb +55 -0
- data/lib/sentry/structured_logger.rb +138 -0
- data/lib/sentry/test_helper.rb +45 -1
- data/lib/sentry/threaded_periodic_worker.rb +3 -3
- data/lib/sentry/transaction.rb +41 -13
- data/lib/sentry/transaction_event.rb +5 -3
- data/lib/sentry/transport/debug_transport.rb +70 -0
- data/lib/sentry/transport/dummy_transport.rb +1 -0
- data/lib/sentry/transport/http_transport.rb +21 -18
- data/lib/sentry/transport.rb +26 -11
- data/lib/sentry/utils/env_helper.rb +21 -0
- data/lib/sentry/utils/http_tracing.rb +74 -0
- data/lib/sentry/utils/logging_helper.rb +10 -3
- data/lib/sentry/utils/real_ip.rb +1 -1
- data/lib/sentry/utils/sample_rand.rb +97 -0
- data/lib/sentry/utils/uuid.rb +13 -0
- data/lib/sentry/vernier/output.rb +89 -0
- data/lib/sentry/vernier/profiler.rb +132 -0
- data/lib/sentry/version.rb +1 -1
- data/lib/sentry-ruby.rb +106 -12
- data/sentry-ruby-core.gemspec +3 -1
- data/sentry-ruby.gemspec +4 -2
- metadata +28 -12
data/lib/sentry/transport.rb
CHANGED
|
@@ -5,7 +5,7 @@ require "sentry/envelope"
|
|
|
5
5
|
|
|
6
6
|
module Sentry
|
|
7
7
|
class Transport
|
|
8
|
-
PROTOCOL_VERSION =
|
|
8
|
+
PROTOCOL_VERSION = "7"
|
|
9
9
|
USER_AGENT = "sentry-ruby/#{Sentry::VERSION}"
|
|
10
10
|
CLIENT_REPORT_INTERVAL = 30
|
|
11
11
|
|
|
@@ -26,11 +26,8 @@ module Sentry
|
|
|
26
26
|
|
|
27
27
|
attr_reader :rate_limits, :discarded_events, :last_client_report_sent
|
|
28
28
|
|
|
29
|
-
# @deprecated Use Sentry.logger to retrieve the current logger instead.
|
|
30
|
-
attr_reader :logger
|
|
31
|
-
|
|
32
29
|
def initialize(configuration)
|
|
33
|
-
@
|
|
30
|
+
@sdk_logger = configuration.sdk_logger
|
|
34
31
|
@transport_configuration = configuration.transport
|
|
35
32
|
@dsn = configuration.dsn
|
|
36
33
|
@rate_limits = {}
|
|
@@ -133,18 +130,35 @@ module Sentry
|
|
|
133
130
|
|
|
134
131
|
envelope = Envelope.new(envelope_headers)
|
|
135
132
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
133
|
+
if event.is_a?(LogEvent)
|
|
134
|
+
envelope.add_item(
|
|
135
|
+
{
|
|
136
|
+
type: "log",
|
|
137
|
+
item_count: 1,
|
|
138
|
+
content_type: "application/vnd.sentry.items.log+json"
|
|
139
|
+
},
|
|
140
|
+
{ items: [event_payload] }
|
|
141
|
+
)
|
|
142
|
+
else
|
|
143
|
+
envelope.add_item(
|
|
144
|
+
{ type: item_type, content_type: "application/json" },
|
|
145
|
+
event_payload
|
|
146
|
+
)
|
|
147
|
+
end
|
|
140
148
|
|
|
141
149
|
if event.is_a?(TransactionEvent) && event.profile
|
|
142
150
|
envelope.add_item(
|
|
143
|
-
{ type:
|
|
151
|
+
{ type: "profile", content_type: "application/json" },
|
|
144
152
|
event.profile
|
|
145
153
|
)
|
|
146
154
|
end
|
|
147
155
|
|
|
156
|
+
if event.is_a?(Event) && event.attachments.any?
|
|
157
|
+
event.attachments.each do |attachment|
|
|
158
|
+
envelope.add_item(attachment.to_envelope_headers, attachment.payload)
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
|
|
148
162
|
client_report_headers, client_report_payload = fetch_pending_client_report
|
|
149
163
|
envelope.add_item(client_report_headers, client_report_payload) if client_report_headers
|
|
150
164
|
|
|
@@ -179,7 +193,7 @@ module Sentry
|
|
|
179
193
|
{ reason: reason, category: category, quantity: val }
|
|
180
194
|
end
|
|
181
195
|
|
|
182
|
-
item_header = { type:
|
|
196
|
+
item_header = { type: "client_report" }
|
|
183
197
|
item_payload = {
|
|
184
198
|
timestamp: Sentry.utc_now.iso8601,
|
|
185
199
|
discarded_events: discarded_events_hash
|
|
@@ -209,3 +223,4 @@ end
|
|
|
209
223
|
require "sentry/transport/dummy_transport"
|
|
210
224
|
require "sentry/transport/http_transport"
|
|
211
225
|
require "sentry/transport/spotlight_transport"
|
|
226
|
+
require "sentry/transport/debug_transport"
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Sentry
|
|
4
|
+
module Utils
|
|
5
|
+
module EnvHelper
|
|
6
|
+
TRUTHY_ENV_VALUES = %w[t true yes y 1 on].freeze
|
|
7
|
+
FALSY_ENV_VALUES = %w[f false no n 0 off].freeze
|
|
8
|
+
|
|
9
|
+
def self.env_to_bool(value, strict: false)
|
|
10
|
+
value = value.to_s
|
|
11
|
+
normalized = value.downcase
|
|
12
|
+
|
|
13
|
+
return false if FALSY_ENV_VALUES.include?(normalized)
|
|
14
|
+
|
|
15
|
+
return true if TRUTHY_ENV_VALUES.include?(normalized)
|
|
16
|
+
|
|
17
|
+
strict ? nil : !(value.nil? || value.empty?)
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Sentry
|
|
4
|
+
module Utils
|
|
5
|
+
module HttpTracing
|
|
6
|
+
def set_span_info(sentry_span, request_info, response_status)
|
|
7
|
+
sentry_span.set_description("#{request_info[:method]} #{request_info[:url]}")
|
|
8
|
+
sentry_span.set_data(Span::DataConventions::URL, request_info[:url])
|
|
9
|
+
sentry_span.set_data(Span::DataConventions::HTTP_METHOD, request_info[:method])
|
|
10
|
+
sentry_span.set_data(Span::DataConventions::HTTP_QUERY, request_info[:query]) if request_info[:query]
|
|
11
|
+
sentry_span.set_data(Span::DataConventions::HTTP_STATUS_CODE, response_status)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def set_propagation_headers(req)
|
|
15
|
+
Sentry.get_trace_propagation_headers&.each { |k, v| req[k] = v }
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def record_sentry_breadcrumb(request_info, response_status)
|
|
19
|
+
crumb = Sentry::Breadcrumb.new(
|
|
20
|
+
level: get_level(response_status),
|
|
21
|
+
category: self.class::BREADCRUMB_CATEGORY,
|
|
22
|
+
type: "info",
|
|
23
|
+
data: { status: response_status, **request_info }
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
Sentry.add_breadcrumb(crumb)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def record_sentry_breadcrumb?
|
|
30
|
+
Sentry.initialized? && Sentry.configuration.breadcrumbs_logger.include?(:http_logger)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def propagate_trace?(url)
|
|
34
|
+
url &&
|
|
35
|
+
Sentry.initialized? &&
|
|
36
|
+
Sentry.configuration.propagate_traces &&
|
|
37
|
+
Sentry.configuration.trace_propagation_targets.any? { |target| url.match?(target) }
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Kindly borrowed from Rack::Utils
|
|
41
|
+
def build_nested_query(value, prefix = nil)
|
|
42
|
+
case value
|
|
43
|
+
when Array
|
|
44
|
+
value.map { |v|
|
|
45
|
+
build_nested_query(v, "#{prefix}[]")
|
|
46
|
+
}.join("&")
|
|
47
|
+
when Hash
|
|
48
|
+
value.map { |k, v|
|
|
49
|
+
build_nested_query(v, prefix ? "#{prefix}[#{k}]" : k)
|
|
50
|
+
}.delete_if(&:empty?).join("&")
|
|
51
|
+
when nil
|
|
52
|
+
URI.encode_www_form_component(prefix)
|
|
53
|
+
else
|
|
54
|
+
raise ArgumentError, "value must be a Hash" if prefix.nil?
|
|
55
|
+
"#{URI.encode_www_form_component(prefix)}=#{URI.encode_www_form_component(value)}"
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
private
|
|
60
|
+
|
|
61
|
+
def get_level(status)
|
|
62
|
+
return :info unless status && status.is_a?(Integer)
|
|
63
|
+
|
|
64
|
+
if status >= 500
|
|
65
|
+
:error
|
|
66
|
+
elsif status >= 400
|
|
67
|
+
:warning
|
|
68
|
+
else
|
|
69
|
+
:info
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
@@ -1,22 +1,29 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module Sentry
|
|
4
|
+
# @private
|
|
4
5
|
module LoggingHelper
|
|
6
|
+
# @!visibility private
|
|
7
|
+
attr_reader :sdk_logger
|
|
8
|
+
|
|
9
|
+
# @!visibility private
|
|
5
10
|
def log_error(message, exception, debug: false)
|
|
6
11
|
message = "#{message}: #{exception.message}"
|
|
7
12
|
message += "\n#{exception.backtrace.join("\n")}" if debug
|
|
8
13
|
|
|
9
|
-
|
|
14
|
+
sdk_logger.error(LOGGER_PROGNAME) do
|
|
10
15
|
message
|
|
11
16
|
end
|
|
12
17
|
end
|
|
13
18
|
|
|
19
|
+
# @!visibility private
|
|
14
20
|
def log_debug(message)
|
|
15
|
-
|
|
21
|
+
sdk_logger.debug(LOGGER_PROGNAME) { message }
|
|
16
22
|
end
|
|
17
23
|
|
|
24
|
+
# @!visibility private
|
|
18
25
|
def log_warn(message)
|
|
19
|
-
|
|
26
|
+
sdk_logger.warn(LOGGER_PROGNAME) { message }
|
|
20
27
|
end
|
|
21
28
|
end
|
|
22
29
|
end
|
data/lib/sentry/utils/real_ip.rb
CHANGED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Sentry
|
|
4
|
+
module Utils
|
|
5
|
+
class SampleRand
|
|
6
|
+
PRECISION = 1_000_000.0
|
|
7
|
+
FORMAT_PRECISION = 6
|
|
8
|
+
|
|
9
|
+
attr_reader :trace_id
|
|
10
|
+
|
|
11
|
+
def self.valid?(value)
|
|
12
|
+
return false unless value
|
|
13
|
+
value >= 0.0 && value < 1.0
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def self.format(value)
|
|
17
|
+
return unless value
|
|
18
|
+
|
|
19
|
+
truncated = (value * PRECISION).floor / PRECISION
|
|
20
|
+
"%.#{FORMAT_PRECISION}f" % truncated
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def initialize(trace_id: nil)
|
|
24
|
+
@trace_id = trace_id
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def generate_from_trace_id
|
|
28
|
+
(random_from_trace_id * PRECISION).floor / PRECISION
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def generate_from_sampling_decision(sampled, sample_rate)
|
|
32
|
+
if invalid_sample_rate?(sample_rate)
|
|
33
|
+
fallback_generation
|
|
34
|
+
else
|
|
35
|
+
generate_based_on_sampling(sampled, sample_rate)
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def generate_from_value(sample_rand_value)
|
|
40
|
+
parsed_value = parse_value(sample_rand_value)
|
|
41
|
+
|
|
42
|
+
if self.class.valid?(parsed_value)
|
|
43
|
+
parsed_value
|
|
44
|
+
else
|
|
45
|
+
fallback_generation
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
private
|
|
50
|
+
|
|
51
|
+
def random_from_trace_id
|
|
52
|
+
if @trace_id
|
|
53
|
+
Random.new(@trace_id[0, 16].to_i(16))
|
|
54
|
+
else
|
|
55
|
+
Random.new
|
|
56
|
+
end.rand(1.0)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def invalid_sample_rate?(sample_rate)
|
|
60
|
+
sample_rate.nil? || sample_rate <= 0.0 || sample_rate > 1.0
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def fallback_generation
|
|
64
|
+
if @trace_id
|
|
65
|
+
(random_from_trace_id * PRECISION).floor / PRECISION
|
|
66
|
+
else
|
|
67
|
+
format_random(Random.rand(1.0))
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def generate_based_on_sampling(sampled, sample_rate)
|
|
72
|
+
random = random_from_trace_id
|
|
73
|
+
|
|
74
|
+
result = if sampled
|
|
75
|
+
random * sample_rate
|
|
76
|
+
elsif sample_rate == 1.0
|
|
77
|
+
random
|
|
78
|
+
else
|
|
79
|
+
sample_rate + random * (1.0 - sample_rate)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
format_random(result)
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def format_random(value)
|
|
86
|
+
truncated = (value * PRECISION).floor / PRECISION
|
|
87
|
+
("%.#{FORMAT_PRECISION}f" % truncated).to_f
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def parse_value(sample_rand_value)
|
|
91
|
+
Float(sample_rand_value)
|
|
92
|
+
rescue ArgumentError
|
|
93
|
+
nil
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
end
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
require "rbconfig"
|
|
5
|
+
|
|
6
|
+
module Sentry
|
|
7
|
+
module Vernier
|
|
8
|
+
class Output
|
|
9
|
+
include Profiler::Helpers
|
|
10
|
+
|
|
11
|
+
attr_reader :profile
|
|
12
|
+
|
|
13
|
+
def initialize(profile, project_root:, in_app_pattern:, app_dirs_pattern:)
|
|
14
|
+
@profile = profile
|
|
15
|
+
@project_root = project_root
|
|
16
|
+
@in_app_pattern = in_app_pattern
|
|
17
|
+
@app_dirs_pattern = app_dirs_pattern
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def to_h
|
|
21
|
+
@to_h ||= {
|
|
22
|
+
frames: frames,
|
|
23
|
+
stacks: stacks,
|
|
24
|
+
samples: samples,
|
|
25
|
+
thread_metadata: thread_metadata
|
|
26
|
+
}
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
private
|
|
30
|
+
|
|
31
|
+
def thread_metadata
|
|
32
|
+
profile.threads.map { |thread_id, thread_info|
|
|
33
|
+
[thread_id, { name: thread_info[:name] }]
|
|
34
|
+
}.to_h
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def samples
|
|
38
|
+
profile.threads.flat_map { |thread_id, thread_info|
|
|
39
|
+
started_at = thread_info[:started_at]
|
|
40
|
+
samples, timestamps = thread_info.values_at(:samples, :timestamps)
|
|
41
|
+
|
|
42
|
+
samples.zip(timestamps).map { |stack_id, timestamp|
|
|
43
|
+
elapsed_since_start_ns = timestamp - started_at
|
|
44
|
+
|
|
45
|
+
next if elapsed_since_start_ns < 0
|
|
46
|
+
|
|
47
|
+
{
|
|
48
|
+
thread_id: thread_id.to_s,
|
|
49
|
+
stack_id: stack_id,
|
|
50
|
+
elapsed_since_start_ns: elapsed_since_start_ns.to_s
|
|
51
|
+
}
|
|
52
|
+
}.compact
|
|
53
|
+
}
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def frames
|
|
57
|
+
funcs = stack_table_hash[:frame_table].fetch(:func)
|
|
58
|
+
lines = stack_table_hash[:func_table].fetch(:first_line)
|
|
59
|
+
|
|
60
|
+
funcs.map do |idx|
|
|
61
|
+
function, mod = split_module(stack_table_hash[:func_table][:name][idx])
|
|
62
|
+
|
|
63
|
+
abs_path = stack_table_hash[:func_table][:filename][idx]
|
|
64
|
+
in_app = in_app?(abs_path)
|
|
65
|
+
filename = compute_filename(abs_path, in_app)
|
|
66
|
+
|
|
67
|
+
{
|
|
68
|
+
function: function,
|
|
69
|
+
module: mod,
|
|
70
|
+
filename: filename,
|
|
71
|
+
abs_path: abs_path,
|
|
72
|
+
lineno: (lineno = lines[idx]) > 0 ? lineno : nil,
|
|
73
|
+
in_app: in_app
|
|
74
|
+
}.compact
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def stacks
|
|
79
|
+
profile._stack_table.stack_count.times.map do |stack_id|
|
|
80
|
+
profile.stack(stack_id).frames.map(&:idx)
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def stack_table_hash
|
|
85
|
+
@stack_table_hash ||= profile._stack_table.to_h
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "securerandom"
|
|
4
|
+
require_relative "../profiler/helpers"
|
|
5
|
+
require_relative "output"
|
|
6
|
+
require "sentry/utils/uuid"
|
|
7
|
+
|
|
8
|
+
module Sentry
|
|
9
|
+
module Vernier
|
|
10
|
+
class Profiler
|
|
11
|
+
EMPTY_RESULT = {}.freeze
|
|
12
|
+
|
|
13
|
+
attr_reader :started, :event_id, :result
|
|
14
|
+
|
|
15
|
+
def initialize(configuration)
|
|
16
|
+
@event_id = Utils.uuid
|
|
17
|
+
|
|
18
|
+
@started = false
|
|
19
|
+
@sampled = nil
|
|
20
|
+
|
|
21
|
+
@profiling_enabled = defined?(Vernier) && configuration.profiling_enabled?
|
|
22
|
+
@profiles_sample_rate = configuration.profiles_sample_rate
|
|
23
|
+
@project_root = configuration.project_root
|
|
24
|
+
@app_dirs_pattern = configuration.app_dirs_pattern
|
|
25
|
+
@in_app_pattern = Regexp.new("^(#{@project_root}/)?#{@app_dirs_pattern}")
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def set_initial_sample_decision(transaction_sampled)
|
|
29
|
+
unless @profiling_enabled
|
|
30
|
+
@sampled = false
|
|
31
|
+
return
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
unless transaction_sampled
|
|
35
|
+
@sampled = false
|
|
36
|
+
log("Discarding profile because transaction not sampled")
|
|
37
|
+
return
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
case @profiles_sample_rate
|
|
41
|
+
when 0.0
|
|
42
|
+
@sampled = false
|
|
43
|
+
log("Discarding profile because sample_rate is 0")
|
|
44
|
+
return
|
|
45
|
+
when 1.0
|
|
46
|
+
@sampled = true
|
|
47
|
+
return
|
|
48
|
+
else
|
|
49
|
+
@sampled = Random.rand < @profiles_sample_rate
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
log("Discarding profile due to sampling decision") unless @sampled
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def start
|
|
56
|
+
return unless @sampled
|
|
57
|
+
return if @started
|
|
58
|
+
|
|
59
|
+
@started = ::Vernier.start_profile
|
|
60
|
+
|
|
61
|
+
log("Started")
|
|
62
|
+
|
|
63
|
+
@started
|
|
64
|
+
rescue RuntimeError => e
|
|
65
|
+
# TODO: once Vernier raises something more dedicated, we should catch that instead
|
|
66
|
+
if e.message.include?("Profile already started")
|
|
67
|
+
log("Not started since running elsewhere")
|
|
68
|
+
else
|
|
69
|
+
log("Failed to start: #{e.message}")
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def stop
|
|
74
|
+
return unless @sampled
|
|
75
|
+
return unless @started
|
|
76
|
+
|
|
77
|
+
@result = ::Vernier.stop_profile
|
|
78
|
+
@started = false
|
|
79
|
+
|
|
80
|
+
log("Stopped")
|
|
81
|
+
rescue RuntimeError => e
|
|
82
|
+
if e.message.include?("Profile not started")
|
|
83
|
+
log("Not stopped since not started")
|
|
84
|
+
else
|
|
85
|
+
log("Failed to stop Vernier: #{e.message}")
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def active_thread_id
|
|
90
|
+
Thread.current.object_id
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def to_hash
|
|
94
|
+
unless @sampled
|
|
95
|
+
record_lost_event(:sample_rate)
|
|
96
|
+
return EMPTY_RESULT
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
return EMPTY_RESULT unless result
|
|
100
|
+
|
|
101
|
+
{ **profile_meta, profile: output.to_h }
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
private
|
|
105
|
+
|
|
106
|
+
def log(message)
|
|
107
|
+
Sentry.sdk_logger.debug(LOGGER_PROGNAME) { "[Profiler::Vernier] #{message}" }
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def record_lost_event(reason)
|
|
111
|
+
Sentry.get_current_client&.transport&.record_lost_event(reason, "profile")
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def profile_meta
|
|
115
|
+
{
|
|
116
|
+
event_id: @event_id,
|
|
117
|
+
version: "1",
|
|
118
|
+
platform: "ruby"
|
|
119
|
+
}
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def output
|
|
123
|
+
@output ||= Output.new(
|
|
124
|
+
result,
|
|
125
|
+
project_root: @project_root,
|
|
126
|
+
app_dirs_pattern: @app_dirs_pattern,
|
|
127
|
+
in_app_pattern: @in_app_pattern
|
|
128
|
+
)
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
end
|
data/lib/sentry/version.rb
CHANGED