sentry-ruby 5.13.0 → 5.19.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 (67) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +4 -18
  3. data/README.md +20 -10
  4. data/Rakefile +1 -1
  5. data/bin/console +1 -0
  6. data/lib/sentry/attachment.rb +42 -0
  7. data/lib/sentry/background_worker.rb +9 -2
  8. data/lib/sentry/backpressure_monitor.rb +45 -0
  9. data/lib/sentry/backtrace.rb +7 -3
  10. data/lib/sentry/check_in_event.rb +1 -1
  11. data/lib/sentry/client.rb +69 -16
  12. data/lib/sentry/configuration.rb +57 -14
  13. data/lib/sentry/cron/configuration.rb +23 -0
  14. data/lib/sentry/cron/monitor_check_ins.rb +40 -26
  15. data/lib/sentry/cron/monitor_schedule.rb +1 -1
  16. data/lib/sentry/dsn.rb +1 -1
  17. data/lib/sentry/envelope.rb +18 -1
  18. data/lib/sentry/error_event.rb +2 -2
  19. data/lib/sentry/event.rb +13 -39
  20. data/lib/sentry/faraday.rb +77 -0
  21. data/lib/sentry/graphql.rb +9 -0
  22. data/lib/sentry/hub.rb +17 -4
  23. data/lib/sentry/integrable.rb +4 -0
  24. data/lib/sentry/interface.rb +1 -0
  25. data/lib/sentry/interfaces/exception.rb +5 -3
  26. data/lib/sentry/interfaces/mechanism.rb +20 -0
  27. data/lib/sentry/interfaces/request.rb +2 -2
  28. data/lib/sentry/interfaces/single_exception.rb +7 -4
  29. data/lib/sentry/interfaces/stacktrace_builder.rb +8 -0
  30. data/lib/sentry/metrics/aggregator.rb +248 -0
  31. data/lib/sentry/metrics/configuration.rb +47 -0
  32. data/lib/sentry/metrics/counter_metric.rb +25 -0
  33. data/lib/sentry/metrics/distribution_metric.rb +25 -0
  34. data/lib/sentry/metrics/gauge_metric.rb +35 -0
  35. data/lib/sentry/metrics/local_aggregator.rb +53 -0
  36. data/lib/sentry/metrics/metric.rb +19 -0
  37. data/lib/sentry/metrics/set_metric.rb +28 -0
  38. data/lib/sentry/metrics/timing.rb +43 -0
  39. data/lib/sentry/metrics.rb +56 -0
  40. data/lib/sentry/net/http.rb +22 -39
  41. data/lib/sentry/propagation_context.rb +9 -8
  42. data/lib/sentry/puma.rb +1 -1
  43. data/lib/sentry/rack/capture_exceptions.rb +14 -2
  44. data/lib/sentry/rake.rb +3 -14
  45. data/lib/sentry/redis.rb +2 -1
  46. data/lib/sentry/release_detector.rb +1 -1
  47. data/lib/sentry/scope.rb +47 -37
  48. data/lib/sentry/session.rb +2 -2
  49. data/lib/sentry/session_flusher.rb +6 -38
  50. data/lib/sentry/span.rb +40 -5
  51. data/lib/sentry/test_helper.rb +2 -1
  52. data/lib/sentry/threaded_periodic_worker.rb +39 -0
  53. data/lib/sentry/transaction.rb +25 -16
  54. data/lib/sentry/transaction_event.rb +5 -0
  55. data/lib/sentry/transport/configuration.rb +73 -1
  56. data/lib/sentry/transport/http_transport.rb +68 -37
  57. data/lib/sentry/transport/spotlight_transport.rb +50 -0
  58. data/lib/sentry/transport.rb +32 -37
  59. data/lib/sentry/utils/argument_checking_helper.rb +6 -0
  60. data/lib/sentry/utils/http_tracing.rb +41 -0
  61. data/lib/sentry/utils/logging_helper.rb +0 -4
  62. data/lib/sentry/utils/real_ip.rb +1 -1
  63. data/lib/sentry/utils/request_id.rb +1 -1
  64. data/lib/sentry/version.rb +1 -1
  65. data/lib/sentry-ruby.rb +57 -24
  66. data/sentry-ruby.gemspec +12 -5
  67. metadata +42 -7
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1d89549da043b049e2dd6f5c73ac6ec0c3dd98ded247be2035316e45110bde88
4
- data.tar.gz: 38844c5d5521bcf40a749d77667e91fa5b819d98951e4a02ebc807724b91c3a9
3
+ metadata.gz: 03cdc73e6585f6e0e058539db55db290752a040c0535676687b2a5bce3a4a6a4
4
+ data.tar.gz: 9480a3870fce66342cffae3f818f220658d8f8368c46d481a27a9bbba9f3c8a1
5
5
  SHA512:
