sentry-ruby 5.16.1 → 5.17.2

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 (50) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -1
  3. data/Rakefile +1 -1
  4. data/lib/sentry/background_worker.rb +1 -1
  5. data/lib/sentry/backtrace.rb +7 -3
  6. data/lib/sentry/check_in_event.rb +1 -1
  7. data/lib/sentry/client.rb +4 -3
  8. data/lib/sentry/configuration.rb +19 -11
  9. data/lib/sentry/cron/monitor_schedule.rb +1 -1
  10. data/lib/sentry/dsn.rb +1 -1
  11. data/lib/sentry/envelope.rb +1 -1
  12. data/lib/sentry/error_event.rb +2 -2
  13. data/lib/sentry/event.rb +8 -8
  14. data/lib/sentry/hub.rb +1 -1
  15. data/lib/sentry/integrable.rb +4 -0
  16. data/lib/sentry/interface.rb +1 -0
  17. data/lib/sentry/interfaces/exception.rb +5 -3
  18. data/lib/sentry/interfaces/mechanism.rb +20 -0
  19. data/lib/sentry/interfaces/request.rb +2 -2
  20. data/lib/sentry/interfaces/single_exception.rb +6 -4
  21. data/lib/sentry/interfaces/stacktrace_builder.rb +8 -0
  22. data/lib/sentry/metrics/aggregator.rb +260 -0
  23. data/lib/sentry/metrics/configuration.rb +47 -0
  24. data/lib/sentry/metrics/counter_metric.rb +25 -0
  25. data/lib/sentry/metrics/distribution_metric.rb +25 -0
  26. data/lib/sentry/metrics/gauge_metric.rb +35 -0
  27. data/lib/sentry/metrics/local_aggregator.rb +53 -0
  28. data/lib/sentry/metrics/metric.rb +19 -0
  29. data/lib/sentry/metrics/set_metric.rb +28 -0
  30. data/lib/sentry/metrics/timing.rb +43 -0
  31. data/lib/sentry/metrics.rb +55 -0
  32. data/lib/sentry/propagation_context.rb +9 -8
  33. data/lib/sentry/puma.rb +1 -1
  34. data/lib/sentry/rack/capture_exceptions.rb +6 -1
  35. data/lib/sentry/rake.rb +3 -1
  36. data/lib/sentry/scope.rb +7 -2
  37. data/lib/sentry/session.rb +2 -2
  38. data/lib/sentry/session_flusher.rb +0 -1
  39. data/lib/sentry/span.rb +16 -2
  40. data/lib/sentry/transaction.rb +15 -14
  41. data/lib/sentry/transaction_event.rb +5 -0
  42. data/lib/sentry/transport/configuration.rb +0 -1
  43. data/lib/sentry/transport.rb +0 -1
  44. data/lib/sentry/utils/argument_checking_helper.rb +6 -0
  45. data/lib/sentry/utils/real_ip.rb +1 -1
  46. data/lib/sentry/utils/request_id.rb +1 -1
  47. data/lib/sentry/version.rb +1 -1
  48. data/lib/sentry-ruby.rb +14 -2
  49. data/sentry-ruby.gemspec +1 -0
  50. metadata +27 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 565289974a80625ef7ecac6926cfb7292569dfafd84f12584a8214619bdd5ed5
4
- data.tar.gz: 005b3e5c27b7188058c412d36981779eb759f7be1032429eea17a2e4a7ffcb9f
3
+ metadata.gz: de5757f08dbb0987ccfc9e447f482358722a3684abae2ee581ab03a6bb290418
4
+ data.tar.gz: 39b5b3fe8b54be43ecfd06cc39981e670a9c07d86d303a412aa4d83577155513
5
5
  SHA512:
