airbrake-ruby 4.6.0 → 6.2.2

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 (107) hide show
  1. checksums.yaml +5 -5
  2. data/lib/airbrake-ruby/async_sender.rb +24 -109
  3. data/lib/airbrake-ruby/backlog.rb +123 -0
  4. data/lib/airbrake-ruby/backtrace.rb +8 -7
  5. data/lib/airbrake-ruby/benchmark.rb +1 -1
  6. data/lib/airbrake-ruby/code_hunk.rb +1 -1
  7. data/lib/airbrake-ruby/config/processor.rb +77 -0
  8. data/lib/airbrake-ruby/config/validator.rb +9 -3
  9. data/lib/airbrake-ruby/config.rb +78 -19
  10. data/lib/airbrake-ruby/context.rb +51 -0
  11. data/lib/airbrake-ruby/deploy_notifier.rb +1 -1
  12. data/lib/airbrake-ruby/file_cache.rb +6 -0
  13. data/lib/airbrake-ruby/filter_chain.rb +18 -1
  14. data/lib/airbrake-ruby/filters/context_filter.rb +4 -5
  15. data/lib/airbrake-ruby/filters/dependency_filter.rb +1 -0
  16. data/lib/airbrake-ruby/filters/exception_attributes_filter.rb +3 -3
  17. data/lib/airbrake-ruby/filters/gem_root_filter.rb +1 -0
  18. data/lib/airbrake-ruby/filters/git_last_checkout_filter.rb +7 -7
  19. data/lib/airbrake-ruby/filters/git_repository_filter.rb +11 -2
  20. data/lib/airbrake-ruby/filters/git_revision_filter.rb +3 -1
  21. data/lib/airbrake-ruby/filters/{keys_whitelist.rb → keys_allowlist.rb} +3 -3
  22. data/lib/airbrake-ruby/filters/{keys_blacklist.rb → keys_blocklist.rb} +3 -3
  23. data/lib/airbrake-ruby/filters/keys_filter.rb +41 -22
  24. data/lib/airbrake-ruby/filters/root_directory_filter.rb +1 -0
  25. data/lib/airbrake-ruby/filters/sql_filter.rb +35 -11
  26. data/lib/airbrake-ruby/filters/system_exit_filter.rb +1 -0
  27. data/lib/airbrake-ruby/filters/thread_filter.rb +6 -5
  28. data/lib/airbrake-ruby/grouppable.rb +12 -0
  29. data/lib/airbrake-ruby/ignorable.rb +1 -2
  30. data/lib/airbrake-ruby/inspectable.rb +2 -2
  31. data/lib/airbrake-ruby/loggable.rb +1 -1
  32. data/lib/airbrake-ruby/mergeable.rb +12 -0
  33. data/lib/airbrake-ruby/monotonic_time.rb +6 -1
  34. data/lib/airbrake-ruby/nested_exception.rb +22 -1
  35. data/lib/airbrake-ruby/notice.rb +12 -17
  36. data/lib/airbrake-ruby/notice_notifier.rb +15 -7
  37. data/lib/airbrake-ruby/performance_breakdown.rb +16 -9
  38. data/lib/airbrake-ruby/performance_notifier.rb +106 -54
  39. data/lib/airbrake-ruby/promise.rb +1 -0
  40. data/lib/airbrake-ruby/query.rb +20 -14
  41. data/lib/airbrake-ruby/queue.rb +65 -0
  42. data/lib/airbrake-ruby/remote_settings/callback.rb +44 -0
  43. data/lib/airbrake-ruby/remote_settings/settings_data.rb +116 -0
  44. data/lib/airbrake-ruby/remote_settings.rb +128 -0
  45. data/lib/airbrake-ruby/request.rb +14 -11
  46. data/lib/airbrake-ruby/response.rb +56 -5
  47. data/lib/airbrake-ruby/stat.rb +27 -34
  48. data/lib/airbrake-ruby/sync_sender.rb +43 -11
  49. data/lib/airbrake-ruby/tdigest.rb +52 -66
  50. data/lib/airbrake-ruby/thread_pool.rb +139 -0
  51. data/lib/airbrake-ruby/time_truncate.rb +2 -2
  52. data/lib/airbrake-ruby/timed_trace.rb +1 -3
  53. data/lib/airbrake-ruby/truncator.rb +10 -4
  54. data/lib/airbrake-ruby/version.rb +11 -1
  55. data/lib/airbrake-ruby.rb +157 -78
  56. metadata +122 -116
  57. data/spec/airbrake_spec.rb +0 -324
  58. data/spec/async_sender_spec.rb +0 -155
  59. data/spec/backtrace_spec.rb +0 -427
  60. data/spec/benchmark_spec.rb +0 -33
  61. data/spec/code_hunk_spec.rb +0 -115
  62. data/spec/config/validator_spec.rb +0 -184
  63. data/spec/config_spec.rb +0 -154
  64. data/spec/deploy_notifier_spec.rb +0 -48
  65. data/spec/file_cache.rb +0 -36
  66. data/spec/filter_chain_spec.rb +0 -92
  67. data/spec/filters/context_filter_spec.rb +0 -23
  68. data/spec/filters/dependency_filter_spec.rb +0 -12
  69. data/spec/filters/exception_attributes_filter_spec.rb +0 -50
  70. data/spec/filters/gem_root_filter_spec.rb +0 -41
  71. data/spec/filters/git_last_checkout_filter_spec.rb +0 -46
  72. data/spec/filters/git_repository_filter.rb +0 -61
  73. data/spec/filters/git_revision_filter_spec.rb +0 -126
  74. data/spec/filters/keys_blacklist_spec.rb +0 -225
  75. data/spec/filters/keys_whitelist_spec.rb +0 -194
  76. data/spec/filters/root_directory_filter_spec.rb +0 -39
  77. data/spec/filters/sql_filter_spec.rb +0 -219
  78. data/spec/filters/system_exit_filter_spec.rb +0 -14
  79. data/spec/filters/thread_filter_spec.rb +0 -277
  80. data/spec/fixtures/notroot.txt +0 -7
  81. data/spec/fixtures/project_root/code.rb +0 -221
  82. data/spec/fixtures/project_root/empty_file.rb +0 -0
  83. data/spec/fixtures/project_root/long_line.txt +0 -1
  84. data/spec/fixtures/project_root/short_file.rb +0 -3
  85. data/spec/fixtures/project_root/vendor/bundle/ignored_file.rb +0 -5
  86. data/spec/helpers.rb +0 -9
  87. data/spec/ignorable_spec.rb +0 -14
  88. data/spec/inspectable_spec.rb +0 -45
  89. data/spec/monotonic_time_spec.rb +0 -12
  90. data/spec/nested_exception_spec.rb +0 -73
  91. data/spec/notice_notifier_spec/options_spec.rb +0 -259
  92. data/spec/notice_notifier_spec.rb +0 -356
  93. data/spec/notice_spec.rb +0 -296
  94. data/spec/performance_breakdown_spec.rb +0 -12
  95. data/spec/performance_notifier_spec.rb +0 -435
  96. data/spec/promise_spec.rb +0 -197
  97. data/spec/query_spec.rb +0 -11
  98. data/spec/request_spec.rb +0 -11
  99. data/spec/response_spec.rb +0 -88
  100. data/spec/spec_helper.rb +0 -100
  101. data/spec/stashable_spec.rb +0 -23
  102. data/spec/stat_spec.rb +0 -47
  103. data/spec/sync_sender_spec.rb +0 -133
  104. data/spec/tdigest_spec.rb +0 -230
  105. data/spec/time_truncate_spec.rb +0 -13
  106. data/spec/timed_trace_spec.rb +0 -125
  107. data/spec/truncator_spec.rb +0 -238
