airbrake-ruby 4.12.0 → 4.13.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8c5e41d6b33c6a201ef67d32e32e69bbef6e560c1af80dc19ae8f4192d6b84fd
4
- data.tar.gz: 34e30b5ce530edc18c512ba0be5392a2c53cdee68ec23eb43174e9690715f821
3
+ metadata.gz: 29f58448d31db594854b99c73fd6574b326f7943215f6c5e89b4407bf254add5
4
+ data.tar.gz: 2f2d9bd955015731f70abc13a6dafd3a73002567cea3b0b8b6bb0a1ae456a8be
5
5
  SHA512:
6
- metadata.gz: '0081a23db3f3ff08f56382e97a67685a7c28eb81c5c93963a440a5f216ddf322ffaf8dae6be14a1394edc079dbd1644511f817693a14eebb3e097243f03f74c9'
7
- data.tar.gz: c9c58c197046e16f7706cddbfb4caab70837f03b457418ff22f756499efe6bd98762eca8547a9e716af9d04e20c53a4440a9ca83affc7c1713623d3a191cb560
6
+ metadata.gz: 52380ea7f5f9288dd967c70fc01f238cd22eb3d1b335dd05962d4b4b7c037b9fec385e32f5f036d7059a012799895dd0f168f5a3e7f0241d4d26690e822490a6
7
+ data.tar.gz: 65fbb12a214ea879b6b69405950f11d2c7d599f09bc1735e90bb3744e86f021482bf44e996a8392270bf6c6818294899eb9c0c672c25f8cd1a188ed5995f45fc
data/lib/airbrake-ruby.rb CHANGED
@@ -84,6 +84,13 @@ module Airbrake
84
84
  # special cases where we need to work around older implementations
85
85
  JRUBY = (RUBY_ENGINE == 'jruby')
86
86
 
87
+ # @return [Boolean] true if this Ruby supports safe levels and tainting,
88
+ # to guard against using deprecated or unsupported features.
89
+ HAS_SAFE_LEVEL = (
90
+ RUBY_ENGINE == 'ruby' &&
91
+ Gem::Version.new(RUBY_VERSION) < Gem::Version.new('2.7')
92
+ )
93
+
87
94
  class << self
88
95
  # @since v4.2.3
89
96
  # @api private
@@ -364,7 +371,7 @@ module Airbrake
364
371
  # @since v3.0.0
365
372
  # @see Airbrake::PerformanceNotifier#notify
366
373
  def notify_request(request_info, stash = {})
367
- request = Request.new(request_info)
374
+ request = Request.new(**request_info)
368
375
  request.stash.merge!(stash)
369
376
  performance_notifier.notify(request)
370
377
  end
@@ -374,7 +381,7 @@ module Airbrake
374
381
  # @since v4.10.0
375
382
  # @see .notify_request
376
383
  def notify_request_sync(request_info, stash = {})
377
- request = Request.new(request_info)
384
+ request = Request.new(**request_info)
378
385
  request.stash.merge!(stash)
379
386
  performance_notifier.notify_sync(request)
380
387
  end
@@ -407,7 +414,7 @@ module Airbrake
407
414
  # @since v3.2.0
408
415
  # @see Airbrake::PerformanceNotifier#notify
409
416
  def notify_query(query_info, stash = {})
410
- query = Query.new(query_info)
417
+ query = Query.new(**query_info)
411
418
  query.stash.merge!(stash)
412
419
  performance_notifier.notify(query)
413
420
  end
@@ -418,7 +425,7 @@ module Airbrake
418
425
  # @since v4.10.0
419
426
  # @see .notify_query
420
427
  def notify_query_sync(query_info, stash = {})
421
- query = Query.new(query_info)
428
+ query = Query.new(**query_info)
422
429
  query.stash.merge!(stash)
423
430
  performance_notifier.notify_sync(query)
424
431
  end
@@ -446,7 +453,7 @@ module Airbrake
446
453
  # @return [void]
447
454
  # @since v4.2.0
448
455
  def notify_performance_breakdown(breakdown_info, stash = {})
449
- performance_breakdown = PerformanceBreakdown.new(breakdown_info)
456
+ performance_breakdown = PerformanceBreakdown.new(**breakdown_info)
450
457
  performance_breakdown.stash.merge!(stash)
451
458
  performance_notifier.notify(performance_breakdown)
452
459
  end
@@ -456,7 +463,7 @@ module Airbrake
456
463
  # @since v4.10.0
457
464
  # @see .notify_performance_breakdown
458
465
  def notify_performance_breakdown_sync(breakdown_info, stash = {})
