sentry-ruby 6.7.0 → 7.1.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.
Files changed (63) hide show
  1. checksums.yaml +4 -4
  2. data/gemfiles/ruby-2.7_rack-2_redis-4.gemfile.lock +9 -9
  3. data/gemfiles/ruby-2.7_rack-3.1_redis-4.gemfile.lock +9 -9
  4. data/gemfiles/ruby-2.7_rack-3_redis-4.gemfile.lock +9 -9
  5. data/gemfiles/ruby-3.0_rack-2_redis-4.gemfile.lock +12 -12
  6. data/gemfiles/ruby-3.0_rack-3.1_redis-4.gemfile.lock +12 -12
  7. data/gemfiles/ruby-3.0_rack-3_redis-4.gemfile.lock +12 -12
  8. data/gemfiles/ruby-3.1_rack-2_redis-4.gemfile.lock +24 -24
  9. data/gemfiles/ruby-3.1_rack-3.1_redis-4.gemfile.lock +24 -24
  10. data/gemfiles/ruby-3.1_rack-3_redis-4.gemfile.lock +24 -24
  11. data/gemfiles/ruby-3.2_rack-0_redis-5.gemfile.lock +24 -24
  12. data/gemfiles/ruby-3.2_rack-2_redis-4.gemfile.lock +26 -26
  13. data/gemfiles/ruby-3.2_rack-2_redis-5.gemfile.lock +26 -26
  14. data/gemfiles/ruby-3.2_rack-3.1_redis-4.gemfile.lock +26 -26
  15. data/gemfiles/ruby-3.2_rack-3_redis-4.gemfile.lock +26 -26
  16. data/gemfiles/ruby-3.2_rack-3_redis-5.gemfile.lock +26 -26
  17. data/gemfiles/ruby-3.3_rack-2_redis-4.gemfile.lock +30 -30
  18. data/gemfiles/ruby-3.3_rack-3.1_redis-4.gemfile.lock +30 -30
  19. data/gemfiles/ruby-3.3_rack-3.1_redis-5.3.gemfile.lock +30 -30
  20. data/gemfiles/ruby-3.3_rack-3_redis-4.gemfile.lock +30 -30
  21. data/gemfiles/ruby-3.4_rack-2_redis-4.gemfile.lock +30 -30
  22. data/gemfiles/ruby-3.4_rack-3.1_redis-4.gemfile.lock +30 -30
  23. data/gemfiles/ruby-3.4_rack-3.1_redis-5.3.gemfile.lock +30 -30
  24. data/gemfiles/ruby-3.4_rack-3_redis-4.gemfile.lock +30 -30
  25. data/gemfiles/ruby-4.0_rack-2_redis-4.gemfile.lock +30 -30
  26. data/gemfiles/ruby-4.0_rack-3.1_redis-4.gemfile.lock +30 -30
  27. data/gemfiles/ruby-4.0_rack-3_redis-4.gemfile.lock +30 -30
  28. data/gemfiles/ruby-jruby-9.4.14.0_rack-2_redis-4.gemfile.lock +24 -24
  29. data/gemfiles/ruby-jruby-9.4.14.0_rack-3.1_redis-4.gemfile.lock +24 -24
  30. data/gemfiles/ruby-jruby-9.4.14.0_rack-3_redis-4.gemfile.lock +24 -24
  31. data/lib/sentry/backtrace.rb +10 -1
  32. data/lib/sentry/client.rb +21 -12
  33. data/lib/sentry/configuration.rb +35 -13
  34. data/lib/sentry/data_collection/key_value_collection.rb +136 -0
  35. data/lib/sentry/data_collection.rb +203 -0
  36. data/lib/sentry/debug_structured_logger.rb +2 -15
  37. data/lib/sentry/error_event.rb +6 -1
  38. data/lib/sentry/event.rb +8 -4
  39. data/lib/sentry/excon/middleware.rb +3 -8
  40. data/lib/sentry/faraday.rb +7 -5
  41. data/lib/sentry/hub.rb +11 -4
  42. data/lib/sentry/integrable.rb +12 -0
  43. data/lib/sentry/interfaces/exception.rb +8 -2
  44. data/lib/sentry/interfaces/request.rb +42 -46
  45. data/lib/sentry/interfaces/single_exception.rb +2 -2
  46. data/lib/sentry/log_event_buffer.rb +1 -1
  47. data/lib/sentry/metric_event.rb +4 -2
  48. data/lib/sentry/metric_event_buffer.rb +1 -1
  49. data/lib/sentry/metrics.rb +12 -6
  50. data/lib/sentry/net/http.rb +3 -4
  51. data/lib/sentry/redis.rb +1 -1
  52. data/lib/sentry/scope.rb +7 -3
  53. data/lib/sentry/session_flusher.rb +7 -0
  54. data/lib/sentry/std_lib_logger.rb +10 -8
  55. data/lib/sentry/telemetry_event_buffer.rb +57 -31
  56. data/lib/sentry/test_helper.rb +6 -4
  57. data/lib/sentry/threaded_periodic_worker.rb +90 -13
  58. data/lib/sentry/transaction.rb +7 -1
  59. data/lib/sentry/utils/callback_helper.rb +27 -0
  60. data/lib/sentry/utils/http_tracing.rb +42 -26
  61. data/lib/sentry/version.rb +1 -1
  62. data/lib/sentry-ruby.rb +7 -18
  63. metadata +9 -6
