sentry-ruby-core 5.2.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: 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