sentry-ruby-core 5.20.1 → 5.21.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 698becb3655b43c16988129ae4176690e6a8a2e67bee4004b8580d71348b843a
4
- data.tar.gz: 7a5e37ce71f2e3f483a3e193f2bfb0e283ae304ed4950068384806780614fc40
3
+ metadata.gz: 890c37ed857f90c7d0b5afbdb4fb07d223b8b3d1a820c1888d680bd653f28688
4
+ data.tar.gz: 348af55b8f56829cbf5fc5569ddc8308d4cd6ae508480de206443349e1221235
5
5
  SHA512:
6
- metadata.gz: 2531811bb4f288dd34ac72b7ad8fdf31cee30ba8bed63bb48a90c527d63ce3df531480ec5c9d69bf03c44ff81ddb9026936c45977f30c35a68ce473c1dc0b999
7
- data.tar.gz: 2cfa9516dcbbddac0b7b8930b400150d2f93b7ff779c93fb237cdd5bfb5e04434a1cae2e8cedd85cade5bfe536807da1d6b1b9d5838a53267426bc5694c62ade
6
+ metadata.gz: a5b8ad23c029b84648a38095ddd52b289983f9ad18096eeb3fb718a5b8106c6d2513476bc9e3c8ddcad35e66e3f1b20567edbce5f12b661448a811764f9ba4de
7
+ data.tar.gz: 8eacb9b0e326b529c743af650920b17e382b379032895d112a83689965a53fe42ae48eb9c070cd86085b1d7d5d9cc7300e0d9199b975edc25ee4aca1f093ecd6
data/Gemfile CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  source "https://rubygems.org"
2
4
  git_source(:github) { |name| "https://github.com/#{name}.git" }
3
5
 
@@ -14,6 +16,7 @@ gem "puma"
14
16
 
15
17
  gem "timecop"
16
18
  gem "stackprof" unless RUBY_PLATFORM == "java"
19
+ gem "vernier", platforms: :ruby if RUBY_VERSION >= "3.2.1"
17
20
 
18
21
  gem "graphql", ">= 2.2.6" if RUBY_VERSION.to_f >= 2.7
19
22
 
data/Rakefile CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "rake/clean"
2
4
  CLOBBER.include "pkg"
3
5
 
data/bin/console CHANGED
@@ -1,4 +1,5 @@
1
1
  #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
2
3
 
3
4
  require "bundler/setup"
4
5
  require "debug"
@@ -13,10 +13,10 @@ module Sentry
13
13
  ^ \s* (?: [a-zA-Z]: | uri:classloader: )? ([^:]+ | <.*>):
14
14
  (\d+)
15
15
  (?: :in\s('|`)([^']+)')?$
16
- /x.freeze
16
+ /x
17
17
 
18
18
  # org.jruby.runtime.callsite.CachingCallSite.call(CachingCallSite.java:170)
19
- JAVA_INPUT_FORMAT = /^(.+)\.([^\.]+)\(([^\:]+)\:(\d+)\)$/.freeze
19
+ JAVA_INPUT_FORMAT = /^(.+)\.([^\.]+)\(([^\:]+)\:(\d+)\)$/
20
20
 
21
21
  # The file portion of the line (such as app/models/user.rb)
22
22
  attr_reader :file
@@ -6,7 +6,7 @@ module Sentry
6
6
  # A {https://www.w3.org/TR/baggage W3C Baggage Header} implementation.
7
7
  class Baggage
8
8
  SENTRY_PREFIX = "sentry-"
9
- SENTRY_PREFIX_REGEX = /^sentry-/.freeze
9
+ SENTRY_PREFIX_REGEX = /^sentry-/
10
10
 
11
11
  # @return [Hash]
12
12
  attr_reader :items
@@ -3,8 +3,8 @@
3
3
  require "concurrent/utility/processor_counter"
4
4
 
5
5
  require "sentry/utils/exception_cause_chain"
