sentry-ruby 5.8.0 → 5.12.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.
data/lib/sentry/hub.rb CHANGED
@@ -88,8 +88,10 @@ module Sentry
88
88
  }
89
89
 
90
90
  sampling_context.merge!(custom_sampling_context)
91
-
92
91
  transaction.set_initial_sample_decision(sampling_context: sampling_context)
92
+
93
+ transaction.start_profiler!
94
+
93
95
  transaction
94
96
  end
95
97
 
@@ -114,7 +116,11 @@ module Sentry
114
116
  end
115
117
 
116
118
  def capture_exception(exception, **options, &block)
117
- check_argument_type!(exception, ::Exception)
119
+ if RUBY_PLATFORM == "java"
120
+ check_argument_type!(exception, ::Exception, ::Java::JavaLang::Throwable)
121
+ else
122
+ check_argument_type!(exception, ::Exception)
123
+ end
118
124
 
119
125
  return if Sentry.exception_captured?(exception)
120
126
 
@@ -122,6 +128,7 @@ module Sentry
122
128
 
123
129
  options[:hint] ||= {}
124
130
  options[:hint][:exception] = exception
131
+
125
132
  event = current_client.event_from_exception(exception, options[:hint])
126
133
 
127
134
  return unless event
@@ -149,6 +156,30 @@ module Sentry
149
156
  capture_event(event, **options, &block)
150
157
  end
151
158
 
159
+ def capture_check_in(slug, status, **options, &block)
160
+ check_argument_type!(slug, ::String)
161
+ check_argument_includes!(status, Sentry::CheckInEvent::VALID_STATUSES)
162
+
163
+ return unless current_client
164
+
165
+ options[:hint] ||= {}
166
+ options[:hint][:slug] = slug
167
+
168
+ event = current_client.event_from_check_in(
169
+ slug,
170
+ status,
171
+ options[:hint],
172
+ duration: options.delete(:duration),
173
+ monitor_config: options.delete(:monitor_config),
174
+ check_in_id: options.delete(:check_in_id)
175
+ )
176
+
177
+ return unless event
178
+
179
+ capture_event(event, **options, &block)
180
+ event.check_in_id
181
+ end
182
+
152
183
  def capture_event(event, **options, &block)
153
184
  check_argument_type!(event, Sentry::Event)
154
185
 
@@ -171,7 +202,7 @@ module Sentry
171
202
  configuration.log_debug(event.to_json_compatible)
172
203
  end
173
204
 
174
- @last_event_id = event&.event_id unless event.is_a?(Sentry::TransactionEvent)
205
+ @last_event_id = event&.event_id if event.is_a?(Sentry::ErrorEvent)
175
206
  event
176
207
  end
177
208
 
@@ -222,6 +253,50 @@ module Sentry
222
253
  end_session
223
254
  end
224
255
 
256
+ def get_traceparent
257
+ return nil unless current_scope
258
+
259
+ current_scope.get_span&.to_sentry_trace ||
260
+ current_scope.propagation_context.get_traceparent
261
+ end
262
+
263
+ def get_baggage
264
+ return nil unless current_scope
265
+
266
+ current_scope.get_span&.to_baggage ||
267
+ current_scope.propagation_context.get_baggage&.serialize
268
+ end
269
+
270
+ def get_trace_propagation_headers
271
+ headers = {}
272
+
273
+ traceparent = get_traceparent
274
+ headers[SENTRY_TRACE_HEADER_NAME] = traceparent if traceparent
275
+
276
+ baggage = get_baggage
277
+ headers[BAGGAGE_HEADER_NAME] = baggage if baggage && !baggage.empty?
278
+
279
+ headers
280
+ end
281
+
282
+ def continue_trace(env, **options)
283
+ configure_scope { |s| s.generate_propagation_context(env) }
284
+
285
+ return nil unless configuration.tracing_enabled?
286
+
287
+ propagation_context = current_scope.propagation_context
288
+ return nil unless propagation_context.incoming_trace
289
+
290
+ Transaction.new(
291
+ hub: self,
292
+ trace_id: propagation_context.trace_id,
293
+ parent_span_id: propagation_context.parent_span_id,
294
+ parent_sampled: propagation_context.parent_sampled,
295
+ baggage: propagation_context.baggage,
296
+ **options
297
+ )
298
+ end
299
+
225
300
  private
