sentry-ruby-core 5.3.0 → 5.4.1

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: 03d233c6e1bd55ddc4044c7a23b6bc74047eec17a8c16d5f33f768065641259c
4
- data.tar.gz: bec07f880bff192b6ef71aaefdf9dc9ff2ff072a539f94ed5c4fd0398ad1146a
3
+ metadata.gz: 3e7ba4b82e18f29dcb0d2949098a489f93ffec19ca2888ca8f096644d182480b
4
+ data.tar.gz: b27555038074ec03421cc287dbecf2fa7e85642c4bffaa71bb770232dc6eb4d1
5
5
  SHA512:
6
- metadata.gz: c3d90a9b888db8a396e260dccf5df3a38d4f153c9bc2b31b92fc206009553fec2d3d0fe47bb1a871b001372fcba8013fee4e16b60405588db4ce3984f56d675e
7
- data.tar.gz: 2ae1db7dee78487fec06b1cb1c6fb2dfa77852ae20b0c28b6c2f0e0b964f728fdea40a8261495439ef6e193182a0f7dc3196dc1bd75368abcf2d78852310361a
6
+ metadata.gz: 9f0101061e4e980b7bdd6c849257aed2bdf3a8f698af8b7a6b7eb1bd2b4b4e00d391acf6c03c152668fa134e5b4c630518b9325fb19c527c03827cb864557feb
7
+ data.tar.gz: ef0594cbf709b46819b970debebec8f2a5be3f49aa483ea15963640af3ab84ea9c180ffbf6138675d8354d840106fca5e4e178d1f40e13e22b5ff57b60402e83
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/README.md CHANGED
@@ -1,8 +1,11 @@
1
1
  <p align="center">
2
- <a href="https://sentry.io" target="_blank" align="center">
3
- <img src="https://sentry-brand.storage.googleapis.com/sentry-logo-black.png" width="280">
2
+ <a href="https://sentry.io/?utm_source=github&utm_medium=logo" target="_blank">
3
+ <picture>
4
+ <source srcset="https://sentry-brand.storage.googleapis.com/sentry-logo-white.png" media="(prefers-color-scheme: dark)" />
5
+ <source srcset="https://sentry-brand.storage.googleapis.com/sentry-logo-black.png" media="(prefers-color-scheme: light), (prefers-color-scheme: no-preference)" />
6
+ <img src="https://sentry-brand.storage.googleapis.com/sentry-logo-black.png" alt="Sentry" width="280">
7
+ </picture>
4
8
  </a>
5
- <br />
6
9
  </p>
7
10
 
8
11
  _Bad software is everywhere, and we're tired of it. Sentry is on a mission to help developers write better software faster, so we can get back to enjoying technology. If you want to join us [<kbd>**Check out our open positions**</kbd>](https://sentry.io/careers/)_
@@ -52,13 +55,11 @@ gem "sentry-resque"
52
55
 
53
56
  ### Configuration
54
57
 
55
- You can use `Sentry.init` to initialize and configure your SDK:
56
-
58
+ You need to use Sentry.init to initialize and configure your SDK:
57
59
  ```ruby
58
60
  Sentry.init do |config|
59
61
  config.dsn = "MY_DSN"
60
62
  end
61
-
62
63
  ```
63
64
 
64
65
  To learn more about available configuration options, please visit the [official documentation](https://docs.sentry.io/platforms/ruby/configuration/options/).
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))
@@ -290,6 +290,16 @@ module Sentry
290
290
  def async=(value)
291
291
  check_callable!("async", value)
292
292
 
293
+ log_warn <<~MSG
294
+
295
+ sentry-ruby now sends events asynchronously by default with its background worker (supported since 4.1.0).
296
+ The `config.async` callback has become redundant while continuing to cause issues.
297
+ (The problems of `async` are detailed in https://github.com/getsentry/sentry-ruby/issues/1522)
298
+
299
+ Therefore, we encourage you to remove it and let the background worker take care of async job sending.
300
+ It's deprecation is planned in the next major release (6.0), which is scheduled around the 3rd quarter of 2022.
301
+ MSG
302
+
293
303
  @async = value
294
304
  end
295
305
 
@@ -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.0"
4
+ VERSION = "5.4.1"
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
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
219
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]
@@ -399,9 +439,7 @@ module Sentry
399
439
  # end
400
440
  #
401
441
  def with_child_span(**attributes, &block)
402
- current_span = get_current_scope.get_span
403
-
404
- if current_span
442
+ if Sentry.initialized? && current_span = get_current_scope.get_span
405
443
  result = nil
406
444
 
407
445
  begin
@@ -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
@@ -12,11 +12,13 @@ Gem::Specification.new do |spec|
12
12
  spec.platform = Gem::Platform::RUBY
13
13
  spec.required_ruby_version = '>= 2.4'
14
14
  spec.extra_rdoc_files = ["README.md", "LICENSE.txt"]
15
+ spec.files = `git ls-files | grep -Ev '^(spec|benchmarks|examples)'`.split("\n")
15
16
 
16
17
  spec.metadata["homepage_uri"] = spec.homepage
17
18
  spec.metadata["source_code_uri"] = spec.homepage
18
19
  spec.metadata["changelog_uri"] = "#{spec.homepage}/blob/master/CHANGELOG.md"
19
20
 
20
- spec.add_dependency "sentry-ruby-core", Sentry::VERSION
21
+ spec.require_paths = ["lib"]
22
+
21
23
  spec.add_dependency "concurrent-ruby", '~> 1.0', '>= 1.0.2'
22
24
  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.0
4
+ version: 5.4.1
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-04-27 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.1
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.1
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