sentry-ruby 5.16.1 → 5.18.1

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 (59) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +2 -0
  3. data/README.md +10 -10
  4. data/Rakefile +1 -1
  5. data/bin/console +1 -0
  6. data/lib/sentry/background_worker.rb +1 -1
  7. data/lib/sentry/backpressure_monitor.rb +2 -32
  8. data/lib/sentry/backtrace.rb +7 -3
  9. data/lib/sentry/check_in_event.rb +1 -1
  10. data/lib/sentry/client.rb +42 -9
  11. data/lib/sentry/configuration.rb +20 -12
  12. data/lib/sentry/cron/monitor_schedule.rb +1 -1
  13. data/lib/sentry/dsn.rb +1 -1
  14. data/lib/sentry/envelope.rb +18 -1
  15. data/lib/sentry/error_event.rb +2 -2
  16. data/lib/sentry/event.rb +8 -8
  17. data/lib/sentry/graphql.rb +9 -0
  18. data/lib/sentry/hub.rb +13 -2
  19. data/lib/sentry/integrable.rb +4 -0
  20. data/lib/sentry/interface.rb +1 -0
  21. data/lib/sentry/interfaces/exception.rb +5 -3
  22. data/lib/sentry/interfaces/mechanism.rb +20 -0
  23. data/lib/sentry/interfaces/request.rb +2 -2
  24. data/lib/sentry/interfaces/single_exception.rb +6 -4
  25. data/lib/sentry/interfaces/stacktrace_builder.rb +8 -0
  26. data/lib/sentry/metrics/aggregator.rb +248 -0
  27. data/lib/sentry/metrics/configuration.rb +47 -0
  28. data/lib/sentry/metrics/counter_metric.rb +25 -0
  29. data/lib/sentry/metrics/distribution_metric.rb +25 -0
  30. data/lib/sentry/metrics/gauge_metric.rb +35 -0
  31. data/lib/sentry/metrics/local_aggregator.rb +53 -0
  32. data/lib/sentry/metrics/metric.rb +19 -0
  33. data/lib/sentry/metrics/set_metric.rb +28 -0
  34. data/lib/sentry/metrics/timing.rb +43 -0
  35. data/lib/sentry/metrics.rb +56 -0
  36. data/lib/sentry/net/http.rb +2 -1
  37. data/lib/sentry/propagation_context.rb +9 -8
  38. data/lib/sentry/puma.rb +1 -1
  39. data/lib/sentry/rack/capture_exceptions.rb +14 -2
  40. data/lib/sentry/rake.rb +3 -1
  41. data/lib/sentry/redis.rb +2 -1
  42. data/lib/sentry/scope.rb +21 -26
  43. data/lib/sentry/session.rb +2 -2
  44. data/lib/sentry/session_flusher.rb +6 -38
  45. data/lib/sentry/span.rb +39 -5
  46. data/lib/sentry/test_helper.rb +1 -1
  47. data/lib/sentry/threaded_periodic_worker.rb +39 -0
  48. data/lib/sentry/transaction.rb +15 -14
  49. data/lib/sentry/transaction_event.rb +5 -0
  50. data/lib/sentry/transport/configuration.rb +0 -1
  51. data/lib/sentry/transport.rb +8 -22
  52. data/lib/sentry/utils/argument_checking_helper.rb +6 -0
  53. data/lib/sentry/utils/logging_helper.rb +0 -4
  54. data/lib/sentry/utils/real_ip.rb +1 -1
  55. data/lib/sentry/utils/request_id.rb +1 -1
  56. data/lib/sentry/version.rb +1 -1
  57. data/lib/sentry-ruby.rb +26 -3
  58. data/sentry-ruby.gemspec +1 -0
  59. metadata +30 -3
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 565289974a80625ef7ecac6926cfb7292569dfafd84f12584a8214619bdd5ed5
4
- data.tar.gz: 005b3e5c27b7188058c412d36981779eb759f7be1032429eea17a2e4a7ffcb9f
3
+ metadata.gz: 735d27f0a0fef13cdfbf4cbb6e000cb53405e3b2ce1ca6a50b764b095017a16e
4
+ data.tar.gz: 52508a18bfc34c68494b37abc4f9907bd18b46aebe32e4dba46e16a60a7c685b
5
5
  SHA512:
