sentry-ruby 5.11.0 → 5.13.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 604c762c416c92d41d143d5800edcc8d4111aa63c91fbec1209f4409ef600c8a
4
- data.tar.gz: ff18479058fdcd44227409610251a2c0b014766be1d0641ac9b83e8badd6489a
3
+ metadata.gz: 1d89549da043b049e2dd6f5c73ac6ec0c3dd98ded247be2035316e45110bde88
4
+ data.tar.gz: 38844c5d5521bcf40a749d77667e91fa5b819d98951e4a02ebc807724b91c3a9
5
5
  SHA512:
6
- metadata.gz: 54899b0d5ab63a546e5c2ea34b821c693ecf7e91b699726630c6baf9badd2b90ca8ba153aad55ea73ae6e7eadfc1625b4c1438965eb9cf52da5f8abc71d0280e
7
- data.tar.gz: 4fdbd60228c2fe23e7db4b4d7d1c2e8841eeec561ef3de01f41c0415b997bc5a5d90cc5fe08e6ccc98eb1352043a25c82e98489cd9720db65967c5a126859d78
6
+ metadata.gz: 1cc2ac4f8394b8dcafd30d2aa4bbd863e313a04953d5b072997783c54fa76285bff16b77e23ca7f6b22644f9d1a69ad2484c844b7e6bf6deefbf3cbcd874ffbf
7
+ data.tar.gz: c30a0fd380e11f93edc599320a3884464e9d0568327512e514d1586f120b217091f67912531167738efb64ffd55cec960c6083dc894556796a00f2f5c79e13f9
data/Gemfile CHANGED
@@ -21,9 +21,15 @@ gem "simplecov-cobertura", "~> 1.4"
21
21
  gem "rexml"
22
22
  gem "stackprof" unless RUBY_PLATFORM == "java"
23
23
 
24
- if RUBY_VERSION.to_f >= 2.6
24
+ ruby_version = Gem::Version.new(RUBY_VERSION)
25
+
26
+ if ruby_version >= Gem::Version.new("2.6.0")
25
27
  gem "debug", github: "ruby/debug", platform: :ruby
26
28
  gem "irb"
29
+
30
+ if ruby_version >= Gem::Version.new("3.0.0")
31
+ gem "ruby-lsp-rspec"
32
+ end
27
33
  end
28
34
 
29
35
  gem "pry"
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/workflows/sentry-ruby%20Test/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/workflows/sentry-rails%20Test/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/workflows/sentry-sidekiq%20Test/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/workflows/sentry-delayed_job%20Test/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/workflows/sentry-resque%20Test/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/workflows/sentry-opentelemetry%20Test/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 | 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/) |
24
24
 
25
25
 
26
26
 
@@ -33,7 +33,7 @@ If you're using `sentry-raven`, we recommend you to migrate to this new SDK. You
33
33
 
34
34
  ## Requirements
35
35
 
36
- We test on Ruby 2.4, 2.5, 2.6, 2.7, 3.0, and 3.1 at the latest patchlevel/teeny version. We also support JRuby 9.0.
36
+ We test from Ruby 2.4 to Ruby 3.2 at the latest patchlevel/teeny version. We also support JRuby 9.0.
37
37
 
38
38
  If you use self-hosted Sentry, please also make sure its version is above `20.6.0`.
39
39
 
@@ -9,7 +9,7 @@ module Sentry
9
9
  # @return [Hash, nil]
10
10
  attr_accessor :data
11
11
  # @return [String, nil]
12
- attr_accessor :level
12
+ attr_reader :level
13
13
  # @return [Time, Integer, nil]
14
14
  attr_accessor :timestamp
15
15
  # @return [String, nil]
@@ -26,10 +26,10 @@ module Sentry
26
26
  def initialize(category: nil, data: nil, message: nil, timestamp: nil, level: nil, type: nil)
27
27
  @category = category
28
28
  @data = data || {}