@@ -24,6 +24,7 @@ module Airbrake
24
24
  # @since v3.2.0
25
25
  class Centroid
26
26
  attr_accessor :mean, :n, :cumn, :mean_cumn
27
+
27
28
  def initialize(mean, n, cumn, mean_cumn = nil)
28
29
  @mean = mean
29
30
  @n = n
@@ -37,14 +38,15 @@ module Airbrake
37
38
  end
38
39
 
39
40
  attr_accessor :centroids
41
+ attr_reader :size
42
+
40
43
  def initialize(delta = 0.01, k = 25, cx = 1.1)
41
44
  @delta = delta
42
45
  @k = k
43
46
  @cx = cx
44
47
  @centroids = RBTree.new
45
- @nreset = 0
46
- @n = 0
47
- reset!
48
+ @size = 0
49
+ @last_cumulate = 0
48
50
  end
49
51
 
50
52
  def +(other)
@@ -59,8 +61,8 @@ module Airbrake
59
61
  # compression as defined by Java implementation
60
62
  size = @centroids.size
61
63
  output = [VERBOSE_ENCODING, compression, size]
62
- output += @centroids.map { |_, c| c.mean }
63
- output += @centroids.map { |_, c| c.n }
64
+ output += @centroids.each_value.map(&:mean)
65
+ output += @centroids.each_value.map(&:n)
64
66
  output.pack("NGNG#{size}N#{size}")
