sentry-ruby-core 5.2.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: 896f183ea604d1e6857591eff87175f8bb8a83dd1e40fc3e80982a71dd3abe0e
4
- data.tar.gz: ffc931b4d436be3c5b1858260a5e2a0508d75455c23d0971bd0f1d2a02f0e178
3
+ metadata.gz: '05368ec66748ebeba74ec529e6395ce1ad5eb36f1beff0f7014e041a507099fe'
4
+ data.tar.gz: 150e09181157a7f36a21a46aeffdc37f47db942d967dbf206167ddb6208e6b51
5
5
  SHA512:
6
- metadata.gz: 467c2493df9a8ba98148b1d467d313bfc23defd9b34a9355242b01ed7ac4f092932b19607ad79e16883f83ba9b1fa53b736872f920f4fb0590761fcdbfc12a25
7
- data.tar.gz: 074c4ac8eff86f456646ceef087433abf38a81ad1e8a3d0cec465caddf5a4e96459ed9c641854f7dba37ac9e851fde8f7e09798f35420f4d12586b89039282d4
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/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/)_
@@ -29,7 +32,7 @@ If you're using `sentry-raven`, we recommend you to migrate to this new SDK. You
29
32
 
30
33
  ## Requirements
31
34
 
32
- We test on Ruby 2.4, 2.5, 2.6, 2.7, and 3.0 at the latest patchlevel/teeny version. We also support JRuby 9.0.
35
+ 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.
33
36
 
34
37
  If you use self-hosted Sentry, please also make sure its version is above `20.6.0`.
35
38
 
@@ -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/).
@@ -29,7 +29,7 @@ module Sentry
29
29
  log_debug("config.background_worker_threads is set to 0, all events will be sent synchronously")
30
30
  Concurrent::ImmediateExecutor.new
31
31
  else
32
- log_debug("initialized a background worker with #{@number_of_threads} threads")
32
+ log_debug("Initializing the background worker with #{@number_of_threads} threads")
33
33
 
