sentry-ruby 5.10.0 → 5.26.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.
Files changed (92) hide show
  1. checksums.yaml +4 -4
  2. data/.rspec +3 -1
  3. data/Gemfile +12 -13
  4. data/README.md +26 -11
  5. data/Rakefile +9 -11
  6. data/bin/console +2 -0
  7. data/lib/sentry/attachment.rb +40 -0
  8. data/lib/sentry/background_worker.rb +11 -5
  9. data/lib/sentry/backpressure_monitor.rb +45 -0
  10. data/lib/sentry/backtrace.rb +12 -9
  11. data/lib/sentry/baggage.rb +7 -7
  12. data/lib/sentry/breadcrumb/sentry_logger.rb +6 -6
  13. data/lib/sentry/breadcrumb.rb +13 -6
  14. data/lib/sentry/check_in_event.rb +61 -0
  15. data/lib/sentry/client.rb +214 -25
  16. data/lib/sentry/configuration.rb +221 -38
  17. data/lib/sentry/core_ext/object/deep_dup.rb +1 -1
  18. data/lib/sentry/cron/configuration.rb +23 -0
  19. data/lib/sentry/cron/monitor_check_ins.rb +77 -0
  20. data/lib/sentry/cron/monitor_config.rb +53 -0
  21. data/lib/sentry/cron/monitor_schedule.rb +42 -0
  22. data/lib/sentry/dsn.rb +4 -4
  23. data/lib/sentry/envelope/item.rb +88 -0
  24. data/lib/sentry/envelope.rb +2 -68
  25. data/lib/sentry/error_event.rb +2 -2
  26. data/lib/sentry/event.rb +28 -47
  27. data/lib/sentry/excon/middleware.rb +77 -0
  28. data/lib/sentry/excon.rb +10 -0
  29. data/lib/sentry/faraday.rb +77 -0
  30. data/lib/sentry/graphql.rb +9 -0
  31. data/lib/sentry/hub.rb +138 -6
  32. data/lib/sentry/integrable.rb +10 -0
  33. data/lib/sentry/interface.rb +1 -0
  34. data/lib/sentry/interfaces/exception.rb +5 -3
  35. data/lib/sentry/interfaces/mechanism.rb +20 -0
  36. data/lib/sentry/interfaces/request.rb +8 -8
  37. data/lib/sentry/interfaces/single_exception.rb +13 -9
  38. data/lib/sentry/interfaces/stacktrace.rb +3 -1
  39. data/lib/sentry/interfaces/stacktrace_builder.rb +23 -2
  40. data/lib/sentry/linecache.rb +3 -3
  41. data/lib/sentry/log_event.rb +206 -0
  42. data/lib/sentry/log_event_buffer.rb +75 -0
  43. data/lib/sentry/logger.rb +1 -1
  44. data/lib/sentry/metrics/aggregator.rb +248 -0
  45. data/lib/sentry/metrics/configuration.rb +47 -0
  46. data/lib/sentry/metrics/counter_metric.rb +25 -0
  47. data/lib/sentry/metrics/distribution_metric.rb +25 -0
  48. data/lib/sentry/metrics/gauge_metric.rb +35 -0
  49. data/lib/sentry/metrics/local_aggregator.rb +53 -0
  50. data/lib/sentry/metrics/metric.rb +19 -0
  51. data/lib/sentry/metrics/set_metric.rb +28 -0
  52. data/lib/sentry/metrics/timing.rb +51 -0
  53. data/lib/sentry/metrics.rb +56 -0
  54. data/lib/sentry/net/http.rb +27 -44
  55. data/lib/sentry/profiler/helpers.rb +46 -0
  56. data/lib/sentry/profiler.rb +41 -60
  57. data/lib/sentry/propagation_context.rb +135 -0
  58. data/lib/sentry/puma.rb +12 -5
  59. data/lib/sentry/rack/capture_exceptions.rb +17 -8
  60. data/lib/sentry/rack.rb +2 -2
  61. data/lib/sentry/rake.rb +4 -15
  62. data/lib/sentry/redis.rb +10 -4
  63. data/lib/sentry/release_detector.rb +5 -5
  64. data/lib/sentry/rspec.rb +91 -0
  65. data/lib/sentry/scope.rb +75 -39
  66. data/lib/sentry/session.rb +2 -2
  67. data/lib/sentry/session_flusher.rb +15 -43
  68. data/lib/sentry/span.rb +92 -8
  69. data/lib/sentry/std_lib_logger.rb +50 -0
  70. data/lib/sentry/structured_logger.rb +138 -0
  71. data/lib/sentry/test_helper.rb +42 -13
  72. data/lib/sentry/threaded_periodic_worker.rb +39 -0
  73. data/lib/sentry/transaction.rb +44 -43
  74. data/lib/sentry/transaction_event.rb +10 -6
  75. data/lib/sentry/transport/configuration.rb +73 -1
  76. data/lib/sentry/transport/http_transport.rb +71 -41
  77. data/lib/sentry/transport/spotlight_transport.rb +50 -0
  78. data/lib/sentry/transport.rb +53 -49
  79. data/lib/sentry/utils/argument_checking_helper.rb +12 -0
  80. data/lib/sentry/utils/env_helper.rb +21 -0
  81. data/lib/sentry/utils/http_tracing.rb +74 -0
  82. data/lib/sentry/utils/logging_helper.rb +10 -7
  83. data/lib/sentry/utils/real_ip.rb +2 -2
  84. data/lib/sentry/utils/request_id.rb +1 -1
  85. data/lib/sentry/utils/uuid.rb +13 -0
  86. data/lib/sentry/vernier/output.rb +89 -0
  87. data/lib/sentry/vernier/profiler.rb +132 -0
  88. data/lib/sentry/version.rb +1 -1
  89. data/lib/sentry-ruby.rb +206 -35
  90. data/sentry-ruby-core.gemspec +3 -1
  91. data/sentry-ruby.gemspec +15 -6
  92. metadata +61 -11