29
- @level = level
30
29
  @timestamp = timestamp || Sentry.utc_now.to_i
31
30
  @type = type
32
31
  self.message = message
32
+ self.level = level
33
33
  end
34
34
 
35
35
  # @return [Hash]
@@ -50,6 +50,12 @@ module Sentry
50
50
  @message = (message || "").byteslice(0..Event::MAX_MESSAGE_SIZE_IN_BYTES)
51
51
  end
52
52
 
53
+ # @param level [String]
54
+ # @return [void]
55
+ def level=(level) # needed to meet the Sentry spec
56
+ @level = level == "warn" ? "warning" : level
57
+ end
58
+
53
59
  private
54
60
 
55
61
  def serialized_data
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'securerandom'
4
+ require 'sentry/cron/monitor_config'
5
+
6
+ module Sentry
7
+ class CheckInEvent < Event
8
+ TYPE = 'check_in'
9
+
10
+ # uuid to identify this check-in.
11
+ # @return [String]
12
+ attr_accessor :check_in_id
13
+
14
+ # Identifier of the monitor for this check-in.
15
+ # @return [String]
16
+ attr_accessor :monitor_slug
17
+
18
+ # Duration of this check since it has started in seconds.
19
+ # @return [Integer, nil]
20
+ attr_accessor :duration
21
+
22
+ # Monitor configuration to support upserts.
23
+ # @return [Cron::MonitorConfig, nil]
24
+ attr_accessor :monitor_config
25
+
26
+ # Status of this check-in.
27
+ # @return [Symbol]
28
+ attr_accessor :status
29
+
30
+ VALID_STATUSES = %i(ok in_progress error)
31
+
32
+ def initialize(
33
+ slug:,
34
+ status:,
35
+ duration: nil,
36
+ monitor_config: nil,
37
+ check_in_id: nil,
38
+ **options
39
+ )
40
+ super(**options)
41
+
42
+ self.monitor_slug = slug
43
+ self.status = status
44
+ self.duration = duration
45
+ self.monitor_config = monitor_config
46
+ self.check_in_id = check_in_id || SecureRandom.uuid.delete('-')
47
+ end
48
+
49
+ # @return [Hash]
50
+ def to_hash
51
+ data = super
52
+ data[:check_in_id] = check_in_id
53
+ data[:monitor_slug] = monitor_slug
54
+ data[:status] = status
55
+ data[:duration] = duration if duration
56
+ data[:monitor_config] = monitor_config.to_hash if monitor_config
57
+ data
58
+ end
59
+ end
60
+ end
data/lib/sentry/client.rb CHANGED
@@ -104,6 +104,37 @@ module Sentry
104
104
  event
105
105
  end
106
106
 
107
+ # Initializes a CheckInEvent object with the given options.
108
+ #
109
+ # @param slug [String] identifier of this monitor
110
+ # @param status [Symbol] status of this check-in, one of {CheckInEvent::VALID_STATUSES}
111
+ # @param hint [Hash] the hint data that'll be passed to `before_send` callback and the scope's event processors.
112
+ # @param duration [Integer, nil] seconds elapsed since this monitor started
113
+ # @param monitor_config [Cron::MonitorConfig, nil] configuration for this monitor
114
+ # @param check_in_id [String, nil] for updating the status of an existing monitor
115
+ #
116
+ # @return [Event]
117
+ def event_from_check_in(
118
+ slug,
119
+ status,
120
+ hint = {},
121
+ duration: nil,
122
+ monitor_config: nil,
123
+ check_in_id: nil
124
+ )
125
+ return unless configuration.sending_allowed?
126
+
127
+ CheckInEvent.new(
128
+ configuration: configuration,
129
+ integration_meta: Sentry.integrations[hint[:integration]],
130
+ slug: slug,
131
+ status: status,
132
+ duration: duration,
133
+ monitor_config: monitor_config,
134
+ check_in_id: check_in_id
135
+ )
136
+ end
137
+
107
138
  # Initializes an Event object with the given Transaction object.