6
- require 'sentry/utils/custom_inspection'
7
- require 'sentry/utils/env_helper'
6
+ require "sentry/utils/custom_inspection"
7
+ require "sentry/utils/env_helper"
8
8
  require "sentry/dsn"
9
9
  require "sentry/release_detector"
10
10
  require "sentry/transport/configuration"
@@ -291,6 +291,10 @@ module Sentry
291
291
  # @return [Symbol]
292
292
  attr_reader :instrumenter
293
293
 
294
+ # The profiler class
295
+ # @return [Class]
296
+ attr_reader :profiler_class
297
+
294
298
  # Take a float between 0.0 and 1.0 as the sample rate for capturing profiles.
295
299
  # Note that this rate is relative to traces_sample_rate / traces_sampler,
296
300
  # i.e. the profile is sampled by this rate after the transaction is sampled.
@@ -331,19 +335,19 @@ module Sentry
331
335
  ].freeze
332
336
 
333
337
  HEROKU_DYNO_METADATA_MESSAGE = "You are running on Heroku but haven't enabled Dyno Metadata. For Sentry's "\
334
- "release detection to work correctly, please run `heroku labs:enable runtime-dyno-metadata`".freeze
338
+ "release detection to work correctly, please run `heroku labs:enable runtime-dyno-metadata`"
335
339
 
336
- LOG_PREFIX = "** [Sentry] ".freeze
337
- MODULE_SEPARATOR = "::".freeze
340
+ LOG_PREFIX = "** [Sentry] "
341
+ MODULE_SEPARATOR = "::"
338
342
  SKIP_INSPECTION_ATTRIBUTES = [:@linecache, :@stacktrace_builder]
339
343
 
340
344
  INSTRUMENTERS = [:sentry, :otel]
341
345
 
342
- PROPAGATION_TARGETS_MATCH_ALL = /.*/.freeze
346
+ PROPAGATION_TARGETS_MATCH_ALL = /.*/
343
347
 
344
348
  DEFAULT_PATCHES = %i[redis puma http].freeze
345
349
 
346
- APP_DIRS_PATTERN = /(bin|exe|app|config|lib|test|spec)/.freeze
350
+ APP_DIRS_PATTERN = /(bin|exe|app|config|lib|test|spec)/
347
351
 
348
352
  class << self
349
353
  # Post initialization callbacks are called at the end of initialization process
@@ -387,9 +391,9 @@ module Sentry
387
391
  self.auto_session_tracking = true
388
392
  self.enable_backpressure_handling = false
389
393
  self.trusted_proxies = []
390
- self.dsn = ENV['SENTRY_DSN']
394
+ self.dsn = ENV["SENTRY_DSN"]
391
395
 
392
- spotlight_env = ENV['SENTRY_SPOTLIGHT']
396
+ spotlight_env = ENV["SENTRY_SPOTLIGHT"]
393
397
  spotlight_bool = Sentry::Utils::EnvHelper.env_to_bool(spotlight_env, strict: true)
394
398
  self.spotlight = spotlight_bool.nil? ? (spotlight_env || false) : spotlight_bool
395
399
  self.server_name = server_name_from_env
@@ -403,6 +407,8 @@ module Sentry
403
407
  self.traces_sampler = nil
404
408
  self.enable_tracing = nil
405
409
 
410
+ self.profiler_class = Sentry::Profiler
411
+
406
412
  @transport = Transport::Configuration.new
407
413
  @cron = Cron::Configuration.new
408
414
  @metrics = Metrics::Configuration.new
@@ -498,6 +504,18 @@ module Sentry
498
504
  @profiles_sample_rate = profiles_sample_rate
499
505
  end
500
506
 
507
+ def profiler_class=(profiler_class)
508
+ if profiler_class == Sentry::Vernier::Profiler
509
+ begin
510
+ require "vernier"
511
+ rescue LoadError
512
+ raise ArgumentError, "Please add the 'vernier' gem to your Gemfile to use the Vernier profiler with Sentry."
513
+ end
514
+ end
515
+
516
+ @profiler_class = profiler_class
517
+ end
518
+
501
519
  def sending_allowed?