226
301
 
227
302
  def current_layer
@@ -22,5 +22,11 @@ module Sentry
22
22
  options[:hint][:integration] = integration_name
23
23
  Sentry.capture_message(message, **options, &block)
24
24
  end
25
+
26
+ def capture_check_in(slug, status, **options, &block)
27
+ options[:hint] ||= {}
28
+ options[:hint][:integration] = integration_name
29
+ Sentry.capture_check_in(slug, status, **options, &block)
30
+ end
25
31
  end
26
32
  end
@@ -11,7 +11,8 @@ module Sentry
11
11
  OMISSION_MARK = "...".freeze
12
12
  MAX_LOCAL_BYTES = 1024
13
13
 
14
- attr_reader :type, :value, :module, :thread_id, :stacktrace
14
+ attr_reader :type, :module, :thread_id, :stacktrace
15
+ attr_accessor :value
15
16
 
16
17
  def initialize(exception:, stacktrace: nil)
17
18
  @type = exception.class.to_s
@@ -22,7 +23,7 @@ module Sentry
22
23
  exception.message || ""
23
24
  end
24
25
 
25
- @value = exception_message.byteslice(0..Event::MAX_MESSAGE_SIZE_IN_BYTES)
26
+ @value = Utils::EncodingHelper.encode_to_utf_8(exception_message.byteslice(0..Event::MAX_MESSAGE_SIZE_IN_BYTES))
26
27
 
27
28
  @module = exception.class.to_s.split('::')[0...-1].join('::')
28
29
  @thread_id = Thread.current.object_id
@@ -50,7 +51,7 @@ module Sentry
50
51
  v = v.byteslice(0..MAX_LOCAL_BYTES - 1) + OMISSION_MARK
51
52
  end
52
53
 
53
- v
54
+ Utils::EncodingHelper.encode_to_utf_8(v)
54
55
  rescue StandardError
55
56
  PROBLEMATIC_LOCAL_VALUE_REPLACEMENT
56
57
  end
@@ -30,15 +30,21 @@ module Sentry
30
30
  return super if from_sentry_sdk?
31
31
 
32
32
  Sentry.with_child_span(op: OP_NAME, start_timestamp: Sentry.utc_now.to_f) do |sentry_span|
33
- set_sentry_trace_header(req, sentry_span)
33
+ request_info = extract_request_info(req)
34
+
35
+ if propagate_trace?(request_info[:url], Sentry.configuration)
36
+ set_propagation_headers(req)
37
+ end
34
38
 
35
39
  super.tap do |res|
36
- record_sentry_breadcrumb(req, res)
40
+ record_sentry_breadcrumb(request_info, res)
37
41
 
38
42
  if sentry_span
39
- request_info = extract_request_info(req)
40
43
  sentry_span.set_description("#{request_info[:method]} #{request_info[:url]}")
41
- sentry_span.set_data(:status, res.code.to_i)
44
+ sentry_span.set_data(Span::DataConventions::URL, request_info[:url])
45
+ sentry_span.set_data(Span::DataConventions::HTTP_METHOD, request_info[:method])
46
+ sentry_span.set_data(Span::DataConventions::HTTP_QUERY, request_info[:query]) if request_info[:query]
47
+ sentry_span.set_data(Span::DataConventions::HTTP_STATUS_CODE, res.code.to_i)
42
48
  end
43
49
  end
44
50
  end
@@ -46,23 +52,13 @@ module Sentry
46
52
 
47
53
  private
48
54
 
49
- def set_sentry_trace_header(req, sentry_span)
50
- return unless sentry_span
51
-
52
- client = Sentry.get_current_client
53
-
54
- trace = client.generate_sentry_trace(sentry_span)
55
- req[SENTRY_TRACE_HEADER_NAME] = trace if trace
56
-
57
- baggage = client.generate_baggage(sentry_span)
58
- req[BAGGAGE_HEADER_NAME] = baggage if baggage && !baggage.empty?
55
+ def set_propagation_headers(req)
56
+ Sentry.get_trace_propagation_headers&.each { |k, v| req[k] = v }
59
57
  end
60
58
 
61
- def record_sentry_breadcrumb(req, res)
59
+ def record_sentry_breadcrumb(request_info, res)
62
60
  return unless Sentry.initialized? && Sentry.configuration.breadcrumbs_logger.include?(:http_logger)