108
139
  # @param transaction [Transaction] the transaction to be recorded.
109
140
  # @return [TransactionEvent]
@@ -258,6 +258,11 @@ module Sentry
258
258
  # @return [Float, nil]
259
259
  attr_reader :profiles_sample_rate
260
260
 
261
+ # Array of patches to apply.
262
+ # Default is {DEFAULT_PATCHES}
263
+ # @return [Array<Symbol>]
264
+ attr_accessor :enabled_patches
265
+
261
266
  # these are not config options
262
267
  # @!visibility private
263
268
  attr_reader :errors, :gem_specs
@@ -297,6 +302,8 @@ module Sentry
297
302
 
298
303
  PROPAGATION_TARGETS_MATCH_ALL = /.*/.freeze
299
304
 
305
+ DEFAULT_PATCHES = %i(redis puma http).freeze
306
+
300
307
  class << self
301
308
  # Post initialization callbacks are called at the end of initialization process
302
309
  # allowing extending the configuration of sentry-ruby by multiple extensions
@@ -340,6 +347,7 @@ module Sentry
340
347
  self.server_name = server_name_from_env
341
348
  self.instrumenter = :sentry
342
349
  self.trace_propagation_targets = [PROPAGATION_TARGETS_MATCH_ALL]
350
+ self.enabled_patches = DEFAULT_PATCHES.dup
343
351
 
344
352
  self.before_send = nil
345
353
  self.before_send_transaction = nil
@@ -0,0 +1,61 @@
1
+ module Sentry
2
+ module Cron
3
+ module MonitorCheckIns
4
+ module Patch
5
+ def perform(*args)
6
+ slug = self.class.sentry_monitor_slug || self.class.name
7
+ monitor_config = self.class.sentry_monitor_config
8
+
9
+ check_in_id = Sentry.capture_check_in(slug,
10
+ :in_progress,
11
+ monitor_config: monitor_config)
12
+
13
+ start = Sentry.utc_now.to_i
14
+ ret = super
15
+ duration = Sentry.utc_now.to_i - start
16
+
17
+ Sentry.capture_check_in(slug,
18
+ :ok,
19
+ check_in_id: check_in_id,
20
+ duration: duration,
21
+ monitor_config: monitor_config)
22
+
23
+ ret
24
+ rescue Exception
25
+ duration = Sentry.utc_now.to_i - start
26
+
27
+ Sentry.capture_check_in(slug,
28
+ :error,
29
+ check_in_id: check_in_id,
30
+ duration: duration,
31
+ monitor_config: monitor_config)
32
+
33
+ raise
34
+ end
35
+ end
36
+
37
+ module ClassMethods
38
+ def sentry_monitor_check_ins(slug: nil, monitor_config: nil)
39
+ @sentry_monitor_slug = slug
40
+ @sentry_monitor_config = monitor_config
41
+
42
+ prepend Patch
43
+ end
44
+
45
+ def sentry_monitor_slug
46
+ @sentry_monitor_slug
47
+ end
48
+
49
+ def sentry_monitor_config
50
+ @sentry_monitor_config
51
+ end
52
+ end
53
+
54
+ extend ClassMethods
55
+
56
+ def self.included(base)
57
+ base.extend(ClassMethods)
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'sentry/cron/monitor_schedule'
4
+
5
+ module Sentry
6
+ module Cron
7
+ class MonitorConfig
8
+ # The monitor schedule configuration
9
+ # @return [MonitorSchedule::Crontab, MonitorSchedule::Interval]
10
+ attr_accessor :schedule
11
+
12
+ # How long (in minutes) after the expected checkin time will we wait
13
+ # until we consider the checkin to have been missed.
14
+ # @return [Integer, nil]
15
+ attr_accessor :checkin_margin
16
+
17
+ # How long (in minutes) is the checkin allowed to run for in in_progress
18
+ # before it is considered failed.
19
+ # @return [Integer, nil]
20
+ attr_accessor :max_runtime
21
+
22
+ # tz database style timezone string
23
+ # @return [String, nil]
24
+ attr_accessor :timezone
25
+
26
+ def initialize(schedule, checkin_margin: nil, max_runtime: nil, timezone: nil)
27
+ @schedule = schedule
28
+ @checkin_margin = checkin_margin
29
+ @max_runtime = max_runtime
30
+ @timezone = timezone
31
+ end
32
+
33
+ def self.from_crontab(crontab, **options)
34
+ new(MonitorSchedule::Crontab.new(crontab), **options)
35
+ end
36
+
37
+ def self.from_interval(num, unit, **options)
38
+ return nil unless MonitorSchedule::Interval::VALID_UNITS.include?(unit)
39
+
40
+ new(MonitorSchedule::Interval.new(num, unit), **options)
41
+ end
42
+
43
+ def to_hash
44
+ {
45
+ schedule: schedule.to_hash,
46
+ checkin_margin: checkin_margin,
47
+ max_runtime: max_runtime,
48
+ timezone: timezone
49
+ }.compact
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sentry
4
+ module Cron
5
+ module MonitorSchedule
6
+ class Crontab
7
+ # A crontab formatted string such as "0 * * * *".
8
+ # @return [String]
9
+ attr_accessor :value
10
+
11
+ def initialize(value)
12
+ @value = value
13
+ end
14
+
15
+ def to_hash
16
+ { type: :crontab, value: value }
17
+ end
18
+ end
19
+
20
+ class Interval
21
+ # The number representing duration of the interval.
22
+ # @return [Integer]
23
+ attr_accessor :value
24
+
25
+ # The unit representing duration of the interval.
26
+ # @return [Symbol]
27
+ attr_accessor :unit
28
+
29
+ VALID_UNITS = %i(year month week day hour minute)
30
+
31
+ def initialize(value, unit)
32
+ @value = value
33
+ @unit = unit
34
+ end
35
+
36
+ def to_hash
37
+ { type: :interval, value: value, unit: unit }
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
@@ -5,7 +5,7 @@ module Sentry
5
5
  class Envelope