502
520
  spotlight || sending_to_dsn_allowed?
503
521
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Sentry
2
4
  module Cron
3
5
  module MonitorCheckIns
@@ -0,0 +1,88 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sentry
4
+ # @api private
5
+ class Envelope::Item
6
+ STACKTRACE_FRAME_LIMIT_ON_OVERSIZED_PAYLOAD = 500
7
+ MAX_SERIALIZED_PAYLOAD_SIZE = 1024 * 1000
8
+
9
+ SIZE_LIMITS = Hash.new(MAX_SERIALIZED_PAYLOAD_SIZE).update(
10
+ "profile" => 1024 * 1000 * 50
11
+ )
12
+
13
+ attr_reader :size_limit, :headers, :payload, :type, :data_category
14
+
15
+ # rate limits and client reports use the data_category rather than envelope item type
16
+ def self.data_category(type)
17
+ case type
18
+ when "session", "attachment", "transaction", "profile", "span" then type
19
+ when "sessions" then "session"
20
+ when "check_in" then "monitor"
21
+ when "statsd", "metric_meta" then "metric_bucket"
22
+ when "event" then "error"
23
+ when "client_report" then "internal"
24
+ else "default"
25
+ end
26
+ end
27
+
28
+ def initialize(headers, payload)
29
+ @headers = headers
30
+ @payload = payload
31
+ @type = headers[:type] || "event"
32
+ @data_category = self.class.data_category(type)
33
+ @size_limit = SIZE_LIMITS[type]
34
+ end
35
+
36
+ def to_s
37
+ [JSON.generate(@headers), @payload.is_a?(String) ? @payload : JSON.generate(@payload)].join("\n")
38
+ end
39
+
40
+ def serialize
41
+ result = to_s
42
+
43
+ if result.bytesize > size_limit
44
+ remove_breadcrumbs!
45
+ result = to_s
46
+ end
47
+
48
+ if result.bytesize > size_limit
49
+ reduce_stacktrace!
50
+ result = to_s
51
+ end
52
+
53
+ [result, result.bytesize > size_limit]
54
+ end
55
+
56
+ def size_breakdown
57
+ payload.map do |key, value|
58
+ "#{key}: #{JSON.generate(value).bytesize}"
59
+ end.join(", ")
60
+ end
61
+
62
+ private
63
+
64
+ def remove_breadcrumbs!
65
+ if payload.key?(:breadcrumbs)
66
+ payload.delete(:breadcrumbs)
67
+ elsif payload.key?("breadcrumbs")
68
+ payload.delete("breadcrumbs")
69
+ end
70
+ end
71
+
72
+ def reduce_stacktrace!
73
+ if exceptions = payload.dig(:exception, :values) || payload.dig("exception", "values")
74
+ exceptions.each do |exception|
75
+ # in most cases there is only one exception (2 or 3 when have multiple causes), so we won't loop through this double condition much
76
+ traces = exception.dig(:stacktrace, :frames) || exception.dig("stacktrace", "frames")
77
+
78
+ if traces && traces.size > STACKTRACE_FRAME_LIMIT_ON_OVERSIZED_PAYLOAD
79
+ size_on_both_ends = STACKTRACE_FRAME_LIMIT_ON_OVERSIZED_PAYLOAD / 2
80
+ traces.replace(
81
+ traces[0..(size_on_both_ends - 1)] + traces[-size_on_both_ends..-1],
82
+ )
83
+ end
84
+ end
85
+ end
86
+ end
87
+ end
88
+ end
@@ -3,91 +3,6 @@
3
3
  module Sentry
4
4
  # @api private
5
5
  class Envelope