34
34
  executor = Concurrent::ThreadPoolExecutor.new(
35
35
  min_threads: 0,
@@ -59,6 +59,7 @@ module Sentry
59
59
  end
60
60
 
61
61
  def shutdown
62
+ log_debug("Shutting down background worker")
62
63
  @shutdown_callback&.call
63
64
  end
64
65
 
data/lib/sentry/client.rb CHANGED
@@ -80,9 +80,10 @@ module Sentry
80
80
 
81
81
  integration_meta = Sentry.integrations[hint[:integration]]
82
82
 
83
- Event.new(configuration: configuration, integration_meta: integration_meta).tap do |event|
83
+ ErrorEvent.new(configuration: configuration, integration_meta: integration_meta).tap do |event|
84
84
  event.add_exception_interface(exception)
85
85
  event.add_threads_interface(crashed: true)
86
+ event.level = :error
86
87
  end
87
88
  end
88
89
 
@@ -94,8 +95,9 @@ module Sentry
94
95
  return unless @configuration.sending_allowed?
95
96
 
96
97
  integration_meta = Sentry.integrations[hint[:integration]]
97
- event = Event.new(configuration: configuration, integration_meta: integration_meta, message: message)
98
+ event = ErrorEvent.new(configuration: configuration, integration_meta: integration_meta, message: message)
98
99
  event.add_threads_interface(backtrace: backtrace || caller)
100
+ event.level = :error
99
101
  event
100
102
  end
101
103
 
@@ -133,7 +135,7 @@ module Sentry
133
135
 
134
136
  event
135
137
  rescue => e
136
- loggable_event_type = (event_type || "event").capitalize
138
+ loggable_event_type = event_type.capitalize
137
139
  log_error("#{loggable_event_type} sending failed", e, debug: configuration.debug)
138
140
 
139
141
  event_info = Event.get_log_message(event.to_hash)
@@ -165,7 +167,14 @@ module Sentry
165
167
  def dispatch_async_event(async_block, event, hint)
166
168
  # We have to convert to a JSON-like hash, because background job
167
169
  # processors (esp ActiveJob) may not like weird types in the event hash
168
- 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
169
178
 
170
179
  if async_block.arity == 2
171
180
  hint = JSON.parse(JSON.generate(hint))
@@ -174,8 +183,7 @@ module Sentry
174
183
  async_block.call(event_hash)
175
184
  end
176
185
  rescue => e
177
- loggable_event_type = event_hash["type"] || "event"
178
- log_error("Async #{loggable_event_type} sending failed", e, debug: configuration.debug)
186
+ log_error("Async #{event_hash["type"]} sending failed", e, debug: configuration.debug)
179
187
  send_event(event, hint)
180
188
  end
181
189
  end
@@ -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
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sentry
4
+ # ErrorEvent represents error or normal message events.
5
+ class ErrorEvent < Event
6
+ # @return [ExceptionInterface]
7
+ attr_reader :exception
8
+
9
+ # @return [ThreadsInterface]
10
+ attr_reader :threads
11
+
12
+ # @return [Hash]
13
+ def to_hash
14
+ data = super
15
+ data[:threads] = threads.to_hash if threads
16
+ data[:exception] = exception.to_hash if exception
17
+ data
18
+ end
19
+
20
+ # @!visibility private
21
+ def add_threads_interface(backtrace: nil, **options)
22
+ @threads = ThreadsInterface.build(
23
+ backtrace: backtrace,
24
+ stacktrace_builder: @stacktrace_builder,
25
+ **options
26
+ )
27
+ end
28
+
29
+ # @!visibility private
30
+ def add_exception_interface(exception)
31
+ if exception.respond_to?(:sentry_context)
32
+ @extra.merge!(exception.sentry_context)
33
+ end
34
+
35
+ @exception = Sentry::ExceptionInterface.build(exception: exception, stacktrace_builder: @stacktrace_builder)
36
+ end
37
+ end
38
+ end
data/lib/sentry/event.rb CHANGED
@@ -9,7 +9,10 @@ require 'sentry/utils/request_id'
9
9
  require 'sentry/utils/custom_inspection'
10
10
 
11
11
  module Sentry
12
+ # This is an abstract class that defines the shared attributes of an event.
13
+ # Please don't use it directly. The user-facing classes are its child classes.
12
14
  class Event
15
+ TYPE = "event"
13
16
  # These are readable attributes.
14
17
  SERIALIZEABLE_ATTRIBUTES = %i(
15
18
  event_id level timestamp
@@ -23,7 +26,6 @@ module Sentry
23
26
  WRITER_ATTRIBUTES = SERIALIZEABLE_ATTRIBUTES - %i(type timestamp level)
24
27
 
25
28
  MAX_MESSAGE_SIZE_IN_BYTES = 1024 * 8
26
- MAX_SERIALIZED_PAYLOAD_SIZE = 1024 * 200
27
29
 
28
30
  SKIP_INSPECTION_ATTRIBUTES = [:@modules, :@stacktrace_builder, :@send_default_pii, :@trusted_proxies, :@rack_env_whitelist]
29
31
 
@@ -35,12 +37,6 @@ module Sentry
35
37
  # @return [RequestInterface]
36
38
  attr_reader :request
37
39
 
38
- # @return [ExceptionInterface]
39
- attr_reader :exception
40
-
41
- # @return [ThreadsInterface]
42
- attr_reader :threads
43
-
44
40
  # @param configuration [Configuration]
45
41
  # @param integration_meta [Hash, nil]
46
42
  # @param message [String, nil]
@@ -49,6 +45,7 @@ module Sentry
49
45
  @event_id = SecureRandom.uuid.delete("-")
50
46
  @timestamp = Sentry.utc_now.iso8601
51
47
  @platform = :ruby
48
+ @type = self.class::TYPE
52
49
  @sdk = integration_meta || Sentry.sdk_meta
53
50
 
54
51
  @user = {}
@@ -71,8 +68,6 @@ module Sentry
71
68
  @rack_env_whitelist = configuration.rack_env_whitelist
72
69
 
73
70
  @message = (message || "").byteslice(0..MAX_MESSAGE_SIZE_IN_BYTES)
74
-
75
- self.level = :error
76
71
  end
77
72
 
78
73
  class << self
@@ -146,9 +141,6 @@ module Sentry
146
141
  data = serialize_attributes
147
142
  data[:breadcrumbs] = breadcrumbs.to_hash if breadcrumbs
148
143
  data[:request] = request.to_hash if request
149
- data[:exception] = exception.to_hash if exception
150
- data[:threads] = threads.to_hash if threads
151
-
152
144
  data
153
145
  end
154
146
 
@@ -157,24 +149,6 @@ module Sentry
157
149
  JSON.parse(JSON.generate(to_hash))
158
150
  end
159
151
 
160
- # @!visibility private
161
- def add_threads_interface(backtrace: nil, **options)
162
- @threads = ThreadsInterface.build(
163
- backtrace: backtrace,
164
- stacktrace_builder: @stacktrace_builder,
165
- **options
166
- )
167
- end
168
-
169
- # @!visibility private
170
- def add_exception_interface(exception)
171
- if exception.respond_to?(:sentry_context)
172
- @extra.merge!(exception.sentry_context)
173
- end
174
-
175
- @exception = Sentry::ExceptionInterface.build(exception: exception, stacktrace_builder: @stacktrace_builder)
176
- end
177
-
178
152
  private
179
153
 
180
154
  def add_request_interface(env)
data/lib/sentry/hub.rb CHANGED
@@ -150,7 +150,7 @@ module Sentry
150
150
  configuration.log_debug(event.to_json_compatible)
151
151
  end
152
152
 
153
- @last_event_id = event&.event_id
153
+ @last_event_id = event&.event_id unless event.is_a?(Sentry::TransactionEvent)
154
154
  event
155
155
  end
156
156
 
@@ -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
@@ -74,11 +74,11 @@ module Sentry
74
74
  end
75
75
 
76
76
  def start_sentry_span
77
- return unless Sentry.initialized? && transaction = Sentry.get_current_scope.get_transaction
77
+ return unless Sentry.initialized? && span = Sentry.get_current_scope.get_span
78
78
  return if from_sentry_sdk?
79
- return if transaction.sampled == false
79
+ return if span.sampled == false
80
80
 
81
- transaction.start_child(op: OP_NAME, start_timestamp: Sentry.utc_now.to_f)
81
+ span.start_child(op: OP_NAME, start_timestamp: Sentry.utc_now.to_f)
82
82
  end
83
83
 
84
84
  def finish_sentry_span(sentry_span)
@@ -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)
@@ -39,6 +47,9 @@ module Sentry
39
47
  end
40
48
 
41
49
  def kill
50
+ log_debug("Killing session flusher")
51
+
52
+ @exited = true
42
53
  @thread&.kill
43
54
  end
44
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
+
@@ -1,32 +1,15 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Sentry
4
+ # TransactionEvent represents events that carry transaction data (type: "transaction").
4
5
  class TransactionEvent < Event
5
6
  TYPE = "transaction"
6
7
 
7
- SERIALIZEABLE_ATTRIBUTES = %i(
8
- event_id level timestamp start_timestamp
9
- release environment server_name modules
10
- user tags contexts extra
11
- transaction platform sdk type
12
- )
13
-
14
- WRITER_ATTRIBUTES = SERIALIZEABLE_ATTRIBUTES - %i(type timestamp start_timestamp level)
15
-
16
- attr_writer(*WRITER_ATTRIBUTES)
17
- attr_reader(*SERIALIZEABLE_ATTRIBUTES)
18
-
19
8
  # @return [<Array[Span]>]
20
9
  attr_accessor :spans
21
10
 
22
- # @param configuration [Configuration]
23
- # @param integration_meta [Hash, nil]
24
- # @param message [String, nil]
25
- def initialize(configuration:, integration_meta: nil, message: nil)
26
- super
27
- @type = TYPE
28
- self.level = nil
29
- end
11
+ # @return [Float, nil]
12
+ attr_reader :start_timestamp
30
13
 
31
14
  # Sets the event's start_timestamp.
32
15
  # @param time [Time, Float]
@@ -39,6 +22,7 @@ module Sentry
39
22
  def to_hash
40
23
  data = super
41
24
  data[:spans] = @spans.map(&:to_hash) if @spans
25
+ data[:start_timestamp] = @start_timestamp
42
26
  data
43
27
  end
44
28
  end
@@ -130,7 +130,7 @@ module Sentry
130
130
  server = URI(@dsn.server)
131
131
 
132
132
  connection =
133
- if proxy = @transport_configuration.proxy
133
+ if proxy = normalize_proxy(@transport_configuration.proxy)
134
134
  ::Net::HTTP.new(server.hostname, server.port, proxy[:uri].hostname, proxy[:uri].port, proxy[:user], proxy[:password])
135
135
  else
136
136
  ::Net::HTTP.new(server.hostname, server.port, nil)
@@ -148,6 +148,20 @@ module Sentry
148
148
  connection
149
149
  end
150
150
 
151
+ def normalize_proxy(proxy)
152
+ return proxy unless proxy
153
+
154
+ case proxy
155
+ when String
156
+ uri = URI(proxy)
157
+ { uri: uri, user: uri.user, password: uri.password }
158
+ when URI
159
+ { uri: proxy, user: proxy.user, password: proxy.password }
160
+ when Hash
161
+ proxy
162
+ end
163
+ end
164
+
151
165
  def ssl_configuration
152
166
  configuration = {
153
167
  verify: @transport_configuration.ssl_verification,
@@ -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
@@ -148,7 +134,7 @@ module Sentry
148
134
  # Convert to hash
149
135
  event_payload = event.to_hash
150
136
  event_id = event_payload[:event_id] || event_payload["event_id"]
151
- item_type = get_item_type(event_payload)
137
+ item_type = event_payload[:type] || event_payload["type"]
152
138
 
153
139
  envelope = Envelope.new(
154
140
  {
@@ -174,16 +160,11 @@ module Sentry
174
160
  return unless @send_client_reports
175
161
  return unless CLIENT_REPORT_REASONS.include?(reason)
176
162
 
177
- item_type ||= 'event'
178
163
  @discarded_events[[reason, item_type]] += 1
179
164
  end
180
165
 
181
166
  private
182
167
 
183
- def get_item_type(event_hash)
184
- event_hash[:type] || event_hash["type"] || "event"
185
- end
186
-
187
168
  def fetch_pending_client_report
188
169
  return nil unless @send_client_reports
189
170
  return nil if @last_client_report_sent > Time.now - CLIENT_REPORT_INTERVAL
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Sentry
4
- VERSION = "5.2.1"
4
+ VERSION = "5.4.0"
5
5
  end
data/lib/sentry-ruby.rb CHANGED
@@ -12,6 +12,7 @@ require "sentry/utils/logging_helper"
12
12
  require "sentry/configuration"
13
13
  require "sentry/logger"
14
14
  require "sentry/event"
15
+ require "sentry/error_event"
15
16
  require "sentry/transaction_event"
16
17
  require "sentry/span"
17
18
  require "sentry/transaction"
@@ -59,11 +60,11 @@ module Sentry
59
60
  end
60
61
 
61
62
  # @!attribute [rw] background_worker
62
- # @return [BackgroundWorker]
63
+ # @return [BackgroundWorker, nil]
63
64
  attr_accessor :background_worker
64
65
 
65
66
  # @!attribute [r] session_flusher
66
- # @return [SessionFlusher]
67
+ # @return [SessionFlusher, nil]
67
68
  attr_reader :session_flusher
68
69
 
69
70
  ##### Patch Registration #####
@@ -102,7 +103,7 @@ module Sentry
102
103
  if initialized?
103
104
  logger.warn(LOGGER_PROGNAME) do
104
105
  <<~MSG
105
- Integration '#{name}' is loaded after the SDK is initialized, which can cause expected behavior. Please make sure all integrations are loaded before SDK initialization.
106
+ Integration '#{name}' is loaded after the SDK is initialized, which can cause unexpected behavior. Please make sure all integrations are loaded before SDK initialization.
106
107
  MSG
107
108
  end
108
109
  end
@@ -212,10 +213,30 @@ module Sentry
212
213
  exception_locals_tp.enable
213
214
  end
214
215
 
215
- at_exit do
216
- @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
217
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
218
236
  end
237
+
238
+ @main_hub = nil
239
+ Thread.current.thread_variable_set(THREAD_LOCAL, nil)
219
240
  end
220
241
 
221
242
  # Returns true if the SDK is initialized.
@@ -286,6 +307,7 @@ module Sentry
286
307
  #
287
308
  # @return [void]
288
309
  def clone_hub_to_current_thread
310
+ return unless initialized?
289
311
  Thread.current.thread_variable_set(THREAD_LOCAL, get_main_hub.clone)
290
312
  end
291
313
 
@@ -359,6 +381,25 @@ module Sentry
359
381
  get_current_hub.capture_exception(exception, **options, &block)
360
382
  end
361
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
+
362
403
  # Takes a message string and reports it to Sentry via the currently active hub.
363
404
  #
364
405
  # @yieldparam scope [Scope]
@@ -384,6 +425,38 @@ module Sentry
384
425
  get_current_hub.start_transaction(**options)
385
426
  end
386
427
 
428
+ # Records the block's execution as a child of the current span.
429
+ # If the current scope doesn't have a span, the block would still be executed but the yield param will be nil.
430
+ # @param attributes [Hash] attributes for the child span.
431
+ # @yieldparam child_span [Span, nil]
432
+ # @return yield result
433
+ #
434
+ # @example
435
+ # Sentry.with_child_span(op: "my operation") do |child_span|
436
+ # child_span.set_data(operation_data)
437
+ # child_span.set_description(operation_detail)
438
+ # # result will be returned
439
+ # end
440
+ #
441
+ def with_child_span(**attributes, &block)
442
+ if Sentry.initialized? && current_span = get_current_scope.get_span
443
+ result = nil
444
+
445
+ begin
446
+ current_span.with_child_span(**attributes) do |child_span|
447
+ get_current_scope.set_span(child_span)
448
+ result = yield(child_span)
449
+ end
450
+ ensure
451
+ get_current_scope.set_span(current_span)
452
+ end
453
+
454
+ result
455
+ else
456
+ yield(nil)
457
+ end
458
+ end
459
+
387
460
  # Returns the id of the lastly reported Sentry::Event.
388
461
  #
389
462
  # @return [String, nil]
@@ -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.2.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-03-18 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
@@ -56,6 +70,7 @@ files:
56
70
  - lib/sentry/core_ext/object/duplicable.rb
57
71
  - lib/sentry/dsn.rb
58
72
  - lib/sentry/envelope.rb
73
+ - lib/sentry/error_event.rb
59
74
  - lib/sentry/event.rb
60
75
  - lib/sentry/exceptions.rb
61
76
  - lib/sentry/hub.rb
@@ -79,6 +94,7 @@ files:
79
94
  - lib/sentry/session.rb
80
95
  - lib/sentry/session_flusher.rb
81
96
  - lib/sentry/span.rb
97
+ - lib/sentry/test_helper.rb
82
98
  - lib/sentry/transaction.rb
83
99
  - lib/sentry/transaction_event.rb
84
100
  - lib/sentry/transport.rb