sentry-ruby 5.19.0 → 5.21.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (54) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +3 -0
  3. data/Rakefile +2 -0
  4. data/bin/console +1 -0
  5. data/lib/sentry/attachment.rb +3 -5
  6. data/lib/sentry/backtrace.rb +3 -5
  7. data/lib/sentry/baggage.rb +7 -7
  8. data/lib/sentry/breadcrumb/sentry_logger.rb +6 -6
  9. data/lib/sentry/check_in_event.rb +4 -4
  10. data/lib/sentry/client.rb +9 -9
  11. data/lib/sentry/configuration.rb +52 -19
  12. data/lib/sentry/core_ext/object/deep_dup.rb +1 -1
  13. data/lib/sentry/cron/monitor_check_ins.rb +3 -1
  14. data/lib/sentry/cron/monitor_config.rb +1 -1
  15. data/lib/sentry/dsn.rb +3 -3
  16. data/lib/sentry/envelope/item.rb +88 -0
  17. data/lib/sentry/envelope.rb +2 -85
  18. data/lib/sentry/event.rb +7 -7
  19. data/lib/sentry/graphql.rb +1 -1
  20. data/lib/sentry/hub.rb +8 -1
  21. data/lib/sentry/interfaces/mechanism.rb +1 -1
  22. data/lib/sentry/interfaces/request.rb +5 -5
  23. data/lib/sentry/interfaces/single_exception.rb +3 -3
  24. data/lib/sentry/interfaces/stacktrace.rb +3 -1
  25. data/lib/sentry/interfaces/stacktrace_builder.rb +15 -2
  26. data/lib/sentry/logger.rb +1 -1
  27. data/lib/sentry/metrics/aggregator.rb +12 -12
  28. data/lib/sentry/metrics/set_metric.rb +2 -2
  29. data/lib/sentry/metrics.rb +15 -15
  30. data/lib/sentry/net/http.rb +1 -1
  31. data/lib/sentry/profiler/helpers.rb +46 -0
  32. data/lib/sentry/profiler.rb +25 -56
  33. data/lib/sentry/propagation_context.rb +1 -1
  34. data/lib/sentry/rack/capture_exceptions.rb +2 -2
  35. data/lib/sentry/rack.rb +2 -2
  36. data/lib/sentry/rake.rb +2 -2
  37. data/lib/sentry/release_detector.rb +4 -4
  38. data/lib/sentry/scope.rb +1 -0
  39. data/lib/sentry/session_flusher.rb +1 -1
  40. data/lib/sentry/span.rb +6 -0
  41. data/lib/sentry/test_helper.rb +3 -1
  42. data/lib/sentry/transaction.rb +4 -4
  43. data/lib/sentry/transaction_event.rb +1 -2
  44. data/lib/sentry/transport/http_transport.rb +12 -12
  45. data/lib/sentry/transport.rb +4 -4
  46. data/lib/sentry/utils/env_helper.rb +21 -0
  47. data/lib/sentry/utils/real_ip.rb +1 -1
  48. data/lib/sentry/vernier/output.rb +89 -0
  49. data/lib/sentry/vernier/profiler.rb +125 -0
  50. data/lib/sentry/version.rb +1 -1
  51. data/lib/sentry-ruby.rb +5 -4
  52. data/sentry-ruby-core.gemspec +3 -1
  53. data/sentry-ruby.gemspec +3 -1
  54. metadata +13 -8
data/lib/sentry/event.rb CHANGED
@@ -1,12 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'socket'
4
- require 'securerandom'
5
- require 'sentry/interface'
6
- require 'sentry/backtrace'
7
- require 'sentry/utils/real_ip'
8
- require 'sentry/utils/request_id'
9
- require 'sentry/utils/custom_inspection'
3
+ require "socket"
4
+ require "securerandom"
5
+ require "sentry/interface"
6
+ require "sentry/backtrace"
7
+ require "sentry/utils/real_ip"
8
+ require "sentry/utils/request_id"
9
+ require "sentry/utils/custom_inspection"
10
10
 
11
11
  module Sentry
12
12
  # This is an abstract class that defines the shared attributes of an event.
@@ -4,6 +4,6 @@ Sentry.register_patch(:graphql) do |config|
4
4
  if defined?(::GraphQL::Schema) && defined?(::GraphQL::Tracing::SentryTrace) && ::GraphQL::Schema.respond_to?(:trace_with)
