sentry-ruby 5.13.0 → 5.21.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 (81) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +7 -18
  3. data/README.md +20 -10
  4. data/Rakefile +3 -1
  5. data/bin/console +2 -0
  6. data/lib/sentry/attachment.rb +40 -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 +10 -8
  10. data/lib/sentry/baggage.rb +7 -7
  11. data/lib/sentry/breadcrumb/sentry_logger.rb +6 -6
  12. data/lib/sentry/check_in_event.rb +5 -5
  13. data/lib/sentry/client.rb +71 -18
  14. data/lib/sentry/configuration.rb +108 -32
  15. data/lib/sentry/core_ext/object/deep_dup.rb +1 -1
  16. data/lib/sentry/cron/configuration.rb +23 -0
  17. data/lib/sentry/cron/monitor_check_ins.rb +42 -26
  18. data/lib/sentry/cron/monitor_config.rb +1 -1
  19. data/lib/sentry/cron/monitor_schedule.rb +1 -1
  20. data/lib/sentry/dsn.rb +4 -4
  21. data/lib/sentry/envelope/item.rb +88 -0
  22. data/lib/sentry/envelope.rb +2 -68
  23. data/lib/sentry/error_event.rb +2 -2
  24. data/lib/sentry/event.rb +20 -46
  25. data/lib/sentry/faraday.rb +77 -0
  26. data/lib/sentry/graphql.rb +9 -0
  27. data/lib/sentry/hub.rb +25 -5
  28. data/lib/sentry/integrable.rb +4 -0
  29. data/lib/sentry/interface.rb +1 -0
  30. data/lib/sentry/interfaces/exception.rb +5 -3
  31. data/lib/sentry/interfaces/mechanism.rb +20 -0
  32. data/lib/sentry/interfaces/request.rb +7 -7
  33. data/lib/sentry/interfaces/single_exception.rb +10 -7
  34. data/lib/sentry/interfaces/stacktrace.rb +3 -1
  35. data/lib/sentry/interfaces/stacktrace_builder.rb +23 -2
  36. data/lib/sentry/logger.rb +1 -1
  37. data/lib/sentry/metrics/aggregator.rb +248 -0
  38. data/lib/sentry/metrics/configuration.rb +47 -0
  39. data/lib/sentry/metrics/counter_metric.rb +25 -0
  40. data/lib/sentry/metrics/distribution_metric.rb +25 -0
  41. data/lib/sentry/metrics/gauge_metric.rb +35 -0
  42. data/lib/sentry/metrics/local_aggregator.rb +53 -0
  43. data/lib/sentry/metrics/metric.rb +19 -0
  44. data/lib/sentry/metrics/set_metric.rb +28 -0
  45. data/lib/sentry/metrics/timing.rb +43 -0
  46. data/lib/sentry/metrics.rb +56 -0
  47. data/lib/sentry/net/http.rb +22 -39
  48. data/lib/sentry/profiler/helpers.rb +46 -0
  49. data/lib/sentry/profiler.rb +25 -56
  50. data/lib/sentry/propagation_context.rb +10 -9
  51. data/lib/sentry/puma.rb +1 -1
  52. data/lib/sentry/rack/capture_exceptions.rb +16 -4
  53. data/lib/sentry/rack.rb +2 -2
  54. data/lib/sentry/rake.rb +4 -15
  55. data/lib/sentry/redis.rb +2 -1
  56. data/lib/sentry/release_detector.rb +5 -5
  57. data/lib/sentry/scope.rb +48 -37
  58. data/lib/sentry/session.rb +2 -2
  59. data/lib/sentry/session_flusher.rb +7 -39
  60. data/lib/sentry/span.rb +46 -5
  61. data/lib/sentry/test_helper.rb +5 -2
  62. data/lib/sentry/threaded_periodic_worker.rb +39 -0
  63. data/lib/sentry/transaction.rb +27 -18
  64. data/lib/sentry/transaction_event.rb +6 -2
  65. data/lib/sentry/transport/configuration.rb +73 -1
  66. data/lib/sentry/transport/http_transport.rb +72 -41
  67. data/lib/sentry/transport/spotlight_transport.rb +50 -0
  68. data/lib/sentry/transport.rb +36 -41
  69. data/lib/sentry/utils/argument_checking_helper.rb +6 -0
  70. data/lib/sentry/utils/env_helper.rb +21 -0
  71. data/lib/sentry/utils/http_tracing.rb +41 -0
  72. data/lib/sentry/utils/logging_helper.rb +0 -4
  73. data/lib/sentry/utils/real_ip.rb +2 -2
  74. data/lib/sentry/utils/request_id.rb +1 -1
  75. data/lib/sentry/vernier/output.rb +89 -0
  76. data/lib/sentry/vernier/profiler.rb +125 -0
  77. data/lib/sentry/version.rb +1 -1
  78. data/lib/sentry-ruby.rb +61 -27
  79. data/sentry-ruby-core.gemspec +3 -1
  80. data/sentry-ruby.gemspec +15 -6
  81. metadata +47 -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: cf62f77a0a968d4080fac612bf67f996bee72b51e050e17bf8d40c04c9c895ea