@@ -1,15 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "json"
4
+
3
5
  module Sentry
4
6
  class RequestInterface < Interface
5
7
  REQUEST_ID_HEADERS = %w[action_dispatch.request_id HTTP_X_REQUEST_ID].freeze
6
8
  CONTENT_HEADERS = %w[CONTENT_TYPE CONTENT_LENGTH].freeze
7
- IP_HEADERS = [
8
- "REMOTE_ADDR",
9
- "HTTP_CLIENT_IP",
10
- "HTTP_X_REAL_IP",
11
- "HTTP_X_FORWARDED_FOR"
12
- ].freeze
13
9
 
14
10
  # Regex to detect lowercase chars — match? is allocation-free (no MatchData/String)
15
11
  LOWERCASE_PATTERN = /[a-z]/.freeze
@@ -27,10 +23,10 @@ module Sentry
27
23
  # @return [Hash]
28
24
  attr_accessor :data
29
25
 
30
- # @return [String]
26
+ # @return [String, Hash]
31
27
  attr_accessor :query_string
32
28
 
33
- # @return [String]
29
+ # @return [Hash]
34
30
  attr_accessor :cookies
35
31
 
36
32
  # @return [Hash]
@@ -40,33 +36,22 @@ module Sentry
40
36
  attr_accessor :env
41
37
 
42
38
  # @param env [Hash]
43
- # @param send_default_pii [Boolean]
39
+ # @param data_collection [DataCollection]
44
40
  # @param rack_env_whitelist [Array]
45
- # @see Configuration#send_default_pii
41
+ # @see Configuration#data_collection
46
42
  # @see Configuration#rack_env_whitelist
47
- def initialize(env:, send_default_pii:, rack_env_whitelist:)
43
+ def initialize(env:, data_collection:, rack_env_whitelist:)
48
44
  env = env.dup
49
-
50
- unless send_default_pii
51
- # need to completely wipe out ip addresses
52
- RequestInterface::IP_HEADERS.each do |header|
53
- env.delete(header)
54
- end
55
- end
56
-
57
45
  request = ::Rack::Request.new(env)
58
-
59
- if send_default_pii
60
- self.data = read_data_from(request)
61
- self.cookies = request.cookies
62
- self.query_string = request.query_string
63
- end
64
-
65
- self.url = request.scheme && request.url.split("?").first
66
- self.method = request.request_method
67
-
68
- self.headers = filter_and_format_headers(env, send_default_pii)
69
- self.env = filter_and_format_env(env, rack_env_whitelist)
46
+ query = data_collection.url_query_params.filter(request.GET) rescue nil
47
+
48
+ self.method = request.request_method
49
+ self.url = request.scheme && request.url.split("?").first
50
+ self.query_string = query unless query&.empty?
51
+ self.cookies = data_collection.cookies.filter(request.cookies, cookie: true)
52
+ self.data = read_data_from(request) if data_collection.collect_incoming_http_body?
53
+ self.headers = filter_and_format_headers(env, data_collection.http_headers.request)
54
+ self.env = filter_and_format_env(env, data_collection.http_headers.request, rack_env_whitelist)
70
55
  end