6
- class Item
7
- STACKTRACE_FRAME_LIMIT_ON_OVERSIZED_PAYLOAD = 500
8
- MAX_SERIALIZED_PAYLOAD_SIZE = 1024 * 1000
9
-
10
- attr_accessor :headers, :payload
11
-
12
- def initialize(headers, payload)
13
- @headers = headers
14
- @payload = payload
15
- end
16
-
17
- def type
18
- @headers[:type] || "event"
19
- end
20
-
21
- # rate limits and client reports use the data_category rather than envelope item type
22
- def self.data_category(type)
23
- case type
24
- when "session", "attachment", "transaction", "profile", "span" then type
25
- when "sessions" then "session"
26
- when "check_in" then "monitor"
27
- when "statsd", "metric_meta" then "metric_bucket"
28
- when "event" then "error"
29
- when "client_report" then "internal"
30
- else "default"
31
- end
32
- end
33
-
34
- def data_category
35
- self.class.data_category(type)
36
- end
37
-
38
- def to_s
39
- [JSON.generate(@headers), @payload.is_a?(String) ? @payload : JSON.generate(@payload)].join("\n")
40
- end
41
-
42
- def serialize
43
- result = to_s
44
-
45
- if result.bytesize > MAX_SERIALIZED_PAYLOAD_SIZE
46
- remove_breadcrumbs!
47
- result = to_s
48
- end
49
-
50
- if result.bytesize > MAX_SERIALIZED_PAYLOAD_SIZE
51
- reduce_stacktrace!
52
- result = to_s
53
- end
54
-
55
- [result, result.bytesize > MAX_SERIALIZED_PAYLOAD_SIZE]
56
- end
57
-
58
- def size_breakdown
59
- payload.map do |key, value|
60
- "#{key}: #{JSON.generate(value).bytesize}"
61
- end.join(", ")
62
- end
63
-
64
- private
65
-
66
- def remove_breadcrumbs!
67
- if payload.key?(:breadcrumbs)
68
- payload.delete(:breadcrumbs)
69
- elsif payload.key?("breadcrumbs")
70
- payload.delete("breadcrumbs")
71
- end
72
- end
73
-
74
- def reduce_stacktrace!
75
- if exceptions = payload.dig(:exception, :values) || payload.dig("exception", "values")
76
- exceptions.each do |exception|
77
- # in most cases there is only one exception (2 or 3 when have multiple causes), so we won't loop through this double condition much
78
- traces = exception.dig(:stacktrace, :frames) || exception.dig("stacktrace", "frames")
79
-
80
- if traces && traces.size > STACKTRACE_FRAME_LIMIT_ON_OVERSIZED_PAYLOAD
81
- size_on_both_ends = STACKTRACE_FRAME_LIMIT_ON_OVERSIZED_PAYLOAD / 2
82
- traces.replace(
83
- traces[0..(size_on_both_ends - 1)] + traces[-size_on_both_ends..-1],
84
- )
85
- end
86
- end
87
- end
88
- end
89
- end
90
-
91
6
  attr_accessor :headers, :items
92
7
 
93
8
  def initialize(headers = {})
@@ -108,3 +23,5 @@ module Sentry
108
23
  end
109
24
  end
110
25
  end
26
+
27
+ require_relative "envelope/item"
@@ -7,8 +7,8 @@ module Sentry
7
7
  include CustomInspection
8
8
 
9
9
  SKIP_INSPECTION_ATTRIBUTES = [:@stacktrace]
10
- PROBLEMATIC_LOCAL_VALUE_REPLACEMENT = "[ignored due to error]".freeze
11
- OMISSION_MARK = "...".freeze
10
+ PROBLEMATIC_LOCAL_VALUE_REPLACEMENT = "[ignored due to error]"
11
+ OMISSION_MARK = "..."
12
12
  MAX_LOCAL_BYTES = 1024
13
13
 