459
- performance_breakdown = PerformanceBreakdown.new(breakdown_info)
466
+ performance_breakdown = PerformanceBreakdown.new(**breakdown_info)
460
467
  performance_breakdown.stash.merge!(stash)
461
468
  performance_notifier.notify_sync(performance_breakdown)
462
469
  end
@@ -484,7 +491,7 @@ module Airbrake
484
491
  # @since v4.9.0
485
492
  # @see .notify_queue_sync
486
493
  def notify_queue(queue_info, stash = {})
487
- queue = Queue.new(queue_info)
494
+ queue = Queue.new(**queue_info)
488
495
  queue.stash.merge!(stash)
489
496
  performance_notifier.notify(queue)
490
497
  end
@@ -493,7 +500,7 @@ module Airbrake
493
500
  # @since v4.10.0
494
501
  # @see .notify_queue
495
502
  def notify_queue_sync(queue_info, stash = {})
496
- queue = Queue.new(queue_info)
503
+ queue = Queue.new(**queue_info)
497
504
  queue.stash.merge!(stash)
498
505
  performance_notifier.notify_sync(queue)
499
506
  end
@@ -119,12 +119,13 @@ module Airbrake
119
119
 
120
120
  # @param [Hash{Symbol=>Object}] user_config the hash to be used to build the
121
121
  # config
122
+ # rubocop:disable Metrics/AbcSize
122
123
  def initialize(user_config = {})
123
124
  self.proxy = {}
124
125
  self.queue_size = 100
125
126
  self.workers = 1
126
127
  self.code_hunks = true
127
- self.logger = ::Logger.new(File::NULL)
128
+ self.logger = ::Logger.new(File::NULL).tap { |l| l.level = Logger::WARN }
128
129
  self.project_id = user_config[:project_id]
129
130
  self.project_key = user_config[:project_key]
130
131
  self.host = 'https://api.airbrake.io'
@@ -149,6 +150,7 @@ module Airbrake
149
150
 
150
151
  merge(user_config)
151
152
  end
153
+ # rubocop:enable Metrics/AbcSize
152
154
 
153
155
  # The full URL to the Airbrake Notice API. Based on the +:host+ option.
154
156
  # @return [URI] the endpoint address
@@ -72,7 +72,7 @@ module Airbrake
72
72
  thread_info[:group] = th.group.list.map(&:inspect)
73
73
  thread_info[:priority] = th.priority
74
74
 
75
- thread_info[:safe_level] = th.safe_level unless Airbrake::JRUBY
75
+ thread_info[:safe_level] = th.safe_level if Airbrake::HAS_SAFE_LEVEL
76
76
  end
77
77
 
78
78
  def sanitize_value(value)
@@ -22,7 +22,7 @@ module Airbrake
22
22
 
23
23
  # @return [Logger]
24
24
  def instance
25
- @instance ||= ::Logger.new(File::NULL)
25
+ @instance ||= ::Logger.new(File::NULL).tap { |l| l.level = ::Logger::WARN }
26
26
  end
27
27
  end
28
28
 
@@ -16,6 +16,11 @@ module Airbrake
16
16
  time_in_nanoseconds / (10.0**6)
17
17
  end
18
18
 
19
+ # @return [Integer] current monotonic time in seconds
20
+ def time_in_s
21
+ time_in_nanoseconds / (10.0**9)
22
+ end
23
+
19
24
  private
20
25
 
21
26
  if defined?(Process::CLOCK_MONOTONIC)
@@ -5,16 +5,16 @@ module Airbrake
5
5
  # @see Airbrake.notify_breakdown
6
6
  # @api public
7
7
  # @since v4.2.0
8
- # rubocop:disable Metrics/BlockLength, Metrics/ParameterLists
9
- PerformanceBreakdown = Struct.new(
10
- :method, :route, :response_type, :groups, :start_time, :end_time, :timing,
11
- :time
12
- ) do
8
+ # rubocop:disable Metrics/ParameterLists
9
+ class PerformanceBreakdown
13
10
  include HashKeyable
14
11
  include Ignorable
15
12
  include Stashable
16
13
  include Mergeable
17
14
 
15
+ attr_accessor :method, :route, :response_type, :groups, :start_time,
16
+ :end_time, :timing, :time
17
+
18
18
  def initialize(
19
19
  method:,
20
20
  route:,
@@ -26,9 +26,14 @@ module Airbrake
26
26
  time: Time.now
27
27
  )
28
28
  @time_utc = TimeTruncate.utc_truncate_minutes(time)
29
- super(
30
- method, route, response_type, groups, start_time, end_time, timing, time
31
- )
29
+ @method = method
30
+ @route = route
31
+ @response_type = response_type
32
+ @groups = groups
33
+ @start_time = start_time
34
+ @end_time = end_time
35
+ @timing = timing
36
+ @time = time
32
37
  end