6
6
  class Item
7
7
  STACKTRACE_FRAME_LIMIT_ON_OVERSIZED_PAYLOAD = 500
8
- MAX_SERIALIZED_PAYLOAD_SIZE = 1024 * 200
8
+ MAX_SERIALIZED_PAYLOAD_SIZE = 1024 * 1000
9
9
 
10
10
  attr_accessor :headers, :payload
11
11
 
data/lib/sentry/hub.rb CHANGED
@@ -156,6 +156,30 @@ module Sentry
156
156
  capture_event(event, **options, &block)
157
157
  end
158
158
 
159
+ def capture_check_in(slug, status, **options, &block)
160
+ check_argument_type!(slug, ::String)
161
+ check_argument_includes!(status, Sentry::CheckInEvent::VALID_STATUSES)
162
+
163
+ return unless current_client
164
+
165
+ options[:hint] ||= {}
166
+ options[:hint][:slug] = slug
167
+
168
+ event = current_client.event_from_check_in(
169
+ slug,
170
+ status,
171
+ options[:hint],
172
+ duration: options.delete(:duration),
173
+ monitor_config: options.delete(:monitor_config),
174
+ check_in_id: options.delete(:check_in_id)
175
+ )
176
+
177
+ return unless event
178
+
179
+ capture_event(event, **options, &block)
180
+ event.check_in_id
181
+ end
182
+
159
183
  def capture_event(event, **options, &block)
160
184
  check_argument_type!(event, Sentry::Event)
161
185
 
@@ -178,7 +202,7 @@ module Sentry
178
202
  configuration.log_debug(event.to_json_compatible)