6
- metadata.gz: 1cc2ac4f8394b8dcafd30d2aa4bbd863e313a04953d5b072997783c54fa76285bff16b77e23ca7f6b22644f9d1a69ad2484c844b7e6bf6deefbf3cbcd874ffbf
7
- data.tar.gz: c30a0fd380e11f93edc599320a3884464e9d0568327512e514d1586f120b217091f67912531167738efb64ffd55cec960c6083dc894556796a00f2f5c79e13f9
6
+ metadata.gz: 00a09f1d1913abc908247cceb99e813137fe45a9521daa429e385d8fd4d2a6e4c1c6d45c264717de5335dde0d4efc4f2aec9bda2cf5131967ba5878bcb596ee7
7
+ data.tar.gz: 1f1236a73ed900dc9d38a5448c154dcf4a66ab55eb490528e214f1b3f393a99eccbaa1d303d9c8a3064060b0447c21d6d59f5fdbbcb843203c0e00a4c18adb89
data/Gemfile CHANGED
@@ -12,27 +12,10 @@ gem "redis", "~> #{redis_rb_version}"
12
12
 
13
13
  gem "puma"
14
14
 
15
- gem "rake", "~> 12.0"
16
- gem "rspec", "~> 3.0"
17
- gem "rspec-retry"
18
15
  gem "timecop"
19
- gem "simplecov"
20
- gem "simplecov-cobertura", "~> 1.4"
21
- gem "rexml"
22
16
  gem "stackprof" unless RUBY_PLATFORM == "java"
23
17
 
24
- ruby_version = Gem::Version.new(RUBY_VERSION)
25
-
26
- if ruby_version >= Gem::Version.new("2.6.0")
27
- gem "debug", github: "ruby/debug", platform: :ruby
28
- gem "irb"
29
-
30
- if ruby_version >= Gem::Version.new("3.0.0")
31
- gem "ruby-lsp-rspec"
32
- end
33
- end
34
-
35
- gem "pry"
18
+ gem "graphql", ">= 2.2.6" if RUBY_VERSION.to_f >= 2.7
36
19
 
37
20
  gem "benchmark-ips"
38
21
  gem "benchmark_driver"
@@ -41,3 +24,6 @@ gem "benchmark-memory"
41
24
 
42
25
  gem "yard", github: "lsegal/yard"
43
26
  gem "webrick"