data/lib/sentry/hub.rb CHANGED
@@ -8,12 +8,42 @@ module Sentry
8
8
  class Hub
9
9
  include ArgumentCheckingHelper
10
10
 
11
+ MUTEX = Mutex.new
12
+
11
13
  attr_reader :last_event_id
12
14
 
15
+ attr_reader :current_profiler
16
+
13
17
  def initialize(client, scope)
14
18
  first_layer = Layer.new(client, scope)
15
19
  @stack = [first_layer]
16
20
  @last_event_id = nil
21
+ @current_profiler = {}
22
+ end
23
+
24
+ # This is an internal private method
25
+ # @api private
26
+ def start_profiler!(transaction)
27
+ MUTEX.synchronize do
28
+ transaction.start_profiler!
29
+ @current_profiler[transaction.__id__] = transaction.profiler
30
+ end
31
+ end
32
+
33
+ # This is an internal private method
34
+ # @api private
35
+ def stop_profiler!(transaction)
36
+ MUTEX.synchronize do
37
+ @current_profiler.delete(transaction.__id__)&.stop
38
+ end
39
+ end
40
+
41
+ # This is an internal private method
42
+ # @api private
43
+ def profiler_running?
44
+ MUTEX.synchronize do
45
+ !@current_profiler.empty?
46
+ end
17
47
  end
18
48
 
19
49
  def new_from_top
@@ -73,7 +103,13 @@ module Sentry
73
103
  end
74
104
 
75
105
  def pop_scope
76
- @stack.pop
106
+ if @stack.size > 1
107
+ @stack.pop
108
+ else
109
+ # We never want to enter a situation where we have no scope and no client
110
+ client = current_client
111
+ @stack = [Layer.new(client, Scope.new)]
112
+ end
77
113
  end
78
114
 
79
115
  def start_transaction(transaction: nil, custom_sampling_context: {}, instrumenter: :sentry, **options)
@@ -90,7 +126,7 @@ module Sentry
90
126
  sampling_context.merge!(custom_sampling_context)
91
127
  transaction.set_initial_sample_decision(sampling_context: sampling_context)
92
128
 
93
- transaction.start_profiler!
129
+ start_profiler!(transaction)
94
130
 
95
131
  transaction
96
132
  end
@@ -156,6 +192,40 @@ module Sentry
156
192
  capture_event(event, **options, &block)
157
193
  end
158
194
 
195
+ def capture_check_in(slug, status, **options)
196
+ check_argument_type!(slug, ::String)
197
+ check_argument_includes!(status, Sentry::CheckInEvent::VALID_STATUSES)
198
+
199
+ return unless current_client
200
+
201
+ options[:hint] ||= {}
202
+ options[:hint][:slug] = slug
203
+
204
+ event = current_client.event_from_check_in(
205
+ slug,
206
+ status,
207
+ options[:hint],
208
+ duration: options.delete(:duration),
209
+ monitor_config: options.delete(:monitor_config),
210
+ check_in_id: options.delete(:check_in_id)
211
+ )
212
+
213
+ return unless event
214
+
215
+ capture_event(event, **options)
216
+ event.check_in_id
217
+ end
218
+
219
+ def capture_log_event(message, **options)
220
+ return unless current_client
221
+
222
+ event = current_client.event_from_log(message, **options)
223
+
224
+ return unless event
225
+
226
+ current_client.buffer_log_event(event, current_scope)
227
+ end
228
+
159
229
  def capture_event(event, **options, &block)