4
+ data.tar.gz: 348af55b8f56829cbf5fc5569ddc8308d4cd6ae508480de206443349e1221235
5
5
  SHA512:
6
- metadata.gz: 1cc2ac4f8394b8dcafd30d2aa4bbd863e313a04953d5b072997783c54fa76285bff16b77e23ca7f6b22644f9d1a69ad2484c844b7e6bf6deefbf3cbcd874ffbf
7
- data.tar.gz: c30a0fd380e11f93edc599320a3884464e9d0568327512e514d1586f120b217091f67912531167738efb64ffd55cec960c6083dc894556796a00f2f5c79e13f9
6
+ metadata.gz: 5baaf6d507772d4a3882a99a43b3f4c88ae4ee56ddfd505433dc88b1e1f21430a2b8fa374932ec4b94478e054a9d1baf12789594bcfe9574db94f449b6526fc2
7
+ data.tar.gz: 8eacb9b0e326b529c743af650920b17e382b379032895d112a83689965a53fe42ae48eb9c070cd86085b1d7d5d9cc7300e0d9199b975edc25ee4aca1f093ecd6
data/Gemfile CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  source "https://rubygems.org"
2
4
  git_source(:github) { |name| "https://github.com/#{name}.git" }
3
5
 
@@ -12,27 +14,11 @@ gem "redis", "~> #{redis_rb_version}"
12
14
 
13
15
  gem "puma"
14
16
 
15
- gem "rake", "~> 12.0"
16
- gem "rspec", "~> 3.0"
17
- gem "rspec-retry"
18
17
  gem "timecop"
19
- gem "simplecov"
20
- gem "simplecov-cobertura", "~> 1.4"
21
- gem "rexml"
22
18
  gem "stackprof" unless RUBY_PLATFORM == "java"
19
+ gem "vernier", platforms: :ruby if RUBY_VERSION >= "3.2.1"
23
20
 
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"
21
+ gem "graphql", ">= 2.2.6" if RUBY_VERSION.to_f >= 2.7
36
22
 
37
23
  gem "benchmark-ips"
38
24
  gem "benchmark_driver"
@@ -41,3 +27,6 @@ gem "benchmark-memory"
41
27
 
42
28
  gem "yard", github: "lsegal/yard"
43
29
  gem "webrick"
30
+ gem "faraday"
31
+
32
+ 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
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "rake/clean"
2
4
  CLOBBER.include "pkg"
3
5
 
@@ -17,4 +19,4 @@ task :isolated_specs do
17
19
  end
18
20
  end
19
21
 
20
- task :default => [:spec, :isolated_specs]
22
+ task default: [:spec, :isolated_specs]
data/bin/console CHANGED
@@ -1,6 +1,8 @@
1
1
  #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
2
3
 
3
4
  require "bundler/setup"
5
+ require "debug"
4
6
  require "sentry-ruby"
5
7
 
6
8
  # You can add fixtures and/or initialization code here to make experimenting
@@ -0,0 +1,40 @@
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 = filename || infer_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(path)
33
+ if path
34
+ File.basename(path)
35
+ else
36
+ raise ArgumentError, "filename or path is required"
37
+ end
38
+ end
39
+ end
40
+ 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,11 +12,11 @@ module Sentry
10
12
  RUBY_INPUT_FORMAT = /
11
13
  ^ \s* (?: [a-zA-Z]: | uri:classloader: )? ([^:]+ | <.*>):
12
14
  (\d+)