65
67
  end
66
68
 
@@ -70,14 +72,14 @@ module Airbrake
70
72
  output = [self.class::SMALL_ENCODING, compression, size]
71
73
  x = 0
72
74
  # delta encoding allows saving 4-bytes floats
73
- mean_arr = @centroids.map do |_, c|
75
+ mean_arr = @centroids.each_value.map do |c|
74
76
  val = c.mean - x
75
77
  x = c.mean
76
78
  val
77
79
  end
78
80
  output += mean_arr
79
81
  # Variable length encoding of numbers
80
- c_arr = @centroids.each_with_object([]) do |(_, c), arr|
82
+ c_arr = @centroids.each_value.with_object([]) do |c, arr|
81
83
  k = 0
82
84
  n = c.n
83
85
  while n < 0 || n > 0x7f
@@ -95,7 +97,7 @@ module Airbrake
95
97
  # rubocop:enable Metrics/AbcSize
96
98
 
97
99
  def as_json(_ = nil)
98
- @centroids.map { |_, c| c.as_json }
100
+ @centroids.each_value.map(&:as_json)
99
101
  end
100
102
 
101
103
  def bound_mean(x)
@@ -129,7 +131,7 @@ module Airbrake
129
131
  points = to_a
130
132
  reset!
131
133
  push_centroid(points.shuffle)
132
- _cumulate(true, true)
134
+ _cumulate(exact: true, force: true)
133
135
  nil
134
136
  end
135
137
 
@@ -138,21 +140,17 @@ module Airbrake
138
140
  end
139
141
 
140
142
  def find_nearest(x)
141
- return nil if size == 0
142
-
143
- ceil = @centroids.upper_bound(x)
144
- floor = @centroids.lower_bound(x)
143
+ return if size == 0
145
144
 
146
- return floor[1] if ceil.nil?
147
- return ceil[1] if floor.nil?
145
+ upper_key, upper = @centroids.upper_bound(x)
146
+ lower_key, lower = @centroids.lower_bound(x)
147
+ return lower unless upper_key
148
+ return upper unless lower_key
148
149
 
149
- ceil_key = ceil[0]
150
- floor_key = floor[0]
151
-
152
- if (floor_key - x).abs < (ceil_key - x).abs
153
- floor[1]
150
+ if (lower_key - x).abs < (upper_key - x).abs
151
+ lower
154
152
  else
155
- ceil[1]
153
+ upper
156
154
  end
157
155
  end
158
156
 
@@ -178,7 +176,7 @@ module Airbrake
178
176
  elsif item > max[1].mean
179
177
  1.0
180
178
  else
181
- _cumulate(true)
179
+ _cumulate(exact: true)
182
180
  bound = bound_mean(item)
183
181
  lower, upper = bound
184
182
  mean_cumn = lower.mean_cumn
@@ -186,7 +184,7 @@ module Airbrake
186
184
  mean_cumn += (item - lower.mean) * (upper.mean_cumn - lower.mean_cumn) \
187
185
  / (upper.mean - lower.mean)
188
186
  end
