sentry-ruby 5.17.1 → 5.17.3

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: 7ca3eee69c858f7d40c18a030c56f4c51539e6f2795223f6a83450f94e8c2984
4
- data.tar.gz: 8289b57931ebf5430eb399ed320a91372fea15c70e1603d45a6f5b49cba9cc16
3
+ metadata.gz: 917e0ed267815d8daeeb5f731a14642be3b901a1a09a9341cc6ca58750094496
4
+ data.tar.gz: db84b44742d0e847b6fd66dac5a3338e63d4497723979b7c71fbbea46eb50480
5
5
  SHA512:
6
- metadata.gz: 5cffbfa857b8dc671c9883b62cff1142abd39f83e0ac630c3a2843bf212486ed0b58318e4375065b4c36826394255e83ef02dc4828ff87f641fe7aacb742166d
7
- data.tar.gz: f38446fd3df90e81687388c0eb26560f158e883e2040083fdd288e8e64a7ebbd796033357f0101dc9b71ba81f1f66e7297c42f3b593988aa7c5052fe57bce905
6
+ metadata.gz: 75e7822da3cf46b8faf9fd3a2528cd56ad8f3d8d38b7e21d178f257695409d07c2e7d643dbc48b09f818d6324c3f96d083d03b8ac3faa00e4fff0a8ea2dcdfc3
7
+ data.tar.gz: 1348bd6636f8283af3d85934ffe414606072758939cc0027c70730f8711f933ab72d9ee23d49cd921ca3562dcd4a034f93255fe5f3eb257a0e4288815be840e5
data/lib/sentry/client.rb CHANGED
@@ -49,16 +49,17 @@ module Sentry
49
49
  return unless configuration.sending_allowed?
50
50
 
51
51
  if event.is_a?(ErrorEvent) && !configuration.sample_allowed?
52
- transport.record_lost_event(:sample_rate, 'event')
52
+ transport.record_lost_event(:sample_rate, 'error')
53
53
  return
54
54
  end
55
55
 
56
56
  event_type = event.is_a?(Event) ? event.type : event["type"]
57
+ data_category = Envelope::Item.data_category(event_type)
57
58
  event = scope.apply_to_event(event, hint)
58
59
 
59
60
  if event.nil?
60
61
  log_debug("Discarded event because one of the event processors returned nil")
61
- transport.record_lost_event(:event_processor, event_type)
62
+ transport.record_lost_event(:event_processor, data_category)
62
63
  return
63
64
  end
64
65
 
@@ -66,7 +67,7 @@ module Sentry
66
67
  dispatch_async_event(async_block, event, hint)
67
68
  elsif configuration.background_worker_threads != 0 && hint.fetch(:background, true)
68
69
  queued = dispatch_background_event(event, hint)
69
- transport.record_lost_event(:queue_overflow, event_type) unless queued
70
+ transport.record_lost_event(:queue_overflow, data_category) unless queued
70
71
  else
71
72
  send_event(event, hint)
72
73
  end
@@ -77,6 +78,20 @@ module Sentry
77
78
  nil
78
79
  end
79
80
 
81
+ # Capture an envelope directly.
82
+ # @param envelope [Envelope] the envelope to be captured.
83
+ # @return [void]
84
+ def capture_envelope(envelope)
85
+ Sentry.background_worker.perform { send_envelope(envelope) }
86
+ end
87
+
88
+ # Flush pending events to Sentry.
89
+ # @return [void]
90
+ def flush
91
+ transport.flush if configuration.sending_to_dsn_allowed?
92
+ spotlight_transport.flush if spotlight_transport
93
+ end
94
+
80
95
  # Initializes an Event object with the given exception. Returns `nil` if the exception's class is excluded from reporting.
81
96
  # @param exception [Exception] the exception to be reported.
82
97
  # @param hint [Hash] the hint data that'll be passed to `before_send` callback and the scope's event processors.
@@ -88,9 +103,10 @@ module Sentry
88
103
  return if !ignore_exclusions && !@configuration.exception_class_allowed?(exception)
89
104
 
90
105
  integration_meta = Sentry.integrations[hint[:integration]]
106
+ mechanism = hint.delete(:mechanism) { Mechanism.new }
91
107
 
92
108
  ErrorEvent.new(configuration: configuration, integration_meta: integration_meta).tap do |event|
93
- event.add_exception_interface(exception)
109
+ event.add_exception_interface(exception, mechanism: mechanism)
94
110
  event.add_threads_interface(crashed: true)
95
111
  event.level = :error
