sentry-ruby-core 5.3.1 → 5.4.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d28ef3b3cde342fa483586f73025f48b729b9ee09bd9835589ffa2d2d62717ff
4
- data.tar.gz: 46eb28e37a955cfa60ed8ab517ab4398a89a310af7897d1543dd817e010f125e
3
+ metadata.gz: '05368ec66748ebeba74ec529e6395ce1ad5eb36f1beff0f7014e041a507099fe'
4
+ data.tar.gz: 150e09181157a7f36a21a46aeffdc37f47db942d967dbf206167ddb6208e6b51
5
5
  SHA512:
6
- metadata.gz: 693b0955dc1dacb5ea6c087e0ec2d331f51c995dd8b2adfa886fbe9b7af33c1f0a0248e750b02a85899808c34cab688cffacf6951c29e06b60336dcffc354777
7
- data.tar.gz: 92b40e64205151ef7f6d530ae27fe179678824dd7f5f4cf567a8671385df076f24b1576df5e7edfb5921a230605cb447349724872f791b8b84eb6d13c050413c
6
+ metadata.gz: eec3eb3c33f43854ff89fdf19c5b0b53726c9f79e8e9325969d5241f51490c1096234cbc27aa090bf408207142a265ec9c4eebdf06d159771a5e22a44d405d8b
7
+ data.tar.gz: e2e50b8e694d3eea9606a41dfcca942ebc41a5e64f128bc455ad34ee9ad5806ae1794993306706039270439bfa26cf7f46614ccf6525d8ecc0a46db6dad3d60a
data/Gemfile CHANGED
@@ -1,7 +1,6 @@
1
1
  source "https://rubygems.org"
2
2
  git_source(:github) { |name| "https://github.com/#{name}.git" }
3
3
 
4
- gem "sentry-ruby-core", path: "./"
5
4
  gem "sentry-ruby", path: "./"
6
5
 
7
6
  gem "rack" unless ENV["WITHOUT_RACK"] == "1"
data/lib/sentry/client.rb CHANGED
@@ -167,7 +167,14 @@ module Sentry
167
167
  def dispatch_async_event(async_block, event, hint)
168
168
  # We have to convert to a JSON-like hash, because background job
169
169
  # processors (esp ActiveJob) may not like weird types in the event hash
170
- event_hash = event.to_json_compatible
170
+
171
+ event_hash =
172
+ begin
173
+ event.to_json_compatible
174
+ rescue => e
175
+ log_error("Converting #{event.type} (#{event.event_id}) to JSON compatible hash failed", e, debug: configuration.debug)
176
+ return
177
+ end
171
178
 
172
179
  if async_block.arity == 2
173
180
  hint = JSON.parse(JSON.generate(hint))
@@ -4,6 +4,9 @@ module Sentry
4
4
  # @api private
5
5
  class Envelope
6
6
  class Item
7
+ STACKTRACE_FRAME_LIMIT_ON_OVERSIZED_PAYLOAD = 500
8
+ MAX_SERIALIZED_PAYLOAD_SIZE = 1024 * 200
9
+
7
10
  attr_accessor :headers, :payload
8
11
 
9
12
  def initialize(headers, payload)
@@ -21,6 +24,54 @@ module Sentry
21
24
  #{JSON.generate(@payload)}
22
25
  ITEM
23
26
  end
27
+
28
+ def serialize
29
+ result = to_s
30
+
31
+ if result.bytesize > MAX_SERIALIZED_PAYLOAD_SIZE
32
+ remove_breadcrumbs!
33
+ result = to_s
34
+ end
35
+
36
+ if result.bytesize > MAX_SERIALIZED_PAYLOAD_SIZE
37
+ reduce_stacktrace!
38
+ result = to_s
39
+ end
40
+
41
+ [result, result.bytesize > MAX_SERIALIZED_PAYLOAD_SIZE]
42
+ end
43
+
44
+ def size_breakdown
45
+ payload.map do |key, value|
46
+ "#{key}: #{JSON.generate(value).bytesize}"
47
+ end.join(", ")
48
+ end
49
+
50
+ private
51
+
52
+ def remove_breadcrumbs!
53
+ if payload.key?(:breadcrumbs)
54
+ payload.delete(:breadcrumbs)
55
+ elsif payload.key?("breadcrumbs")
56
+ payload.delete("breadcrumbs")
57
+ end
58
+ end
59
+
60
+ def reduce_stacktrace!
61
+ if exceptions = payload.dig(:exception, :values) || payload.dig("exception", "values")
62
+ exceptions.each do |exception|
63
+ # in most cases there is only one exception (2 or 3 when have multiple causes), so we won't loop through this double condition much
64
+ traces = exception.dig(:stacktrace, :frames) || exception.dig("stacktrace", "frames")
65
+
66
+ if traces && traces.size > STACKTRACE_FRAME_LIMIT_ON_OVERSIZED_PAYLOAD
67
+ size_on_both_ends = STACKTRACE_FRAME_LIMIT_ON_OVERSIZED_PAYLOAD / 2
68
+ traces.replace(
69
+ traces[0..(size_on_both_ends - 1)] + traces[-size_on_both_ends..-1],
70
+ )
71
+ end
72
+ end
73
+ end
74
+ end
24
75
  end