27
+ gem "faraday"
28
+
29
+ eval_gemfile File.expand_path("../Gemfile", __dir__)
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,16 @@ 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)
109
+
110
+ ## Contributing to the SDK
111
+
112
+ Please make sure to read the [CONTRIBUTING.md](https://github.com/getsentry/sentry-ruby/blob/master/CONTRIBUTING.md) before making a pull request.
113
+
114
+ Thanks to everyone who has contributed to this project so far.
115
+
116
+ <a href="https://github.com/getsentry/sentry-ruby/graphs/contributors">
117
+ <img src="https://contributors-img.web.app/image?repo=getsentry/sentry-ruby" />
118
+ </a>
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
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sentry
4
+ class Attachment
5
+ PathNotFoundError = Class.new(StandardError)
6
+
7
+ attr_reader :bytes, :filename, :path, :content_type
8
+
9
+ def initialize(bytes: nil, filename: nil, content_type: nil, path: nil)
10
+ @bytes = bytes
11
+ @filename = infer_filename(filename, path)
12
+ @path = path
13
+ @content_type = content_type
14
+ end
15
+
16
+ def to_envelope_headers
17
+ { type: 'attachment', filename: filename, content_type: content_type, length: payload.bytesize }
18
+ end
19
+
20
+ def payload
21
+ @payload ||= if bytes
22
+ bytes
23
+ else
24
+ File.binread(path)
25
+ end
26
+ rescue Errno::ENOENT
27
+ raise PathNotFoundError, "Failed to read attachment file, file not found: #{path}"
28
+ end
29
+
30
+ private
31
+
32
+ def infer_filename(filename, path)
33
+ return filename if filename
34
+
35
+ if path
36
+ File.basename(path)
37
+ else
38
+ raise ArgumentError, "filename or path is required"
39
+ end
40
+ end
41
+ end
42
+ end
@@ -13,10 +13,12 @@ module Sentry
13
13
  attr_reader :logger
14
14
  attr_accessor :shutdown_timeout
15
15
 
16
+ DEFAULT_MAX_QUEUE = 30
17
+
16
18
  def initialize(configuration)
17
- @max_queue = 30
18
19
  @shutdown_timeout = 1
19
20
  @number_of_threads = configuration.background_worker_threads
21
+ @max_queue = configuration.background_worker_max_queue
20
22
  @logger = configuration.logger
21
23
  @debug = configuration.debug
22
24
  @shutdown_callback = nil
@@ -29,7 +31,7 @@ module Sentry
29
31
  log_debug("config.background_worker_threads is set to 0, all events will be sent synchronously")
30
32
  Concurrent::ImmediateExecutor.new
31
33
  else
32
- log_debug("Initializing the background worker with #{@number_of_threads} threads")
34
+ log_debug("Initializing the Sentry background worker with #{@number_of_threads} threads")
33
35
 
34
36
  executor = Concurrent::ThreadPoolExecutor.new(
35
37
  min_threads: 0,
@@ -63,6 +65,11 @@ module Sentry
63
65
  @shutdown_callback&.call
64
66
  end
65
67
 
68
+ def full?
69
+ @executor.is_a?(Concurrent::ThreadPoolExecutor) &&
70
+ @executor.remaining_capacity == 0
71
+ end
72
+
66
73
  private
67
74
 
68
75
  def _perform(&block)
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sentry
4
+ class BackpressureMonitor < ThreadedPeriodicWorker
5
+ DEFAULT_INTERVAL = 10
6
+ MAX_DOWNSAMPLE_FACTOR = 10
7
+
8
+ def initialize(configuration, client, interval: DEFAULT_INTERVAL)
9
+ super(configuration.logger, interval)
10
+ @client = client
11
+
12
+ @healthy = true
13
+ @downsample_factor = 0
14
+ end
15
+
16
+ def healthy?
17
+ ensure_thread
18
+ @healthy
19
+ end
20
+
21
+ def downsample_factor
22
+ ensure_thread
23
+ @downsample_factor
24
+ end
25
+
26
+ def run
27
+ check_health
28
+ set_downsample_factor
29
+ end
30
+
31
+ def check_health
32
+ @healthy = !(@client.transport.any_rate_limited? || Sentry.background_worker&.full?)
33
+ end
34
+
35
+ def set_downsample_factor
36
+ if @healthy
37
+ log_debug("[BackpressureMonitor] health check positive, reverting to normal sampling") if @downsample_factor.positive?
38
+ @downsample_factor = 0
39
+ else
40
+ @downsample_factor += 1 if @downsample_factor < MAX_DOWNSAMPLE_FACTOR
41
+ log_debug("[BackpressureMonitor] health check negative, downsampling with a factor of #{@downsample_factor}")
42
+ end
43
+ end
44
+ end
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
@@ -10,6 +10,10 @@ module Sentry
10
10
  # @return [Transport]
11
11
  attr_reader :transport
12
12
 
13
+ # The Transport object that'll send events for the client.
14
+ # @return [SpotlightTransport, nil]
15
+ attr_reader :spotlight_transport
16
+
13
17
  # @!macro configuration
14
18
  attr_reader :configuration
15
19
 
@@ -32,6 +36,8 @@ module Sentry
32
36
  DummyTransport.new(configuration)
33
37
  end
34
38
  end
39
+
40
+ @spotlight_transport = SpotlightTransport.new(configuration) if configuration.spotlight
35
41
  end
36
42
 
37
43
  # Applies the given scope's data to the event and sends it to Sentry.
@@ -42,25 +48,36 @@ module Sentry
42
48
  def capture_event(event, scope, hint = {})
43
49
  return unless configuration.sending_allowed?
44
50
 
45
- unless event.is_a?(TransactionEvent) || configuration.sample_allowed?
46
- transport.record_lost_event(:sample_rate, 'event')
51
+ if event.is_a?(ErrorEvent) && !configuration.sample_allowed?
52
+ transport.record_lost_event(:sample_rate, 'error')
47
53
  return
48
54
  end
49
55
 
50
56
  event_type = event.is_a?(Event) ? event.type : event["type"]
57
+ data_category = Envelope::Item.data_category(event_type)
58
+
59
+ is_transaction = event.is_a?(TransactionEvent)
60
+ spans_before = is_transaction ? event.spans.size : 0
61
+
51
62
  event = scope.apply_to_event(event, hint)
52
63
 
53
64
  if event.nil?
54
- log_info("Discarded event because one of the event processors returned nil")
55
- transport.record_lost_event(:event_processor, event_type)
65
+ log_debug("Discarded event because one of the event processors returned nil")
66
+ transport.record_lost_event(:event_processor, data_category)
67
+ transport.record_lost_event(:event_processor, 'span', num: spans_before + 1) if is_transaction
56
68
  return
69
+ elsif is_transaction
70
+ spans_delta = spans_before - event.spans.size
71
+ transport.record_lost_event(:event_processor, 'span', num: spans_delta) if spans_delta > 0
57
72
  end
58
73
 
59
74
  if async_block = configuration.async
60
75
  dispatch_async_event(async_block, event, hint)
61
76
  elsif configuration.background_worker_threads != 0 && hint.fetch(:background, true)
62
- queued = dispatch_background_event(event, hint)
63
- transport.record_lost_event(:queue_overflow, event_type) unless queued
77
+ unless dispatch_background_event(event, hint)
78
+ transport.record_lost_event(:queue_overflow, data_category)
79
+ transport.record_lost_event(:queue_overflow, 'span', num: spans_before + 1) if is_transaction
80
+ end
64
81
  else
65
82
  send_event(event, hint)
66
83
  end
@@ -71,6 +88,20 @@ module Sentry
71
88
  nil
72
89
  end
73
90
 
91
+ # Capture an envelope directly.
92
+ # @param envelope [Envelope] the envelope to be captured.
93
+ # @return [void]
94
+ def capture_envelope(envelope)
95
+ Sentry.background_worker.perform { send_envelope(envelope) }
96
+ end
97
+
98
+ # Flush pending events to Sentry.
99
+ # @return [void]
100
+ def flush
101
+ transport.flush if configuration.sending_to_dsn_allowed?
102
+ spotlight_transport.flush if spotlight_transport
103
+ end
104
+
74
105
  # Initializes an Event object with the given exception. Returns `nil` if the exception's class is excluded from reporting.
75
106
  # @param exception [Exception] the exception to be reported.
76
107
  # @param hint [Hash] the hint data that'll be passed to `before_send` callback and the scope's event processors.
@@ -82,9 +113,10 @@ module Sentry
82
113
  return if !ignore_exclusions && !@configuration.exception_class_allowed?(exception)
83
114
 
84
115
  integration_meta = Sentry.integrations[hint[:integration]]
116
+ mechanism = hint.delete(:mechanism) { Mechanism.new }
85
117
 
86
118
  ErrorEvent.new(configuration: configuration, integration_meta: integration_meta).tap do |event|
87
- event.add_exception_interface(exception)
119
+ event.add_exception_interface(exception, mechanism: mechanism)
88
120
  event.add_threads_interface(crashed: true)
89
121
  event.level = :error
90
122
  end
@@ -145,13 +177,15 @@ module Sentry
145
177
  # @!macro send_event
146
178
  def send_event(event, hint = nil)
147
179
  event_type = event.is_a?(Event) ? event.type : event["type"]
180
+ data_category = Envelope::Item.data_category(event_type)
181
+ spans_before = event.is_a?(TransactionEvent) ? event.spans.size : 0
148
182
 
149
183
  if event_type != TransactionEvent::TYPE && configuration.before_send
150
184
  event = configuration.before_send.call(event, hint)
151
185
 
152
186
  if event.nil?
153
- log_info("Discarded event because before_send returned nil")
154
- transport.record_lost_event(:before_send, 'event')
187
+ log_debug("Discarded event because before_send returned nil")
188
+ transport.record_lost_event(:before_send, data_category)
155
189
  return
156
190
  end
157
191
  end
@@ -160,22 +194,41 @@ module Sentry
160
194
  event = configuration.before_send_transaction.call(event, hint)
161
195
 
162
196
  if event.nil?
163
- log_info("Discarded event because before_send_transaction returned nil")
197
+ log_debug("Discarded event because before_send_transaction returned nil")
164
198
  transport.record_lost_event(:before_send, 'transaction')
199
+ transport.record_lost_event(:before_send, 'span', num: spans_before + 1)
165
200
  return
201
+ else
202
+ spans_after = event.is_a?(TransactionEvent) ? event.spans.size : 0
203
+ spans_delta = spans_before - spans_after
204
+ transport.record_lost_event(:before_send, 'span', num: spans_delta) if spans_delta > 0
166
205
  end
167
206
  end
168
207
 
169
- transport.send_event(event)
208
+ transport.send_event(event) if configuration.sending_to_dsn_allowed?
209
+ spotlight_transport.send_event(event) if spotlight_transport
170
210
 
171
211
  event
172
212
  rescue => e
173
- loggable_event_type = event_type.capitalize
174
- log_error("#{loggable_event_type} sending failed", e, debug: configuration.debug)
213
+ log_error("Event sending failed", e, debug: configuration.debug)
214
+ transport.record_lost_event(:network_error, data_category)
215
+ transport.record_lost_event(:network_error, 'span', num: spans_before + 1) if event.is_a?(TransactionEvent)
216
+ raise
217
+ end
218
+
219
+ # Send an envelope directly to Sentry.
220
+ # @param envelope [Envelope] the envelope to be sent.
221
+ # @return [void]
222
+ def send_envelope(envelope)
223
+ transport.send_envelope(envelope) if configuration.sending_to_dsn_allowed?
224
+ spotlight_transport.send_envelope(envelope) if spotlight_transport
225
+ rescue => e
226
+ log_error("Envelope sending failed", e, debug: configuration.debug)
227
+
228
+ envelope.items.map(&:data_category).each do |data_category|
229
+ transport.record_lost_event(:network_error, data_category)
230
+ end
175
231
 
176
- event_info = Event.get_log_message(event.to_hash)
177
- log_info("Unreported #{loggable_event_type}: #{event_info}")
178
- transport.record_lost_event(:network_error, event_type)
179
232
  raise
180
233
  end
181
234
 
@@ -7,6 +7,8 @@ require 'sentry/utils/custom_inspection'
7
7
  require "sentry/dsn"
8
8
  require "sentry/release_detector"
9
9
  require "sentry/transport/configuration"
10
+ require "sentry/cron/configuration"
11
+ require "sentry/metrics/configuration"
10
12
  require "sentry/linecache"
11
13
  require "sentry/interfaces/stacktrace_builder"
12
14
 
@@ -40,6 +42,13 @@ module Sentry
40
42
  # @return [Integer]
41
43
  attr_accessor :background_worker_threads
42
44
 
45
+ # The maximum queue size for the background worker.
46
+ # Jobs will be rejected above this limit.
47
+ #
48
+ # Default is {BackgroundWorker::DEFAULT_MAX_QUEUE}.
49
+ # @return [Integer]
50
+ attr_accessor :background_worker_max_queue
51
+
43
52
  # a proc/lambda that takes an array of stack traces
44
53
  # it'll be used to silence (reduce) backtrace of the exception
45
54
  #
@@ -142,6 +151,14 @@ module Sentry
142
151
  # @return [Boolean]
143
152
  attr_accessor :include_local_variables
144
153
 
154
+ # Whether to capture events and traces into Spotlight. Default is false.
155
+ # If you set this to true, Sentry will send events and traces to the local
156
+ # Sidecar proxy at http://localhost:8969/stream.
157
+ # If you want to use a different Sidecar proxy address, set this to String
158
+ # with the proxy URL.
159
+ # @return [Boolean, String]
160
+ attr_accessor :spotlight
161
+
145
162
  # @deprecated Use {#include_local_variables} instead.
146
163
  alias_method :capture_exception_frame_locals, :include_local_variables
147
164
 
@@ -211,10 +228,18 @@ module Sentry
211
228
  # @return [String]
212
229
  attr_accessor :server_name
213
230
 
214
- # Return a Transport::Configuration object for transport-related configurations.
215
- # @return [Transport]
231
+ # Transport related configuration.
232
+ # @return [Transport::Configuration]
216
233
  attr_reader :transport
217
234
 
235
+ # Cron related configuration.
236
+ # @return [Cron::Configuration]
237
+ attr_reader :cron
238
+
239
+ # Metrics related configuration.
240
+ # @return [Metrics::Configuration]
241
+ attr_reader :metrics
242
+
218
243
  # Take a float between 0.0 and 1.0 as the sample rate for tracing events (transactions).
219
244
  # @return [Float, nil]
220
245
  attr_reader :traces_sample_rate
@@ -243,6 +268,12 @@ module Sentry
243
268
  # @return [Boolean]
244
269
  attr_accessor :auto_session_tracking
245
270
 
271
+ # Whether to downsample transactions automatically because of backpressure.
272
+ # Starts a new monitor thread to check health of the SDK every 10 seconds.
273
+ # Default is false
274
+ # @return [Boolean]
275
+ attr_accessor :enable_backpressure_handling
276
+
246
277
  # Allowlist of outgoing request targets to which sentry-trace and baggage headers are attached.
247
278
  # Default is all (/.*/)
248
279
  # @return [Array<String, Regexp>]
@@ -285,11 +316,11 @@ module Sentry
285
316
  'Sinatra::NotFound'
286
317
  ].freeze
287
318
 
288
- RACK_ENV_WHITELIST_DEFAULT = %w(
319
+ RACK_ENV_WHITELIST_DEFAULT = %w[
289
320
  REMOTE_ADDR
290
321
  SERVER_NAME
291
322
  SERVER_PORT
292
- ).freeze
323
+ ].freeze
293
324
 
294
325
  HEROKU_DYNO_METADATA_MESSAGE = "You are running on Heroku but haven't enabled Dyno Metadata. For Sentry's "\
295
326
  "release detection to work correctly, please run `heroku labs:enable runtime-dyno-metadata`".freeze
@@ -302,7 +333,7 @@ module Sentry
302
333
 
303
334
  PROPAGATION_TARGETS_MATCH_ALL = /.*/.freeze
304
335
 
305
- DEFAULT_PATCHES = %i(redis puma http).freeze
336
+ DEFAULT_PATCHES = %i[redis puma http].freeze
306
337
 
307
338
  class << self
308
339
  # Post initialization callbacks are called at the end of initialization process
@@ -311,7 +342,7 @@ module Sentry
311
342
  @post_initialization_callbacks ||= []
312
343
  end
313
344
 
314
- # allow extensions to add their hooks to the Configuration class
345
+ # allow extensions to add their hooks to the Configuration class
315
346
  def add_post_initialization_callback(&block)
316
347
  post_initialization_callbacks << block
317
348
  end
@@ -320,7 +351,8 @@ module Sentry
320
351
  def initialize
321
352
  self.app_dirs_pattern = nil
322
353
  self.debug = false
323
- self.background_worker_threads = Concurrent.processor_count
354
+ self.background_worker_threads = (processor_count / 2.0).ceil
355
+ self.background_worker_max_queue = BackgroundWorker::DEFAULT_MAX_QUEUE
324
356
  self.backtrace_cleanup_callback = nil
325
357
  self.max_breadcrumbs = BreadcrumbBuffer::DEFAULT_SIZE
326
358
  self.breadcrumbs_logger = []
@@ -342,8 +374,10 @@ module Sentry
342
374
  self.skip_rake_integration = false
343
375
  self.send_client_reports = true
344
376
  self.auto_session_tracking = true
377
+ self.enable_backpressure_handling = false
345
378
  self.trusted_proxies = []
346
379
  self.dsn = ENV['SENTRY_DSN']
380
+ self.spotlight = false
347
381
  self.server_name = server_name_from_env
348
382
  self.instrumenter = :sentry
349
383
  self.trace_propagation_targets = [PROPAGATION_TARGETS_MATCH_ALL]
@@ -356,6 +390,8 @@ module Sentry
356
390
  self.enable_tracing = nil
357
391
 
358
392
  @transport = Transport::Configuration.new
393
+ @cron = Cron::Configuration.new
394
+ @metrics = Metrics::Configuration.new
359
395
  @gem_specs = Hash[Gem::Specification.map { |spec| [spec.name, spec.version.to_s] }] if Gem::Specification.respond_to?(:map)
360
396
 
361
397
  run_post_initialization_callbacks
@@ -444,11 +480,15 @@ module Sentry
444
480
 
445
481
  def profiles_sample_rate=(profiles_sample_rate)
446
482
  raise ArgumentError, "profiles_sample_rate must be a Numeric or nil" unless is_numeric_or_nil?(profiles_sample_rate)
447
- log_info("Please make sure to include the 'stackprof' gem in your Gemfile to use Profiling with Sentry.") unless defined?(StackProf)
483
+ log_warn("Please make sure to include the 'stackprof' gem in your Gemfile to use Profiling with Sentry.") unless defined?(StackProf)
448
484
  @profiles_sample_rate = profiles_sample_rate
449
485
  end
450
486
 
451
487
  def sending_allowed?
488
+ spotlight || sending_to_dsn_allowed?
489
+ end
490
+
491
+ def sending_to_dsn_allowed?
452
492
  @errors = []
453
493
 
454
494
  valid? && capture_in_environment?
@@ -460,6 +500,10 @@ module Sentry
460
500
  Random.rand < sample_rate
461
501
  end
462
502
 
503
+ def session_tracking?
504
+ auto_session_tracking && enabled_in_current_env?
505
+ end
506
+
463
507
  def exception_class_allowed?(exc)
464
508
  if exc.is_a?(Sentry::Error)
465
509
  # Try to prevent error reporting loops
@@ -536,12 +580,6 @@ module Sentry
536
580
 
537
581
  private
538
582
 
539
- def check_callable!(name, value)
540
- unless value == nil || value.respond_to?(:call)
541
- raise ArgumentError, "#{name} must be callable (or nil to disable)"
542
- end
543
- end
544
-
545
583
  def init_dsn(dsn_string)
546
584
  return if dsn_string.nil? || dsn_string.empty?
547
585
 
@@ -616,5 +654,10 @@ module Sentry
616
654
  instance_eval(&hook)
617
655
  end
618
656
  end
657
+
658
+ def processor_count
659
+ available_processor_count = Concurrent.available_processor_count if Concurrent.respond_to?(:available_processor_count)
660
+ available_processor_count || Concurrent.processor_count
661
+ end
619
662
  end
620
663
  end