160
230
  check_argument_type!(event, Sentry::Event)
161
231
 
@@ -169,7 +239,14 @@ module Sentry
169
239
  elsif custom_scope = options[:scope]
170
240
  scope.update_from_scope(custom_scope)
171
241
  elsif !options.empty?
172
- scope.update_from_options(**options)
242
+ unsupported_option_keys = scope.update_from_options(**options)
243
+
244
+ unless unsupported_option_keys.empty?
245
+ configuration.log_debug <<~MSG
246
+ Options #{unsupported_option_keys} are not supported and will not be applied to the event.
247
+ You may want to set them under the `extra` option.
248
+ MSG
249
+ end
173
250
  end
174
251
 
175
252
  event = current_client.capture_event(event, scope, hint)
@@ -178,11 +255,12 @@ module Sentry
178
255
  configuration.log_debug(event.to_json_compatible)
179
256
  end
180
257
 
181
- @last_event_id = event&.event_id unless event.is_a?(Sentry::TransactionEvent)
258
+ @last_event_id = event&.event_id if event.is_a?(Sentry::ErrorEvent)
182
259
  event
183
260
  end
184
261
 
185
262
  def add_breadcrumb(breadcrumb, hint: {})
263
+ return unless current_client
186
264
  return unless configuration.enabled_in_current_env?
187
265
 
188
266
  if before_breadcrumb = current_client.configuration.before_breadcrumb
@@ -217,11 +295,15 @@ module Sentry
217
295
 
218
296
  return unless session
219
297
  session.close
220
- Sentry.session_flusher.add_session(session)
298
+
299
+ # NOTE: Under some circumstances, session_flusher nilified out of sync
300
+ # See: https://github.com/getsentry/sentry-ruby/issues/2378
301
+ # See: https://github.com/getsentry/sentry-ruby/pull/2396
302
+ Sentry.session_flusher&.add_session(session)
221
303
  end
222
304
 
223
305
  def with_session_tracking(&block)
224
- return yield unless configuration.auto_session_tracking
306
+ return yield unless configuration.session_tracking?
225
307
 
226
308
  start_session
227
309
  yield
@@ -229,6 +311,56 @@ module Sentry
229
311
  end_session
230
312
  end
231
313
 
314
+ def get_traceparent
315
+ return nil unless current_scope
316
+
317
+ current_scope.get_span&.to_sentry_trace ||
318
+ current_scope.propagation_context.get_traceparent
319
+ end
320
+
321
+ def get_baggage
322
+ return nil unless current_scope
323
+
324
+ current_scope.get_span&.to_baggage ||
325
+ current_scope.propagation_context.get_baggage&.serialize
326
+ end
327
+
328
+ def get_trace_propagation_headers
329
+ headers = {}
330
+
331
+ traceparent = get_traceparent
332
+ headers[SENTRY_TRACE_HEADER_NAME] = traceparent if traceparent
333
+
334
+ baggage = get_baggage
335
+ headers[BAGGAGE_HEADER_NAME] = baggage if baggage && !baggage.empty?
336
+
337
+ headers
338
+ end
339
+
340
+ def get_trace_propagation_meta
341
+ get_trace_propagation_headers.map do |k, v|
342
+ "<meta name=\"#{k}\" content=\"#{v}\">"
343
+ end.join("\n")
344
+ end
345
+
346
+ def continue_trace(env, **options)
347
+ configure_scope { |s| s.generate_propagation_context(env) }
348
+
349
+ return nil unless configuration.tracing_enabled?
350
+
351
+ propagation_context = current_scope.propagation_context
352
+ return nil unless propagation_context.incoming_trace
353
+
354
+ Transaction.new(
355
+ hub: self,
356
+ trace_id: propagation_context.trace_id,
357
+ parent_span_id: propagation_context.parent_span_id,
358
+ parent_sampled: propagation_context.parent_sampled,
359
+ baggage: propagation_context.baggage,
360
+ **options
361
+ )
362
+ end
363
+
232
364
  private