33
38
 
34
39
  def destination
@@ -48,5 +53,5 @@ module Airbrake
48
53
  }.delete_if { |_key, val| val.nil? }
49
54
  end
50
55
  end
51
- # rubocop:enable Metrics/BlockLength, Metrics/ParameterLists
56
+ # rubocop:enable Metrics/ParameterLists
52
57
  end
@@ -14,18 +14,20 @@ module Airbrake
14
14
  @flush_period = Airbrake::Config.instance.performance_stats_flush_period
15
15
  @async_sender = AsyncSender.new(:put)
16
16
  @sync_sender = SyncSender.new(:put)
17
- @payload = {}
18
17
  @schedule_flush = nil
19
- @mutex = Mutex.new
20
18
  @filter_chain = FilterChain.new
21
- @waiting = false
19
+
20
+ @payload = {}.extend(MonitorMixin)
21
+ @has_payload = @payload.new_cond
22
22
  end
23
23
 
24
24
  # @param [Hash] resource
25
25
  # @see Airbrake.notify_query
26
26
  # @see Airbrake.notify_request
27
27
  def notify(resource)
28
- send_resource(resource, sync: false)
28
+ @payload.synchronize do
29
+ send_resource(resource, sync: false)
30
+ end
29
31
  end
30
32
 
31
33
  # @param [Hash] resource
@@ -46,7 +48,7 @@ module Airbrake
46
48
  end
47
49
 
48
50
  def close
49
- @mutex.synchronize do
51
+ @payload.synchronize do
50
52
  @schedule_flush.kill if @schedule_flush
51
53
  @async_sender.close
52
54
  logger.debug("#{LOG_LABEL} performance notifier closed")
@@ -55,6 +57,46 @@ module Airbrake
55
57
 
56
58
  private
57
59
 
60
+ def schedule_flush
61
+ @schedule_flush ||= Thread.new do
62
+ loop do
63
+ @payload.synchronize do
64
+ @last_flush_time ||= MonotonicTime.time_in_s
65
+
66
+ while (MonotonicTime.time_in_s - @last_flush_time) < @flush_period
67
+ @has_payload.wait(@flush_period)
68
+ end
69
+
70
+ if @payload.none?
71
+ @last_flush_time = nil
72
+ next
73
+ end
74
+
75
+ send(@async_sender, @payload, Airbrake::Promise.new)
76
+ @payload.clear
77
+ end
78
+ end
79
+ end
80
+ end
81
+
82
+ def send_resource(resource, sync:)
83
+ promise = check_configuration(resource)
84
+ return promise if promise.rejected?
85
+
86
+ @filter_chain.refine(resource)
87
+ if resource.ignored?
88
+ return Promise.new.reject("#{resource.class} was ignored by a filter")
89
+ end
90
+
91
+ update_payload(resource)
92
+ if sync || @flush_period == 0
93
+ send(@sync_sender, @payload, promise)
94
+ else
95
+ @has_payload.signal
96
+ schedule_flush
97
+ end
98
+ end
99
+
58
100
  def update_payload(resource)
59
101
  if (total_stat = @payload[resource])
60
102
  @payload.key(total_stat).merge(resource)
@@ -83,61 +125,6 @@ module Airbrake
83
125
  end
84
126
  end
85
127
 
86
- def schedule_flush
87
- return if @payload.empty?
88
-
89
- if @schedule_flush && @schedule_flush.status == 'sleep' && @waiting
90
- begin
91
- @schedule_flush.run
92
- rescue ThreadError => exception
93
- logger.error("#{LOG_LABEL}: error occurred while flushing: #{exception}")
94
- end
95
- end
96
-
97
- @schedule_flush ||= spawn_timer
98
- end
99
-
100
- def spawn_timer
101
- Thread.new do
102
- loop do
103
- if @payload.none?
104
- @waiting = true
105
- Thread.stop
106
- @waiting = false
107
- end
108
-
109
- sleep(@flush_period)
110
-
111
- payload = nil
112
- @mutex.synchronize do
113
- payload = @payload
114
- @payload = {}
115
- end
116
-
117
- send(@async_sender, payload, Airbrake::Promise.new)
118
- end
119
- end
120
- end
121
-
122
- def send_resource(resource, sync:)
123
- promise = check_configuration(resource)
124
- return promise if promise.rejected?
125
-
126
- @filter_chain.refine(resource)
127
- if resource.ignored?
128
- return Promise.new.reject("#{resource.class} was ignored by a filter")
129
- end
130
-
131
- @mutex.synchronize do
132
- update_payload(resource)
133
- if sync || @flush_period == 0
134
- send(@sync_sender, @payload, promise)
135
- else
136
- schedule_flush
137
- end
138
- end
139
- end
140
-
141
128
  def check_configuration(resource)