179
203
  end
180
204
 
181
- @last_event_id = event&.event_id unless event.is_a?(Sentry::TransactionEvent)
205
+ @last_event_id = event&.event_id if event.is_a?(Sentry::ErrorEvent)
182
206
  event
183
207
  end
184
208
 
@@ -22,5 +22,11 @@ module Sentry
22
22
  options[:hint][:integration] = integration_name
23
23
  Sentry.capture_message(message, **options, &block)
24
24
  end
25
+
26
+ def capture_check_in(slug, status, **options, &block)
27
+ options[:hint] ||= {}
28
+ options[:hint][:integration] = integration_name
29
+ Sentry.capture_check_in(slug, status, **options, &block)
30
+ end
25
31
  end
26
32
  end
@@ -23,7 +23,7 @@ module Sentry
23
23
  exception.message || ""
24
24
  end
25
25
 
26
- @value = exception_message.byteslice(0..Event::MAX_MESSAGE_SIZE_IN_BYTES)
26
+ @value = Utils::EncodingHelper.encode_to_utf_8(exception_message.byteslice(0..Event::MAX_MESSAGE_SIZE_IN_BYTES))
27
27
 
28
28
  @module = exception.class.to_s.split('::')[0...-1].join('::')
29
29
  @thread_id = Thread.current.object_id
@@ -51,7 +51,7 @@ module Sentry
51
51
  v = v.byteslice(0..MAX_LOCAL_BYTES - 1) + OMISSION_MARK
52
52
  end
53
53
 
54
- v
54
+ Utils::EncodingHelper.encode_to_utf_8(v)
55
55
  rescue StandardError
56
56
  PROBLEMATIC_LOCAL_VALUE_REPLACEMENT
57
57
  end
@@ -99,4 +99,4 @@ module Sentry
99
99
  end
100
100
  end
101
101
 
102
- Sentry.register_patch(Sentry::Net::HTTP, Net::HTTP)
102
+ Sentry.register_patch(:http, Sentry::Net::HTTP, Net::HTTP)
@@ -9,6 +9,7 @@ module Sentry
9
9
  # 101 Hz in microseconds
10
10
  DEFAULT_INTERVAL = 1e6 / 101
11
11
  MICRO_TO_NANO_SECONDS = 1e3
12
+ MIN_SAMPLES_REQUIRED = 2
12
13
 
13
14
  attr_reader :sampled, :started, :event_id
14
15
 
@@ -73,14 +74,19 @@ module Sentry
73
74
  end
74
75
 
75
76
  def to_hash
76
- return {} unless @sampled
77
+ unless @sampled
78
+ record_lost_event(:sample_rate)
79
+ return {}
80
+ end
81
+
77
82
  return {} unless @started
78
83
 
79
84
  results = StackProf.results
80
- return {} unless results
81
- return {} if results.empty?
82
- return {} if results[:samples] == 0
83
- return {} unless results[:raw]
85
+
86
+ if !results || results.empty? || results[:samples] == 0 || !results[:raw]
87
+ record_lost_event(:insufficient_data)
88
+ return {}
89
+ end
84
90
 
85
91
  frame_map = {}
86
92
 
@@ -103,7 +109,7 @@ module Sentry
103
109
  }
104
110
 
105
111
  frame_hash[:module] = mod if mod
106
- frame_hash[:lineno] = frame_data[:line] if frame_data[:line]
112
+ frame_hash[:lineno] = frame_data[:line] if frame_data[:line] && frame_data[:line] >= 0
107
113
 
108
114
  frame_hash
109
115
  end
@@ -157,8 +163,9 @@ module Sentry
157
163
 
158
164
  log('Some samples thrown away') if samples.size != results[:samples]
159
165
 
160
- if samples.size <= 2
166
+ if samples.size <= MIN_SAMPLES_REQUIRED
161
167
  log('Not enough samples, discarding profiler')