233
365
 
234
366
  def current_layer
@@ -14,6 +14,10 @@ module Sentry
14
14
  def capture_exception(exception, **options, &block)
15
15
  options[:hint] ||= {}
16
16
  options[:hint][:integration] = integration_name
17
+
18
+ # within an integration, we usually intercept uncaught exceptions so we set handled to false.
19
+ options[:hint][:mechanism] ||= Sentry::Mechanism.new(type: integration_name, handled: false)
20
+
17
21
  Sentry.capture_exception(exception, **options, &block)
18
22
  end
19
23
 
@@ -22,5 +26,11 @@ module Sentry
22
26
  options[:hint][:integration] = integration_name
23
27
  Sentry.capture_message(message, **options, &block)
24
28
  end
29
+
30
+ def capture_check_in(slug, status, **options, &block)
31
+ options[:hint] ||= {}
32
+ options[:hint][:integration] = integration_name
33
+ Sentry.capture_check_in(slug, status, **options, &block)
34
+ end
25
35
  end
26
36
  end
@@ -14,3 +14,4 @@ require "sentry/interfaces/request"
14
14
  require "sentry/interfaces/single_exception"
15
15
  require "sentry/interfaces/stacktrace"
16
16
  require "sentry/interfaces/threads"
17
+ require "sentry/interfaces/mechanism"
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  require "set"
3
4
 
4
5
  module Sentry
@@ -23,17 +24,18 @@ module Sentry
23
24
  # @param stacktrace_builder [StacktraceBuilder]
24
25
  # @see SingleExceptionInterface#build_with_stacktrace
25
26
  # @see SingleExceptionInterface#initialize
27
+ # @param mechanism [Mechanism]
26
28
  # @return [ExceptionInterface]
27
- def self.build(exception:, stacktrace_builder:)
29
+ def self.build(exception:, stacktrace_builder:, mechanism:)
28
30
  exceptions = Sentry::Utils::ExceptionCauseChain.exception_to_array(exception).reverse
29
31
  processed_backtrace_ids = Set.new
30
32
 
31
33
  exceptions = exceptions.map do |e|
32
34
  if e.backtrace && !processed_backtrace_ids.include?(e.backtrace.object_id)
33
35
  processed_backtrace_ids << e.backtrace.object_id
34
- SingleExceptionInterface.build_with_stacktrace(exception: e, stacktrace_builder: stacktrace_builder)
36
+ SingleExceptionInterface.build_with_stacktrace(exception: e, stacktrace_builder: stacktrace_builder, mechanism: mechanism)
35
37
  else
36
- SingleExceptionInterface.new(exception: exception)
38
+ SingleExceptionInterface.new(exception: exception, mechanism: mechanism)
37
39
  end
38
40
  end
39
41
 
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sentry
4
+ class Mechanism < Interface
5
+ # Generic identifier, mostly the source integration for this exception.
6
+ # @return [String]
7
+ attr_accessor :type
8
+
9
+ # A manually captured exception has handled set to true,
10
+ # false if coming from an integration where we intercept an uncaught exception.
11
+ # Defaults to true here and will be set to false explicitly in integrations.
12
+ # @return [Boolean]
13
+ attr_accessor :handled
14
+
15
+ def initialize(type: "generic", handled: true)
16
+ @type = type
17
+ @handled = handled
18
+ end
19
+ end
20
+ end
@@ -2,8 +2,8 @@
2
2
 
3
3
  module Sentry
4
4
  class RequestInterface < Interface