142
129
  promise = @config.check_configuration
143
130
  return promise if promise.rejected?
@@ -153,16 +140,17 @@ module Airbrake
153
140
  end
154
141
 
155
142
  def send(sender, payload, promise)
156
- signature = "#{self.class.name}##{__method__}"
157
- raise "#{signature}: payload (#{payload}) cannot be empty. Race?" if payload.none?
158
-
159
- logger.debug { "#{LOG_LABEL} #{signature}: #{payload}" }
143
+ raise "payload cannot be empty. Race?" if payload.none?
160
144
 
161
145
  with_grouped_payload(payload) do |resource_hash, destination|
162
146
  url = URI.join(
163
147
  @config.host,
164
148
  "api/v5/projects/#{@config.project_id}/#{destination}",
165
149
  )
150
+
151
+ logger.debug do
152
+ "#{LOG_LABEL} #{self.class.name}##{__method__}: #{resource_hash}"
153
+ end
166
154
  sender.send(resource_hash, promise, url)
167
155
  end
168
156
 
@@ -4,17 +4,17 @@ module Airbrake
4
4
  # @see Airbrake.notify_query
5
5
  # @api public
6
6
  # @since v3.2.0
7
- # rubocop:disable Metrics/ParameterLists, Metrics/BlockLength
8
- Query = Struct.new(
9
- :method, :route, :query, :func, :file, :line, :start_time, :end_time,
10
- :timing, :time
11
- ) do
7
+ # rubocop:disable Metrics/ParameterLists
8
+ class Query
12
9
  include HashKeyable
13
10
  include Ignorable
14
11
  include Stashable
15
12
  include Mergeable
16
13
  include Grouppable
17
14
 
15
+ attr_accessor :method, :route, :query, :func, :file, :line, :start_time,
16
+ :end_time, :timing, :time
17
+
18
18
  def initialize(
19
19
  method:,
20
20
  route:,
@@ -28,10 +28,16 @@ module Airbrake
28
28
  time: Time.now
29
29
  )
30
30
  @time_utc = TimeTruncate.utc_truncate_minutes(time)
31
- super(
32
- method, route, query, func, file, line, start_time, end_time, timing,
33
- time
34
- )
31
+ @method = method
32
+ @route = route
33
+ @query = query
34
+ @func = func
35
+ @file = file
36
+ @line = line
37
+ @start_time = start_time
38
+ @end_time = end_time
39
+ @timing = timing
40
+ @time = time
35
41
  end
36
42
 
37
43
  def destination
@@ -53,6 +59,6 @@ module Airbrake
53
59
  'line' => line,
54
60
  }.delete_if { |_key, val| val.nil? }
55
61
  end
56
- # rubocop:enable Metrics/ParameterLists, Metrics/BlockLength
62
+ # rubocop:enable Metrics/ParameterLists
57
63
  end
58
64
  end
@@ -4,14 +4,15 @@ module Airbrake
4
4
  # @see Airbrake.notify_queue
5
5
  # @api public
6
6
  # @since v4.9.0
7
- # rubocop:disable Metrics/BlockLength, Metrics/ParameterLists
8
- Queue = Struct.new(
9
- :queue, :error_count, :groups, :start_time, :end_time, :timing, :time
10
- ) do
7
+ # rubocop:disable Metrics/ParameterLists
8
+ class Queue
11
9
  include HashKeyable
12
10
  include Ignorable
13
11
  include Stashable
14
12
 
13
+ attr_accessor :queue, :error_count, :groups, :start_time, :end_time,
14
+ :timing, :time
15
+
15
16
  def initialize(
16
17
  queue:,
17
18
  error_count:,
@@ -22,7 +23,13 @@ module Airbrake
22
23
  time: Time.now
23
24
  )
24
25
  @time_utc = TimeTruncate.utc_truncate_minutes(time)
25
- super(queue, error_count, groups, start_time, end_time, timing, time)
26
+ @queue = queue
27
+ @error_count = error_count
28
+ @groups = groups
29
+ @start_time = start_time
30
+ @end_time = end_time
31
+ @timing = timing
32
+ @time = time
26
33
  end
27
34
 
28
35
  def destination
@@ -51,6 +58,15 @@ module Airbrake
51
58
  def merge(other)
52
59
  self.error_count += other.error_count
53
60
  end