6
- metadata.gz: d0e68db7961263d6aa070feaa131fae4c2039884e565cd248795298a99f08d66e7c921df1a6f6175e68ac0ed554185b4b94769f8b0fcba12d76e6aa042fc3359
7
- data.tar.gz: 5e10f1406d7a7809aed02cef80e1d4355e61af54ed42ffd9a1b3a54a730229abfef4f0a690980c646757b93fffa6219d6500c5700b08a6eef61b6eb682d63620
6
+ metadata.gz: 034a330fcb4e8d1722eadcba6dbf6cf69f02919555f255ca98493ade5ebc2441a5b15bb6ce9bc3f2a6305b05e867f2a1e87f7eedc7b0a029036d4939a715ff69
7
+ data.tar.gz: d75b0125443e8cff217fc1c4a6c9ff5dc1ff7ee51542e19ab3d3768e2f716233196afdcc72883b9b451d42817bd7a974c73095c27b48abfaffa089ae9a78d066
data/README.md CHANGED
@@ -90,7 +90,7 @@ To learn more about sampling transactions, please visit the [official documentat
90
90
  - [Sidekiq](https://docs.sentry.io/platforms/ruby/guides/sidekiq/)
91
91
  - [DelayedJob](https://docs.sentry.io/platforms/ruby/guides/delayed_job/)
92
92
  - [Resque](https://docs.sentry.io/platforms/ruby/guides/resque/)
93
- - [OpenTemeletry](https://docs.sentry.io/platforms/ruby/performance/instrumentation/opentelemetry/)
93
+ - [OpenTelemetry](https://docs.sentry.io/platforms/ruby/performance/instrumentation/opentelemetry/)
94
94
 
95
95
  ### Enriching Events
96
96
 
data/Rakefile CHANGED
@@ -17,4 +17,4 @@ task :isolated_specs do
17
17
  end
18
18
  end
19
19
 
20
- task :default => [:spec, :isolated_specs]
20
+ task default: [:spec, :isolated_specs]
@@ -31,7 +31,7 @@ module Sentry
31
31
  log_debug("config.background_worker_threads is set to 0, all events will be sent synchronously")
32
32
  Concurrent::ImmediateExecutor.new
33
33
  else
34
- log_debug("Initializing the background worker with #{@number_of_threads} threads")
34
+ log_debug("Initializing the Sentry background worker with #{@number_of_threads} threads")
35
35
 
36
36
  executor = Concurrent::ThreadPoolExecutor.new(
37
37
  min_threads: 0,
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "rubygems"
4
+
3
5
  module Sentry
4
6
  # @api private
5
7
  class Backtrace
@@ -10,7 +12,7 @@ module Sentry
10
12
  RUBY_INPUT_FORMAT = /
11
13
  ^ \s* (?: [a-zA-Z]: | uri:classloader: )? ([^:]+ | <.*>):
12
14
  (\d+)
13
- (?: :in \s `([^']+)')?$
15
+ (?: :in\s('|`)([^']+)')?$
14
16
  /x.freeze
15
17
 
16
18
  # org.jruby.runtime.callsite.CachingCallSite.call(CachingCallSite.java:170)
@@ -33,10 +35,10 @@ module Sentry
33
35
  # Parses a single line of a given backtrace
34
36
  # @param [String] unparsed_line The raw line from +caller+ or some backtrace
35
37
  # @return [Line] The parsed backtrace line
36
- def self.parse(unparsed_line, in_app_pattern)
38
+ def self.parse(unparsed_line, in_app_pattern = nil)
37
39
  ruby_match = unparsed_line.match(RUBY_INPUT_FORMAT)
38
40
  if ruby_match
39
- _, file, number, method = ruby_match.to_a
41
+ _, file, number, _, method = ruby_match.to_a
40
42
  file.sub!(/\.class$/, RB_EXTENSION)
41
43
  module_name = nil
42
44
  else
@@ -55,6 +57,8 @@ module Sentry
55
57
  end
56
58
 
57
59
  def in_app
60
+ return false unless in_app_pattern
61
+
58
62
  if file =~ in_app_pattern
59
63
  true
60
64
  else
@@ -27,7 +27,7 @@ module Sentry
27
27
  # @return [Symbol]
28
28
  attr_accessor :status
29
29
 
30
- VALID_STATUSES = %i(ok in_progress error)
30
+ VALID_STATUSES = %i[ok in_progress error]
31
31
 
32
32
  def initialize(
33
33
  slug:,
data/lib/sentry/client.rb CHANGED
@@ -88,9 +88,10 @@ module Sentry
88
88
  return if !ignore_exclusions && !@configuration.exception_class_allowed?(exception)
89
89
 
90
90
  integration_meta = Sentry.integrations[hint[:integration]]
91
+ mechanism = hint.delete(:mechanism) { Mechanism.new }
91
92
 
92
93
  ErrorEvent.new(configuration: configuration, integration_meta: integration_meta).tap do |event|
93
- event.add_exception_interface(exception)
94
+ event.add_exception_interface(exception, mechanism: mechanism)
94
95
  event.add_threads_interface(crashed: true)
95
96
  event.level = :error
96
97
  end
@@ -172,8 +173,8 @@ module Sentry
172
173
  end
173
174
  end
174
175
 
175
- transport.send_event(event)
176
- spotlight_transport&.send_event(event)
176
+ transport.send_event(event) if configuration.sending_to_dsn_allowed?
177
+ spotlight_transport.send_event(event) if spotlight_transport
177
178
 
178
179
  event
179
180
  rescue => e
@@ -8,6 +8,7 @@ require "sentry/dsn"
8
8
  require "sentry/release_detector"
9
9
  require "sentry/transport/configuration"
10
10
  require "sentry/cron/configuration"
11
+ require "sentry/metrics/configuration"
11
12
  require "sentry/linecache"
12
13
  require "sentry/interfaces/stacktrace_builder"
13
14
 
@@ -235,6 +236,10 @@ module Sentry
235
236
  # @return [Cron::Configuration]
236
237
  attr_reader :cron
237
238
 
239
+ # Metrics related configuration.
240
+ # @return [Metrics::Configuration]
241
+ attr_reader :metrics
242
+
238
243
  # Take a float between 0.0 and 1.0 as the sample rate for tracing events (transactions).
239
244
  # @return [Float, nil]
240
245
  attr_reader :traces_sample_rate
@@ -311,11 +316,11 @@ module Sentry
311
316
  'Sinatra::NotFound'
312
317
  ].freeze
313
318
 
314
- RACK_ENV_WHITELIST_DEFAULT = %w(
319
+ RACK_ENV_WHITELIST_DEFAULT = %w[
315
320
  REMOTE_ADDR
316
321
  SERVER_NAME
317
322
  SERVER_PORT
318
- ).freeze
323
+ ].freeze
319
324
 
320
325
  HEROKU_DYNO_METADATA_MESSAGE = "You are running on Heroku but haven't enabled Dyno Metadata. For Sentry's "\
321
326
  "release detection to work correctly, please run `heroku labs:enable runtime-dyno-metadata`".freeze
@@ -328,7 +333,7 @@ module Sentry
328
333
 
329
334
  PROPAGATION_TARGETS_MATCH_ALL = /.*/.freeze
330
335
 
331
- DEFAULT_PATCHES = %i(redis puma http).freeze
336
+ DEFAULT_PATCHES = %i[redis puma http].freeze
332
337
 
333
338
  class << self
334
339
  # Post initialization callbacks are called at the end of initialization process
@@ -337,7 +342,7 @@ module Sentry
337
342
  @post_initialization_callbacks ||= []
338
343
  end
339
344
 
340
- # allow extensions to add their hooks to the Configuration class
345
+ # allow extensions to add their hooks to the Configuration class
341
346
  def add_post_initialization_callback(&block)
342
347
  post_initialization_callbacks << block
343
348
  end
@@ -386,6 +391,7 @@ module Sentry
386
391
 
387
392
  @transport = Transport::Configuration.new
388
393
  @cron = Cron::Configuration.new
394
+ @metrics = Metrics::Configuration.new
389
395
  @gem_specs = Hash[Gem::Specification.map { |spec| [spec.name, spec.version.to_s] }] if Gem::Specification.respond_to?(:map)
390
396
 
391
397
  run_post_initialization_callbacks
@@ -479,9 +485,13 @@ module Sentry
479
485
  end
480
486
 
481
487
  def sending_allowed?
488
+ spotlight || sending_to_dsn_allowed?
489
+ end
490
+
491
+ def sending_to_dsn_allowed?
482
492
  @errors = []
483
493
 
484
- spotlight || (valid? && capture_in_environment?)
494
+ valid? && capture_in_environment?
485
495
  end
486
496
 
487
497
  def sample_allowed?
@@ -490,6 +500,10 @@ module Sentry
490
500
  Random.rand < sample_rate
491
501
  end
492
502
 
503
+ def session_tracking?
504
+ auto_session_tracking && enabled_in_current_env?
505
+ end
506
+
493
507
  def exception_class_allowed?(exc)
494
508
  if exc.is_a?(Sentry::Error)
495
509
  # Try to prevent error reporting loops
@@ -566,12 +580,6 @@ module Sentry
566
580
 
567
581
  private
568
582
 
569
- def check_callable!(name, value)
570
- unless value == nil || value.respond_to?(:call)
571
- raise ArgumentError, "#{name} must be callable (or nil to disable)"
572
- end
573
- end
574
-
575
583
  def init_dsn(dsn_string)
576
584
  return if dsn_string.nil? || dsn_string.empty?
577
585
 
@@ -26,7 +26,7 @@ module Sentry
26
26
  # @return [Symbol]
27
27
  attr_accessor :unit
28
28
 
29
- VALID_UNITS = %i(year month week day hour minute)
29
+ VALID_UNITS = %i[year month week day hour minute]
30
30
 
31
31
  def initialize(value, unit)
32
32
  @value = value
data/lib/sentry/dsn.rb CHANGED
@@ -5,7 +5,7 @@ require "uri"
5
5
  module Sentry
6
6
  class DSN
7
7
  PORT_MAP = { 'http' => 80, 'https' => 443 }.freeze
8
- REQUIRED_ATTRIBUTES = %w(host path public_key project_id).freeze
8
+ REQUIRED_ATTRIBUTES = %w[host path public_key project_id].freeze
9
9
 
10
10
  attr_reader :scheme, :secret_key, :port, *REQUIRED_ATTRIBUTES
11
11
 
@@ -19,7 +19,7 @@ module Sentry
19
19
  end
20
20
 
21
21
  def to_s
22
- [JSON.generate(@headers), JSON.generate(@payload)].join("\n")
22
+ [JSON.generate(@headers), @payload.is_a?(String) ? @payload : JSON.generate(@payload)].join("\n")
23
23
  end
24
24
 
25
25
  def serialize
@@ -27,12 +27,12 @@ module Sentry
27
27
  end
28
28
 
29
29
  # @!visibility private
30
- def add_exception_interface(exception)
30
+ def add_exception_interface(exception, mechanism:)
31
31
  if exception.respond_to?(:sentry_context)
32
32
  @extra.merge!(exception.sentry_context)
33
33
  end
34
34
 
35
- @exception = Sentry::ExceptionInterface.build(exception: exception, stacktrace_builder: @stacktrace_builder)
35
+ @exception = Sentry::ExceptionInterface.build(exception: exception, stacktrace_builder: @stacktrace_builder, mechanism: mechanism)
36
36
  end
37
37
  end
38
38
  end
data/lib/sentry/event.rb CHANGED
@@ -14,16 +14,16 @@ module Sentry
14
14
  class Event
15
15
  TYPE = "event"
16
16
  # These are readable attributes.
17
- SERIALIZEABLE_ATTRIBUTES = %i(
17
+ SERIALIZEABLE_ATTRIBUTES = %i[
18
18
  event_id level timestamp
19
19
  release environment server_name modules
20
20
  message user tags contexts extra
21
21
  fingerprint breadcrumbs transaction transaction_info
22
22
  platform sdk type
23
- )
23
+ ]
24
24
 
25
25
  # These are writable attributes.
26
- WRITER_ATTRIBUTES = SERIALIZEABLE_ATTRIBUTES - %i(type timestamp level)
26
+ WRITER_ATTRIBUTES = SERIALIZEABLE_ATTRIBUTES - %i[type timestamp level]
27
27
 
28
28
  MAX_MESSAGE_SIZE_IN_BYTES = 1024 * 8
29
29
 
@@ -145,11 +145,11 @@ module Sentry
145
145
  # REMOTE_ADDR to determine the Event IP, and must use other headers instead.
146
146
  def calculate_real_ip_from_rack(env)
147
147
  Utils::RealIp.new(
148
- :remote_addr => env["REMOTE_ADDR"],
149
- :client_ip => env["HTTP_CLIENT_IP"],
150
- :real_ip => env["HTTP_X_REAL_IP"],
151
- :forwarded_for => env["HTTP_X_FORWARDED_FOR"],
152
- :trusted_proxies => @trusted_proxies
148
+ remote_addr: env["REMOTE_ADDR"],
149
+ client_ip: env["HTTP_CLIENT_IP"],
150
+ real_ip: env["HTTP_X_REAL_IP"],
151
+ forwarded_for: env["HTTP_X_FORWARDED_FOR"],
152
+ trusted_proxies: @trusted_proxies
153
153
  ).calculate_ip
154
154
  end
155
155
  end
data/lib/sentry/hub.rb CHANGED
@@ -245,7 +245,7 @@ module Sentry
245
245
  end
246
246
 
247
247
  def with_session_tracking(&block)
248
- return yield unless configuration.auto_session_tracking
248
+ return yield unless configuration.session_tracking?
249
249
 
250
250
  start_session
251
251
  yield
@@ -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
 
@@ -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",
@@ -11,10 +11,10 @@ module Sentry
11
11
  OMISSION_MARK = "...".freeze
12
12
  MAX_LOCAL_BYTES = 1024
13
13
 
14
- attr_reader :type, :module, :thread_id, :stacktrace
14
+ attr_reader :type, :module, :thread_id, :stacktrace, :mechanism
15
15
  attr_accessor :value
16
16
 
17
- def initialize(exception:, stacktrace: nil)
17
+ def initialize(exception:, mechanism:, stacktrace: nil)
18
18
  @type = exception.class.to_s
19
19
  exception_message =
20
20
  if exception.respond_to?(:detailed_message)
@@ -29,17 +29,19 @@ module Sentry
29
29
  @module = exception.class.to_s.split('::')[0...-1].join('::')
30
30
  @thread_id = Thread.current.object_id
31
31
  @stacktrace = stacktrace
32
+ @mechanism = mechanism
32
33
  end
33
34
 
34
35
  def to_hash
35
36
  data = super
36
37
  data[:stacktrace] = data[:stacktrace].to_hash if data[:stacktrace]
38
+ data[:mechanism] = data[:mechanism].to_hash
37
39
  data
38
40
  end
39
41
 
40
42
  # patch this method if you want to change an exception's stacktrace frames
41
43
  # also see `StacktraceBuilder.build`.
42
- def self.build_with_stacktrace(exception:, stacktrace_builder:)
44
+ def self.build_with_stacktrace(exception:, stacktrace_builder:, mechanism:)
43
45
  stacktrace = stacktrace_builder.build(backtrace: exception.backtrace)
44
46
 
45
47
  if locals = exception.instance_variable_get(:@sentry_locals)
@@ -61,7 +63,7 @@ module Sentry
61
63
  stacktrace.frames.last.vars = locals
62
64
  end
63
65
 
64
- new(exception: exception, stacktrace: stacktrace)
66
+ new(exception: exception, stacktrace: stacktrace, mechanism: mechanism)
65
67
  end
66
68
  end
67
69
  end
@@ -62,6 +62,14 @@ module Sentry
62
62
  StacktraceInterface.new(frames: frames)
63
63
  end
64
64
 
65
+ # Get the code location hash for a single line for where metrics where added.
66
+ # @return [Hash]
67
+ def metrics_code_location(unparsed_line)
68
+ parsed_line = Backtrace::Line.parse(unparsed_line)
69
+ frame = convert_parsed_line_into_frame(parsed_line)
70
+ frame.to_hash.reject { |k, _| %i[project_root in_app].include?(k) }
71
+ end
72
+
65
73
  private
66
74
 
67
75
  def convert_parsed_line_into_frame(line)