14
14
  attr_reader :type, :module, :thread_id, :stacktrace, :mechanism
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "securerandom"
4
+
5
+ module Sentry
6
+ class Profiler
7
+ module Helpers
8
+ def in_app?(abs_path)
9
+ abs_path.match?(@in_app_pattern)
10
+ end
11
+
12
+ # copied from stacktrace.rb since I don't want to touch existing code
13
+ # TODO-neel-profiler try to fetch this from stackprof once we patch
14
+ # the native extension
15
+ def compute_filename(abs_path, in_app)
16
+ return nil if abs_path.nil?
17
+
18
+ under_project_root = @project_root && abs_path.start_with?(@project_root)
19
+
20
+ prefix =
21
+ if under_project_root && in_app
22
+ @project_root
23
+ else
24
+ longest_load_path = $LOAD_PATH.select { |path| abs_path.start_with?(path.to_s) }.max_by(&:size)
25
+
26
+ if under_project_root
27
+ longest_load_path || @project_root
28
+ else
29
+ longest_load_path
30
+ end
31
+ end
32
+
33
+ prefix ? abs_path[prefix.to_s.chomp(File::SEPARATOR).length + 1..-1] : abs_path
34
+ end
35
+
36
+ def split_module(name)
37
+ # last module plus class/instance method
38
+ i = name.rindex("::")
39
+ function = i ? name[(i + 2)..-1] : name
40
+ mod = i ? name[0...i] : nil
41
+
42
+ [function, mod]
43
+ end
44
+ end
45
+ end
46
+ end
@@ -1,9 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "securerandom"
4
+ require_relative "profiler/helpers"
4
5
 
5
6
  module Sentry
6
7
  class Profiler
8
+ include Profiler::Helpers
9
+
7
10
  VERSION = "1"
8
11
  PLATFORM = "ruby"
9
12
  # 101 Hz in microseconds
@@ -44,6 +47,10 @@ module Sentry
44
47
  log("Stopped")
45
48
  end
46
49
 
50
+ def active_thread_id
51
+ "0"
52
+ end
53
+
47
54
  # Sets initial sampling decision of the profile.
48
55
  # @return [void]
49
56
  def set_initial_sample_decision(transaction_sampled)
@@ -188,43 +195,6 @@ module Sentry
188
195
  Sentry.logger.debug(LOGGER_PROGNAME) { "[Profiler] #{message}" }
189
196
  end
190
197
 
191
- def in_app?(abs_path)
192
- abs_path.match?(@in_app_pattern)
193
- end
194
-
195
- # copied from stacktrace.rb since I don't want to touch existing code
196
- # TODO-neel-profiler try to fetch this from stackprof once we patch
197
- # the native extension
198
- def compute_filename(abs_path, in_app)
199
- return nil if abs_path.nil?
200
-
201
- under_project_root = @project_root && abs_path.start_with?(@project_root)
202
-
203
- prefix =
204
- if under_project_root && in_app
205
- @project_root
206
- else
207
- longest_load_path = $LOAD_PATH.select { |path| abs_path.start_with?(path.to_s) }.max_by(&:size)
208
-
209
- if under_project_root
210
- longest_load_path || @project_root
211
- else
212
- longest_load_path
213
- end
214
- end
215
-
216
- prefix ? abs_path[prefix.to_s.chomp(File::SEPARATOR).length + 1..-1] : abs_path
217
- end
218
-
219
- def split_module(name)
220
- # last module plus class/instance method
221
- i = name.rindex("::")
222
- function = i ? name[(i + 2)..-1] : name
223
- mod = i ? name[0...i] : nil
224
-
225
- [function, mod]
226
- end
227
-
228
198
  def record_lost_event(reason)
229
199
  Sentry.get_current_client&.transport&.record_lost_event(reason, "profile")
230
200
  end
@@ -54,7 +54,7 @@ module Sentry
54
54
  end
55
55
 
56
56
  def transaction_op
57
- "http.server".freeze
57
+ "http.server"
58
58
  end
59
59
 
60
60
  def capture_exception(exception, env)
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Sentry
2
4
  module TestHelper