5
5
  ::GraphQL::Schema.trace_with(::GraphQL::Tracing::SentryTrace, set_transaction_name: true)
6
6
  else
7
- config.logger.warn(Sentry::LOGGER_PROGNAME) { 'You tried to enable the GraphQL integration but no GraphQL gem was detected. Make sure you have the `graphql` gem (>= 2.2.6) in your Gemfile.' }
7
+ config.logger.warn(Sentry::LOGGER_PROGNAME) { "You tried to enable the GraphQL integration but no GraphQL gem was detected. Make sure you have the `graphql` gem (>= 2.2.6) in your Gemfile." }
8
8
  end
9
9
  end
data/lib/sentry/hub.rb CHANGED
@@ -73,7 +73,13 @@ module Sentry
73
73
  end
74
74
 
75
75
  def pop_scope
76
- @stack.pop
76
+ if @stack.size > 1
77
+ @stack.pop
78
+ else
79
+ # We never want to enter a situation where we have no scope and no client
80
+ client = current_client
81
+ @stack = [Layer.new(client, Scope.new)]
82
+ end
77
83
  end
78
84
 
79
85
  def start_transaction(transaction: nil, custom_sampling_context: {}, instrumenter: :sentry, **options)
@@ -214,6 +220,7 @@ module Sentry
214
220
  end
215
221
 
216
222
  def add_breadcrumb(breadcrumb, hint: {})
223
+ return unless current_client
217
224
  return unless configuration.enabled_in_current_env?
218
225
 
219
226
  if before_breadcrumb = current_client.configuration.before_breadcrumb
@@ -12,7 +12,7 @@ module Sentry
12
12
  # @return [Boolean]
13
13
  attr_accessor :handled
14
14
 
15
- def initialize(type: 'generic', handled: true)
15
+ def initialize(type: "generic", handled: true)
16
16
  @type = type
17
17
  @handled = handled
18
18
  end
@@ -59,7 +59,7 @@ module Sentry
59
59
  self.query_string = request.query_string
60
60
  end
61
61
 
62
- self.url = request.scheme && request.url.split('?').first
62
+ self.url = request.scheme && request.url.split("?").first
63
63
  self.method = request.request_method
64
64
 
65
65
  self.headers = filter_and_format_headers(env, send_default_pii)
@@ -85,14 +85,14 @@ module Sentry
85
85
  env.each_with_object({}) do |(key, value), memo|
86
86
  begin
87
87
  key = key.to_s # rack env can contain symbols
88
- next memo['X-Request-Id'] ||= Utils::RequestId.read_from(env) if Utils::RequestId::REQUEST_ID_HEADERS.include?(key)
88
+ next memo["X-Request-Id"] ||= Utils::RequestId.read_from(env) if Utils::RequestId::REQUEST_ID_HEADERS.include?(key)
89
89
  next if is_server_protocol?(key, value, env["SERVER_PROTOCOL"])
90
90
  next if is_skippable_header?(key)
91
91
  next if key == "HTTP_AUTHORIZATION" && !send_default_pii
92
92
 
93
93
  # Rack stores headers as HTTP_WHAT_EVER, we need What-Ever
94
94
  key = key.sub(/^HTTP_/, "")
95
- key = key.split('_').map(&:capitalize).join('-')
95
+ key = key.split("_").map(&:capitalize).join("-")
96
96
 
97
97
  memo[key] = Utils::EncodingHelper.encode_to_utf_8(value.to_s)
98
98
  rescue StandardError => e
@@ -108,7 +108,7 @@ module Sentry
108
108
  def is_skippable_header?(key)
109
109
  key.upcase != key || # lower-case envs aren't real http headers
110
110
  key == "HTTP_COOKIE" || # Cookies don't go here, they go somewhere else
111
- !(key.start_with?('HTTP_') || CONTENT_HEADERS.include?(key))
111
+ !(key.start_with?("HTTP_") || CONTENT_HEADERS.include?(key))
112
112
  end
113
113
 
114
114
  # In versions < 3, Rack adds in an incorrect HTTP_VERSION key, which causes downstream
@@ -120,7 +120,7 @@ module Sentry
120
120
  rack_version = Gem::Version.new(::Rack.release)