189
- mean_cumn / @n
187
+ mean_cumn / @size
190
188
  end
191
189
  end
192
190
  is_array ? x : x.first
@@ -203,11 +201,12 @@ module Airbrake
203
201
  unless (0..1).cover?(item)
204
202
  raise ArgumentError, "p should be in [0,1], got #{item}"
205
203
  end
204
+
206
205
  if size == 0
207
206
  nil
208
207
  else
209
- _cumulate(true)
210
- h = @n * item
208
+ _cumulate(exact: true)
209
+ h = @size * item
211
210
  lower, upper = bound_mean_cumn(h)
212
211
  if lower.nil? && upper.nil?
213
212
  nil
@@ -237,17 +236,12 @@ module Airbrake
237
236
 
238
237
  def reset!
239
238
  @centroids.clear
240
- @n = 0
241
- @nreset += 1
239
+ @size = 0
242
240
  @last_cumulate = 0
243
241
  end
244
242
 
245
- def size
246
- @n || 0
247
- end
248
-
249
243
  def to_a
250
- @centroids.map { |_, c| c }
244
+ @centroids.each_value.to_a
251
245
  end
252
246
 
253
247
  # rubocop:disable Metrics/PerceivedComplexity, Metrics/MethodLength
@@ -262,7 +256,7 @@ module Airbrake
262
256
  array = bytes[start_idx..-1].unpack("G#{size}N#{size}")
263
257
  means, counts = array.each_slice(size).to_a if array.any?
264
258
  when SMALL_ENCODING
265
- means = bytes[start_idx..(start_idx + 4 * size)].unpack("g#{size}")
259
+ means = bytes[start_idx..(start_idx + (4 * size))].unpack("g#{size}")
266
260
  # Decode delta encoding of means
267
261
  x = 0
268
262
  means.map! do |m|
@@ -270,7 +264,7 @@ module Airbrake
270
264
  x = m
271
265
  m
272
266
  end
273
- counts_bytes = bytes[(start_idx + 4 * size)..-1].unpack('C*')
267
+ counts_bytes = bytes[(start_idx + (4 * size))..-1].unpack('C*')
274
268
  counts = []
275
269
  # Decode variable length integer bytes
276
270
  size.times do
@@ -279,6 +273,7 @@ module Airbrake
279
273
  shift = 7
280
274
  while (v & 0x80) != 0
281
275
  raise 'Shift too large in decode' if shift > 28
276
+
282
277
  v = counts_bytes.shift || 0
283
278
  z += (v & 0x7f) << shift
284
279
  shift += 7
@@ -307,35 +302,35 @@ module Airbrake
307
302
 
308
303
  private
309
304
 
310
- def _add_weight(nearest, x, n)
311
- nearest.mean += n * (x - nearest.mean) / (nearest.n + n) unless x == nearest.mean
312
-
313
- _cumulate(false, true) if nearest.mean_cumn.nil?
305
+ def _add_weight(centroid, x, n)
306
+ unless x == centroid.mean
307
+ centroid.mean += n * (x - centroid.mean) / (centroid.n + n)
308
+ end
314
309
 
315
- nearest.cumn += n
316
- nearest.mean_cumn += n / 2.0
317
- nearest.n += n
310
+ _cumulate(exact: false, force: true) if centroid.mean_cumn.nil?
318
311
 
319
- nil
312
+ centroid.cumn += n
313
+ centroid.mean_cumn += n / 2.0
314
+ centroid.n += n
320
315
  end
321
316
 
322
317
  # rubocop:disable Metrics/PerceivedComplexity, Metrics/CyclomaticComplexity
323
- def _cumulate(exact = false, force = false)
318
+ def _cumulate(exact: false, force: false)
324
319
  unless force
325
320
  factor = if @last_cumulate == 0
326
321
  Float::INFINITY
327
322
  else
328
- (@n.to_f / @last_cumulate)
323
+ (@size.to_f / @last_cumulate)
329
324
  end
330
- return if @n == @last_cumulate || (!exact && @cx && @cx > factor)
325
+ return if @size == @last_cumulate || (!exact && @cx && @cx > factor)
331
326
  end