168
+ record_lost_event(:insufficient_data)
162
169
  return {}
163
170
  end
164
171
 
@@ -218,5 +225,9 @@ module Sentry
218
225
 
219
226
  [function, mod]
220
227
  end
228
+
229
+ def record_lost_event(reason)
230
+ Sentry.get_current_client&.transport&.record_lost_event(reason, 'profile')
231
+ end
221
232
  end
222
233
  end
data/lib/sentry/puma.rb CHANGED
@@ -1,10 +1,19 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ return unless defined?(Puma::Server)
4
+
3
5
  module Sentry
4
6
  module Puma
5
7
  module Server
8
+ PUMA_4_AND_PRIOR = Gem::Version.new(::Puma::Const::PUMA_VERSION) < Gem::Version.new("5.0.0")
9
+
6
10
  def lowlevel_error(e, env, status=500)
7
- result = super
11
+ result =
12
+ if PUMA_4_AND_PRIOR
13
+ super(e, env)
14
+ else
15
+ super
16
+ end
8
17
 
9
18
  begin
10
19
  Sentry.capture_exception(e) do |scope|
@@ -20,6 +29,4 @@ module Sentry
20
29
  end
21
30
  end
22
31
 
23
- if defined?(Puma::Server)
24
- Sentry.register_patch(Sentry::Puma::Server, Puma::Server)
25
- end
32
+ Sentry.register_patch(:puma, Sentry::Puma::Server, Puma::Server)
data/lib/sentry/redis.rb CHANGED
@@ -99,8 +99,10 @@ end
99
99
 
100
100
  if defined?(::Redis::Client)
101
101
  if Gem::Version.new(::Redis::VERSION) < Gem::Version.new("5.0")
102
- Sentry.register_patch(Sentry::Redis::OldClientPatch, ::Redis::Client)
102
+ Sentry.register_patch(:redis, Sentry::Redis::OldClientPatch, ::Redis::Client)
103
103
  elsif defined?(RedisClient)
104
- RedisClient.register(Sentry::Redis::GlobalRedisInstrumentation)
104
+ Sentry.register_patch(:redis) do
105
+ RedisClient.register(Sentry::Redis::GlobalRedisInstrumentation)
106
+ end
105
107
  end
106
108
  end
data/lib/sentry/span.rb CHANGED
@@ -11,7 +11,7 @@ module Sentry
11
11
  URL = "url"
12
12
  HTTP_STATUS_CODE = "http.response.status_code"
13
13
  HTTP_QUERY = "http.query"
14
- HTTP_METHOD = "http.method"
14
+ HTTP_METHOD = "http.request.method"
15
15
 
16
16
  # An identifier for the database management system (DBMS) product being used.
17
17
  # Example: postgresql
@@ -14,24 +14,28 @@ module Sentry
14
14
  # @return [void]
15
15
  def setup_sentry_test(&block)
16
16
  raise "please make sure the SDK is initialized for testing" unless Sentry.initialized?
17
- copied_config = Sentry.configuration.dup
17
+ dummy_config = Sentry.configuration.dup
18
18
  # configure dummy DSN, so the events will not be sent to the actual service
19
- copied_config.dsn = DUMMY_DSN
19
+ dummy_config.dsn = DUMMY_DSN
20
20
  # set transport to DummyTransport, so we can easily intercept the captured events
21
- copied_config.transport.transport_class = Sentry::DummyTransport
21
+ dummy_config.transport.transport_class = Sentry::DummyTransport
22
22
  # make sure SDK allows sending under the current environment
23
- copied_config.enabled_environments << copied_config.environment unless copied_config.enabled_environments.include?(copied_config.environment)
23
+ dummy_config.enabled_environments << dummy_config.environment unless dummy_config.enabled_environments.include?(dummy_config.environment)
24
24
  # disble async event sending
25
- copied_config.background_worker_threads = 0
25
+ dummy_config.background_worker_threads = 0
26
26
 