121
121
  return false if rack_version >= Gem::Version.new("3.0")
122
122
 
123
- key == 'HTTP_VERSION' && value == protocol_version
123
+ key == "HTTP_VERSION" && value == protocol_version
124
124
  end
125
125
 
126
126
  def filter_and_format_env(env, rack_env_whitelist)
@@ -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
@@ -26,7 +26,7 @@ module Sentry
26
26
 
27
27
  @value = Utils::EncodingHelper.encode_to_utf_8(exception_message.byteslice(0..Event::MAX_MESSAGE_SIZE_IN_BYTES))
28
28
 
29
- @module = exception.class.to_s.split('::')[0...-1].join('::')
29
+ @module = exception.class.to_s.split("::")[0...-1].join("::")
30
30
  @thread_id = Thread.current.object_id
31
31
  @stacktrace = stacktrace
32
32
  @mechanism = mechanism
@@ -27,8 +27,9 @@ module Sentry
27
27
  attr_accessor :abs_path, :context_line, :function, :in_app, :filename,
28
28
  :lineno, :module, :pre_context, :post_context, :vars
29
29
 
30
- def initialize(project_root, line)
30
+ def initialize(project_root, line, strip_backtrace_load_path = true)
31
31
  @project_root = project_root
32
+ @strip_backtrace_load_path = strip_backtrace_load_path
32
33
 
33
34
  @abs_path = line.file
34
35
  @function = line.method if line.method
@@ -44,6 +45,7 @@ module Sentry
44
45
 
45
46
  def compute_filename
46
47
  return if abs_path.nil?
48
+ return abs_path unless @strip_backtrace_load_path
47
49
 
48
50
  prefix =
49
51
  if under_project_root? && in_app
@@ -17,22 +17,35 @@ module Sentry
17
17
  # @return [Proc, nil]
18
18
  attr_reader :backtrace_cleanup_callback
19
19
 
20
+ # @return [Boolean]
21
+ attr_reader :strip_backtrace_load_path
22
+
20
23
  # @param project_root [String]
21
24
  # @param app_dirs_pattern [Regexp, nil]
22
25
  # @param linecache [LineCache]
23
26
  # @param context_lines [Integer, nil]
24
27
  # @param backtrace_cleanup_callback [Proc, nil]
28
+ # @param strip_backtrace_load_path [Boolean]
25
29
  # @see Configuration#project_root
26
30
  # @see Configuration#app_dirs_pattern
27
31
  # @see Configuration#linecache
28
32
  # @see Configuration#context_lines
29
33
  # @see Configuration#backtrace_cleanup_callback
30
- def initialize(project_root:, app_dirs_pattern:, linecache:, context_lines:, backtrace_cleanup_callback: nil)
34
+ # @see Configuration#strip_backtrace_load_path
35
+ def initialize(
36
+ project_root:,
37
+ app_dirs_pattern:,
38
+ linecache:,
39
+ context_lines:,
40
+ backtrace_cleanup_callback: nil,
41
+ strip_backtrace_load_path: true
42
+ )
31
43
  @project_root = project_root
32
44
  @app_dirs_pattern = app_dirs_pattern
33
45
  @linecache = linecache
34
46
  @context_lines = context_lines
35
47
  @backtrace_cleanup_callback = backtrace_cleanup_callback
48
+ @strip_backtrace_load_path = strip_backtrace_load_path
36
49
  end
37
50
 
38
51
  # Generates a StacktraceInterface with the given backtrace.
@@ -73,7 +86,7 @@ module Sentry
73
86
  private
74
87
 
75
88
  def convert_parsed_line_into_frame(line)
76
- frame = StacktraceInterface::Frame.new(project_root, line)
89
+ frame = StacktraceInterface::Frame.new(project_root, line, strip_backtrace_load_path)
77
90
  frame.set_context(linecache, context_lines) if context_lines
78
91
  frame
79
92
  end
data/lib/sentry/logger.rb CHANGED
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'logger'
3
+ require "logger"
4
4
 
5
5
  module Sentry
6
6
  class Logger < ::Logger
@@ -41,8 +41,8 @@ module Sentry
41
41
  @stacktrace_builder = configuration.stacktrace_builder
42
42
 
43
43
  @default_tags = {}
