sentry-ruby-core 5.3.1 → 5.4.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: 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