332
327
 
333
328
  cumn = 0
334
- @centroids.each do |_, c|
335
- c.mean_cumn = cumn + c.n / 2.0
329
+ @centroids.each_value do |c|
330
+ c.mean_cumn = cumn + (c.n / 2.0)
336
331
  cumn = c.cumn = cumn + c.n
337
332
  end
338
- @n = @last_cumulate = cumn
333
+ @size = @last_cumulate = cumn
339
334
  nil
340
335
  end
341
336
  # rubocop:enable Metrics/PerceivedComplexity, Metrics/CyclomaticComplexity
@@ -345,32 +340,29 @@ module Airbrake
345
340
  def _digest(x, n)
346
341
  # Use 'first' and 'last' instead of min/max because of performance reasons
347
342
  # This works because RBTree is sorted
348
- min = @centroids.first
349
- max = @centroids.last
350
-
351
- min = min.nil? ? nil : min[1]
352
- max = max.nil? ? nil : max[1]
343
+ min = min.last if (min = @centroids.first)
344
+ max = max.last if (max = @centroids.last)
353
345
  nearest = find_nearest(x)
354
346
 
355
- @n += n
347
+ @size += n
356
348
 
357
349
  if nearest && nearest.mean == x
358
350
  _add_weight(nearest, x, n)
359
351
  elsif nearest == min
360
- _new_centroid(x, n, 0)
352
+ @centroids[x] = Centroid.new(x, n, 0)
361
353
  elsif nearest == max
362
- _new_centroid(x, n, @n)
354
+ @centroids[x] = Centroid.new(x, n, @size)
363
355
  else
364
- p = nearest.mean_cumn.to_f / @n
365
- max_n = (4 * @n * @delta * p * (1 - p)).floor
356
+ p = nearest.mean_cumn.to_f / @size
357
+ max_n = (4 * @size * @delta * p * (1 - p)).floor
366
358
  if max_n - nearest.n >= n
367
359
  _add_weight(nearest, x, n)
368
360
  else
369
- _new_centroid(x, n, nearest.cumn)
361
+ @centroids[x] = Centroid.new(x, n, nearest.cumn)
370
362
  end
371
363
  end
372
364
 
373
- _cumulate(false)
365
+ _cumulate(exact: false)
374
366
 
375
367
  # If the number of centroids has grown to a very large size,
376
368
  # it may be due to values being inserted in sorted order.
@@ -382,12 +374,6 @@ module Airbrake
382
374
  end
383
375
  # rubocop:enable Metrics/PerceivedComplexity, Metrics/CyclomaticComplexity,
384
376
  # rubocop:enable Metrics/AbcSize
385
-
386
- def _new_centroid(x, n, cumn)
387
- c = Centroid.new(x, n, cumn)
388
- @centroids[x] = c
389
- c
390
- end
391
377
  end
392
378
  # rubocop:enable Metrics/ClassLength
393
379
  end