44
- @default_tags['release'] = configuration.release if configuration.release
45
- @default_tags['environment'] = configuration.environment if configuration.environment
44
+ @default_tags["release"] = configuration.release if configuration.release
45
+ @default_tags["environment"] = configuration.environment if configuration.environment
46
46
 
47
47
  @mutex = Mutex.new
48
48
 
@@ -59,7 +59,7 @@ module Sentry
59
59
  def add(type,
60
60
  key,
61
61
  value,
62
- unit: 'none',
62
+ unit: "none",
63
63
  tags: {},
64
64
  timestamp: nil,
65
65
  stacklevel: nil)
@@ -98,7 +98,7 @@ module Sentry
98
98
  unless flushable_buckets.empty?
99
99
  payload = serialize_buckets(flushable_buckets)
100
100
  envelope.add_item(
101
- { type: 'statsd', length: payload.bytesize },
101
+ { type: "statsd", length: payload.bytesize },
102
102
  payload
103
103
  )
104
104
  end
@@ -107,7 +107,7 @@ module Sentry
107
107
  code_locations.each do |timestamp, locations|
108
108
  payload = serialize_locations(timestamp, locations)
109
109
  envelope.add_item(
110
- { type: 'metric_meta', content_type: 'application/json' },
110
+ { type: "metric_meta", content_type: "application/json" },
111
111
  payload
112
112
  )
113
113
  end
@@ -161,8 +161,8 @@ module Sentry
161
161
  buckets.map do |timestamp, timestamp_buckets|
162
162
  timestamp_buckets.map do |metric_key, metric|
163
163
  type, key, unit, tags = metric_key
164
- values = metric.serialize.join(':')
165
- sanitized_tags = tags.map { |k, v| "#{sanitize_tag_key(k)}:#{sanitize_tag_value(v)}" }.join(',')
164
+ values = metric.serialize.join(":")
165
+ sanitized_tags = tags.map { |k, v| "#{sanitize_tag_key(k)}:#{sanitize_tag_value(v)}" }.join(",")
166
166
 
167
167
  "#{sanitize_key(key)}@#{sanitize_unit(unit)}:#{values}|#{type}|\##{sanitized_tags}|T#{timestamp}"
168
168
  end
@@ -175,22 +175,22 @@ module Sentry
175
175
  mri = "#{type}:#{sanitize_key(key)}@#{sanitize_unit(unit)}"
176
176
 
177
177
  # note this needs to be an array but it really doesn't serve a purpose right now
178
- [mri, [location.merge(type: 'location')]]
178
+ [mri, [location.merge(type: "location")]]
179
179
  end.to_h
180
180
 
181
181
  { timestamp: timestamp, mapping: mapping }
182
182
  end
183
183
 
184
184
  def sanitize_key(key)
185
- key.gsub(KEY_SANITIZATION_REGEX, '_')
185
+ key.gsub(KEY_SANITIZATION_REGEX, "_")
186
186
  end
187
187
 
188
188
  def sanitize_unit(unit)
189
- unit.gsub(UNIT_SANITIZATION_REGEX, '')
189
+ unit.gsub(UNIT_SANITIZATION_REGEX, "")
190
190
  end
191
191
 
192
192
  def sanitize_tag_key(key)
193
- key.gsub(TAG_KEY_SANITIZATION_REGEX, '')
193
+ key.gsub(TAG_KEY_SANITIZATION_REGEX, "")
194
194
  end
195
195
 
196
196
  def sanitize_tag_value(value)
@@ -209,7 +209,7 @@ module Sentry
209
209
  updated_tags = @default_tags.merge(tags)
210
210
 
211
211
  transaction_name = get_transaction_name
212
- updated_tags['transaction'] = transaction_name if transaction_name
212
+ updated_tags["transaction"] = transaction_name if transaction_name
213
213
 
214
214
  updated_tags
215
215
  end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'set'
4
- require 'zlib'
3
+ require "set"
4
+ require "zlib"
5
5
 
6
6
  module Sentry
7
7
  module Metrics
