sentry-ruby 5.8.0 → 5.9.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 +3 -0
- data/Rakefile +8 -1
- data/lib/sentry/backtrace.rb +1 -1
- data/lib/sentry/baggage.rb +1 -12
- data/lib/sentry/client.rb +4 -1
- data/lib/sentry/configuration.rb +36 -1
- data/lib/sentry/envelope.rb +1 -4
- data/lib/sentry/hub.rb +4 -1
- data/lib/sentry/net/http.rb +1 -4
- data/lib/sentry/profiler.rb +222 -0
- data/lib/sentry/puma.rb +25 -0
- data/lib/sentry/redis.rb +1 -6
- data/lib/sentry/scope.rb +6 -1
- data/lib/sentry/transaction.rb +15 -0
- data/lib/sentry/transaction_event.rb +30 -0
- data/lib/sentry/transport.rb +7 -0
- data/lib/sentry/version.rb +1 -1
- data/lib/sentry-ruby.rb +13 -2
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7a114a391fe058601b40369376152bee8ba9b1b96ab94710344283cfe1a4490b
|
4
|
+
data.tar.gz: 7a17648c7a5d06f22d2d643f6ff1cb25d3fed2348609e372fc5762043ebd538a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: dab19469a8e68201380e2be405332bb7c8caef7bf46fd4dae70f4dc3beaac2902ce51b2b2c902b39ca048e1bc5ff47f85b1e7d3a824b5f20ff002dcbdca2c354
|
7
|
+
data.tar.gz: 6bf0cba7b42dd9bfd5078e4353ad7f92d932b959a87c748cb30e505ffd8d205899c819ea4f72e5f3099f0b861139864062b870219678abdeda0a0629604963d3
|
data/Gemfile
CHANGED
@@ -10,6 +10,8 @@ gem "rack", "~> #{Gem::Version.new(rack_version)}" unless rack_version == "0"
|
|
10
10
|
redis_rb_version = ENV.fetch("REDIS_RB_VERSION", "5.0")
|
11
11
|
gem "redis", "~> #{redis_rb_version}"
|
12
12
|
|
13
|
+
gem "puma"
|
14
|
+
|
13
15
|
gem "rake", "~> 12.0"
|
14
16
|
gem "rspec", "~> 3.0"
|
15
17
|
gem "rspec-retry"
|
@@ -17,6 +19,7 @@ gem "timecop"
|
|
17
19
|
gem "simplecov"
|
18
20
|
gem "simplecov-cobertura", "~> 1.4"
|
19
21
|
gem "rexml"
|
22
|
+
gem "stackprof" unless RUBY_PLATFORM == "java"
|
20
23
|
|
21
24
|
gem "object_tracer"
|
22
25
|
gem "debug", github: "ruby/debug", platform: :ruby if RUBY_VERSION.to_f >= 2.6
|
data/Rakefile
CHANGED
@@ -8,6 +8,13 @@ require "rspec/core/rake_task"
|
|
8
8
|
|
9
9
|
RSpec::Core::RakeTask.new(:spec).tap do |task|
|
10
10
|
task.rspec_opts = "--order rand"
|
11
|
+
task.exclude_pattern = "spec/isolated/**/*_spec.rb"
|
11
12
|
end
|
12
13
|
|
13
|
-
task :
|
14
|
+
task :isolated_specs do
|
15
|
+
Dir["spec/isolated/*"].each do |file|
|
16
|
+
sh "bundle exec rspec #{file}"
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
task :default => [:spec, :isolated_specs]
|
data/lib/sentry/backtrace.rb
CHANGED
data/lib/sentry/baggage.rb
CHANGED
@@ -8,17 +8,6 @@ module Sentry
|
|
8
8
|
SENTRY_PREFIX = 'sentry-'
|
9
9
|
SENTRY_PREFIX_REGEX = /^sentry-/.freeze
|
10
10
|
|
11
|
-
DSC_KEYS = %w(
|
12
|
-
trace_id
|
13
|
-
public_key
|
14
|
-
sample_rate
|
15
|
-
release
|
16
|
-
environment
|
17
|
-
transaction
|
18
|
-
user_id
|
19
|
-
user_segment
|
20
|
-
).freeze
|
21
|
-
|
22
11
|
# @return [Hash]
|
23
12
|
attr_reader :items
|
24
13
|
|
@@ -68,7 +57,7 @@ module Sentry
|
|
68
57
|
# hash to be used in the trace envelope header.
|
69
58
|
# @return [Hash]
|
70
59
|
def dynamic_sampling_context
|
71
|
-
@items
|
60
|
+
@items
|
72
61
|
end
|
73
62
|
|
74
63
|
# Serialize the Baggage object back to a string.
|
data/lib/sentry/client.rb
CHANGED
@@ -76,7 +76,10 @@ module Sentry
|
|
76
76
|
# @param hint [Hash] the hint data that'll be passed to `before_send` callback and the scope's event processors.
|
77
77
|
# @return [Event, nil]
|
78
78
|
def event_from_exception(exception, hint = {})
|
79
|
-
return unless @configuration.sending_allowed?
|
79
|
+
return unless @configuration.sending_allowed?
|
80
|
+
|
81
|
+
ignore_exclusions = hint.delete(:ignore_exclusions) { false }
|
82
|
+
return if !ignore_exclusions && !@configuration.exception_class_allowed?(exception)
|
80
83
|
|
81
84
|
integration_meta = Sentry.integrations[hint[:integration]]
|
82
85
|
|
data/lib/sentry/configuration.rb
CHANGED
@@ -227,6 +227,11 @@ module Sentry
|
|
227
227
|
# @return [Proc]
|
228
228
|
attr_accessor :traces_sampler
|
229
229
|
|
230
|
+
# Easier way to use performance tracing
|
231
|
+
# If set to true, will set traces_sample_rate to 1.0
|
232
|
+
# @return [Boolean, nil]
|
233
|
+
attr_reader :enable_tracing
|
234
|
+
|
230
235
|
# Send diagnostic client reports about dropped events, true by default
|
231
236
|
# tries to attach to an existing envelope max once every 30s
|
232
237
|
# @return [Boolean]
|
@@ -240,6 +245,12 @@ module Sentry
|
|
240
245
|
# @return [Symbol]
|
241
246
|
attr_reader :instrumenter
|
242
247
|
|
248
|
+
# Take a float between 0.0 and 1.0 as the sample rate for capturing profiles.
|
249
|
+
# Note that this rate is relative to traces_sample_rate / traces_sampler,
|
250
|
+
# i.e. the profile is sampled by this rate after the transaction is sampled.
|
251
|
+
# @return [Float, nil]
|
252
|
+
attr_reader :profiles_sample_rate
|
253
|
+
|
243
254
|
# these are not config options
|
244
255
|
# @!visibility private
|
245
256
|
attr_reader :errors, :gem_specs
|
@@ -316,6 +327,7 @@ module Sentry
|
|
316
327
|
self.rack_env_whitelist = RACK_ENV_WHITELIST_DEFAULT
|
317
328
|
self.traces_sample_rate = nil
|
318
329
|
self.traces_sampler = nil
|
330
|
+
self.enable_tracing = nil
|
319
331
|
|
320
332
|
@transport = Transport::Configuration.new
|
321
333
|
@gem_specs = Hash[Gem::Specification.map { |spec| [spec.name, spec.version.to_s] }] if Gem::Specification.respond_to?(:map)
|
@@ -384,6 +396,16 @@ module Sentry
|
|
384
396
|
@instrumenter = INSTRUMENTERS.include?(instrumenter) ? instrumenter : :sentry
|
385
397
|
end
|
386
398
|
|
399
|
+
def enable_tracing=(enable_tracing)
|
400
|
+
@enable_tracing = enable_tracing
|
401
|
+
@traces_sample_rate ||= 1.0 if enable_tracing
|
402
|
+
end
|
403
|
+
|
404
|
+
def profiles_sample_rate=(profiles_sample_rate)
|
405
|
+
log_info("Please make sure to include the 'stackprof' gem in your Gemfile to use Profiling with Sentry.") unless defined?(StackProf)
|
406
|
+
@profiles_sample_rate = profiles_sample_rate
|
407
|
+
end
|
408
|
+
|
387
409
|
def sending_allowed?
|
388
410
|
@errors = []
|
389
411
|
|
@@ -414,7 +436,20 @@ module Sentry
|
|
414
436
|
end
|
415
437
|
|
416
438
|
def tracing_enabled?
|
417
|
-
!!((@traces_sample_rate &&
|
439
|
+
valid_sampler = !!((@traces_sample_rate &&
|
440
|
+
@traces_sample_rate >= 0.0 &&
|
441
|
+
@traces_sample_rate <= 1.0) ||
|
442
|
+
@traces_sampler)
|
443
|
+
|
444
|
+
(@enable_tracing != false) && valid_sampler && sending_allowed?
|
445
|
+
end
|
446
|
+
|
447
|
+
def profiling_enabled?
|
448
|
+
valid_sampler = !!(@profiles_sample_rate &&
|
449
|
+
@profiles_sample_rate >= 0.0 &&
|
450
|
+
@profiles_sample_rate <= 1.0)
|
451
|
+
|
452
|
+
tracing_enabled? && valid_sampler && sending_allowed?
|
418
453
|
end
|
419
454
|
|
420
455
|
# @return [String, nil]
|
data/lib/sentry/envelope.rb
CHANGED
data/lib/sentry/hub.rb
CHANGED
@@ -88,8 +88,10 @@ module Sentry
|
|
88
88
|
}
|
89
89
|
|
90
90
|
sampling_context.merge!(custom_sampling_context)
|
91
|
-
|
92
91
|
transaction.set_initial_sample_decision(sampling_context: sampling_context)
|
92
|
+
|
93
|
+
transaction.start_profiler!
|
94
|
+
|
93
95
|
transaction
|
94
96
|
end
|
95
97
|
|
@@ -122,6 +124,7 @@ module Sentry
|
|
122
124
|
|
123
125
|
options[:hint] ||= {}
|
124
126
|
options[:hint][:exception] = exception
|
127
|
+
|
125
128
|
event = current_client.event_from_exception(exception, options[:hint])
|
126
129
|
|
127
130
|
return unless event
|
data/lib/sentry/net/http.rb
CHANGED
@@ -0,0 +1,222 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'securerandom'
|
4
|
+
|
5
|
+
module Sentry
|
6
|
+
class Profiler
|
7
|
+
VERSION = '1'
|
8
|
+
PLATFORM = 'ruby'
|
9
|
+
# 101 Hz in microseconds
|
10
|
+
DEFAULT_INTERVAL = 1e6 / 101
|
11
|
+
MICRO_TO_NANO_SECONDS = 1e3
|
12
|
+
|
13
|
+
attr_reader :sampled, :started, :event_id
|
14
|
+
|
15
|
+
def initialize(configuration)
|
16
|
+
@event_id = SecureRandom.uuid.delete('-')
|
17
|
+
@started = false
|
18
|
+
@sampled = nil
|
19
|
+
|
20
|
+
@profiling_enabled = defined?(StackProf) && configuration.profiling_enabled?
|
21
|
+
@profiles_sample_rate = configuration.profiles_sample_rate
|
22
|
+
@project_root = configuration.project_root
|
23
|
+
@app_dirs_pattern = configuration.app_dirs_pattern || Backtrace::APP_DIRS_PATTERN
|
24
|
+
@in_app_pattern = Regexp.new("^(#{@project_root}/)?#{@app_dirs_pattern}")
|
25
|
+
end
|
26
|
+
|
27
|
+
def start
|
28
|
+
return unless @sampled
|
29
|
+
|
30
|
+
@started = StackProf.start(interval: DEFAULT_INTERVAL,
|
31
|
+
mode: :wall,
|
32
|
+
raw: true,
|
33
|
+
aggregate: false)
|
34
|
+
|
35
|
+
@started ? log('Started') : log('Not started since running elsewhere')
|
36
|
+
end
|
37
|
+
|
38
|
+
def stop
|
39
|
+
return unless @sampled
|
40
|
+
return unless @started
|
41
|
+
|
42
|
+
StackProf.stop
|
43
|
+
log('Stopped')
|
44
|
+
end
|
45
|
+
|
46
|
+
# Sets initial sampling decision of the profile.
|
47
|
+
# @return [void]
|
48
|
+
def set_initial_sample_decision(transaction_sampled)
|
49
|
+
unless @profiling_enabled
|
50
|
+
@sampled = false
|
51
|
+
return
|
52
|
+
end
|
53
|
+
|
54
|
+
unless transaction_sampled
|
55
|
+
@sampled = false
|
56
|
+
log('Discarding profile because transaction not sampled')
|
57
|
+
return
|
58
|
+
end
|
59
|
+
|
60
|
+
case @profiles_sample_rate
|
61
|
+
when 0.0
|
62
|
+
@sampled = false
|
63
|
+
log('Discarding profile because sample_rate is 0')
|
64
|
+
return
|
65
|
+
when 1.0
|
66
|
+
@sampled = true
|
67
|
+
return
|
68
|
+
else
|
69
|
+
@sampled = Random.rand < @profiles_sample_rate
|
70
|
+
end
|
71
|
+
|
72
|
+
log('Discarding profile due to sampling decision') unless @sampled
|
73
|
+
end
|
74
|
+
|
75
|
+
def to_hash
|
76
|
+
return {} unless @sampled
|
77
|
+
return {} unless @started
|
78
|
+
|
79
|
+
results = StackProf.results
|
80
|
+
return {} unless results
|
81
|
+
return {} if results.empty?
|
82
|
+
return {} if results[:samples] == 0
|
83
|
+
return {} unless results[:raw]
|
84
|
+
|
85
|
+
frame_map = {}
|
86
|
+
|
87
|
+
frames = results[:frames].to_enum.with_index.map do |frame, idx|
|
88
|
+
frame_id, frame_data = frame
|
89
|
+
|
90
|
+
# need to map over stackprof frame ids to ours
|
91
|
+
frame_map[frame_id] = idx
|
92
|
+
|
93
|
+
file_path = frame_data[:file]
|
94
|
+
in_app = in_app?(file_path)
|
95
|
+
filename = compute_filename(file_path, in_app)
|
96
|
+
function, mod = split_module(frame_data[:name])
|
97
|
+
|
98
|
+
frame_hash = {
|
99
|
+
abs_path: file_path,
|
100
|
+
function: function,
|
101
|
+
filename: filename,
|
102
|
+
in_app: in_app
|
103
|
+
}
|
104
|
+
|
105
|
+
frame_hash[:module] = mod if mod
|
106
|
+
frame_hash[:lineno] = frame_data[:line] if frame_data[:line]
|
107
|
+
|
108
|
+
frame_hash
|
109
|
+
end
|
110
|
+
|
111
|
+
idx = 0
|
112
|
+
stacks = []
|
113
|
+
num_seen = []
|
114
|
+
|
115
|
+
# extract stacks from raw
|
116
|
+
# raw is a single array of [.., len_stack, *stack_frames(len_stack), num_stack_seen , ..]
|
117
|
+
while (len = results[:raw][idx])
|
118
|
+
idx += 1
|
119
|
+
|
120
|
+
# our call graph is reversed
|
121
|
+
stack = results[:raw].slice(idx, len).map { |id| frame_map[id] }.compact.reverse
|
122
|
+
stacks << stack
|
123
|
+
|
124
|
+
num_seen << results[:raw][idx + len]
|
125
|
+
idx += len + 1
|
126
|
+
|
127
|
+
log('Unknown frame in stack') if stack.size != len
|
128
|
+
end
|
129
|
+
|
130
|
+
idx = 0
|
131
|
+
elapsed_since_start_ns = 0
|
132
|
+
samples = []
|
133
|
+
|
134
|
+
num_seen.each_with_index do |n, i|
|
135
|
+
n.times do
|
136
|
+
# stackprof deltas are in microseconds
|
137
|
+
delta = results[:raw_timestamp_deltas][idx]
|
138
|
+
elapsed_since_start_ns += (delta * MICRO_TO_NANO_SECONDS).to_i
|
139
|
+
idx += 1
|
140
|
+
|
141
|
+
# Not sure why but some deltas are very small like 0/1 values,
|
142
|
+
# they pollute our flamegraph so just ignore them for now.
|
143
|
+
# Open issue at https://github.com/tmm1/stackprof/issues/201
|
144
|
+
next if delta < 10
|
145
|
+
|
146
|
+
samples << {
|
147
|
+
stack_id: i,
|
148
|
+
# TODO-neel-profiler we need to patch rb_profile_frames and write our own C extension to enable threading info.
|
149
|
+
# Till then, on multi-threaded servers like puma, we will get frames from other active threads when the one
|
150
|
+
# we're profiling is idle/sleeping/waiting for IO etc.
|
151
|
+
# https://bugs.ruby-lang.org/issues/10602
|
152
|
+
thread_id: '0',
|
153
|
+
elapsed_since_start_ns: elapsed_since_start_ns.to_s
|
154
|
+
}
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
log('Some samples thrown away') if samples.size != results[:samples]
|
159
|
+
|
160
|
+
if samples.size <= 2
|
161
|
+
log('Not enough samples, discarding profiler')
|
162
|
+
return {}
|
163
|
+
end
|
164
|
+
|
165
|
+
profile = {
|
166
|
+
frames: frames,
|
167
|
+
stacks: stacks,
|
168
|
+
samples: samples
|
169
|
+
}
|
170
|
+
|
171
|
+
{
|
172
|
+
event_id: @event_id,
|
173
|
+
platform: PLATFORM,
|
174
|
+
version: VERSION,
|
175
|
+
profile: profile
|
176
|
+
}
|
177
|
+
end
|
178
|
+
|
179
|
+
private
|
180
|
+
|
181
|
+
def log(message)
|
182
|
+
Sentry.logger.debug(LOGGER_PROGNAME) { "[Profiler] #{message}" }
|
183
|
+
end
|
184
|
+
|
185
|
+
def in_app?(abs_path)
|
186
|
+
abs_path.match?(@in_app_pattern)
|
187
|
+
end
|
188
|
+
|
189
|
+
# copied from stacktrace.rb since I don't want to touch existing code
|
190
|
+
# TODO-neel-profiler try to fetch this from stackprof once we patch
|
191
|
+
# the native extension
|
192
|
+
def compute_filename(abs_path, in_app)
|
193
|
+
return nil if abs_path.nil?
|
194
|
+
|
195
|
+
under_project_root = @project_root && abs_path.start_with?(@project_root)
|
196
|
+
|
197
|
+
prefix =
|
198
|
+
if under_project_root && in_app
|
199
|
+
@project_root
|
200
|
+
else
|
201
|
+
longest_load_path = $LOAD_PATH.select { |path| abs_path.start_with?(path.to_s) }.max_by(&:size)
|
202
|
+
|
203
|
+
if under_project_root
|
204
|
+
longest_load_path || @project_root
|
205
|
+
else
|
206
|
+
longest_load_path
|
207
|
+
end
|
208
|
+
end
|
209
|
+
|
210
|
+
prefix ? abs_path[prefix.to_s.chomp(File::SEPARATOR).length + 1..-1] : abs_path
|
211
|
+
end
|
212
|
+
|
213
|
+
def split_module(name)
|
214
|
+
# last module plus class/instance method
|
215
|
+
i = name.rindex('::')
|
216
|
+
function = i ? name[(i + 2)..-1] : name
|
217
|
+
mod = i ? name[0...i] : nil
|
218
|
+
|
219
|
+
[function, mod]
|
220
|
+
end
|
221
|
+
end
|
222
|
+
end
|
data/lib/sentry/puma.rb
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Sentry
|
4
|
+
module Puma
|
5
|
+
module Server
|
6
|
+
def lowlevel_error(e, env, status=500)
|
7
|
+
result = super
|
8
|
+
|
9
|
+
begin
|
10
|
+
Sentry.capture_exception(e) do |scope|
|
11
|
+
scope.set_rack_env(env)
|
12
|
+
end
|
13
|
+
rescue
|
14
|
+
# if anything happens, we don't want to break the app
|
15
|
+
end
|
16
|
+
|
17
|
+
result
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
if defined?(Puma::Server)
|
24
|
+
Sentry.register_patch(Sentry::Puma::Server, Puma::Server)
|
25
|
+
end
|
data/lib/sentry/redis.rb
CHANGED
@@ -95,12 +95,7 @@ end
|
|
95
95
|
|
96
96
|
if defined?(::Redis::Client)
|
97
97
|
if Gem::Version.new(::Redis::VERSION) < Gem::Version.new("5.0")
|
98
|
-
Sentry.register_patch
|
99
|
-
patch = Sentry::Redis::OldClientPatch
|
100
|
-
unless Redis::Client.ancestors.include?(patch)
|
101
|
-
Redis::Client.prepend(patch)
|
102
|
-
end
|
103
|
-
end
|
98
|
+
Sentry.register_patch(Sentry::Redis::OldClientPatch, ::Redis::Client)
|
104
99
|
elsif defined?(RedisClient)
|
105
100
|
RedisClient.register(Sentry::Redis::GlobalRedisInstrumentation)
|
106
101
|
end
|
data/lib/sentry/scope.rb
CHANGED
@@ -192,6 +192,10 @@ module Sentry
|
|
192
192
|
# @return [Hash]
|
193
193
|
def set_contexts(contexts_hash)
|
194
194
|
check_argument_type!(contexts_hash, Hash)
|
195
|
+
contexts_hash.values.each do |val|
|
196
|
+
check_argument_type!(val, Hash)
|
197
|
+
end
|
198
|
+
|
195
199
|
@contexts.merge!(contexts_hash) do |key, old, new|
|
196
200
|
old.merge(new)
|
197
201
|
end
|
@@ -305,7 +309,8 @@ module Sentry
|
|
305
309
|
name: uname[:sysname] || RbConfig::CONFIG["host_os"],
|
306
310
|
version: uname[:version],
|
307
311
|
build: uname[:release],
|
308
|
-
kernel_version: uname[:version]
|
312
|
+
kernel_version: uname[:version],
|
313
|
+
machine: uname[:machine]
|
309
314
|
}
|
310
315
|
end
|
311
316
|
end
|
data/lib/sentry/transaction.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require "sentry/baggage"
|
4
|
+
require "sentry/profiler"
|
4
5
|
|
5
6
|
module Sentry
|
6
7
|
class Transaction < Span
|
@@ -58,6 +59,10 @@ module Sentry
|
|
58
59
|
# @return [Hash]
|
59
60
|
attr_reader :contexts
|
60
61
|
|
62
|
+
# The Profiler instance for this transaction.
|
63
|
+
# @return [Profiler]
|
64
|
+
attr_reader :profiler
|
65
|
+
|
61
66
|
def initialize(
|
62
67
|
hub:,
|
63
68
|
name: nil,
|
@@ -83,6 +88,7 @@ module Sentry
|
|
83
88
|
@effective_sample_rate = nil
|
84
89
|
@contexts = {}
|
85
90
|
@measurements = {}
|
91
|
+
@profiler = Profiler.new(@configuration)
|
86
92
|
init_span_recorder
|
87
93
|
end
|
88
94
|
|
@@ -254,6 +260,8 @@ module Sentry
|
|
254
260
|
@name = UNLABELD_NAME
|
255
261
|
end
|
256
262
|
|
263
|
+
@profiler.stop
|
264
|
+
|
257
265
|
if @sampled
|
258
266
|
event = hub.current_client.event_from_transaction(self)
|
259
267
|
hub.capture_event(event)
|
@@ -288,6 +296,13 @@ module Sentry
|
|
288
296
|
@contexts[key] = value
|
289
297
|
end
|
290
298
|
|
299
|
+
# Start the profiler.
|
300
|
+
# @return [void]
|
301
|
+
def start_profiler!
|
302
|
+
profiler.set_initial_sample_decision(sampled)
|
303
|
+
profiler.start
|
304
|
+
end
|
305
|
+
|
291
306
|
protected
|
292
307
|
|
293
308
|
def init_span_recorder(limit = 1000)
|
@@ -17,6 +17,9 @@ module Sentry
|
|
17
17
|
# @return [Float, nil]
|
18
18
|
attr_reader :start_timestamp
|
19
19
|
|
20
|
+
# @return [Hash, nil]
|
21
|
+
attr_accessor :profile
|
22
|
+
|
20
23
|
def initialize(transaction:, **options)
|
21
24
|
super(**options)
|
22
25
|
|
@@ -32,6 +35,8 @@ module Sentry
|
|
32
35
|
|
33
36
|
finished_spans = transaction.span_recorder.spans.select { |span| span.timestamp && span != transaction }
|
34
37
|
self.spans = finished_spans.map(&:to_hash)
|
38
|
+
|
39
|
+
populate_profile(transaction)
|
35
40
|
end
|
36
41
|
|
37
42
|
# Sets the event's start_timestamp.
|
@@ -49,5 +54,30 @@ module Sentry
|
|
49
54
|
data[:measurements] = @measurements
|
50
55
|
data
|
51
56
|
end
|
57
|
+
|
58
|
+
private
|
59
|
+
|
60
|
+
def populate_profile(transaction)
|
61
|
+
profile_hash = transaction.profiler.to_hash
|
62
|
+
return if profile_hash.empty?
|
63
|
+
|
64
|
+
profile_hash.merge!(
|
65
|
+
environment: environment,
|
66
|
+
release: release,
|
67
|
+
timestamp: Time.at(start_timestamp).iso8601,
|
68
|
+
device: { architecture: Scope.os_context[:machine] },
|
69
|
+
os: { name: Scope.os_context[:name], version: Scope.os_context[:version] },
|
70
|
+
runtime: Scope.runtime_context,
|
71
|
+
transaction: {
|
72
|
+
id: event_id,
|
73
|
+
name: transaction.name,
|
74
|
+
trace_id: transaction.trace_id,
|
75
|
+
# TODO-neel-profiler stubbed for now, see thread_id note in profiler.rb
|
76
|
+
active_thead_id: '0'
|
77
|
+
}
|
78
|
+
)
|
79
|
+
|
80
|
+
self.profile = profile_hash
|
81
|
+
end
|
52
82
|
end
|
53
83
|
end
|
data/lib/sentry/transport.rb
CHANGED
@@ -154,6 +154,13 @@ module Sentry
|
|
154
154
|
event_payload
|
155
155
|
)
|
156
156
|
|
157
|
+
if event.is_a?(TransactionEvent) && event.profile
|
158
|
+
envelope.add_item(
|
159
|
+
{ type: 'profile', content_type: 'application/json' },
|
160
|
+
event.profile
|
161
|
+
)
|
162
|
+
end
|
163
|
+
|
157
164
|
client_report_headers, client_report_payload = fetch_pending_client_report
|
158
165
|
envelope.add_item(client_report_headers, client_report_payload) if client_report_headers
|
159
166
|
|
data/lib/sentry/version.rb
CHANGED
data/lib/sentry-ruby.rb
CHANGED
@@ -73,8 +73,18 @@ module Sentry
|
|
73
73
|
##### Patch Registration #####
|
74
74
|
|
75
75
|
# @!visibility private
|
76
|
-
def register_patch(&block)
|
77
|
-
|
76
|
+
def register_patch(patch = nil, target = nil, &block)
|
77
|
+
if patch && block
|
78
|
+
raise ArgumentError.new("Please provide either a patch and its target OR a block, but not both")
|
79
|
+
end
|
80
|
+
|
81
|
+
if block
|
82
|
+
registered_patches << block
|
83
|
+
else
|
84
|
+
registered_patches << proc do
|
85
|
+
target.send(:prepend, patch) unless target.ancestors.include?(patch)
|
86
|
+
end
|
87
|
+
end
|
78
88
|
end
|
79
89
|
|
80
90
|
# @!visibility private
|
@@ -509,3 +519,4 @@ end
|
|
509
519
|
# patches
|
510
520
|
require "sentry/net/http"
|
511
521
|
require "sentry/redis"
|
522
|
+
require "sentry/puma"
|
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.9.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Sentry Team
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-
|
11
|
+
date: 2023-04-19 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: concurrent-ruby
|
@@ -78,6 +78,8 @@ files:
|
|
78
78
|
- lib/sentry/linecache.rb
|
79
79
|
- lib/sentry/logger.rb
|
80
80
|
- lib/sentry/net/http.rb
|
81
|
+
- lib/sentry/profiler.rb
|
82
|
+
- lib/sentry/puma.rb
|
81
83
|
- lib/sentry/rack.rb
|
82
84
|
- lib/sentry/rack/capture_exceptions.rb
|
83
85
|
- lib/sentry/rake.rb
|