@@ -0,0 +1,139 @@
1
+ module Airbrake
2
+ # ThreadPool implements a simple thread pool that can configure the number of
3
+ # worker threads and the size of the queue to process.
4
+ #
5
+ # @example
6
+ # # Initialize a new thread pool with 5 workers and a queue size of 100. Set
7
+ # # the block to be run concurrently.
8
+ # thread_pool = ThreadPool.new(
9
+ # name: 'performance-notifier',
10
+ # worker_size: 5,
11
+ # queue_size: 100,
12
+ # block: proc { |message| print "ECHO: #{message}..."}
13
+ # )
14
+ #
15
+ # # Send work.
16
+ # 10.times { |i| thread_pool << i }
17
+ # #=> ECHO: 0...ECHO: 1...ECHO: 2...
18
+ #
19
+ # @api private
20
+ # @since v4.6.1
21
+ class ThreadPool
22
+ include Loggable
23
+
24
+ # @return [ThreadGroup] the list of workers
25
+ # @note This is exposed for eaiser unit testing
26
+ attr_reader :workers
27
+
28
+ def initialize(worker_size:, queue_size:, block:, name: nil)
29
+ @name = name
30
+ @worker_size = worker_size
31
+ @queue_size = queue_size
32
+ @block = block
33
+
34
+ @queue = SizedQueue.new(queue_size)
35
+ @workers = ThreadGroup.new
36
+ @mutex = Mutex.new
37
+ @pid = nil
38
+ @closed = false
39
+
40
+ has_workers?
41
+ end
42
+
43
+ # Adds a new message to the thread pool. Rejects messages if the queue is at
44
+ # its capacity.
45
+ #
46
+ # @param [Object] message The message that gets passed to the block
47
+ # @return [Boolean] true if the message was successfully sent to the pool,
48
+ # false if the queue is full
49
+ def <<(message)
50
+ if backlog >= @queue_size
51
+ logger.info do
52
+ "#{LOG_LABEL} ThreadPool has reached its capacity of " \
53
+ "#{@queue_size} and the following message will not be " \
54
+ "processed: #{message.inspect}"
55
+ end
56
+ return false
57
+ end
58
+
59
+ @queue << message
60
+ true
61
+ end
62
+
63
+ # @return [Integer] how big the queue is at the moment
64
+ def backlog
65
+ @queue.size
66
+ end
67
+
68
+ # Checks if a thread pool has any workers. A thread pool doesn't have any
69
+ # workers only in two cases: when it was closed or when all workers
70
+ # crashed. An *active* thread pool doesn't have any workers only when
71
+ # something went wrong.
72
+ #
73
+ # Workers are expected to crash when you +fork+ the process the workers are
74
+ # living in. In this case we detect a +fork+ and try to revive them here.
75
+ #
76
+ # Another possible scenario that crashes workers is when you close the
77
+ # instance on +at_exit+, but some other +at_exit+ hook prevents the process
78
+ # from exiting.
79
+ #
80
+ # @return [Boolean] true if an instance wasn't closed, but has no workers
81
+ # @see https://goo.gl/oydz8h Example of at_exit that prevents exit
82
+ def has_workers?
83
+ @mutex.synchronize do
84
+ return false if @closed
85
+
86
+ if @pid != Process.pid && @workers.list.empty?
87
+ @pid = Process.pid
88
+ @workers = ThreadGroup.new
89
+ spawn_workers
90
+ end
91
+
92
+ !@closed && @workers.list.any?
93
+ end
94
+ end
95
+
96
+ # Closes the thread pool making it a no-op (it shut downs all worker
97
+ # threads). Before closing, waits on all unprocessed tasks to be processed.
98
+ #
99
+ # @return [void]
100
+ # @raise [Airbrake::Error] when invoked more than one time
101
+ def close
102
+ threads = @mutex.synchronize do
103
+ raise Airbrake::Error, 'this thread pool is closed already' if @closed
104
+
105
+ unless @queue.empty?
106
+ msg = "#{LOG_LABEL} waiting to process #{@queue.size} task(s)..."
107
+ logger.debug("#{msg} (Ctrl-C to abort)")
108
+ end
109
+
110
+ @worker_size.times { @queue << :stop }
111
+ @closed = true
112
+ @workers.list.dup
113
+ end
114
+
115
+ threads.each(&:join)
116
+ logger.debug("#{LOG_LABEL} #{@name} thread pool closed")
117
+ end
118
+
119
+ def closed?
120
+ @closed
121
+ end
122
+
123
+ def spawn_workers
124
+ @worker_size.times { @workers.add(spawn_worker) }
125
+ end
126
+
127
+ private
128
+
129
+ def spawn_worker
130
+ Thread.new do
131
+ while (message = @queue.pop)
132
+ break if message == :stop
133
+
134
+ @block.call(message)
135
+ end
136
+ end
137
+ end
138
+ end
139
+ end
@@ -6,10 +6,10 @@ module Airbrake
6
6
  module TimeTruncate
7
7
  # Truncate +time+ to floor minute and turn it into an RFC3339 timestamp.
8
8
  #
9
- # @param [Time] time
9
+ # @param [Time, Integer, Float] time
10
10
  # @return [String]
11
11
  def self.utc_truncate_minutes(time)