71
56
 
72
57
  private
@@ -75,25 +60,31 @@ module Sentry
75
60
  return "Skipped non-rewindable request body" unless request.body.respond_to?(:rewind)
76
61
 
77
62
  if request.form_data?
78
- request.POST
79
- elsif request.body # JSON requests, etc
80
- data = request.body.read(MAX_BODY_LIMIT)
81
- data = Utils::EncodingHelper.encode_to_utf_8(data.to_s)
82
- request.body.rewind
83
- data
63
+ DataCollection.filter(request.POST)
64
+ else
65
+ body = request.body.read(MAX_BODY_LIMIT)
66
+ body = Utils::EncodingHelper.encode_to_utf_8(body.to_s)
67
+
68
+ if request.media_type == "application/json" || request.media_type&.end_with?("+json")
69
+ parsed_body = JSON.parse(body)
70
+ parsed_body.is_a?(Hash) ? DataCollection.filter(parsed_body) : parsed_body
71
+ else
72
+ body
73
+ end
84
74
  end
85
- rescue IOError => e
75
+ rescue JSON::ParserError, IOError => e
86
76
  e.message
77
+ ensure
78
+ request.body.rewind if request.body.respond_to?(:rewind)
87
79
  end
88
80
 
89
- def filter_and_format_headers(env, send_default_pii)
81
+ def filter_and_format_headers(env, collection)
90
82
  env.each_with_object({}) do |(key, value), memo|
91
83
  begin
92
84
  key = key.to_s # rack env can contain symbols
93
85
  next memo["X-Request-Id"] ||= Utils::RequestId.read_from(env) if Utils::RequestId::REQUEST_ID_HEADERS.include?(key)
94
86
  next if is_server_protocol?(key, value, env["SERVER_PROTOCOL"])
95
87
  next if is_skippable_header?(key)
96
- next if key == "HTTP_AUTHORIZATION" && !send_default_pii
97
88
 
98
89
  # Rack stores headers as HTTP_WHAT_EVER, we need What-Ever
99
90
  key = key.delete_prefix("HTTP_")
@@ -107,12 +98,13 @@ module Sentry
107
98
  Sentry.sdk_logger.warn(LOGGER_PROGNAME) { "Error raised while formatting headers: #{e.message}" }
108
99
  next
109
100
  end
101
+ end.then do |e|
102
+ collection.filter(e)
110
103
  end
111
104
  end
112
105
 
113
106
  def is_skippable_header?(key)
114
107
  key.match?(LOWERCASE_PATTERN) || # lower-case envs aren't real http headers
115
- key == "HTTP_COOKIE" || # Cookies don't go here, they go somewhere else
116
108
  !(key.start_with?("HTTP_") || CONTENT_HEADERS.include?(key))
117
109
  end
118
110
 
@@ -134,11 +126,15 @@ module Sentry
134
126
  Gem::Version.new(::Rack.release) >= Gem::Version.new("3.0")
135
127
  end
136
128
 
137
- def filter_and_format_env(env, rack_env_whitelist)
138
- return env if rack_env_whitelist.empty?
139
-
140
- env.select do |k, _v|
141
- rack_env_whitelist.include? k.to_s
129
+ def filter_and_format_env(env, collection, rack_env_whitelist)
130
+ if rack_env_whitelist.empty?
131
+ env
132
+ else
133
+ env.select do |k, _v|
134
+ rack_env_whitelist.include? k.to_s
135
+ end
136
+ end.then do |e|
137
+ collection.filter(e)
142
138
  end
143
139
  end
144
140
  end
@@ -41,7 +41,7 @@ module Sentry
41
41
 
42
42
  # patch this method if you want to change an exception's stacktrace frames
43
43
  # also see `StacktraceBuilder.build`.
44
- def self.build_with_stacktrace(exception:, stacktrace_builder:, mechanism:)
44
+ def self.build_with_stacktrace(exception:, stacktrace_builder:, mechanism:, data_collection:)
45
45
  stacktrace = stacktrace_builder.build(backtrace: exception.backtrace)