96
112
  end
@@ -151,13 +167,14 @@ module Sentry
151
167
  # @!macro send_event
152
168
  def send_event(event, hint = nil)
153
169
  event_type = event.is_a?(Event) ? event.type : event["type"]
170
+ data_category = Envelope::Item.data_category(event_type)
154
171
 
155
172
  if event_type != TransactionEvent::TYPE && configuration.before_send
156
173
  event = configuration.before_send.call(event, hint)
157
174
 
158
175
  if event.nil?
159
176
  log_debug("Discarded event because before_send returned nil")
160
- transport.record_lost_event(:before_send, 'event')
177
+ transport.record_lost_event(:before_send, data_category)
161
178
  return
162
179
  end
163
180
  end
@@ -167,7 +184,7 @@ module Sentry
167
184
 
168
185
  if event.nil?
169
186
  log_debug("Discarded event because before_send_transaction returned nil")
170
- transport.record_lost_event(:before_send, 'transaction')
187
+ transport.record_lost_event(:before_send, data_category)
171
188
  return
172
189
  end
173
190
  end
@@ -178,7 +195,23 @@ module Sentry
178
195
  event
179
196
  rescue => e
180
197
  log_error("Event sending failed", e, debug: configuration.debug)
181
- transport.record_lost_event(:network_error, event_type)
198
+ transport.record_lost_event(:network_error, data_category)
199
+ raise
200
+ end
201
+
202
+ # Send an envelope directly to Sentry.
203
+ # @param envelope [Envelope] the envelope to be sent.
204
+ # @return [void]
205
+ def send_envelope(envelope)
206
+ transport.send_envelope(envelope) if configuration.sending_to_dsn_allowed?
207
+ spotlight_transport.send_envelope(envelope) if spotlight_transport
208
+ rescue => e
209
+ log_error("Envelope sending failed", e, debug: configuration.debug)
210
+
211
+ envelope.items.map(&:data_category).each do |data_category|
212
+ transport.record_lost_event(:network_error, data_category)
213
+ end
214
+
182
215
  raise
183
216
  end
184
217
 
@@ -18,6 +18,23 @@ module Sentry
18
18
  @headers[:type] || 'event'
19
19
  end
20
20
 
21
+ # rate limits and client reports use the data_category rather than envelope item type
22
+ def self.data_category(type)
23
+ case type
24
+ when 'session', 'attachment', 'transaction', 'profile' then type
25
+ when 'sessions' then 'session'
26
+ when 'check_in' then 'monitor'
27
+ when 'statsd', 'metric_meta' then 'metric_bucket'
28
+ when 'event' then 'error'
29
+ when 'client_report' then 'internal'
30
+ else 'default'
31
+ end
32
+ end
33
+
34
+ def data_category
35
+ self.class.data_category(type)
36
+ end
37
+
21
38
  def to_s
22
39
  [JSON.generate(@headers), @payload.is_a?(String) ? @payload : JSON.generate(@payload)].join("\n")
23
40
  end
@@ -27,12 +27,12 @@ module Sentry
27
27
  end
28
28
 
29
29
  # @!visibility private
30
- def add_exception_interface(exception)
30
+ def add_exception_interface(exception, mechanism:)
31
31
  if exception.respond_to?(:sentry_context)
32
32
  @extra.merge!(exception.sentry_context)
33
33
  end
34
34
 
35
- @exception = Sentry::ExceptionInterface.build(exception: exception, stacktrace_builder: @stacktrace_builder)
35
+ @exception = Sentry::ExceptionInterface.build(exception: exception, stacktrace_builder: @stacktrace_builder, mechanism: mechanism)
36
36
  end
37
37
  end
38
38
  end
@@ -14,6 +14,10 @@ module Sentry
14
14
  def capture_exception(exception, **options, &block)
15
15
  options[:hint] ||= {}
16
16
  options[:hint][:integration] = integration_name
17
+
18
+ # within an integration, we usually intercept uncaught exceptions so we set handled to false.
19
+ options[:hint][:mechanism] ||= Sentry::Mechanism.new(type: integration_name, handled: false)
20
+
17
21
  Sentry.capture_exception(exception, **options, &block)
18
22
  end
19
23
 
@@ -14,3 +14,4 @@ require "sentry/interfaces/request"
14
14
  require "sentry/interfaces/single_exception"
15
15
  require "sentry/interfaces/stacktrace"
16
16
  require "sentry/interfaces/threads"