63
61
 
64
- request_info = extract_request_info(req)
65
-
66
62
  crumb = Sentry::Breadcrumb.new(
67
63
  level: :info,
68
64
  category: BREADCRUMB_CATEGORY,
@@ -87,17 +83,20 @@ module Sentry
87
83
  result = { method: req.method, url: url }
88
84
 
89
85
  if Sentry.configuration.send_default_pii
90
- result[:url] = result[:url] + "?#{uri.query}"
86
+ result[:query] = uri.query
91
87
  result[:body] = req.body
92
88
  end
93
89
 
94
90
  result
95
91
  end
92
+
93
+ def propagate_trace?(url, configuration)
94
+ url &&
95
+ configuration.propagate_traces &&
96
+ configuration.trace_propagation_targets.any? { |target| url.match?(target) }
97
+ end
96
98
  end
97
99
  end
98
100
  end
99
101
 
100
- Sentry.register_patch do
101
- patch = Sentry::Net::HTTP
102
- Net::HTTP.send(:prepend, patch) unless Net::HTTP.ancestors.include?(patch)
103
- end
102
+ Sentry.register_patch(Sentry::Net::HTTP, Net::HTTP)
@@ -0,0 +1,233 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'securerandom'
4
+
5
+ module Sentry
6
+ class Profiler
7
+ VERSION = '1'
8
+ PLATFORM = 'ruby'
9
+ # 101 Hz in microseconds
10
+ DEFAULT_INTERVAL = 1e6 / 101
11
+ MICRO_TO_NANO_SECONDS = 1e3
12
+ MIN_SAMPLES_REQUIRED = 2
13
+
14
+ attr_reader :sampled, :started, :event_id
15
+
16
+ def initialize(configuration)
17
+ @event_id = SecureRandom.uuid.delete('-')
18
+ @started = false
19
+ @sampled = nil
20
+
21
+ @profiling_enabled = defined?(StackProf) && configuration.profiling_enabled?
22
+ @profiles_sample_rate = configuration.profiles_sample_rate
23
+ @project_root = configuration.project_root
24
+ @app_dirs_pattern = configuration.app_dirs_pattern || Backtrace::APP_DIRS_PATTERN
25
+ @in_app_pattern = Regexp.new("^(#{@project_root}/)?#{@app_dirs_pattern}")
26
+ end
27
+
28
+ def start
29
+ return unless @sampled
30
+
31
+ @started = StackProf.start(interval: DEFAULT_INTERVAL,
32
+ mode: :wall,
33
+ raw: true,
34
+ aggregate: false)
35
+
36
+ @started ? log('Started') : log('Not started since running elsewhere')
37
+ end
38
+
39
+ def stop
40
+ return unless @sampled
41
+ return unless @started
42
+
43
+ StackProf.stop
44
+ log('Stopped')
45
+ end
46
+
47
+ # Sets initial sampling decision of the profile.
48
+ # @return [void]
49
+ def set_initial_sample_decision(transaction_sampled)
50
+ unless @profiling_enabled
51
+ @sampled = false
52
+ return
53
+ end
54
+
55
+ unless transaction_sampled
56
+ @sampled = false
57
+ log('Discarding profile because transaction not sampled')
58
+ return
59
+ end
60
+
61
+ case @profiles_sample_rate
62
+ when 0.0
63
+ @sampled = false
64
+ log('Discarding profile because sample_rate is 0')
65
+ return
66
+ when 1.0
67
+ @sampled = true
68
+ return
69
+ else
70
+ @sampled = Random.rand < @profiles_sample_rate
71
+ end
72
+
73
+ log('Discarding profile due to sampling decision') unless @sampled
74
+ end
75
+
76
+ def to_hash
77
+ unless @sampled
78
+ record_lost_event(:sample_rate)
79
+ return {}
80
+ end
81
+
82
+ return {} unless @started
83
+
84
+ results = StackProf.results
85
+
86
+ if !results || results.empty? || results[:samples] == 0 || !results[:raw]
87
+ record_lost_event(:insufficient_data)
88
+ return {}
89
+ end
90
+
91
+ frame_map = {}
92
+
93
+ frames = results[:frames].to_enum.with_index.map do |frame, idx|
94
+ frame_id, frame_data = frame
95
+
96
+ # need to map over stackprof frame ids to ours
97
+ frame_map[frame_id] = idx
98
+
99
+ file_path = frame_data[:file]
100
+ in_app = in_app?(file_path)
101
+ filename = compute_filename(file_path, in_app)
102
+ function, mod = split_module(frame_data[:name])
103
+
104
+ frame_hash = {
105
+ abs_path: file_path,
106
+ function: function,
107
+ filename: filename,
108
+ in_app: in_app
109
+ }
110
+
111
+ frame_hash[:module] = mod if mod
112
+ frame_hash[:lineno] = frame_data[:line] if frame_data[:line]
113
+
114
+ frame_hash
115
+ end
116
+
117
+ idx = 0
118
+ stacks = []
119
+ num_seen = []
120
+
121
+ # extract stacks from raw
122
+ # raw is a single array of [.., len_stack, *stack_frames(len_stack), num_stack_seen , ..]
123
+ while (len = results[:raw][idx])
124
+ idx += 1
125
+
126
+ # our call graph is reversed
127
+ stack = results[:raw].slice(idx, len).map { |id| frame_map[id] }.compact.reverse
128
+ stacks << stack
129
+
130
+ num_seen << results[:raw][idx + len]
131
+ idx += len + 1
132
+
133
+ log('Unknown frame in stack') if stack.size != len
134
+ end
135
+
136
+ idx = 0
137
+ elapsed_since_start_ns = 0
138
+ samples = []
139
+
140
+ num_seen.each_with_index do |n, i|
141
+ n.times do
142
+ # stackprof deltas are in microseconds
143
+ delta = results[:raw_timestamp_deltas][idx]
144
+ elapsed_since_start_ns += (delta * MICRO_TO_NANO_SECONDS).to_i
145
+ idx += 1
146
+
147
+ # Not sure why but some deltas are very small like 0/1 values,
148
+ # they pollute our flamegraph so just ignore them for now.
149
+ # Open issue at https://github.com/tmm1/stackprof/issues/201
150
+ next if delta < 10
151
+
152
+ samples << {
153
+ stack_id: i,
154
+ # TODO-neel-profiler we need to patch rb_profile_frames and write our own C extension to enable threading info.
155
+ # Till then, on multi-threaded servers like puma, we will get frames from other active threads when the one
156
+ # we're profiling is idle/sleeping/waiting for IO etc.
157
+ # https://bugs.ruby-lang.org/issues/10602
158
+ thread_id: '0',
159
+ elapsed_since_start_ns: elapsed_since_start_ns.to_s
160
+ }
161
+ end
162
+ end
163
+
164
+ log('Some samples thrown away') if samples.size != results[:samples]
165
+
166
+ if samples.size <= MIN_SAMPLES_REQUIRED
167
+ log('Not enough samples, discarding profiler')
168
+ record_lost_event(:insufficient_data)
169
+ return {}
170
+ end
171
+
172
+ profile = {
173
+ frames: frames,
174
+ stacks: stacks,
175
+ samples: samples
176
+ }
177
+
178
+ {
179
+ event_id: @event_id,
180
+ platform: PLATFORM,
181
+ version: VERSION,
182
+ profile: profile
183
+ }
184
+ end
185
+
186
+ private
187
+
188
+ def log(message)
189
+ Sentry.logger.debug(LOGGER_PROGNAME) { "[Profiler] #{message}" }
190
+ end
191
+
192
+ def in_app?(abs_path)
193
+ abs_path.match?(@in_app_pattern)
194
+ end
195
+
196
+ # copied from stacktrace.rb since I don't want to touch existing code
197
+ # TODO-neel-profiler try to fetch this from stackprof once we patch
198
+ # the native extension
199
+ def compute_filename(abs_path, in_app)
200
+ return nil if abs_path.nil?
201
+
202
+ under_project_root = @project_root && abs_path.start_with?(@project_root)
203
+
204
+ prefix =
205
+ if under_project_root && in_app
206
+ @project_root
207
+ else
208
+ longest_load_path = $LOAD_PATH.select { |path| abs_path.start_with?(path.to_s) }.max_by(&:size)
209
+
210
+ if under_project_root
211
+ longest_load_path || @project_root
212
+ else
213
+ longest_load_path
214
+ end
215
+ end
216
+
217
+ prefix ? abs_path[prefix.to_s.chomp(File::SEPARATOR).length + 1..-1] : abs_path
218
+ end
219
+
220
+ def split_module(name)
221
+ # last module plus class/instance method
222
+ i = name.rindex('::')
223
+ function = i ? name[(i + 2)..-1] : name
224
+ mod = i ? name[0...i] : nil
225
+
226
+ [function, mod]
227
+ end
228
+
229
+ def record_lost_event(reason)
230
+ Sentry.get_current_client&.transport&.record_lost_event(reason, 'profile')
231
+ end
232
+ end
233
+ end
@@ -0,0 +1,134 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "securerandom"
4
+ require "sentry/baggage"
5
+
6
+ module Sentry
7
+ class PropagationContext
8
+ SENTRY_TRACE_REGEXP = Regexp.new(
9
+ "^[ \t]*" + # whitespace
10
+ "([0-9a-f]{32})?" + # trace_id
11
+ "-?([0-9a-f]{16})?" + # span_id
12
+ "-?([01])?" + # sampled
13
+ "[ \t]*$" # whitespace
14
+ )
15
+
16
+ # An uuid that can be used to identify a trace.
17
+ # @return [String]
18
+ attr_reader :trace_id
19
+ # An uuid that can be used to identify the span.
20
+ # @return [String]
21
+ attr_reader :span_id
22
+ # Span parent's span_id.
23
+ # @return [String, nil]
24
+ attr_reader :parent_span_id
25
+ # The sampling decision of the parent transaction.
26
+ # @return [Boolean, nil]
27
+ attr_reader :parent_sampled
28
+ # Is there an incoming trace or not?
29
+ # @return [Boolean]
30
+ attr_reader :incoming_trace
31
+ # This is only for accessing the current baggage variable.
32
+ # Please use the #get_baggage method for interfacing outside this class.
33
+ # @return [Baggage, nil]
34
+ attr_reader :baggage
35
+
36
+ def initialize(scope, env = nil)
37
+ @scope = scope
38
+ @parent_span_id = nil
39
+ @parent_sampled = nil
40
+ @baggage = nil
41
+ @incoming_trace = false
42
+
43
+ if env
44
+ sentry_trace_header = env["HTTP_SENTRY_TRACE"] || env[SENTRY_TRACE_HEADER_NAME]
45
+ baggage_header = env["HTTP_BAGGAGE"] || env[BAGGAGE_HEADER_NAME]
46
+
47
+ if sentry_trace_header
48
+ sentry_trace_data = self.class.extract_sentry_trace(sentry_trace_header)
49
+
50
+ if sentry_trace_data
51
+ @trace_id, @parent_span_id, @parent_sampled = sentry_trace_data
52
+
53
+ @baggage = if baggage_header && !baggage_header.empty?
54
+ Baggage.from_incoming_header(baggage_header)
55
+ else
56
+ # If there's an incoming sentry-trace but no incoming baggage header,
57
+ # for instance in traces coming from older SDKs,
58
+ # baggage will be empty and frozen and won't be populated as head SDK.
59
+ Baggage.new({})
60
+ end
61
+
62
+ @baggage.freeze!
63
+ @incoming_trace = true
64
+ end
65
+ end
66
+ end
67
+
68
+ @trace_id ||= SecureRandom.uuid.delete("-")
69
+ @span_id = SecureRandom.uuid.delete("-").slice(0, 16)
70
+ end
71
+
72
+ # Extract the trace_id, parent_span_id and parent_sampled values from a sentry-trace header.
73
+ #
74
+ # @param sentry_trace [String] the sentry-trace header value from the previous transaction.
75
+ # @return [Array, nil]
76
+ def self.extract_sentry_trace(sentry_trace)
77
+ match = SENTRY_TRACE_REGEXP.match(sentry_trace)
78
+ return nil if match.nil?
79
+
80
+ trace_id, parent_span_id, sampled_flag = match[1..3]
81
+ parent_sampled = sampled_flag.nil? ? nil : sampled_flag != "0"
82
+
83
+ [trace_id, parent_span_id, parent_sampled]
84
+ end
85
+
86
+ # Returns the trace context that can be used to embed in an Event.
87
+ # @return [Hash]
88
+ def get_trace_context
89
+ {
90
+ trace_id: trace_id,
91
+ span_id: span_id,
92
+ parent_span_id: parent_span_id
93
+ }
94
+ end
95
+
96
+ # Returns the sentry-trace header from the propagation context.
97
+ # @return [String]
98
+ def get_traceparent
99
+ "#{trace_id}-#{span_id}"
100
+ end
101
+
102
+ # Returns the Baggage from the propagation context or populates as head SDK if empty.
103
+ # @return [Baggage, nil]
104
+ def get_baggage
105
+ populate_head_baggage if @baggage.nil? || @baggage.mutable
106
+ @baggage
107
+ end
108
+
109
+ # Returns the Dynamic Sampling Context from the baggage.
110
+ # @return [String, nil]
111
+ def get_dynamic_sampling_context
112
+ get_baggage&.dynamic_sampling_context
113
+ end
114
+
115
+ private
116
+
117
+ def populate_head_baggage
118
+ return unless Sentry.initialized?
119
+
120
+ configuration = Sentry.configuration
121
+
122
+ items = {
123
+ "trace_id" => trace_id,
124
+ "environment" => configuration.environment,
125
+ "release" => configuration.release,
126
+ "public_key" => configuration.dsn&.public_key,
127
+ "user_segment" => @scope.user && @scope.user["segment"]
128
+ }
129
+
130
+ items.compact!
131
+ @baggage = Baggage.new(items, mutable: false)
132
+ end
133
+ end
134
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sentry
4
+ module Puma
5
+ module Server
6
+ def lowlevel_error(e, env, status=500)
7
+ result = super
8
+
9
+ begin
10
+ Sentry.capture_exception(e) do |scope|
11
+ scope.set_rack_env(env)
12
+ end
13
+ rescue
14
+ # if anything happens, we don't want to break the app
15
+ end
16
+
17
+ result
18
+ end
19
+ end
20
+ end
21
+ end
22
+
23
+ if defined?(Puma::Server)
24
+ Sentry.register_patch(Sentry::Puma::Server, Puma::Server)
25
+ end
@@ -62,11 +62,8 @@ module Sentry
62
62
  end
63
63
 
64
64
  def start_transaction(env, scope)
65
- sentry_trace = env["HTTP_SENTRY_TRACE"]
66
- baggage = env["HTTP_BAGGAGE"]
67
-
68
65
  options = { name: scope.transaction_name, source: scope.transaction_source, op: transaction_op }
69
- transaction = Sentry::Transaction.from_sentry_trace(sentry_trace, baggage: baggage, **options) if sentry_trace
66
+ transaction = Sentry.continue_trace(env, **options)
70
67
  Sentry.start_transaction(transaction: transaction, custom_sampling_context: { env: env }, **options)
71
68
  end
72
69
 
data/lib/sentry/redis.rb CHANGED
@@ -19,7 +19,10 @@ module Sentry
19
19
 
20
20
  if span
21
21
  span.set_description(commands_description)
22
- span.set_data(:server, server_description)
22
+ span.set_data(Span::DataConventions::DB_SYSTEM, "redis")
23
+ span.set_data(Span::DataConventions::DB_NAME, db)
24
+ span.set_data(Span::DataConventions::SERVER_ADDRESS, host)
25
+ span.set_data(Span::DataConventions::SERVER_PORT, port)
23
26
  end
24
27
  end
25
28
  end
@@ -30,6 +33,7 @@ module Sentry
30
33
  attr_reader :commands, :host, :port, :db
31
34
 
32
35
  def record_breadcrumb
36
+ return unless Sentry.initialized?
33
37
  return unless Sentry.configuration.breadcrumbs_logger.include?(LOGGER_NAME)
34
38
 
35
39
  Sentry.add_breadcrumb(
@@ -95,12 +99,7 @@ end
95
99
 
96
100
  if defined?(::Redis::Client)
97
101
  if Gem::Version.new(::Redis::VERSION) < Gem::Version.new("5.0")
98
- Sentry.register_patch do
99
- patch = Sentry::Redis::OldClientPatch
100
- unless Redis::Client.ancestors.include?(patch)
101
- Redis::Client.prepend(patch)
102
- end
103
- end
102
+ Sentry.register_patch(Sentry::Redis::OldClientPatch, ::Redis::Client)
104
103
  elsif defined?(RedisClient)
105
104
  RedisClient.register(Sentry::Redis::GlobalRedisInstrumentation)
106
105
  end