6
- metadata.gz: d0e68db7961263d6aa070feaa131fae4c2039884e565cd248795298a99f08d66e7c921df1a6f6175e68ac0ed554185b4b94769f8b0fcba12d76e6aa042fc3359
7
- data.tar.gz: 5e10f1406d7a7809aed02cef80e1d4355e61af54ed42ffd9a1b3a54a730229abfef4f0a690980c646757b93fffa6219d6500c5700b08a6eef61b6eb682d63620
6
+ metadata.gz: 7b42c7c4af2329a6f77a3a6123bbc68d908fdbd879fe24925e3aa8c537c211565195a9bc39901803d4b612308c5119bf5f934674000ae9fc3ac0a432f5dffc7a
7
+ data.tar.gz: 0f5824802082fba7fa352ad86402e56a230a2daa0b2ffc894c0fb771da6b719e5ee6e3cb864a79f7d75bdcb8600310435ef4b5c97a8acb7504e6232a777f1314
data/Gemfile CHANGED
@@ -15,6 +15,8 @@ gem "puma"
15
15
  gem "timecop"
16
16
  gem "stackprof" unless RUBY_PLATFORM == "java"
17
17
 
18
+ gem "graphql", ">= 2.2.6" if RUBY_VERSION.to_f >= 2.7
19
+
18
20
  gem "benchmark-ips"
19
21
  gem "benchmark_driver"
20
22
  gem "benchmark-ipsa"
data/README.md CHANGED
@@ -13,14 +13,14 @@ _Bad software is everywhere, and we're tired of it. Sentry is on a mission to he
13
13
  Sentry SDK for Ruby
14
14
  ===========
15
15
 