@@ -1,12 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'sentry/metrics/metric'
4
- require 'sentry/metrics/counter_metric'
5
- require 'sentry/metrics/distribution_metric'
6
- require 'sentry/metrics/gauge_metric'
7
- require 'sentry/metrics/set_metric'
8
- require 'sentry/metrics/timing'
9
- require 'sentry/metrics/aggregator'
3
+ require "sentry/metrics/metric"
4
+ require "sentry/metrics/counter_metric"
5
+ require "sentry/metrics/distribution_metric"
6
+ require "sentry/metrics/gauge_metric"
7
+ require "sentry/metrics/set_metric"
8
+ require "sentry/metrics/timing"
9
+ require "sentry/metrics/aggregator"
10
10
 
11
11
  module Sentry
12
12
  module Metrics
@@ -14,32 +14,32 @@ module Sentry
14
14
  INFORMATION_UNITS = %w[bit byte kilobyte kibibyte megabyte mebibyte gigabyte gibibyte terabyte tebibyte petabyte pebibyte exabyte exbibyte]
15
15
  FRACTIONAL_UNITS = %w[ratio percent]
16
16
 
17
- OP_NAME = 'metric.timing'
18
- SPAN_ORIGIN = 'auto.metric.timing'
17
+ OP_NAME = "metric.timing"
18
+ SPAN_ORIGIN = "auto.metric.timing"
19
19
 
20
20
  class << self
21
- def increment(key, value = 1.0, unit: 'none', tags: {}, timestamp: nil)
21
+ def increment(key, value = 1.0, unit: "none", tags: {}, timestamp: nil)
22
22
  Sentry.metrics_aggregator&.add(:c, key, value, unit: unit, tags: tags, timestamp: timestamp)
23
23
  end
24
24
 
25
- def distribution(key, value, unit: 'none', tags: {}, timestamp: nil)
25
+ def distribution(key, value, unit: "none", tags: {}, timestamp: nil)
26
26
  Sentry.metrics_aggregator&.add(:d, key, value, unit: unit, tags: tags, timestamp: timestamp)
27
27
  end
28
28
 
29
- def set(key, value, unit: 'none', tags: {}, timestamp: nil)
29
+ def set(key, value, unit: "none", tags: {}, timestamp: nil)
30
30
  Sentry.metrics_aggregator&.add(:s, key, value, unit: unit, tags: tags, timestamp: timestamp)
31
31
  end
32
32
 
33
- def gauge(key, value, unit: 'none', tags: {}, timestamp: nil)
33
+ def gauge(key, value, unit: "none", tags: {}, timestamp: nil)
34
34
  Sentry.metrics_aggregator&.add(:g, key, value, unit: unit, tags: tags, timestamp: timestamp)
35
35
  end
36
36
 
37
- def timing(key, unit: 'second', tags: {}, timestamp: nil, &block)
37
+ def timing(key, unit: "second", tags: {}, timestamp: nil, &block)
38
38
  return unless block_given?
39
39
  return yield unless DURATION_UNITS.include?(unit)
40
40
 
41
41
  result, value = Sentry.with_child_span(op: OP_NAME, description: key, origin: SPAN_ORIGIN) do |span|
42
- tags.each { |k, v| span.set_tag(k, v.is_a?(Array) ? v.join(', ') : v.to_s) } if span
42
+ tags.each { |k, v| span.set_tag(k, v.is_a?(Array) ? v.join(", ") : v.to_s) } if span
43
43
 
44
44
  start = Timing.send(unit.to_sym)
45
45
  result = yield
@@ -66,7 +66,7 @@ module Sentry
66
66
  # IPv6 url could look like '::1/path', and that won't parse without
67
67
  # wrapping it in square brackets.
68
68
  hostname = address =~ Resolv::IPv6::Regex ? "[#{address}]" : address
69
- uri = req.uri || URI.parse("#{use_ssl? ? 'https' : 'http'}://#{hostname}#{req.path}")
69
+ uri = req.uri || URI.parse(URI::DEFAULT_PARSER.escape("#{use_ssl? ? 'https' : 'http'}://#{hostname}#{req.path}"))
70
70
  url = "#{uri.scheme}://#{uri.host}#{uri.path}" rescue uri.to_s
71
71
 
72
72
  result = { method: req.method, url: url }
@@ -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,11 +1,14 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'securerandom'
3
+ require "securerandom"
4
+ require_relative "profiler/helpers"
4
5
 
5
6
  module Sentry
6
7
  class Profiler