27
27
  # user can overwrite some of the configs, with a few exceptions like:
28
28
  # - include_local_variables
29
29
  # - auto_session_tracking
30
- block&.call(copied_config)
30
+ block&.call(dummy_config)
31
31
 
32
- test_client = Sentry::Client.new(copied_config)
32
+ # the base layer's client should already use the dummy config so nothing will be sent by accident
33
+ base_client = Sentry::Client.new(dummy_config)
34
+ Sentry.get_current_hub.bind_client(base_client)
35
+ # create a new layer so mutations made to the testing scope or configuration could be simply popped later
36
+ Sentry.get_current_hub.push_scope
37
+ test_client = Sentry::Client.new(dummy_config.dup)
33
38
  Sentry.get_current_hub.bind_client(test_client)
34
- Sentry.get_current_scope.clear
35
39
  end
36
40
 
37
41
  # Clears all stored events and envelopes.
@@ -40,9 +44,12 @@ module Sentry
40
44
  def teardown_sentry_test
41
45
  return unless Sentry.initialized?
42
46
 
43
- sentry_transport.events = []
44
- sentry_transport.envelopes = []
45
- Sentry.get_current_scope.clear
47
+ # pop testing layer created by `setup_sentry_test`
48
+ # but keep the base layer to avoid nil-pointer errors
49
+ # TODO: find a way to notify users if they somehow popped the test layer before calling this method
50
+ if Sentry.get_current_hub.instance_variable_get(:@stack).size > 1
51
+ Sentry.get_current_hub.pop_scope
52
+ end
46
53
  end
47
54
 
48
55
  # @return [Transport]
@@ -75,4 +82,3 @@ module Sentry
75
82
  end
76
83
  end
77
84
  end
78
-
@@ -18,7 +18,8 @@ module Sentry
18
18
  :network_error,
19
19
  :sample_rate,
20
20
  :before_send,
21
- :event_processor
21
+ :event_processor,
22
+ :insufficient_data
22
23
  ]
23
24
 
24
25
  include LoggingHelper
@@ -143,7 +144,7 @@ module Sentry
143
144
  sent_at: Sentry.utc_now.iso8601
144
145
  }
145
146
 
146
- if event.is_a?(TransactionEvent) && event.dynamic_sampling_context
147
+ if event.is_a?(Event) && event.dynamic_sampling_context
147
148
  envelope_headers[:trace] = event.dynamic_sampling_context
148
149
  end
149
150
 
@@ -185,7 +186,7 @@ module Sentry
185
186
  reason, type = key
186
187
 
187
188
  # 'event' has to be mapped to 'error'
188
- category = type == 'transaction' ? 'transaction' : 'error'
189
+ category = type == 'event' ? 'error' : type
189
190
 
190
191
  { reason: reason, category: category, quantity: val }
191
192
  end
@@ -9,5 +9,11 @@ module Sentry
9
9
  raise ArgumentError, "expect the argument to be a #{expected_types.join(' or ')}, got #{argument.class} (#{argument.inspect})"
10
10
  end
11
11
  end
12
+
13
+ def check_argument_includes!(argument, values)
14
+ unless values.include?(argument)
15
+ raise ArgumentError, "expect the argument to be one of #{values.map(&:inspect).join(' or ')}, got #{argument.inspect}"
16
+ end
17
+ end
12
18
  end
13
19
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Sentry
4
- VERSION = "5.11.0"
4
+ VERSION = "5.13.0"
5
5
  end
data/lib/sentry-ruby.rb CHANGED
@@ -15,11 +15,13 @@ require "sentry/logger"
15
15
  require "sentry/event"
16
16
  require "sentry/error_event"
17
17
  require "sentry/transaction_event"
18
+ require "sentry/check_in_event"
18
19
  require "sentry/span"
19
20
  require "sentry/transaction"
20
21
  require "sentry/hub"