25
76
 
26
77
  attr_accessor :headers, :items
data/lib/sentry/event.rb CHANGED
@@ -26,7 +26,6 @@ module Sentry
26
26
  WRITER_ATTRIBUTES = SERIALIZEABLE_ATTRIBUTES - %i(type timestamp level)
27
27
 
28
28
  MAX_MESSAGE_SIZE_IN_BYTES = 1024 * 8
29
- MAX_SERIALIZED_PAYLOAD_SIZE = 1024 * 200
30
29
 
31
30
  SKIP_INSPECTION_ATTRIBUTES = [:@modules, :@stacktrace_builder, :@send_default_pii, :@trusted_proxies, :@rack_env_whitelist]
32
31
 
@@ -3,6 +3,9 @@ require "set"
3
3
 
4
4
  module Sentry
5
5
  class ExceptionInterface < Interface
6
+ # @return [<Array[SingleExceptionInterface]>]
7
+ attr_reader :values
8
+
6
9
  # @param exceptions [Array<SingleExceptionInterface>]
7
10
  def initialize(exceptions:)
8
11
  @values = exceptions
@@ -3,6 +3,8 @@
3
3
  module Sentry
4
4
  module Rack
5
5
  class CaptureExceptions
6
+ ERROR_EVENT_ID_KEY = "sentry.error_event_id"
7
+
6
8
  def initialize(app)
7
9
  @app = app
8
10
  end
@@ -28,13 +30,13 @@ module Sentry
28
30
  finish_transaction(transaction, 500)
29
31
  raise # Don't capture Sentry errors
30
32
  rescue Exception => e
31
- capture_exception(e)
33
+ capture_exception(e, env)
32
34
  finish_transaction(transaction, 500)
33
35
  raise
34
36
  end
35
37
 
36
38
  exception = collect_exception(env)
37
- capture_exception(exception) if exception
39
+ capture_exception(exception, env) if exception
38
40
 
39
41
  finish_transaction(transaction, response[0])
40
42
 
@@ -53,8 +55,10 @@ module Sentry
53
55
  "rack.request".freeze
54
56
  end
55
57
 
56
- def capture_exception(exception)
57
- Sentry.capture_exception(exception)
58
+ def capture_exception(exception, env)
59
+ Sentry.capture_exception(exception).tap do |event|
60
+ env[ERROR_EVENT_ID_KEY] = event.event_id if event
61
+ end
58
62
  end
59
63
 
60
64
  def start_transaction(env, scope)
@@ -8,6 +8,7 @@ module Sentry
8
8
 
9
9
  def initialize(configuration, client)
10
10
  @thread = nil
11
+ @exited = false
11
12
  @client = client
12
13
  @pending_aggregates = {}
13
14
  @release = configuration.release
@@ -29,9 +30,16 @@ module Sentry
29
30
  end
30
31
 
31
32
  def add_session(session)
33
+ return if @exited
32
34
  return unless @release
33
35
 
34
- ensure_thread
36
+ begin
37
+ ensure_thread
38
+ rescue ThreadError
39
+ log_debug("Session flusher thread creation failed")
40
+ @exited = true
41
+ return
42
+ end
35
43
 
36
44
  return unless Session::AGGREGATE_STATUSES.include?(session.status)
37
45
  @pending_aggregates[session.aggregation_key] ||= init_aggregates(session.aggregation_key)
@@ -40,6 +48,8 @@ module Sentry
40
48
 
41
49
  def kill
42
50
  log_debug("Killing session flusher")
51
+
52
+ @exited = true
43
53
  @thread&.kill
44
54
  end
45
55
 