17
+ require "sentry/interfaces/mechanism"
@@ -24,17 +24,18 @@ module Sentry
24
24
  # @param stacktrace_builder [StacktraceBuilder]
25
25
  # @see SingleExceptionInterface#build_with_stacktrace
26
26
  # @see SingleExceptionInterface#initialize
27
+ # @param mechanism [Mechanism]
27
28
  # @return [ExceptionInterface]
28
- def self.build(exception:, stacktrace_builder:)
29
+ def self.build(exception:, stacktrace_builder:, mechanism:)
29
30
  exceptions = Sentry::Utils::ExceptionCauseChain.exception_to_array(exception).reverse
30
31
  processed_backtrace_ids = Set.new
31
32
 
32
33
  exceptions = exceptions.map do |e|
33
34
  if e.backtrace && !processed_backtrace_ids.include?(e.backtrace.object_id)
34
35
  processed_backtrace_ids << e.backtrace.object_id
35
- SingleExceptionInterface.build_with_stacktrace(exception: e, stacktrace_builder: stacktrace_builder)
36
+ SingleExceptionInterface.build_with_stacktrace(exception: e, stacktrace_builder: stacktrace_builder, mechanism: mechanism)
36
37
  else
37
- SingleExceptionInterface.new(exception: exception)
38
+ SingleExceptionInterface.new(exception: exception, mechanism: mechanism)
38
39
  end
39
40
  end
40
41
 
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sentry
4
+ class Mechanism < Interface
5
+ # Generic identifier, mostly the source integration for this exception.
6
+ # @return [String]
7
+ attr_accessor :type
8
+
9
+ # A manually captured exception has handled set to true,
10
+ # false if coming from an integration where we intercept an uncaught exception.
11
+ # Defaults to true here and will be set to false explicitly in integrations.
12
+ # @return [Boolean]
13
+ attr_accessor :handled
14
+
15
+ def initialize(type: 'generic', handled: true)
16
+ @type = type
17
+ @handled = handled
18
+ end
19
+ end
20
+ end
@@ -11,10 +11,10 @@ module Sentry
11
11
  OMISSION_MARK = "...".freeze
12
12
  MAX_LOCAL_BYTES = 1024
13
13
 
14
- attr_reader :type, :module, :thread_id, :stacktrace
14
+ attr_reader :type, :module, :thread_id, :stacktrace, :mechanism
15
15
  attr_accessor :value
16
16
 
17
- def initialize(exception:, stacktrace: nil)
17
+ def initialize(exception:, mechanism:, stacktrace: nil)
18
18
  @type = exception.class.to_s
19
19
  exception_message =
20
20
  if exception.respond_to?(:detailed_message)
@@ -29,17 +29,19 @@ module Sentry
29
29
  @module = exception.class.to_s.split('::')[0...-1].join('::')
30
30
  @thread_id = Thread.current.object_id
31
31
  @stacktrace = stacktrace
32
+ @mechanism = mechanism
32
33
  end
33
34
 
34
35
  def to_hash
35
36
  data = super
36
37
  data[:stacktrace] = data[:stacktrace].to_hash if data[:stacktrace]
38
+ data[:mechanism] = data[:mechanism].to_hash
37
39
  data
38
40
  end
39
41
 
40
42
  # patch this method if you want to change an exception's stacktrace frames
41
43
  # also see `StacktraceBuilder.build`.
42
- def self.build_with_stacktrace(exception:, stacktrace_builder:)
44
+ def self.build_with_stacktrace(exception:, stacktrace_builder:, mechanism:)
43
45
  stacktrace = stacktrace_builder.build(backtrace: exception.backtrace)
44
46
 
45
47
  if locals = exception.instance_variable_get(:@sentry_locals)
@@ -61,7 +63,7 @@ module Sentry
61
63
  stacktrace.frames.last.vars = locals
62
64
  end
63
65
 
64
- new(exception: exception, stacktrace: stacktrace)
66
+ new(exception: exception, stacktrace: stacktrace, mechanism: mechanism)
65
67
  end
66
68
  end
67
69
  end
@@ -12,8 +12,18 @@ module Sentry
12
12
  # when we record code locations
13
13
  DEFAULT_STACKLEVEL = 4
14
14
 