46
46
 
47
47
  if locals = exception.instance_variable_get(:@sentry_locals)
@@ -60,7 +60,7 @@ module Sentry
60
60
  end
61
61
  end
62
62
 
63
- stacktrace.frames.last&.vars = locals
63
+ stacktrace.frames.last&.vars = data_collection.stack_frame_variables.filter(locals)
64
64
  end
65
65
 
66
66
  new(exception: exception, stacktrace: stacktrace, mechanism: mechanism)
@@ -21,7 +21,7 @@ module Sentry
21
21
  max_items_before_drop: MAX_EVENTS_BEFORE_DROP,
22
22
  envelope_type: "log",
23
23
  envelope_content_type: "application/vnd.sentry.items.log+json",
24
- before_send: configuration.before_send_log
24
+ before_send: :before_send_log
25
25
  )
26
26
  end
27
27
  end
@@ -6,7 +6,7 @@ module Sentry
6
6
  class MetricEvent
7
7
  include Sentry::Utils::TelemetryAttributes
8
8
 
9
- attr_reader :name, :type, :value, :unit, :timestamp, :trace_id, :span_id, :attributes
9
+ attr_reader :name, :type, :value, :unit, :timestamp, :trace_id, :span_id, :attributes, :integration_meta
10
10
  attr_writer :trace_id, :span_id, :attributes
11
11
 
12
12
  def initialize(
@@ -14,13 +14,15 @@ module Sentry
14
14
  type:,
15
15
  value:,
16
16
  unit: nil,
17
- attributes: nil
17
+ attributes: nil,
18
+ integration_meta: nil
18
19
  )
19
20
  @name = name
20
21
  @type = type
21
22
  @value = value
22
23
  @unit = unit
23
24
  @attributes = attributes || {}
25
+ @integration_meta = integration_meta
24
26
 
25
27
  @timestamp = Sentry.utc_now
26
28
  @trace_id = nil
@@ -21,7 +21,7 @@ module Sentry
21
21
  max_items_before_drop: MAX_METRICS_BEFORE_DROP,
22
22
  envelope_type: "trace_metric",
23
23
  envelope_content_type: "application/vnd.sentry.items.trace-metric+json",
24
- before_send: configuration.before_send_metric
24
+ before_send: :before_send_metric
25
25
  )
26
26
  end
27
27
  end
@@ -9,15 +9,17 @@ module Sentry
9
9
  # @param name [String] the metric name
10
10
  # @param value [Numeric] the value to increment by (default: 1)
11
11
  # @param attributes [Hash, nil] additional attributes for the metric (optional)
12
+ # @param integration [String, Symbol, nil] registered integration emitting the metric (optional)
12
13
  # @return [void]
13
- def count(name, value: 1, attributes: nil)
14
+ def count(name, value: 1, attributes: nil, integration: nil)
14
15
  return unless Sentry.initialized?
15
16
 
16
17
  Sentry.get_current_hub.capture_metric(
17
18
  name: name,
18
19
  type: :counter,
19
20
  value: value,
20
- attributes: attributes
21
+ attributes: attributes,
22
+ integration: integration
21
23
  )
22
24
  end
23
25
 
@@ -26,8 +28,9 @@ module Sentry
26
28
  # @param value [Numeric] the gauge value
27
29
  # @param unit [String, nil] the metric unit (optional)
28
30
  # @param attributes [Hash, nil] additional attributes for the metric (optional)
31
+ # @param integration [String, Symbol, nil] registered integration emitting the metric (optional)
29
32
  # @return [void]
30
- def gauge(name, value, unit: nil, attributes: nil)
33
+ def gauge(name, value, unit: nil, attributes: nil, integration: nil)
31
34
  return unless Sentry.initialized?
32
35
 
33
36
  Sentry.get_current_hub.capture_metric(
@@ -35,7 +38,8 @@ module Sentry
35
38
  type: :gauge,
36
39
  value: value,
37
40
  unit: unit,
38
- attributes: attributes
41
+ attributes: attributes,
42
+ integration: integration
39
43
  )
40
44
  end
41
45
 