61
+
62
+ # Queues don't have routes, but we want to define this to make sure our
63
+ # filter API is consistent (other models define this property)
64
+ #
65
+ # @return [String] empty route
66
+ # @see https://github.com/airbrake/airbrake-ruby/pull/537
67
+ def route
68
+ ''
69
+ end
54
70
  end
55
- # rubocop:enable Metrics/BlockLength, Metrics/ParameterLists
71
+ # rubocop:enable Metrics/ParameterLists
56
72
  end
@@ -4,16 +4,17 @@ module Airbrake
4
4
  # @see Airbrake.notify_request
5
5
  # @api public
6
6
  # @since v3.2.0
7
- # rubocop:disable Metrics/BlockLength, Metrics/ParameterLists
8
- Request = Struct.new(
9
- :method, :route, :status_code, :start_time, :end_time, :timing, :time
10
- ) do
7
+ # rubocop:disable Metrics/ParameterLists
8
+ class Request
11
9
  include HashKeyable
12
10
  include Ignorable
13
11
  include Stashable
14
12
  include Mergeable
15
13
  include Grouppable
16
14
 
15
+ attr_accessor :method, :route, :status_code, :start_time, :end_time,
16
+ :timing, :time
17
+
17
18
  def initialize(
18
19
  method:,
19
20
  route:,
@@ -24,7 +25,13 @@ module Airbrake
24
25
  time: Time.now
25
26
  )
26
27
  @time_utc = TimeTruncate.utc_truncate_minutes(time)
27
- super(method, route, status_code, start_time, end_time, timing, time)
28
+ @method = method
29
+ @route = route
30
+ @status_code = status_code
31
+ @start_time = start_time
32
+ @end_time = end_time
33
+ @timing = timing
34
+ @time = time
28
35
  end
29
36
 
30
37
  def destination
@@ -44,5 +51,5 @@ module Airbrake
44
51
  }.delete_if { |_key, val| val.nil? }
45
52
  end
46
53
  end
47
- # rubocop:enable Metrics/BlockLength, Metrics/ParameterLists
54
+ # rubocop:enable Metrics/ParameterLists
48
55
  end
@@ -1,6 +1,5 @@
1
1
  require 'base64'
2
2
 
3
- # rubocop:disable Metrics/BlockLength
4
3
  module Airbrake
5
4
  # Stat is a data structure that allows accumulating performance data (route
6
5
  # performance, SQL query performance and such). It's powered by TDigests.
@@ -14,14 +13,17 @@ module Airbrake
14
13
  # stat.to_h # Pack and serialize data so it can be transmitted.
15
14
  #
16
15
  # @since v3.2.0
17
- Stat = Struct.new(:count, :sum, :sumsq, :tdigest) do
18
- # @param [Integer] count How many times this stat was incremented
16
+ class Stat
17
+ attr_accessor :sum, :sumsq, :tdigest
18
+
19
19
  # @param [Float] sum The sum of duration in milliseconds
20
20
  # @param [Float] sumsq The squared sum of duration in milliseconds
21
21
  # @param [TDigest::TDigest] tdigest Packed durations. By default,
22
22
  # compression is 20
23
- def initialize(count: 0, sum: 0.0, sumsq: 0.0, tdigest: TDigest.new(0.05))
24
- super(count, sum, sumsq, tdigest)
23
+ def initialize(sum: 0.0, sumsq: 0.0, tdigest: TDigest.new(0.05))
24
+ @sum = sum
25
+ @sumsq = sumsq
26
+ @tdigest = tdigest
25
27
  end
26
28
 
27
29
  # @return [Hash{String=>Object}] stats as a hash with compressed TDigest
@@ -29,15 +31,15 @@ module Airbrake
29
31
  def to_h
30
32
  tdigest.compress!
31
33
  {
32
- 'count' => count,
34
+ 'count' => tdigest.size,
33
35
  'sum' => sum,
34
36
  'sumsq' => sumsq,
35
37
  'tdigest' => Base64.strict_encode64(tdigest.as_small_bytes),
36
38
  }
37
39
  end
38
40
 
39
- # Increments count and updates performance with the difference of +end_time+
40
- # and +start_time+.
41
+ # Increments tdigest timings and updates tdigest with the difference between
42
+ # +end_time+ and +start_time+.
41
43
  #
42
44
  # @param [Date] start_time
43
45
  # @param [Date] end_time
@@ -47,13 +49,11 @@ module Airbrake
47
49
  increment_ms((end_time - start_time) * 1000)
48
50
  end
49
51
 
50
- # Increments count and updates performance with given +ms+ value.
52
+ # Increments tdigest timings and updates tdigest with given +ms+ value.
51
53
  #
52
54
  # @param [Float] ms