7
- VERSION = '1'
8
- PLATFORM = 'ruby'
8
+ include Profiler::Helpers
9
+
10
+ VERSION = "1"
11
+ PLATFORM = "ruby"
9
12
  # 101 Hz in microseconds
10
13
  DEFAULT_INTERVAL = 1e6 / 101
11
14
  MICRO_TO_NANO_SECONDS = 1e3
@@ -14,14 +17,14 @@ module Sentry
14
17
  attr_reader :sampled, :started, :event_id
15
18
 
16
19
  def initialize(configuration)
17
- @event_id = SecureRandom.uuid.delete('-')
20
+ @event_id = SecureRandom.uuid.delete("-")
18
21
  @started = false
19
22
  @sampled = nil
20
23
 
21
24
  @profiling_enabled = defined?(StackProf) && configuration.profiling_enabled?
22
25
  @profiles_sample_rate = configuration.profiles_sample_rate
23
26
  @project_root = configuration.project_root
24
- @app_dirs_pattern = configuration.app_dirs_pattern || Backtrace::APP_DIRS_PATTERN
27
+ @app_dirs_pattern = configuration.app_dirs_pattern
25
28
  @in_app_pattern = Regexp.new("^(#{@project_root}/)?#{@app_dirs_pattern}")
26
29
  end
27
30
 
@@ -33,7 +36,7 @@ module Sentry
33
36
  raw: true,
34
37
  aggregate: false)
35
38
 
36
- @started ? log('Started') : log('Not started since running elsewhere')
39
+ @started ? log("Started") : log("Not started since running elsewhere")
37
40
  end
38
41
 
39
42
  def stop
@@ -41,7 +44,11 @@ module Sentry
41
44
  return unless @started
42
45
 
43
46
  StackProf.stop
44
- log('Stopped')
47
+ log("Stopped")
48
+ end
49
+
50
+ def active_thread_id
51
+ "0"
45
52
  end
46
53
 
47
54
  # Sets initial sampling decision of the profile.
@@ -54,14 +61,14 @@ module Sentry
54
61
 
55
62
  unless transaction_sampled
56
63
  @sampled = false
57
- log('Discarding profile because transaction not sampled')
64
+ log("Discarding profile because transaction not sampled")
58
65
  return
59
66
  end
60
67
 
61
68
  case @profiles_sample_rate
62
69
  when 0.0
63
70
  @sampled = false
64
- log('Discarding profile because sample_rate is 0')
71
+ log("Discarding profile because sample_rate is 0")
65
72
  return
66
73
  when 1.0
67
74
  @sampled = true
@@ -70,7 +77,7 @@ module Sentry
70
77
  @sampled = Random.rand < @profiles_sample_rate
71
78
  end
72
79
 
73
- log('Discarding profile due to sampling decision') unless @sampled
80
+ log("Discarding profile due to sampling decision") unless @sampled
74
81
  end
75
82
 
76
83
  def to_hash
@@ -90,13 +97,12 @@ module Sentry
90
97
 
91
98
  frame_map = {}
92
99
 
93
- frames = results[:frames].to_enum.with_index.map do |frame, idx|
94
- frame_id, frame_data = frame
95
-
100
+ frames = results[:frames].map.with_index do |(frame_id, frame_data), idx|
96
101
  # need to map over stackprof frame ids to ours
97
102
  frame_map[frame_id] = idx
98
103
 
99
104
  file_path = frame_data[:file]
105
+ lineno = frame_data[:line]
100
106
  in_app = in_app?(file_path)
101
107
  filename = compute_filename(file_path, in_app)
102
108
  function, mod = split_module(frame_data[:name])
@@ -109,7 +115,7 @@ module Sentry
109
115
  }
110
116
 
111
117
  frame_hash[:module] = mod if mod
112
- frame_hash[:lineno] = frame_data[:line] if frame_data[:line] && frame_data[:line] >= 0
118
+ frame_hash[:lineno] = lineno if lineno && lineno >= 0
113
119
 
114
120
  frame_hash
115
121
  end
@@ -130,7 +136,7 @@ module Sentry
130
136
  num_seen << results[:raw][idx + len]
131
137
  idx += len + 1
132
138
 
133
- log('Unknown frame in stack') if stack.size != len
139
+ log("Unknown frame in stack") if stack.size != len
134
140
  end
135
141
 