@@ -0,0 +1,76 @@
1
+ module Sentry
2
+ module TestHelper
3
+ DUMMY_DSN = 'http://12345:67890@sentry.localdomain/sentry/42'
4
+
5
+ # Alters the existing SDK configuration with test-suitable options. Mainly:
6
+ # - Sets a dummy DSN instead of `nil` or an actual DSN.
7
+ # - Sets the transport to DummyTransport, which allows easy access to the captured events.
8
+ # - Disables background worker.
9
+ # - Makes sure the SDK is enabled under the current environment ("test" in most cases).
10
+ #
11
+ # It should be called **before** every test case.
12
+ #
13
+ # @yieldparam config [Configuration]
14
+ # @return [void]
15
+ def setup_sentry_test(&block)
16
+ raise "please make sure the SDK is initialized for testing" unless Sentry.initialized?
17
+ copied_config = Sentry.configuration.dup
18
+ # configure dummy DSN, so the events will not be sent to the actual service
19
+ copied_config.dsn = DUMMY_DSN
20
+ # set transport to DummyTransport, so we can easily intercept the captured events
21
+ copied_config.transport.transport_class = Sentry::DummyTransport
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)
24
+ # disble async event sending
25
+ copied_config.background_worker_threads = 0
26
+
27
+ # user can overwrite some of the configs, with a few exceptions like:
28
+ # - capture_exception_frame_locals
29
+ # - auto_session_tracking
30
+ block&.call(copied_config)
31
+
32
+ test_client = Sentry::Client.new(copied_config)
33
+ Sentry.get_current_hub.bind_client(test_client)
34
+ end
35
+
36
+ # Clears all stored events and envelopes.
37
+ # It should be called **after** every test case.
38
+ # @return [void]
39
+ def teardown_sentry_test
40
+ return unless Sentry.initialized?
41
+
42
+ sentry_transport.events = []
43
+ sentry_transport.envelopes = []
44
+ end
45
+
46
+ # @return [Transport]
47
+ def sentry_transport
48
+ Sentry.get_current_client.transport
49
+ end
50
+
51
+ # Returns the captured event objects.
52
+ # @return [Array<Event>]
53
+ def sentry_events
54
+ sentry_transport.events
55
+ end
56
+
57
+ # Returns the captured envelope objects.
58
+ # @return [Array<Envelope>]
59
+ def sentry_envelopes
60
+ sentry_transport.envelopes
61
+ end
62
+
63
+ # Returns the last captured event object.
64
+ # @return [Event, nil]
65
+ def last_sentry_event
66
+ sentry_events.last
67
+ end
68
+
69
+ # Extracts SDK's internal exception container (not actual exception objects) from an given event.
70
+ # @return [Array<Sentry::SingleExceptionInterface>]
71
+ def extract_sentry_exceptions(event)
72
+ event&.exception&.values || []
73
+ end
74
+ end
75
+ end
76
+
@@ -70,24 +70,10 @@ module Sentry
70
70
  serialized_results = []
71
71
 
72
72
  envelope.items.each do |item|
73
- result = item.to_s
73
+ result, oversized = item.serialize
74
74
 
75
- if result.bytesize > Event::MAX_SERIALIZED_PAYLOAD_SIZE
76
- if item.payload.key?(:breadcrumbs)
77
- item.payload.delete(:breadcrumbs)
78
- elsif item.payload.key?("breadcrumbs")
79
- item.payload.delete("breadcrumbs")
80
- end
81
-
82
- result = item.to_s
83
- end
84
-
85
- if result.bytesize > Event::MAX_SERIALIZED_PAYLOAD_SIZE
86
- size_breakdown = item.payload.map do |key, value|
87
- "#{key}: #{JSON.generate(value).bytesize}"
88
- end.join(", ")
89
-
90
- log_debug("Envelope item [#{item.type}] is still oversized without breadcrumbs: {#{size_breakdown}}")
75
+ if oversized
76
+ log_info("Envelope item [#{item.type}] is still oversized after size reduction: {#{item.size_breakdown}}")
91
77
 
92
78
  next
93
79
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Sentry
4
- VERSION = "5.3.1"
4
+ VERSION = "5.4.0"
5
5
  end
data/lib/sentry-ruby.rb CHANGED
@@ -60,11 +60,11 @@ module Sentry
60
60
  end
61
61
 
62
62
  # @!attribute [rw] background_worker
63
- # @return [BackgroundWorker]
63
+ # @return [BackgroundWorker, nil]
64
64
  attr_accessor :background_worker
65
65
 
66
66
  # @!attribute [r] session_flusher
67
- # @return [SessionFlusher]
67
+ # @return [SessionFlusher, nil]
68
68
  attr_reader :session_flusher
69
69
 
70
70
  ##### Patch Registration #####
@@ -213,10 +213,30 @@ module Sentry
213
213
  exception_locals_tp.enable
214
214
  end