3
5
  DUMMY_DSN = "http://12345:67890@sentry.localdomain/sentry/42"
@@ -9,7 +9,7 @@ module Sentry
9
9
  # @deprecated Use Sentry::PropagationContext::SENTRY_TRACE_REGEXP instead.
10
10
  SENTRY_TRACE_REGEXP = PropagationContext::SENTRY_TRACE_REGEXP
11
11
 
12
- UNLABELD_NAME = "<unlabeled transaction>".freeze
12
+ UNLABELD_NAME = "<unlabeled transaction>"
13
13
  MESSAGE_PREFIX = "[Tracing]"
14
14
 
15
15
  # https://develop.sentry.dev/sdk/event-payloads/transaction/#transaction-annotations
@@ -85,7 +85,7 @@ module Sentry
85
85
  @effective_sample_rate = nil
86
86
  @contexts = {}
87
87
  @measurements = {}
88
- @profiler = Profiler.new(@configuration)
88
+ @profiler = @configuration.profiler_class.new(@configuration)
89
89
  init_span_recorder
90
90
  end
91
91
 
@@ -74,8 +74,7 @@ module Sentry
74
74
  id: event_id,
75
75
  name: transaction.name,
76
76
  trace_id: transaction.trace_id,
77
- # TODO-neel-profiler stubbed for now, see thread_id note in profiler.rb
78
- active_thead_id: "0"
77
+ active_thread_id: transaction.profiler.active_thread_id.to_s
79
78
  }
80
79
  )
81
80
 
@@ -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,125 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "securerandom"
4
+ require_relative "../profiler/helpers"
5
+ require_relative "output"
6
+
7
+ module Sentry
8
+ module Vernier
9
+ class Profiler
10
+ EMPTY_RESULT = {}.freeze
11
+
12
+ attr_reader :started, :event_id, :result
13
+
14
+ def initialize(configuration)
15
+ @event_id = SecureRandom.uuid.delete("-")
16
+
17
+ @started = false
18
+ @sampled = nil
19
+
20
+ @profiling_enabled = defined?(Vernier) && 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
24
+ @in_app_pattern = Regexp.new("^(#{@project_root}/)?#{@app_dirs_pattern}")
25
+ end
26
+
27
+ def set_initial_sample_decision(transaction_sampled)
28
+ unless @profiling_enabled
29
+ @sampled = false
30
+ return
31
+ end
32
+
33
+ unless transaction_sampled
34
+ @sampled = false
35
+ log("Discarding profile because transaction not sampled")
36
+ return
37
+ end
38
+
39
+ case @profiles_sample_rate
40
+ when 0.0
41
+ @sampled = false
42
+ log("Discarding profile because sample_rate is 0")
43
+ return
44
+ when 1.0
45
+ @sampled = true
46
+ return
47
+ else
48
+ @sampled = Random.rand < @profiles_sample_rate
49
+ end
50
+
51
+ log("Discarding profile due to sampling decision") unless @sampled
52
+ end
53
+
54
+ def start
55
+ return unless @sampled
56
+ return if @started
57
+
58
+ ::Vernier.start_profile
59
+ @started = true
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
+
79
+ log("Stopped")
80
+ end
81
+
82
+ def active_thread_id
83
+ Thread.current.object_id
84
+ end
85
+
86
+ def to_hash
87
+ return EMPTY_RESULT unless @started
88
+
89
+ unless @sampled
90
+ record_lost_event(:sample_rate)
91
+ return EMPTY_RESULT
92
+ end
93
+
94
+ { **profile_meta, profile: output.to_h }
95
+ end
96
+
97
+ private
98
+
99
+ def log(message)
100
+ Sentry.logger.debug(LOGGER_PROGNAME) { "[Profiler::Vernier] #{message}" }
101
+ end
102
+
103
+ def record_lost_event(reason)
104
+ Sentry.get_current_client&.transport&.record_lost_event(reason, "profile")
105
+ end
106
+
107
+ def profile_meta
108
+ {
109
+ event_id: @event_id,
110
+ version: "1",
111
+ platform: "ruby"
112
+ }
113
+ end
114
+
115
+ def output
116
+ @output ||= Output.new(
117
+ result,
118
+ project_root: @project_root,
119
+ app_dirs_pattern: @app_dirs_pattern,
120
+ in_app_pattern: @in_app_pattern
121
+ )
122
+ end
123
+ end
124
+ end
125
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Sentry
4
- VERSION = "5.20.1"
4
+ VERSION = "5.21.0"
5
5
  end