53
55
  # @return [void]
54
56
  def increment_ms(ms)
55
- self.count += 1
56
-
57
57
  self.sum += ms
58
58
  self.sumsq += ms * ms
59
59
 
@@ -65,9 +65,8 @@ module Airbrake
65
65
  #
66
66
  # @return [String]
67
67
  def inspect
68
- "#<struct Airbrake::Stat count=#{count}, sum=#{sum}, sumsq=#{sumsq}>"
68
+ "#<struct Airbrake::Stat count=#{tdigest.size}, sum=#{sum}, sumsq=#{sumsq}>"
69
69
  end
70
- alias_method :pretty_print, :inspect
70
+ alias pretty_print inspect
71
71
  end
72
72
  end
73
- # rubocop:enable Metrics/BlockLength
@@ -37,14 +37,15 @@ module Airbrake
37
37
  end
38
38
 
39
39
  attr_accessor :centroids
40
+ attr_reader :size
41
+
40
42
  def initialize(delta = 0.01, k = 25, cx = 1.1)
41
43
  @delta = delta
42
44
  @k = k
43
45
  @cx = cx
44
46
  @centroids = RBTree.new
45
- @nreset = 0
46
- @n = 0
47
- reset!
47
+ @size = 0
48
+ @last_cumulate = 0
48
49
  end
49
50
 
50
51
  def +(other)
@@ -59,8 +60,8 @@ module Airbrake
59
60
  # compression as defined by Java implementation
60
61
  size = @centroids.size
61
62
  output = [VERBOSE_ENCODING, compression, size]
62
- output += @centroids.map { |_, c| c.mean }
63
- output += @centroids.map { |_, c| c.n }
63
+ output += @centroids.each_value.map(&:mean)
64
+ output += @centroids.each_value.map(&:n)
64
65
  output.pack("NGNG#{size}N#{size}")
65
66
  end
66
67
 
@@ -70,14 +71,14 @@ module Airbrake
70
71
  output = [self.class::SMALL_ENCODING, compression, size]
71
72
  x = 0
72
73
  # delta encoding allows saving 4-bytes floats
73
- mean_arr = @centroids.map do |_, c|
74
+ mean_arr = @centroids.each_value.map do |c|
74
75
  val = c.mean - x
75
76
  x = c.mean
76
77
  val
77
78
  end
78
79
  output += mean_arr
79
80
  # Variable length encoding of numbers
80
- c_arr = @centroids.each_with_object([]) do |(_, c), arr|
81
+ c_arr = @centroids.each_value.each_with_object([]) do |c, arr|
81
82
  k = 0
82
83
  n = c.n
83
84
  while n < 0 || n > 0x7f
@@ -95,7 +96,7 @@ module Airbrake
95
96
  # rubocop:enable Metrics/AbcSize
96
97
 
97
98
  def as_json(_ = nil)
98
- @centroids.map { |_, c| c.as_json }
99
+ @centroids.each_value.map(&:as_json)
99
100
  end
100
101
 
101
102
  def bound_mean(x)
@@ -138,21 +139,17 @@ module Airbrake
138
139
  end
139
140
 
140
141
  def find_nearest(x)
141
- return nil if size == 0
142
-
143
- ceil = @centroids.upper_bound(x)
144
- floor = @centroids.lower_bound(x)
142
+ return if size == 0
145
143
 
146
- return floor[1] if ceil.nil?
147
- return ceil[1] if floor.nil?
144
+ upper_key, upper = @centroids.upper_bound(x)
145
+ lower_key, lower = @centroids.lower_bound(x)
146
+ return lower unless upper_key
147
+ return upper unless lower_key
148
148
 
149
- ceil_key = ceil[0]
150
- floor_key = floor[0]
151
-
152
- if (floor_key - x).abs < (ceil_key - x).abs
153
- floor[1]
149
+ if (lower_key - x).abs < (upper_key - x).abs
150
+ lower
154
151
  else
155
- ceil[1]
152
+ upper
156
153
  end
157
154
  end
158
155
 
@@ -186,7 +183,7 @@ module Airbrake
186
183
  mean_cumn += (item - lower.mean) * (upper.mean_cumn - lower.mean_cumn) \
187
184
  / (upper.mean - lower.mean)
188
185
  end
189
- mean_cumn / @n
186
+ mean_cumn / @size
190
187
  end
191
188
  end
192
189
  is_array ? x : x.first
@@ -207,7 +204,7 @@ module Airbrake
207
204
  nil
208
205
  else
209
206
  _cumulate(true)
210
- h = @n * item
207
+ h = @size * item
211
208
  lower, upper = bound_mean_cumn(h)