@@ -44,8 +48,9 @@ module Sentry
44
48
  # @param value [Numeric] the distribution value
45
49
  # @param unit [String, nil] the metric unit (optional)
46
50
  # @param attributes [Hash, nil] additional attributes for the metric (optional)
51
+ # @param integration [String, Symbol, nil] registered integration emitting the metric (optional)
47
52
  # @return [void]
48
- def distribution(name, value, unit: nil, attributes: nil)
53
+ def distribution(name, value, unit: nil, attributes: nil, integration: nil)
49
54
  return unless Sentry.initialized?
50
55
 
51
56
  Sentry.get_current_hub.capture_metric(
@@ -53,7 +58,8 @@ module Sentry
53
58
  type: :distribution,
54
59
  value: value,
55
60
  unit: unit,
56
- attributes: attributes
61
+ attributes: attributes,
62
+ integration: integration
57
63
  )
58
64
  end
59
65
  end
@@ -72,10 +72,9 @@ module Sentry
72
72
 
73
73
  result = { method: req.method, url: url }
74
74
 
75
- if Sentry.configuration.send_default_pii
76
- result[:query] = uri.query
77
- result[:body] = req.body
78
- end
75
+ query = filter_query_params(uri.query)
76
+ result[:query] = query if query
77
+ result[:body] = req.body if Sentry.configuration.data_collection.collect_outgoing_http_body?
79
78
 
80
79
  result
81
80
  end
data/lib/sentry/redis.rb CHANGED
@@ -62,7 +62,7 @@ module Sentry
62
62
  command_set = { command: command.to_s.upcase }
63
63
  command_set[:key] = key if Utils::EncodingHelper.valid_utf_8?(key)
64
64
 
65
- if Sentry.configuration.send_default_pii
65
+ if Sentry.configuration.data_collection.database_query_data
66
66
  command_set[:arguments] = arguments
67
67
  .select { |a| Utils::EncodingHelper.valid_utf_8?(a) }
68
68
  .join(" ")
data/lib/sentry/scope.rb CHANGED
@@ -9,6 +9,7 @@ require "etc"
9
9
  module Sentry
10
10
  class Scope
11
11
  include ArgumentCheckingHelper
12
+ include CallbackHelper
12
13
 