15
- KEY_SANITIZATION_REGEX = /[^a-zA-Z0-9_\/.-]+/
16
- VALUE_SANITIZATION_REGEX = /[^[[:word:]][[:digit:]][[:space:]]_:\/@\.{}\[\]$-]+/
15
+ KEY_SANITIZATION_REGEX = /[^a-zA-Z0-9_\-.]+/
16
+ UNIT_SANITIZATION_REGEX = /[^a-zA-Z0-9_]+/
17
+ TAG_KEY_SANITIZATION_REGEX = /[^a-zA-Z0-9_\-.\/]+/
18
+
19
+ TAG_VALUE_SANITIZATION_MAP = {
20
+ "\n" => "\\n",
21
+ "\r" => "\\r",
22
+ "\t" => "\\t",
23
+ "\\" => "\\\\",
24
+ "|" => "\\u{7c}",
25
+ "," => "\\u{2c}"
26
+ }
17
27
 
18
28
  METRIC_TYPES = {
19
29
  c: CounterMetric,
@@ -23,7 +33,7 @@ module Sentry
23
33
  }
24
34
 
25
35
  # exposed only for testing
26
- attr_reader :thread, :buckets, :flush_shift, :code_locations
36
+ attr_reader :client, :thread, :buckets, :flush_shift, :code_locations
27
37
 
28
38
  def initialize(configuration, client)
29
39
  @client = client
@@ -107,9 +117,7 @@ module Sentry
107
117
  end
108
118
  end
109
119
 
110
- Sentry.background_worker.perform do
111
- @client.transport.send_envelope(envelope)
112
- end
120
+ @client.capture_envelope(envelope)
113
121
  end
114
122
 
115
123
  def kill
@@ -182,9 +190,9 @@ module Sentry
182
190
  timestamp_buckets.map do |metric_key, metric|
183
191
  type, key, unit, tags = metric_key
184
192
  values = metric.serialize.join(':')
185
- sanitized_tags = tags.map { |k, v| "#{sanitize_key(k)}:#{sanitize_value(v)}" }.join(',')
193
+ sanitized_tags = tags.map { |k, v| "#{sanitize_tag_key(k)}:#{sanitize_tag_value(v)}" }.join(',')
186
194
 
187
- "#{sanitize_key(key)}@#{unit}:#{values}|#{type}|\##{sanitized_tags}|T#{timestamp}"
195
+ "#{sanitize_key(key)}@#{sanitize_unit(unit)}:#{values}|#{type}|\##{sanitized_tags}|T#{timestamp}"
188
196
  end
189
197
  end.flatten.join("\n")
190
198
  end
@@ -192,7 +200,7 @@ module Sentry
192
200
  def serialize_locations(timestamp, locations)
193
201
  mapping = locations.map do |meta_key, location|
194
202
  type, key, unit = meta_key
195
- mri = "#{type}:#{sanitize_key(key)}@#{unit}"
203
+ mri = "#{type}:#{sanitize_key(key)}@#{sanitize_unit(unit)}"
196
204
 
197
205
  # note this needs to be an array but it really doesn't serve a purpose right now
198
206
  [mri, [location.merge(type: 'location')]]
@@ -205,8 +213,16 @@ module Sentry
205
213
  key.gsub(KEY_SANITIZATION_REGEX, '_')
206
214
  end
207
215
 
208
- def sanitize_value(value)
209
- value.gsub(VALUE_SANITIZATION_REGEX, '')
216
+ def sanitize_unit(unit)
217
+ unit.gsub(UNIT_SANITIZATION_REGEX, '')
218
+ end
219
+
220
+ def sanitize_tag_key(key)
221
+ key.gsub(TAG_KEY_SANITIZATION_REGEX, '')
222
+ end
223
+
224
+ def sanitize_tag_value(value)
225
+ value.chars.map { |c| TAG_VALUE_SANITIZATION_MAP[c] || c }.join
210
226
  end
211
227
 
212
228
  def get_transaction_name
@@ -4,6 +4,7 @@ module Sentry
4
4
  module Rack
5
5
  class CaptureExceptions
6
6
  ERROR_EVENT_ID_KEY = "sentry.error_event_id"
7
+ MECHANISM_TYPE = "rack"
7
8
 
8
9
  def initialize(app)
9
10
  @app = app
@@ -56,7 +57,7 @@ module Sentry
56
57
  end
57
58
 
58
59
  def capture_exception(exception, env)
59
- Sentry.capture_exception(exception).tap do |event|
60
+ Sentry.capture_exception(exception, hint: { mechanism: mechanism }).tap do |event|
60
61
  env[ERROR_EVENT_ID_KEY] = event.event_id if event
61
62
  end
62
63
  end
@@ -74,6 +75,10 @@ module Sentry
74
75
  transaction.set_http_status(status_code)
75
76
  transaction.finish
76
77
  end