212
209
  if lower.nil? && upper.nil?
213
210
  nil
@@ -237,17 +234,12 @@ module Airbrake
237
234
 
238
235
  def reset!
239
236
  @centroids.clear
240
- @n = 0
241
- @nreset += 1
237
+ @size = 0
242
238
  @last_cumulate = 0
243
239
  end
244
240
 
245
- def size
246
- @n || 0
247
- end
248
-
249
241
  def to_a
250
- @centroids.map { |_, c| c }
242
+ @centroids.each_value.to_a
251
243
  end
252
244
 
253
245
  # rubocop:disable Metrics/PerceivedComplexity, Metrics/MethodLength
@@ -307,16 +299,16 @@ module Airbrake
307
299
 
308
300
  private
309
301
 
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?
302
+ def _add_weight(centroid, x, n)
303
+ unless x == centroid.mean
304
+ centroid.mean += n * (x - centroid.mean) / (centroid.n + n)
305
+ end
314
306
 
315
- nearest.cumn += n
316
- nearest.mean_cumn += n / 2.0
317
- nearest.n += n
307
+ _cumulate(false, true) if centroid.mean_cumn.nil?
318
308
 
319
- nil
309
+ centroid.cumn += n
310
+ centroid.mean_cumn += n / 2.0
311
+ centroid.n += n
320
312
  end
321
313
 
322
314
  # rubocop:disable Metrics/PerceivedComplexity, Metrics/CyclomaticComplexity
@@ -325,17 +317,17 @@ module Airbrake
325
317
  factor = if @last_cumulate == 0
326
318
  Float::INFINITY
327
319
  else
328
- (@n.to_f / @last_cumulate)
320
+ (@size.to_f / @last_cumulate)
329
321
  end
330
- return if @n == @last_cumulate || (!exact && @cx && @cx > factor)
322
+ return if @size == @last_cumulate || (!exact && @cx && @cx > factor)
331
323
  end
332
324
 
333
325
  cumn = 0
334
- @centroids.each do |_, c|
326
+ @centroids.each_value do |c|
335
327
  c.mean_cumn = cumn + c.n / 2.0
336
328
  cumn = c.cumn = cumn + c.n
337
329
  end
338
- @n = @last_cumulate = cumn
330
+ @size = @last_cumulate = cumn
339
331
  nil
340
332
  end
341
333
  # rubocop:enable Metrics/PerceivedComplexity, Metrics/CyclomaticComplexity
@@ -345,28 +337,25 @@ module Airbrake
345
337
  def _digest(x, n)
346
338
  # Use 'first' and 'last' instead of min/max because of performance reasons
347
339
  # 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]
340
+ min = min.last if (min = @centroids.first)
341
+ max = max.last if (max = @centroids.last)
353
342
  nearest = find_nearest(x)
354
343
 
355
- @n += n
344
+ @size += n
356
345
 
357
346
  if nearest && nearest.mean == x
358
347
  _add_weight(nearest, x, n)
359
348
  elsif nearest == min
360
- _new_centroid(x, n, 0)
349
+ @centroids[x] = Centroid.new(x, n, 0)
361
350
  elsif nearest == max
362
- _new_centroid(x, n, @n)
351
+ @centroids[x] = Centroid.new(x, n, @size)
363
352
  else
364
- p = nearest.mean_cumn.to_f / @n
365
- max_n = (4 * @n * @delta * p * (1 - p)).floor
353
+ p = nearest.mean_cumn.to_f / @size
354
+ max_n = (4 * @size * @delta * p * (1 - p)).floor
366
355
  if max_n - nearest.n >= n
367
356
  _add_weight(nearest, x, n)
368
357
  else
369
- _new_centroid(x, n, nearest.cumn)
358
+ @centroids[x] = Centroid.new(x, n, nearest.cumn)
370
359
  end
371
360
  end
372
361
 
@@ -382,12 +371,6 @@ module Airbrake
382
371
  end
383
372
  # rubocop:enable Metrics/PerceivedComplexity, Metrics/CyclomaticComplexity,
384
373
  # 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
374
  end
392
375
  # rubocop:enable Metrics/ClassLength
393
376
  end
@@ -108,8 +108,8 @@ module Airbrake
108
108
  return str if utf8_string && str.valid_encoding?
109
109
 
110
110
  temp_str = str.dup
111
- temp_str.encode!(TEMP_ENCODING, ENCODING_OPTIONS) if utf8_string
112
- temp_str.encode!('utf-8', ENCODING_OPTIONS)
111
+ temp_str.encode!(TEMP_ENCODING, **ENCODING_OPTIONS) if utf8_string
112
+ temp_str.encode!('utf-8', **ENCODING_OPTIONS)
113
113
  end