136
142
  idx = 0
@@ -155,16 +161,16 @@ module Sentry
155
161
  # Till then, on multi-threaded servers like puma, we will get frames from other active threads when the one
156
162
  # we're profiling is idle/sleeping/waiting for IO etc.
157
163
  # https://bugs.ruby-lang.org/issues/10602
158
- thread_id: '0',
164
+ thread_id: "0",
159
165
  elapsed_since_start_ns: elapsed_since_start_ns.to_s
160
166
  }
161
167
  end
162
168
  end
163
169
 
164
- log('Some samples thrown away') if samples.size != results[:samples]
170
+ log("Some samples thrown away") if samples.size != results[:samples]
165
171
 
166
172
  if samples.size <= MIN_SAMPLES_REQUIRED
167
- log('Not enough samples, discarding profiler')
173
+ log("Not enough samples, discarding profiler")
168
174
  record_lost_event(:insufficient_data)
169
175
  return {}
170
176
  end
@@ -189,45 +195,8 @@ module Sentry
189
195
  Sentry.logger.debug(LOGGER_PROGNAME) { "[Profiler] #{message}" }
190
196
  end
191
197
 
192
- def in_app?(abs_path)
193
- abs_path.match?(@in_app_pattern)
194
- end
195
-
196
- # copied from stacktrace.rb since I don't want to touch existing code
197
- # TODO-neel-profiler try to fetch this from stackprof once we patch
198
- # the native extension
199
- def compute_filename(abs_path, in_app)
200
- return nil if abs_path.nil?
201
-
202
- under_project_root = @project_root && abs_path.start_with?(@project_root)
203
-
204
- prefix =
205
- if under_project_root && in_app
206
- @project_root
207
- else
208
- longest_load_path = $LOAD_PATH.select { |path| abs_path.start_with?(path.to_s) }.max_by(&:size)
209
-
210
- if under_project_root
211
- longest_load_path || @project_root
212
- else
213
- longest_load_path
214
- end
215
- end
216
-
217
- prefix ? abs_path[prefix.to_s.chomp(File::SEPARATOR).length + 1..-1] : abs_path
218
- end
219
-
220
- def split_module(name)
221
- # last module plus class/instance method
222
- i = name.rindex('::')
223
- function = i ? name[(i + 2)..-1] : name
224
- mod = i ? name[0...i] : nil
225
-
226
- [function, mod]
227
- end
228
-
229
198
  def record_lost_event(reason)
230
- Sentry.get_current_client&.transport&.record_lost_event(reason, 'profile')
199
+ Sentry.get_current_client&.transport&.record_lost_event(reason, "profile")
231
200
  end
232
201
  end
233
202
  end
@@ -108,7 +108,7 @@ module Sentry
108
108
  end
109
109
 
110
110
  # Returns the Dynamic Sampling Context from the baggage.
111
- # @return [String, nil]
111
+ # @return [Hash, nil]
112
112
  def get_dynamic_sampling_context
113
113
  get_baggage&.dynamic_sampling_context
114
114
  end
@@ -50,11 +50,11 @@ module Sentry
50
50
  private
51
51
 
52
52
  def collect_exception(env)
53
- env['rack.exception'] || env['sinatra.error']
53
+ env["rack.exception"] || env["sinatra.error"]
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)
data/lib/sentry/rack.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'rack'
3
+ require "rack"
4
4
 
5
- require 'sentry/rack/capture_exceptions'
5
+ require "sentry/rack/capture_exceptions"
data/lib/sentry/rake.rb CHANGED
@@ -8,10 +8,10 @@ module Sentry
8
8
  module Application
9
9
  # @api private
10
10
  def display_error_message(ex)
11
- mechanism = Sentry::Mechanism.new(type: 'rake', handled: false)
11
+ mechanism = Sentry::Mechanism.new(type: "rake", handled: false)
12
12
 
13
13
  Sentry.capture_exception(ex, hint: { mechanism: mechanism }) do |scope|
14
- task_name = top_level_tasks.join(' ')
14
+ task_name = top_level_tasks.join(" ")
15
15
  scope.set_transaction_name(task_name, source: :task)
16
16
  scope.set_tag("rake_task", task_name)
17
17
  end if Sentry.initialized? && !Sentry.configuration.skip_rake_integration