data/lib/sentry-ruby.rb CHANGED
@@ -25,6 +25,7 @@ require "sentry/session_flusher"
25
25
  require "sentry/backpressure_monitor"
26
26
  require "sentry/cron/monitor_check_ins"
27
27
  require "sentry/metrics"
28
+ require "sentry/vernier/profiler"
28
29
 
29
30
  [
30
31
  "sentry/rake",
@@ -41,11 +42,11 @@ module Sentry
41
42
 
42
43
  CAPTURED_SIGNATURE = :@__sentry_captured
43
44
 
44
- LOGGER_PROGNAME = "sentry".freeze
45
+ LOGGER_PROGNAME = "sentry"
45
46
 
46
- SENTRY_TRACE_HEADER_NAME = "sentry-trace".freeze
47
+ SENTRY_TRACE_HEADER_NAME = "sentry-trace"
47
48
 
48
- BAGGAGE_HEADER_NAME = "baggage".freeze
49
+ BAGGAGE_HEADER_NAME = "baggage"
49
50
 
50
51
  THREAD_LOCAL = :sentry_hub
51
52
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require_relative "lib/sentry/version"
2
4
 
3
5
  Gem::Specification.new do |spec|
data/sentry-ruby.gemspec CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require_relative "lib/sentry/version"
2
4
 
3
5
  Gem::Specification.new do |spec|
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sentry-ruby-core
3
3
  version: !ruby/object:Gem::Version
4
- version: 5.20.1
4
+ version: 5.21.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: 2024-09-27 00:00:00.000000000 Z
11
+ date: 2024-10-07 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: sentry-ruby
@@ -16,14 +16,14 @@ dependencies:
16
16
  requirements:
17
17
  - - '='
18
18
  - !ruby/object:Gem::Version
19
- version: 5.20.1
19
+ version: 5.21.0
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - '='
25
25
  - !ruby/object:Gem::Version
26
- version: 5.20.1
26
+ version: 5.21.0
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: concurrent-ruby
29
29
  requirement: !ruby/object:Gem::Requirement
@@ -77,6 +77,7 @@ files:
77
77
  - lib/sentry/cron/monitor_schedule.rb
78
78
  - lib/sentry/dsn.rb
79
79
  - lib/sentry/envelope.rb
80
+ - lib/sentry/envelope/item.rb
80
81
  - lib/sentry/error_event.rb
81
82
  - lib/sentry/event.rb
82
83
  - lib/sentry/exceptions.rb
@@ -106,6 +107,7 @@ files:
106
107
  - lib/sentry/metrics/timing.rb
107
108
  - lib/sentry/net/http.rb
108
109
  - lib/sentry/profiler.rb
110
+ - lib/sentry/profiler/helpers.rb
109
111
  - lib/sentry/propagation_context.rb
110
112
  - lib/sentry/puma.rb
111
113
  - lib/sentry/rack.rb
@@ -135,6 +137,8 @@ files:
135
137
  - lib/sentry/utils/logging_helper.rb
136
138
  - lib/sentry/utils/real_ip.rb
137
139
  - lib/sentry/utils/request_id.rb
140
+ - lib/sentry/vernier/output.rb
141
+ - lib/sentry/vernier/profiler.rb
138
142
  - lib/sentry/version.rb
139
143
  - sentry-ruby-core.gemspec
140
144
  - sentry-ruby.gemspec