114
114
  end
115
115
  end
@@ -2,5 +2,5 @@
2
2
  # More information: http://semver.org/
3
3
  module Airbrake
4
4
  # @return [String] the library version
5
- AIRBRAKE_RUBY_VERSION = '4.12.0'.freeze
5
+ AIRBRAKE_RUBY_VERSION = '4.13.1'.freeze
6
6
  end
data/spec/config_spec.rb CHANGED
@@ -163,4 +163,10 @@ RSpec.describe Airbrake::Config do
163
163
  end
164
164
  end
165
165
  end
166
+
167
+ describe "#logger" do
168
+ it "sets logger level to Logger::WARN" do
169
+ expect(subject.logger.level).to eq(Logger::WARN)
170
+ end
171
+ end
166
172
  end
@@ -258,7 +258,9 @@ RSpec.describe Airbrake::Filters::ThreadFilter do
258
258
  expect(notice[:params][:thread][:priority]).to eq(0)
259
259
  end
260
260
 
261
- it "appends safe_level", skip: Airbrake::JRUBY do
261
+ it "appends safe_level", skip: (
262
+ "Not supported on this version of Ruby." unless Airbrake::HAS_SAFE_LEVEL
263
+ ) do
262
264
  subject.call(notice)
263
265
  expect(notice[:params][:thread][:safe_level]).to eq(0)
264
266
  end
@@ -0,0 +1,17 @@
1
+ RSpec.describe Airbrake::Loggable do
2
+ describe ".instance" do
3
+ it "returns a logger" do
4
+ expect(described_class.instance).to be_a(Logger)
5
+ end
6
+ end
7
+
8
+ describe "#logger" do
9
+ let(:subject) do
10
+ Class.new { include Airbrake::Loggable }.new
11
+ end
12
+
13
+ it "returns a logger that has Logger::WARN severity" do
14
+ expect(subject.logger.level).to eq(Logger::WARN)
15
+ end
16
+ end
17
+ end
@@ -9,4 +9,15 @@ RSpec.describe Airbrake::MonotonicTime do
9
9
  expect(subject.time_in_ms).to be > old_time
10
10
  end
11
11
  end
12
+
13
+ describe ".time_in_s" do
14
+ it "returns monotonic time in seconds" do
15
+ expect(subject.time_in_s).to be_a(Float)
16
+ end
17
+
18
+ it "always returns time in the future" do
19
+ old_time = subject.time_in_s
20
+ expect(subject.time_in_s).to be > old_time
21
+ end
22
+ end
12
23
  end
data/spec/queue_spec.rb CHANGED
@@ -18,4 +18,13 @@ RSpec.describe Airbrake::Queue do
18
18
  expect(queue.end_time).to eq(time + 1)
19
19
  end
20
20
  end
21
+
22
+ describe "#route" do
23
+ it "always returns an empty route" do
24
+ queue = described_class.new(
25
+ queue: 'a', error_count: 0, start_time: Time.now,
26
+ )
27
+ expect(queue.route).to be_empty
28
+ end
29
+ end
21
30
  end
data/spec/stat_spec.rb CHANGED
@@ -22,7 +22,6 @@ RSpec.describe Airbrake::Stat do
22
22
  describe "#increment_ms" do
23
23
  before { subject.increment_ms(1000) }
24
24
 
25
- its(:count) { is_expected.to eq(1) }
26
25
  its(:sum) { is_expected.to eq(1000) }
27
26
  its(:sumsq) { is_expected.to eq(1000000) }
28
27
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: airbrake-ruby
3
3
  version: !ruby/object:Gem::Version
4
- version: 4.12.0
4
+ version: 4.13.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Airbrake Technologies, Inc.
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-01-07 00:00:00.000000000 Z
11
+ date: 2020-02-11 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rbtree3
@@ -120,6 +120,7 @@ files:
120
120
  - spec/helpers.rb
121
121
  - spec/ignorable_spec.rb
122
122
  - spec/inspectable_spec.rb
123
+ - spec/loggable_spec.rb
123
124
  - spec/monotonic_time_spec.rb
124
125
  - spec/nested_exception_spec.rb
125
126
  - spec/notice_notifier/options_spec.rb
@@ -189,6 +190,7 @@ test_files:
189
190
  - spec/tdigest_spec.rb
190
191
  - spec/async_sender_spec.rb
191
192
  - spec/stat_spec.rb
193
+ - spec/loggable_spec.rb
192
194
  - spec/backtrace_spec.rb
193
195
  - spec/notice_notifier_spec.rb
194
196
  - spec/time_truncate_spec.rb