5
- REQUEST_ID_HEADERS = %w(action_dispatch.request_id HTTP_X_REQUEST_ID).freeze
6
- CONTENT_HEADERS = %w(CONTENT_TYPE CONTENT_LENGTH).freeze
5
+ REQUEST_ID_HEADERS = %w[action_dispatch.request_id HTTP_X_REQUEST_ID].freeze
6
+ CONTENT_HEADERS = %w[CONTENT_TYPE CONTENT_LENGTH].freeze
7
7
  IP_HEADERS = [
8
8
  "REMOTE_ADDR",
9
9
  "HTTP_CLIENT_IP",
@@ -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,21 +85,21 @@ 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
99
99
  # Rails adds objects to the Rack env that can sometimes raise exceptions
100
100
  # when `to_s` is called.
101
101
  # See: https://github.com/rails/rails/blob/master/actionpack/lib/action_dispatch/middleware/remote_ip.rb#L134
102
- Sentry.logger.warn(LOGGER_PROGNAME) { "Error raised while formatting headers: #{e.message}" }
102
+ Sentry.sdk_logger.warn(LOGGER_PROGNAME) { "Error raised while formatting headers: #{e.message}" }
103
103
  next
104
104
  end
105
105
  end
@@ -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,13 +7,14 @@ 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
- attr_reader :type, :value, :module, :thread_id, :stacktrace
14
+ attr_reader :type, :module, :thread_id, :stacktrace, :mechanism
15
+ attr_accessor :value
15
16
 
16
- def initialize(exception:, stacktrace: nil)
17
+ def initialize(exception:, mechanism:, stacktrace: nil)
17
18
  @type = exception.class.to_s
18
19
  exception_message =
19
20
  if exception.respond_to?(:detailed_message)
@@ -21,23 +22,26 @@ module Sentry
21
22
  else
22
23
  exception.message || ""
23
24
  end
25
+ exception_message = exception_message.inspect unless exception_message.is_a?(String)
24
26
 
25
- @value = exception_message.byteslice(0..Event::MAX_MESSAGE_SIZE_IN_BYTES)
27
+ @value = Utils::EncodingHelper.encode_to_utf_8(exception_message.byteslice(0..Event::MAX_MESSAGE_SIZE_IN_BYTES))
26
28
 
27
- @module = exception.class.to_s.split('::')[0...-1].join('::')
29
+ @module = exception.class.to_s.split("::")[0...-1].join("::")
28
30
  @thread_id = Thread.current.object_id
29
31
  @stacktrace = stacktrace
32
+ @mechanism = mechanism
30
33
  end
31
34
 
32
35
  def to_hash
33
36
  data = super
34
37
  data[:stacktrace] = data[:stacktrace].to_hash if data[:stacktrace]
38
+ data[:mechanism] = data[:mechanism].to_hash
35
39
  data
36
40
  end
37
41
 
38
42
  # patch this method if you want to change an exception's stacktrace frames
39
43
  # also see `StacktraceBuilder.build`.
40
- def self.build_with_stacktrace(exception:, stacktrace_builder:)
44
+ def self.build_with_stacktrace(exception:, stacktrace_builder:, mechanism:)
41
45
  stacktrace = stacktrace_builder.build(backtrace: exception.backtrace)
42
46
 
43
47
  if locals = exception.instance_variable_get(:@sentry_locals)
@@ -50,7 +54,7 @@ module Sentry
50
54
  v = v.byteslice(0..MAX_LOCAL_BYTES - 1) + OMISSION_MARK
51
55
  end
52
56
 
53
- v
57
+ Utils::EncodingHelper.encode_to_utf_8(v)
54
58
  rescue StandardError
55
59
  PROBLEMATIC_LOCAL_VALUE_REPLACEMENT
56
60
  end
@@ -59,7 +63,7 @@ module Sentry
59
63
  stacktrace.frames.last.vars = locals
60
64
  end
61
65
 
62
- new(exception: exception, stacktrace: stacktrace)
66
+ new(exception: exception, stacktrace: stacktrace, mechanism: mechanism)
63
67
  end
64
68
  end
65
69
  end
@@ -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.
@@ -62,10 +75,18 @@ module Sentry
62
75
  StacktraceInterface.new(frames: frames)
63
76
  end
64
77
 
78
+ # Get the code location hash for a single line for where metrics where added.
79
+ # @return [Hash]
80
+ def metrics_code_location(unparsed_line)
81
+ parsed_line = Backtrace::Line.parse(unparsed_line)
82
+ frame = convert_parsed_line_into_frame(parsed_line)
83
+ frame.to_hash.reject { |k, _| %i[project_root in_app].include?(k) }
84
+ end
85
+
65
86
  private
66
87
 
67
88
  def convert_parsed_line_into_frame(line)
68
- frame = StacktraceInterface::Frame.new(project_root, line)
89
+ frame = StacktraceInterface::Frame.new(project_root, line, strip_backtrace_load_path)
69
90
  frame.set_context(linecache, context_lines) if context_lines
70
91
  frame
71
92
  end
@@ -29,9 +29,9 @@ module Sentry
29
29
 
30
30
  def getlines(path)
31
31
  @cache[path] ||= begin
32
- IO.readlines(path)
33
- rescue
34
- nil
32
+ File.open(path, "r", &:readlines)
33
+ rescue
34
+ nil
35
35
  end
36
36
  end
37
37