215
215
 
216
- at_exit do
217
- @session_flusher&.kill
216
+ at_exit { close }
217
+ end
218
+
219
+ # Flushes pending events and cleans up SDK state.
220
+ # SDK will stop sending events and all top-level APIs will be no-ops after this.
221
+ #
222
+ # @return [void]
223
+ def close
224
+ if @background_worker
218
225
  @background_worker.shutdown
226
+ @background_worker = nil
219
227
  end
228
+
229
+ if @session_flusher
230
+ @session_flusher.kill
231
+ @session_flusher = nil
232
+ end
233
+
234
+ if configuration&.capture_exception_frame_locals
235
+ exception_locals_tp.disable
236
+ end
237
+
238
+ @main_hub = nil
239
+ Thread.current.thread_variable_set(THREAD_LOCAL, nil)
220
240
  end
221
241
 
222
242
  # Returns true if the SDK is initialized.
@@ -287,6 +307,7 @@ module Sentry
287
307
  #
288
308
  # @return [void]
289
309
  def clone_hub_to_current_thread
310
+ return unless initialized?
290
311
  Thread.current.thread_variable_set(THREAD_LOCAL, get_main_hub.clone)
291
312
  end
292
313
 
@@ -360,6 +381,25 @@ module Sentry
360
381
  get_current_hub.capture_exception(exception, **options, &block)
361
382
  end
362
383
 
384
+ # Takes a block and evaluates it. If the block raised an exception, it reports the exception to Sentry and re-raises it.
385
+ # If the block ran without exception, it returns the evaluation result.
386
+ #
387
+ # @example
388
+ # Sentry.with_exception_captured do
389
+ # 1/1 #=> 1 will be returned
390
+ # end
391
+ #
392
+ # Sentry.with_exception_captured do
393
+ # 1/0 #=> ZeroDivisionError will be reported and re-raised
394
+ # end
395
+ #
396
+ def with_exception_captured(**options, &block)
397
+ yield
398
+ rescue Exception => e
399
+ capture_exception(e, **options)
400
+ raise
401
+ end
402
+
363
403
  # Takes a message string and reports it to Sentry via the currently active hub.
364
404
  #
365
405
  # @yieldparam scope [Scope]
@@ -18,9 +18,6 @@ Gem::Specification.new do |spec|
18
18
  spec.metadata["source_code_uri"] = spec.homepage
19
19
  spec.metadata["changelog_uri"] = "#{spec.homepage}/blob/master/CHANGELOG.md"
20
20
 
21
- spec.bindir = "exe"
22
- spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
23
- spec.require_paths = ["lib"]
24
-
21
+ spec.add_dependency "sentry-ruby", Sentry::VERSION
25
22
  spec.add_dependency "concurrent-ruby"
26
23
  end
data/sentry-ruby.gemspec CHANGED
@@ -17,6 +17,7 @@ Gem::Specification.new do |spec|
17
17
  spec.metadata["source_code_uri"] = spec.homepage
18
18
  spec.metadata["changelog_uri"] = "#{spec.homepage}/blob/master/CHANGELOG.md"
19
19
 
20
- spec.add_dependency "sentry-ruby-core", Sentry::VERSION
20
+ spec.require_paths = ["lib"]
21
+
21
22
  spec.add_dependency "concurrent-ruby", '~> 1.0', '>= 1.0.2'
22
23
  end
metadata CHANGED
@@ -1,15 +1,29 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sentry-ruby-core
3
3
  version: !ruby/object:Gem::Version
4
- version: 5.3.1
4
+ version: 5.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sentry Team
8
8
  autorequire:
9
- bindir: exe
9
+ bindir: bin
10
10
  cert_chain: []
11
- date: 2022-05-14 00:00:00.000000000 Z
11
+ date: 2022-07-26 00:00:00.000000000 Z
12
12
  dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: sentry-ruby
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - '='
18
+ - !ruby/object:Gem::Version
19
+ version: 5.4.0
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - '='
25
+ - !ruby/object:Gem::Version
26
+ version: 5.4.0
13
27
  - !ruby/object:Gem::Dependency
14
28
  name: concurrent-ruby
15
29
  requirement: !ruby/object:Gem::Requirement
@@ -80,6 +94,7 @@ files:
80
94
  - lib/sentry/session.rb
81
95
  - lib/sentry/session_flusher.rb
82
96
  - lib/sentry/span.rb
97
+ - lib/sentry/test_helper.rb
83
98
  - lib/sentry/transaction.rb
84
99
  - lib/sentry/transaction_event.rb
85
100
  - lib/sentry/transport.rb