21
22
  require "sentry/background_worker"
22
23
  require "sentry/session_flusher"
24
+ require "sentry/cron/monitor_check_ins"
23
25
 
24
26
  [
25
27
  "sentry/rake",
@@ -73,15 +75,15 @@ module Sentry
73
75
  ##### Patch Registration #####
74
76
 
75
77
  # @!visibility private
76
- def register_patch(patch = nil, target = nil, &block)
78
+ def register_patch(key, patch = nil, target = nil, &block)
77
79
  if patch && block
78
80
  raise ArgumentError.new("Please provide either a patch and its target OR a block, but not both")
79
81
  end
80
82
 
81
83
  if block
82
- registered_patches << block
84
+ registered_patches[key] = block
83
85
  else
84
- registered_patches << proc do
86
+ registered_patches[key] = proc do
85
87
  target.send(:prepend, patch) unless target.ancestors.include?(patch)
86
88
  end
87
89
  end
@@ -89,14 +91,14 @@ module Sentry
89
91
 
90
92
  # @!visibility private
91
93
  def apply_patches(config)
92
- registered_patches.each do |patch|
93
- patch.call(config)
94
+ registered_patches.each do |key, patch|
95
+ patch.call(config) if config.enabled_patches.include?(key)
94
96
  end
95
97
  end
96
98
 
97
99
  # @!visibility private
98
100
  def registered_patches
99
- @registered_patches ||= []
101
+ @registered_patches ||= {}
100
102
  end
101
103
 
102
104
  ##### Integrations #####
@@ -430,6 +432,24 @@ module Sentry
430
432
  get_current_hub.capture_event(event)
431
433
  end
432
434
 
435
+ # Captures a check-in and sends it to Sentry via the currently active hub.
436
+ #
437
+ # @param slug [String] identifier of this monitor
438
+ # @param status [Symbol] status of this check-in, one of {CheckInEvent::VALID_STATUSES}
439
+ #
440
+ # @param [Hash] options extra check-in options
441
+ # @option options [String] check_in_id for updating the status of an existing monitor
442
+ # @option options [Integer] duration seconds elapsed since this monitor started
443
+ # @option options [Cron::MonitorConfig] monitor_config configuration for this monitor
444
+ #
445
+ # @yieldparam scope [Scope]
446
+ #
447
+ # @return [String, nil] The {CheckInEvent#check_in_id} to use for later updates on the same slug
448
+ def capture_check_in(slug, status, **options, &block)
449
+ return unless initialized?
450
+ get_current_hub.capture_check_in(slug, status, **options, &block)
451
+ end
452
+
433
453
  # Takes or initializes a new Sentry::Transaction and makes a sampling decision for it.
434
454
  #
435
455
  # @return [Transaction, nil]
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sentry-ruby
3
3
  version: !ruby/object:Gem::Version
4
- version: 5.11.0
4
+ version: 5.13.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sentry Team
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-09-06 00:00:00.000000000 Z
11
+ date: 2023-11-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: concurrent-ruby
@@ -56,10 +56,14 @@ files:
56
56
  - lib/sentry/breadcrumb.rb
57
57
  - lib/sentry/breadcrumb/sentry_logger.rb
58
58
  - lib/sentry/breadcrumb_buffer.rb
59
+ - lib/sentry/check_in_event.rb
59
60
  - lib/sentry/client.rb
60
61
  - lib/sentry/configuration.rb
61
62
  - lib/sentry/core_ext/object/deep_dup.rb
62
63
  - lib/sentry/core_ext/object/duplicable.rb
64
+ - lib/sentry/cron/monitor_check_ins.rb
65
+ - lib/sentry/cron/monitor_config.rb
66
+ - lib/sentry/cron/monitor_schedule.rb
63
67
  - lib/sentry/dsn.rb
64
68
  - lib/sentry/envelope.rb
65
69
  - lib/sentry/error_event.rb