16
- | current version | build | coverage | downloads |
17
- | --- | ----- | -------- | --------- |
18
- | [![Gem Version](https://img.shields.io/gem/v/sentry-ruby?label=sentry-ruby)](https://rubygems.org/gems/sentry-ruby) | [![Build Status](https://github.com/getsentry/sentry-ruby/actions/workflows/sentry_ruby_test.yml/badge.svg)](https://github.com/getsentry/sentry-ruby/actions/workflows/sentry_ruby_test.yml) | [![Coverage Status](https://img.shields.io/codecov/c/github/getsentry/sentry-ruby/master?logo=codecov)](https://codecov.io/gh/getsentry/sentry-ruby/branch/master) | [![Downloads](https://img.shields.io/gem/dt/sentry-ruby.svg)](https://rubygems.org/gems/sentry-ruby/) |
19
- | [![Gem Version](https://img.shields.io/gem/v/sentry-rails?label=sentry-rails)](https://rubygems.org/gems/sentry-rails) | [![Build Status](https://github.com/getsentry/sentry-ruby/actions/workflows/sentry_rails_test.yml/badge.svg)](https://github.com/getsentry/sentry-ruby/actions/workflows/sentry_rails_test.yml) | [![Coverage Status](https://img.shields.io/codecov/c/github/getsentry/sentry-ruby/master?logo=codecov)](https://codecov.io/gh/getsentry/sentry-ruby/branch/master) | [![Downloads](https://img.shields.io/gem/dt/sentry-rails.svg)](https://rubygems.org/gems/sentry-rails/) |
20
- | [![Gem Version](https://img.shields.io/gem/v/sentry-sidekiq?label=sentry-sidekiq)](https://rubygems.org/gems/sentry-sidekiq) | [![Build Status](https://github.com/getsentry/sentry-ruby/actions/workflows/sentry_sidekiq_test.yml/badge.svg)](https://github.com/getsentry/sentry-ruby/actions/workflows/sentry_sidekiq_test.yml) | [![Coverage Status](https://img.shields.io/codecov/c/github/getsentry/sentry-ruby/master?logo=codecov)](https://codecov.io/gh/getsentry/sentry-ruby/branch/master) | [![Downloads](https://img.shields.io/gem/dt/sentry-sidekiq.svg)](https://rubygems.org/gems/sentry-sidekiq/) |
21
- | [![Gem Version](https://img.shields.io/gem/v/sentry-delayed_job?label=sentry-delayed_job)](https://rubygems.org/gems/sentry-delayed_job) | [![Build Status](https://github.com/getsentry/sentry-ruby/actions/workflows/sentry_delayed_job_test.yml/badge.svg)](https://github.com/getsentry/sentry-ruby/actions/workflows/sentry_delayed_job_test.yml) | [![Coverage Status](https://img.shields.io/codecov/c/github/getsentry/sentry-ruby/master?logo=codecov)](https://codecov.io/gh/getsentry/sentry-ruby/branch/master) | [![Downloads](https://img.shields.io/gem/dt/sentry-delayed_job.svg)](https://rubygems.org/gems/sentry-delayed_job/) |
22
- | [![Gem Version](https://img.shields.io/gem/v/sentry-resque?label=sentry-resque)](https://rubygems.org/gems/sentry-resque) | [![Build Status](https://github.com/getsentry/sentry-ruby/actions/workflows/sentry_resque_test.yml/badge.svg)](https://github.com/getsentry/sentry-ruby/actions/workflows/sentry_resque_test.yml) | [![Coverage Status](https://img.shields.io/codecov/c/github/getsentry/sentry-ruby/master?logo=codecov)](https://codecov.io/gh/getsentry/sentry-ruby/branch/master) | [![Downloads](https://img.shields.io/gem/dt/sentry-resque.svg)](https://rubygems.org/gems/sentry-resque/) |
23
- | [![Gem Version](https://img.shields.io/gem/v/sentry-opentelemetry?label=sentry-opentelemetry)](https://rubygems.org/gems/sentry-opentelemetry) | [![Build Status](https://github.com/getsentry/sentry-ruby/actions/workflows/sentry_opentelemetry_test.yml/badge.svg)](https://github.com/getsentry/sentry-ruby/actions/workflows/sentry_opentelemetry_test.yml) | [![Coverage Status](https://img.shields.io/codecov/c/github/getsentry/sentry-ruby/master?logo=codecov)](https://codecov.io/gh/getsentry/sentry-ruby/branch/master) | [![Downloads](https://img.shields.io/gem/dt/sentry-opentelemetry.svg)](https://rubygems.org/gems/sentry-opentelemetry/) |
16
+ | Current version | Build | Coverage | API doc |
17
+ | ---------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------- |
18
+ | [![Gem Version](https://img.shields.io/gem/v/sentry-ruby?label=sentry-ruby)](https://rubygems.org/gems/sentry-ruby) | [![Build Status](https://github.com/getsentry/sentry-ruby/actions/workflows/sentry_ruby_test.yml/badge.svg)](https://github.com/getsentry/sentry-ruby/actions/workflows/sentry_ruby_test.yml) | [![Coverage Status](https://img.shields.io/codecov/c/github/getsentry/sentry-ruby/master?logo=codecov)](https://codecov.io/gh/getsentry/sentry-ruby/branch/master) | [![API doc](https://img.shields.io/badge/API%20doc-rubydoc.info-blue)](https://www.rubydoc.info/gems/sentry-ruby) |
19
+ | [![Gem Version](https://img.shields.io/gem/v/sentry-rails?label=sentry-rails)](https://rubygems.org/gems/sentry-rails) | [![Build Status](https://github.com/getsentry/sentry-ruby/actions/workflows/sentry_rails_test.yml/badge.svg)](https://github.com/getsentry/sentry-ruby/actions/workflows/sentry_rails_test.yml) | [![Coverage Status](https://img.shields.io/codecov/c/github/getsentry/sentry-ruby/master?logo=codecov)](https://codecov.io/gh/getsentry/sentry-ruby/branch/master) | [![API doc](https://img.shields.io/badge/API%20doc-rubydoc.info-blue)](https://www.rubydoc.info/gems/sentry-rails) |
20
+ | [![Gem Version](https://img.shields.io/gem/v/sentry-sidekiq?label=sentry-sidekiq)](https://rubygems.org/gems/sentry-sidekiq) | [![Build Status](https://github.com/getsentry/sentry-ruby/actions/workflows/sentry_sidekiq_test.yml/badge.svg)](https://github.com/getsentry/sentry-ruby/actions/workflows/sentry_sidekiq_test.yml) | [![Coverage Status](https://img.shields.io/codecov/c/github/getsentry/sentry-ruby/master?logo=codecov)](https://codecov.io/gh/getsentry/sentry-ruby/branch/master) | [![API doc](https://img.shields.io/badge/API%20doc-rubydoc.info-blue)](https://www.rubydoc.info/gems/sentry-sidekiq) |
21
+ | [![Gem Version](https://img.shields.io/gem/v/sentry-delayed_job?label=sentry-delayed_job)](https://rubygems.org/gems/sentry-delayed_job) | [![Build Status](https://github.com/getsentry/sentry-ruby/actions/workflows/sentry_delayed_job_test.yml/badge.svg)](https://github.com/getsentry/sentry-ruby/actions/workflows/sentry_delayed_job_test.yml) | [![Coverage Status](https://img.shields.io/codecov/c/github/getsentry/sentry-ruby/master?logo=codecov)](https://codecov.io/gh/getsentry/sentry-ruby/branch/master) | [![API doc](https://img.shields.io/badge/API%20doc-rubydoc.info-blue)](https://www.rubydoc.info/gems/sentry-delayed_job) |
22
+ | [![Gem Version](https://img.shields.io/gem/v/sentry-resque?label=sentry-resque)](https://rubygems.org/gems/sentry-resque) | [![Build Status](https://github.com/getsentry/sentry-ruby/actions/workflows/sentry_resque_test.yml/badge.svg)](https://github.com/getsentry/sentry-ruby/actions/workflows/sentry_resque_test.yml) | [![Coverage Status](https://img.shields.io/codecov/c/github/getsentry/sentry-ruby/master?logo=codecov)](https://codecov.io/gh/getsentry/sentry-ruby/branch/master) | [![API doc](https://img.shields.io/badge/API%20doc-rubydoc.info-blue)](https://www.rubydoc.info/gems/sentry-resque) |
23
+ | [![Gem Version](https://img.shields.io/gem/v/sentry-opentelemetry?label=sentry-opentelemetry)](https://rubygems.org/gems/sentry-opentelemetry) | [![Build Status](https://github.com/getsentry/sentry-ruby/actions/workflows/sentry_opentelemetry_test.yml/badge.svg)](https://github.com/getsentry/sentry-ruby/actions/workflows/sentry_opentelemetry_test.yml) | [![Coverage Status](https://img.shields.io/codecov/c/github/getsentry/sentry-ruby/master?logo=codecov)](https://codecov.io/gh/getsentry/sentry-ruby/branch/master) | [![API doc](https://img.shields.io/badge/API%20doc-rubydoc.info-blue)](https://www.rubydoc.info/gems/sentry-opentelemetry) |
24
24
 
25
25
 
26
26
 
@@ -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
 
@@ -103,6 +103,6 @@ To learn more about sampling transactions, please visit the [official documentat
103
103
 
104
104
  * [![Ruby docs](https://img.shields.io/badge/documentation-sentry.io-green.svg?label=ruby%20docs)](https://docs.sentry.io/platforms/ruby/)
105
105
  * [![Forum](https://img.shields.io/badge/forum-sentry-green.svg)](https://forum.sentry.io/c/sdks)
106
- * [![Discord Chat](https://img.shields.io/discord/621778831602221064?logo=discord&logoColor=ffffff&color=7389D8)](https://discord.gg/PXa5Apfe7K)
106
+ * [![Discord Chat](https://img.shields.io/discord/621778831602221064?logo=discord&logoColor=ffffff&color=7389D8)](https://discord.gg/PXa5Apfe7K)
107
107
  * [![Stack Overflow](https://img.shields.io/badge/stack%20overflow-sentry-green.svg)](https://stackoverflow.com/questions/tagged/sentry)
108
108
  * [![Twitter Follow](https://img.shields.io/twitter/follow/getsentry?label=getsentry&style=social)](https://twitter.com/intent/follow?screen_name=getsentry)
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]
data/bin/console CHANGED
@@ -1,6 +1,7 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
3
  require "bundler/setup"
4
+ require "debug"
4
5
  require "sentry-ruby"
5
6
 
6
7
  # You can add fixtures and/or initialization code here to make experimenting
@@ -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,19 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Sentry
4
- class BackpressureMonitor
5
- include LoggingHelper
6
-
4
+ class BackpressureMonitor < ThreadedPeriodicWorker
7
5
  DEFAULT_INTERVAL = 10
8
6
  MAX_DOWNSAMPLE_FACTOR = 10
9
7
 
10
8
  def initialize(configuration, client, interval: DEFAULT_INTERVAL)
11
- @interval = interval
9
+ super(configuration.logger, interval)
12
10
  @client = client
13
- @logger = configuration.logger
14
-
15
- @thread = nil
16
- @exited = false
17
11
 
18
12
  @healthy = true
19
13
  @downsample_factor = 0
@@ -47,29 +41,5 @@ module Sentry
47
41
  log_debug("[BackpressureMonitor] health check negative, downsampling with a factor of #{@downsample_factor}")
48
42
  end
49
43
  end
50
-
51
- def kill
52
- log_debug("[BackpressureMonitor] killing monitor")
53
-
54
- @exited = true
55
- @thread&.kill
56
- end
57
-
58
- private
59
-
60
- def ensure_thread
61
- return if @exited
62
- return if @thread&.alive?
63
-
64
- @thread = Thread.new do
65
- loop do
66
- sleep(@interval)
67
- run
68
- end
69
- end
70
- rescue ThreadError
71
- log_debug("[BackpressureMonitor] Thread creation failed")
72
- @exited = true
73
- end
74
44
  end
75
45
  end
@@ -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
@@ -49,16 +49,17 @@ module Sentry
49
49
  return unless configuration.sending_allowed?
50
50
 
51
51
  if event.is_a?(ErrorEvent) && !configuration.sample_allowed?
52
- transport.record_lost_event(:sample_rate, 'event')
52
+ transport.record_lost_event(:sample_rate, 'error')
53
53
  return
54
54
  end
55
55
 
56
56
  event_type = event.is_a?(Event) ? event.type : event["type"]
57
+ data_category = Envelope::Item.data_category(event_type)
57
58
  event = scope.apply_to_event(event, hint)
58
59
 
59
60
  if event.nil?
60
61
  log_debug("Discarded event because one of the event processors returned nil")
61
- transport.record_lost_event(:event_processor, event_type)
62
+ transport.record_lost_event(:event_processor, data_category)
62
63
  return
63
64
  end
64
65
 
@@ -66,7 +67,7 @@ module Sentry
66
67
  dispatch_async_event(async_block, event, hint)
67
68
  elsif configuration.background_worker_threads != 0 && hint.fetch(:background, true)
68
69
  queued = dispatch_background_event(event, hint)
69
- transport.record_lost_event(:queue_overflow, event_type) unless queued
70
+ transport.record_lost_event(:queue_overflow, data_category) unless queued
70
71
  else
71
72
  send_event(event, hint)
72
73
  end
@@ -77,6 +78,20 @@ module Sentry
77
78
  nil
78
79
  end
79
80
 
81
+ # Capture an envelope directly.
82
+ # @param envelope [Envelope] the envelope to be captured.
83
+ # @return [void]
84
+ def capture_envelope(envelope)
85
+ Sentry.background_worker.perform { send_envelope(envelope) }
86
+ end
87
+
88
+ # Flush pending events to Sentry.
89
+ # @return [void]
90
+ def flush
91
+ transport.flush if configuration.sending_to_dsn_allowed?
92
+ spotlight_transport.flush if spotlight_transport
93
+ end
94
+
80
95
  # Initializes an Event object with the given exception. Returns `nil` if the exception's class is excluded from reporting.
81
96
  # @param exception [Exception] the exception to be reported.
82
97
  # @param hint [Hash] the hint data that'll be passed to `before_send` callback and the scope's event processors.
@@ -88,9 +103,10 @@ module Sentry
88
103
  return if !ignore_exclusions && !@configuration.exception_class_allowed?(exception)
89
104
 
90
105
  integration_meta = Sentry.integrations[hint[:integration]]
106
+ mechanism = hint.delete(:mechanism) { Mechanism.new }
91
107
 
92
108
  ErrorEvent.new(configuration: configuration, integration_meta: integration_meta).tap do |event|
93
- event.add_exception_interface(exception)
109
+ event.add_exception_interface(exception, mechanism: mechanism)
94
110
  event.add_threads_interface(crashed: true)
95
111
  event.level = :error
96
112
  end
@@ -151,13 +167,14 @@ module Sentry
151
167
  # @!macro send_event
152
168
  def send_event(event, hint = nil)
153
169
  event_type = event.is_a?(Event) ? event.type : event["type"]
170
+ data_category = Envelope::Item.data_category(event_type)
154
171
 
155
172
  if event_type != TransactionEvent::TYPE && configuration.before_send
156
173
  event = configuration.before_send.call(event, hint)
157
174
 
158
175
  if event.nil?
159
176
  log_debug("Discarded event because before_send returned nil")
160
- transport.record_lost_event(:before_send, 'event')
177
+ transport.record_lost_event(:before_send, data_category)
161
178
  return
162
179
  end
163
180
  end
@@ -167,18 +184,34 @@ module Sentry
167
184
 
168
185
  if event.nil?
169
186
  log_debug("Discarded event because before_send_transaction returned nil")
170
- transport.record_lost_event(:before_send, 'transaction')
187
+ transport.record_lost_event(:before_send, data_category)
171
188
  return
172
189
  end
173
190
  end
174
191
 
175
- transport.send_event(event)
176
- spotlight_transport&.send_event(event)
192
+ transport.send_event(event) if configuration.sending_to_dsn_allowed?
193
+ spotlight_transport.send_event(event) if spotlight_transport
177
194
 
178
195
  event
179
196
  rescue => e
180
197
  log_error("Event sending failed", e, debug: configuration.debug)
181
- transport.record_lost_event(:network_error, event_type)
198
+ transport.record_lost_event(:network_error, data_category)
199
+ raise
200
+ end
201
+
202
+ # Send an envelope directly to Sentry.
203
+ # @param envelope [Envelope] the envelope to be sent.
204
+ # @return [void]
205
+ def send_envelope(envelope)
206
+ transport.send_envelope(envelope) if configuration.sending_to_dsn_allowed?
207
+ spotlight_transport.send_envelope(envelope) if spotlight_transport
208
+ rescue => e
209
+ log_error("Envelope sending failed", e, debug: configuration.debug)
210
+
211
+ envelope.items.map(&:data_category).each do |data_category|
212
+ transport.record_lost_event(:network_error, data_category)
213
+ end
214
+
182
215
  raise
183
216
  end
184
217
 
@@ -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
@@ -346,7 +351,7 @@ module Sentry
346
351
  def initialize
347
352
  self.app_dirs_pattern = nil
348
353
  self.debug = false
349
- self.background_worker_threads = Concurrent.processor_count
354
+ self.background_worker_threads = (Concurrent.processor_count / 2.0).ceil
350
355
  self.background_worker_max_queue = BackgroundWorker::DEFAULT_MAX_QUEUE
351
356
  self.backtrace_cleanup_callback = nil
352
357
  self.max_breadcrumbs = BreadcrumbBuffer::DEFAULT_SIZE
@@ -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
 
@@ -18,8 +18,25 @@ module Sentry
18
18
  @headers[:type] || 'event'
19
19
  end
20
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' 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
+
21
38
  def to_s
22
- [JSON.generate(@headers), JSON.generate(@payload)].join("\n")
39
+ [JSON.generate(@headers), @payload.is_a?(String) ? @payload : JSON.generate(@payload)].join("\n")
23
40
  end
24
41
 
25
42
  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
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ Sentry.register_patch(:graphql) do |config|
4
+ if defined?(::GraphQL::Schema) && defined?(::GraphQL::Tracing::SentryTrace) && ::GraphQL::Schema.respond_to?(:trace_with)
5
+ ::GraphQL::Schema.trace_with(::GraphQL::Tracing::SentryTrace, set_transaction_name: true)
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.' }
8
+ end
9
+ end
data/lib/sentry/hub.rb CHANGED
@@ -193,7 +193,12 @@ module Sentry
193
193
  elsif custom_scope = options[:scope]
194
194
  scope.update_from_scope(custom_scope)
195
195
  elsif !options.empty?
196
- scope.update_from_options(**options)
196
+ unsupported_option_keys = scope.update_from_options(**options)
197
+
198
+ configuration.log_debug <<~MSG
199
+ Options #{unsupported_option_keys} are not supported and will not be applied to the event.
200
+ You may want to set them under the `extra` option.
201
+ MSG
197
202
  end
198
203
 
199
204
  event = current_client.capture_event(event, scope, hint)
@@ -245,7 +250,7 @@ module Sentry
245
250
  end
246
251
 
247
252
  def with_session_tracking(&block)
248
- return yield unless configuration.auto_session_tracking
253
+ return yield unless configuration.session_tracking?
249
254
 
250
255
  start_session
251
256
  yield
@@ -279,6 +284,12 @@ module Sentry
279
284
  headers
280
285
  end
281
286
 
287
+ def get_trace_propagation_meta
288
+ get_trace_propagation_headers.map do |k, v|
289
+ "<meta name=\"#{k}\" content=\"#{v}\">"
290
+ end.join("\n")
291
+ end
292
+
282
293
  def continue_trace(env, **options)
283
294
  configure_scope { |s| s.generate_propagation_context(env) }
284
295
 
@@ -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