13
- (?: :in \s `([^']+)')?$
14
- /x.freeze
15
+ (?: :in\s('|`)([^']+)')?$
16
+ /x
15
17
 
16
18
  # org.jruby.runtime.callsite.CachingCallSite.call(CachingCallSite.java:170)
17
- JAVA_INPUT_FORMAT = /^(.+)\.([^\.]+)\(([^\:]+)\:(\d+)\)$/.freeze
19
+ JAVA_INPUT_FORMAT = /^(.+)\.([^\.]+)\(([^\:]+)\:(\d+)\)$/
18
20
 
19
21
  # The file portion of the line (such as app/models/user.rb)
20
22
  attr_reader :file
@@ -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
@@ -76,8 +80,6 @@ module Sentry
76
80
  end
77
81
  end
78
82
 
79
- APP_DIRS_PATTERN = /(bin|exe|app|config|lib|test|spec)/.freeze
80
-
81
83
  # holder for an Array of Backtrace::Line instances
82
84
  attr_reader :lines
83
85
 
@@ -87,7 +89,7 @@ module Sentry
87
89
  ruby_lines = backtrace_cleanup_callback.call(ruby_lines) if backtrace_cleanup_callback
88
90
 
89
91
  in_app_pattern ||= begin
90
- Regexp.new("^(#{project_root}/)?#{app_dirs_pattern || APP_DIRS_PATTERN}")
92
+ Regexp.new("^(#{project_root}/)?#{app_dirs_pattern}")
91
93
  end
92
94
 
93
95
  lines = ruby_lines.to_a.map do |unparsed_line|
@@ -1,12 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'cgi'
3
+ require "cgi"
4
4
 
5
5
  module Sentry
6
6
  # A {https://www.w3.org/TR/baggage W3C Baggage Header} implementation.
7
7
  class Baggage
8
- SENTRY_PREFIX = 'sentry-'
9
- SENTRY_PREFIX_REGEX = /^sentry-/.freeze
8
+ SENTRY_PREFIX = "sentry-"
9
+ SENTRY_PREFIX_REGEX = /^sentry-/
10
10
 
11
11
  # @return [Hash]
12
12
  attr_reader :items
@@ -30,14 +30,14 @@ module Sentry
30
30
  items = {}
31
31
  mutable = true
32
32
 
33
- header.split(',').each do |item|
33
+ header.split(",").each do |item|
34
34
  item = item.strip
35
- key, val = item.split('=')
35
+ key, val = item.split("=")
36
36
 
37
37
  next unless key && val
38
38
  next unless key =~ SENTRY_PREFIX_REGEX
39
39
 
40
- baggage_key = key.split('-')[1]
40
+ baggage_key = key.split("-")[1]
41
41
  next unless baggage_key
42
42
 
43
43
  items[CGI.unescape(baggage_key)] = CGI.unescape(val)
@@ -64,7 +64,7 @@ module Sentry
64
64
  # @return [String]
65
65
  def serialize
66
66
  items = @items.map { |k, v| "#{SENTRY_PREFIX}#{CGI.escape(k)}=#{CGI.escape(v)}" }
67
- items.join(',')
67
+ items.join(",")
68
68
  end
69
69
  end
70
70
  end
@@ -1,16 +1,16 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'logger'
3
+ require "logger"
4
4
 
5
5
  module Sentry
6
6
  class Breadcrumb
7
7
  module SentryLogger
8
8
  LEVELS = {
9
- ::Logger::DEBUG => 'debug',
10
- ::Logger::INFO => 'info',
11
- ::Logger::WARN => 'warn',
12
- ::Logger::ERROR => 'error',
13
- ::Logger::FATAL => 'fatal'
9
+ ::Logger::DEBUG => "debug",
10
+ ::Logger::INFO => "info",
11
+ ::Logger::WARN => "warn",
12
+ ::Logger::ERROR => "error",
13
+ ::Logger::FATAL => "fatal"
14
14
  }.freeze
15
15
 
16
16
  def add(*args, &block)
@@ -1,11 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'securerandom'
4
- require 'sentry/cron/monitor_config'
3
+ require "securerandom"
4
+ require "sentry/cron/monitor_config"
5
5
 
6
6
  module Sentry
7
7
  class CheckInEvent < Event
8
- TYPE = 'check_in'
8
+ TYPE = "check_in"
9
9
 
10
10
  # uuid to identify this check-in.
11
11
  # @return [String]
@@ -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:,
@@ -43,7 +43,7 @@ module Sentry
43
43
  self.status = status
44
44
  self.duration = duration
45
45
  self.monitor_config = monitor_config
46
- self.check_in_id = check_in_id || SecureRandom.uuid.delete('-')
46
+ self.check_in_id = check_in_id || SecureRandom.uuid.delete("-")
47
47
  end
48
48
 
49
49
  # @return [Hash]
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
 
@@ -26,12 +30,14 @@ module Sentry
26
30
  else
27
31
  @transport =
28
32
  case configuration.dsn&.scheme
29
- when 'http', 'https'
33
+ when "http", "https"
30
34
  HTTPTransport.new(configuration)
31
35
  else
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")
164
- transport.record_lost_event(:before_send, 'transaction')
197
+ log_debug("Discarded event because before_send_transaction returned nil")
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