78
+
79
+ def mechanism
80
+ Sentry::Mechanism.new(type: MECHANISM_TYPE, handled: false)
81
+ end
77
82
  end
78
83
  end
79
84
  end
data/lib/sentry/rake.rb CHANGED
@@ -8,7 +8,9 @@ module Sentry
8
8
  module Application
9
9
  # @api private
10
10
  def display_error_message(ex)
11
- Sentry.capture_exception(ex) do |scope|
11
+ mechanism = Sentry::Mechanism.new(type: 'rake', handled: false)
12
+
13
+ Sentry.capture_exception(ex, hint: { mechanism: mechanism }) do |scope|
12
14
  task_name = top_level_tasks.join(' ')
13
15
  scope.set_transaction_name(task_name, source: :task)
14
16
  scope.set_tag("rake_task", task_name)
@@ -20,12 +20,8 @@ module Sentry
20
20
 
21
21
  def flush
22
22
  return if @pending_aggregates.empty?
23
- envelope = pending_envelope
24
-
25
- Sentry.background_worker.perform do
26
- @client.transport.send_envelope(envelope)
27
- end
28
23
 
24
+ @client.capture_envelope(pending_envelope)
29
25
  @pending_aggregates = {}
30
26
  end
31
27
 
@@ -88,18 +88,9 @@ module Sentry
88
88
  [data, serialized_items]
89
89
  end
90
90
 
91
- def is_rate_limited?(item_type)
91
+ def is_rate_limited?(data_category)
92
92
  # check category-specific limit
93
- category_delay =
94
- case item_type
95
- when "transaction"
96
- @rate_limits["transaction"]
97
- when "sessions"
98
- @rate_limits["session"]
99
- else
100
- @rate_limits["error"]
101
- end
102
-
93
+ category_delay = @rate_limits[data_category]
103
94
  # check universal limit if not category limit
104
95
  universal_delay = @rate_limits[nil]
105
96
 
@@ -160,11 +151,11 @@ module Sentry
160
151
  envelope
161
152
  end
162
153
 
163
- def record_lost_event(reason, item_type)
154
+ def record_lost_event(reason, data_category)
164
155
  return unless @send_client_reports
165
156
  return unless CLIENT_REPORT_REASONS.include?(reason)
166
157
 
167
- @discarded_events[[reason, item_type]] += 1
158
+ @discarded_events[[reason, data_category]] += 1
168
159
  end
169
160
 
170
161
  def flush
@@ -184,11 +175,7 @@ module Sentry
184
175
  return nil if @discarded_events.empty?
185
176
 
186
177
  discarded_events_hash = @discarded_events.map do |key, val|
187
- reason, type = key
188
-
189
- # 'event' has to be mapped to 'error'
190
- category = type == 'event' ? 'error' : type
191
-
178
+ reason, category = key
192
179
  { reason: reason, category: category, quantity: val }
193
180
  end
194
181
 
@@ -206,9 +193,9 @@ module Sentry
206
193
 
207
194
  def reject_rate_limited_items(envelope)
208
195
  envelope.items.reject! do |item|
209
- if is_rate_limited?(item.type)
196
+ if is_rate_limited?(item.data_category)
210
197
  log_debug("[Transport] Envelope item [#{item.type}] not sent: rate limiting")
211
- record_lost_event(:ratelimit_backoff, item.type)
198
+ record_lost_event(:ratelimit_backoff, item.data_category)
212
199
 
213
200
  true
214
201
  else
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Sentry
4
- VERSION = "5.17.1"
4
+ VERSION = "5.17.3"
5
5
  end
data/lib/sentry-ruby.rb CHANGED
@@ -257,7 +257,7 @@ module Sentry
257
257
  end
258
258
 
259
259
  if client = get_current_client
260
- client.transport.flush
260
+ client.flush
261
261
 
262
262
  if client.configuration.include_local_variables
263
263
  exception_locals_tp.disable
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.17.1
4
+ version: 5.17.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sentry Team
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-03-15 00:00:00.000000000 Z
11
+ date: 2024-04-11 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: concurrent-ruby
@@ -89,6 +89,7 @@ files:
89
89
  - lib/sentry/integrable.rb
90
90
  - lib/sentry/interface.rb
91
91
  - lib/sentry/interfaces/exception.rb
92
+ - lib/sentry/interfaces/mechanism.rb
92
93
  - lib/sentry/interfaces/request.rb
93
94
  - lib/sentry/interfaces/single_exception.rb
94
95
  - lib/sentry/interfaces/stacktrace.rb