13
14
  ATTRIBUTES = [
14
15
  :transaction_name,
@@ -71,7 +72,8 @@ module Sentry
71
72
 
72
73
  unless all_event_processors.empty?
73
74
  all_event_processors.each do |processor_block|
74
- event = processor_block.call(event, hint)
75
+ event = safe_dispatch_callback("event_processor", processor_block, [event, hint])
76
+ return unless event
75
77
  end
76
78
  end
77
79
 
@@ -102,8 +104,10 @@ module Sentry
102
104
  configuration = Sentry.configuration
103
105
  return telemetry unless configuration
104
106
 
105
- telemetry.attributes["sentry.sdk.name"] ||= Sentry.sdk_meta["name"]
106
- telemetry.attributes["sentry.sdk.version"] ||= Sentry.sdk_meta["version"]
107
+ sdk_meta = telemetry.respond_to?(:integration_meta) ? telemetry.integration_meta : nil
108
+ sdk_meta ||= Sentry.sdk_meta
109
+ telemetry.attributes["sentry.sdk.name"] ||= sdk_meta[:name] || sdk_meta["name"]
110
+ telemetry.attributes["sentry.sdk.version"] ||= sdk_meta[:version] || sdk_meta["version"]
107
111
  telemetry.attributes["sentry.environment"] ||= configuration.environment if configuration.environment
108
112
  telemetry.attributes["sentry.release"] ||= configuration.release if configuration.release
109
113
  telemetry.attributes["server.address"] ||= configuration.server_name if configuration.server_name
@@ -35,6 +35,13 @@ module Sentry
35
35
 
36
36
  private
37
37
 
38
+ def reset_if_forked
39
+ return unless super
40
+
41
+ @mutex = Mutex.new
42
+ @pending_aggregates = {}
43
+ end
44
+
38
45
  def init_aggregates(aggregation_key)
39
46
  aggregates = { started: aggregation_key.iso8601 }
40
47
  Session::AGGREGATE_STATUSES.each { |k| aggregates[k] = 0 }
@@ -4,6 +4,8 @@ module Sentry
4
4
  # Ruby Logger support Add commentMore actions
5
5
  # intercepts any logger instance and send the log to Sentry too.
6
6
  module StdLibLogger
7
+ include CallbackHelper
8
+
7
9
  SEVERITY_MAP = {
8
10
  0 => :debug,
9
11
  1 => :info,
@@ -37,8 +39,12 @@ module Sentry
37
39
  message = message.to_s.strip
38
40
 
39
41
  if !message.nil? && message != Sentry::Logger::PROGNAME && method = SEVERITY_MAP[severity]
40
- if (filter = Sentry.configuration.std_lib_logger_filter) && !filter.call(self, message, method)
41
- return result
42
+ if filter = Sentry.configuration.std_lib_logger_filter
43
+ return result unless safe_dispatch_callback(
44
+ "std_lib_logger_filter",
45
+ filter,
46
+ [self, message, method]
47
+ )
42
48
  end
43
49
 
44
50
  Sentry.logger.send(method, message, origin: ORIGIN)
@@ -50,10 +56,6 @@ module Sentry
50
56
  end
51
57
  end
52
58
 
53
- Sentry.register_patch(:logger) do |config|
54
- if config.enable_logs
55
- ::Logger.prepend(Sentry::StdLibLogger)
56
- else
57
- config.sdk_logger.warn(":logger patch enabled but `enable_logs` is turned off - skipping applying patch")
58
- end
59
+ Sentry.register_patch(:logger) do
60
+ ::Logger.prepend(Sentry::StdLibLogger)
59
61
  end
@@ -12,7 +12,10 @@ module Sentry
12
12
  #
13
13
  # @!visibility private
14
14
  class TelemetryEventBuffer < ThreadedPeriodicWorker
15
+ include CallbackHelper
16
+
15
17
  FLUSH_INTERVAL = 5 # seconds
18
+ FLUSH_TIMEOUT = 2 # seconds
16
19
 
17
20
  # @!visibility private
18
21
  attr_reader :pending_items, :envelope_type, :data_category, :thread
@@ -21,6 +24,7 @@ module Sentry
21
24
  super(configuration.sdk_logger, FLUSH_INTERVAL)
22
25
 
23
26
  @client = client
27
+ @configuration = configuration
24
28
  @dsn = configuration.dsn
25
29
  @debug = configuration.debug
26
30
  @event_class = event_class
@@ -38,39 +42,44 @@ module Sentry
38
42
  end
39
43
 
40
44
  def flush
41
- @mutex.synchronize do
42
- return if empty?
43
-
44
- log_debug("[#{self.class}] flushing #{size} #{@event_class}")
45
+ return unless thread&.alive? && wake
45
46
 
46
- send_items
47
- end
47
+ wait_until_idle(FLUSH_TIMEOUT)
48
+ end
48
49
 
49
- self
50
+ def run
51
+ flush_pending_items
50
52
  end
51
- alias_method :run, :flush
52
53
 
53
54
  def add_item(item)
55
+ # the buffer thread can never add telemetry itself to prevent recursion
56
+ return self if Thread.current == thread
57
+ return unless ensure_thread
58
+
54
59
  # Prevent ThreadError from re-entrant locking (e.g. transport instrumentation calling Sentry.metrics.*)
55
60
  return self if @mutex.owned?
56
61
 
57
- @mutex.synchronize do
58
- return unless ensure_thread
59
-
62
+ dropped = false
63
+ size_exceeded = @mutex.synchronize do
60
64
  if size >= @max_items_before_drop
61
- log_debug("[#{self.class}] exceeded max capacity, dropping event")
62
- @client.transport.record_lost_event(
63
- :queue_overflow,
64
- @data_category,
65
- num_bytes: JSON.generate(item.to_h).bytesize
66
- )
65
+ dropped = true
67
66
  else
68
67
  @pending_items << item
69
68
  end
70
69
 
71
- send_items if size >= @max_items
70
+ size >= @max_items
71
+ end
72
+
73
+ if dropped
74
+ log_debug("[#{self.class}] exceeded max capacity, dropping event")
75
+ @client.transport.record_lost_event(
76
+ :queue_overflow,
77
+ @data_category,
78
+ num_bytes: JSON.generate(item.to_h).bytesize
79
+ )
72
80
  end
73
81
 
82
+ wake if size_exceeded
74
83
  self
75
84
  end
76
85
 
@@ -88,21 +97,40 @@ module Sentry
88
97
 
89
98
  private
90
99
 
91
- def send_items
92
- envelope = Envelope.new(
93
- event_id: Sentry::Utils.uuid,
94
- sent_at: Sentry.utc_now.iso8601,
95
- dsn: @dsn,
96
- sdk: Sentry.sdk_meta
97
- )
100
+ def reset_if_forked
101
+ return unless super
102
+
103
+ # Discard items inherited from the parent to avoid duplicate delivery.
104
+ @mutex = Mutex.new
105
+ @pending_items = []
106
+ end
107
+
108
+ def flush_pending_items
109
+ pending_items = @mutex.synchronize do
110
+ next if @pending_items.empty?
111
+
112
+ items = @pending_items
113
+ @pending_items = []
114
+ items
115
+ end
116
+
117
+ return unless pending_items
118
+
119
+ log_debug("[#{self.class}] flushing #{pending_items.size} #{@event_class}")
120
+ send_items(pending_items)
121
+ self
122
+ end
123
+
124
+ def send_items(pending_items)
125
+ envelope = Envelope.new(sent_at: Sentry.utc_now.iso8601)
98
126
 
99
127
  discarded_count = 0
100
128
  discarded_bytes = 0
101
129
  envelope_items = []
102
130
 
103
- if @before_send
104
- @pending_items.each do |item|
105
- processed_item = @before_send.call(item)
131
+ if callback = @configuration.send(@before_send)
132
+ pending_items.each do |item|
133
+ processed_item = safe_dispatch_callback(@before_send.to_s, callback, [item])
106
134
 
107
135
  if processed_item
108
136
  envelope_items << processed_item.to_h
@@ -112,7 +140,7 @@ module Sentry
112
140
  end
113
141
  end
114
142
  else
115
- envelope_items = @pending_items.map(&:to_h)
143
+ envelope_items = pending_items.map(&:to_h)
116
144
  end
117
145
 
118
146
  unless discarded_count.zero?
@@ -133,8 +161,6 @@ module Sentry
133
161
  @client.send_envelope(envelope)
134
162
  rescue => e
135
163
  log_error("[#{self.class}] Failed to send #{@event_class}", e, debug: @debug)
136
- ensure
137
- clear!
138
164
  end
139
165
  end
140
166
  end
@@ -33,7 +33,7 @@ module Sentry
33
33
  dummy_config.background_worker_threads = 0
34
34
 
35
35
  # user can overwrite some of the configs, with a few exceptions like:
36
- # - include_local_variables
36
+ # - data_collection
37
37
  # - auto_session_tracking
38
38
  block&.call(dummy_config)
39
39
 
@@ -90,9 +90,7 @@ module Sentry
90
90
  transport.clear if transport.respond_to?(:clear)
91
91
  end
92
92
 
93
- if Sentry.configuration.enable_logs && sentry_logger.respond_to?(:clear)
94
- sentry_logger.clear
95
- end
93
+ sentry_logger.clear if sentry_logger.respond_to?(:clear)
96
94
  end
97
95
 
98
96
  # @return [Sentry::StructuredLogger, Sentry::DebugStructuredLogger]
@@ -129,6 +127,8 @@ module Sentry
129
127
  end
130
128
 
131
129
  def sentry_logs
130
+ Sentry.get_current_client&.log_event_buffer&.flush
131
+
132
132
  sentry_envelopes
133
133
  .flat_map(&:items)
134
134
  .select { |item| item.headers[:type] == "log" }
@@ -136,6 +136,8 @@ module Sentry
136
136
  end
137
137
 
138
138
  def sentry_metrics
139
+ Sentry.get_current_client&.metric_event_buffer&.flush
140
+
139
141
  sentry_envelopes
140
142
  .flat_map(&:items)
141
143
  .select { |item| item.headers[:type] == "trace_metric" }