12
- tm = time.getutc
12
+ tm = Time.at(time).getutc
13
13
 
14
14
  Time.utc(tm.year, tm.month, tm.day, tm.hour, tm.min).to_datetime.rfc3339
15
15
  end
@@ -50,9 +50,7 @@ module Airbrake
50
50
 
51
51
  # @return [Hash<String=>Float>]
52
52
  def spans
53
- @spans.each_with_object({}) do |(label, benchmark), new_spans|
54
- new_spans[label] = benchmark.duration
55
- end
53
+ @spans.transform_values(&:duration)
56
54
  end
57
55
  end
58
56
  end
@@ -12,6 +12,10 @@ module Airbrake
12
12
  # strings with +ENCODING_OPTIONS+
13
13
  TEMP_ENCODING = 'utf-16'.freeze
14
14
 
15
+ # @return [Array<Encoding>] encodings that are eligible for fixing invalid
16
+ # characters
17
+ SUPPORTED_ENCODINGS = [Encoding::UTF_8, Encoding::ASCII].freeze
18
+
15
19
  # @return [String] what to append when something is a circular reference
16
20
  CIRCULAR = '[Circular]'.freeze
17
21
 
@@ -35,6 +39,7 @@ module Airbrake
35
39
  def truncate(object, seen = Set.new)
36
40
  if seen.include?(object.object_id)
37
41
  return CIRCULAR if CIRCULAR_TYPES.any? { |t| object.is_a?(t) }
42
+
38
43
  return object
39
44
  end
40
45
  truncate_object(object, seen << object.object_id)
@@ -63,6 +68,7 @@ module Airbrake
63
68
  def truncate_string(str)
64
69
  fixed_str = replace_invalid_characters(str)
65
70
  return fixed_str if fixed_str.length <= @max_size
71
+
66
72
  (fixed_str.slice(0, @max_size) + TRUNCATED).freeze
67
73
  end
68
74
 
@@ -76,6 +82,7 @@ module Airbrake
76
82
  truncated_hash = {}
77
83
  hash.each_with_index do |(key, val), idx|
78
84
  break if idx + 1 > @max_size
85
+
79
86
  truncated_hash[key] = truncate(val, seen)
80
87
  end
81
88
 
@@ -103,13 +110,12 @@ module Airbrake
103
110
  # @return [String] a UTF-8 encoded string
104
111
  # @see https://github.com/flori/json/commit/3e158410e81f94dbbc3da6b7b35f4f64983aa4e3
105
112
  def replace_invalid_characters(str)
106
- encoding = str.encoding
107
- utf8_string = (encoding == Encoding::UTF_8 || encoding == Encoding::ASCII)
113
+ utf8_string = SUPPORTED_ENCODINGS.include?(str.encoding)
108
114
  return str if utf8_string && str.valid_encoding?
109
115
 
110
116
  temp_str = str.dup
111
- temp_str.encode!(TEMP_ENCODING, ENCODING_OPTIONS) if utf8_string
112
- temp_str.encode!('utf-8', ENCODING_OPTIONS)
117
+ temp_str.encode!(TEMP_ENCODING, **ENCODING_OPTIONS) if utf8_string
118
+ temp_str.encode!('utf-8', **ENCODING_OPTIONS)
113
119
  end
114
120
  end
115
121
  end
@@ -2,5 +2,15 @@
2
2
  # More information: http://semver.org/
3
3
  module Airbrake
4
4
  # @return [String] the library version
5
- AIRBRAKE_RUBY_VERSION = '4.6.0'.freeze
5
+ # @api public
6
+ AIRBRAKE_RUBY_VERSION = '6.2.2'.freeze
7
+
8
+ # @return [Hash{Symbol=>String}] the information about the notifier library
9
+ # @since v5.0.0
10
+ # @api public
11
+ NOTIFIER_INFO = {
12
+ name: 'airbrake-ruby'.freeze,
13
+ version: Airbrake::AIRBRAKE_RUBY_VERSION,
14
+ url: 'https://github.com/airbrake/airbrake-ruby'.freeze,
15
+ }.freeze
6
16
  end