airbrake-ruby 4.13.0 → 4.13.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 21b45126851240764d75da35e8f7fbc7c73effdda1b01854065dc25db0c351e3
4
- data.tar.gz: 5dd7a704b0a16a841fb93b30b72a0acf28cb887f2e7abdda57e48078289d107b
3
+ metadata.gz: b4ca256b96d64ce95df54220e572323892f0b3496710a7273409306f2b0a7088
4
+ data.tar.gz: a5b264fb9ac50c258bd7f239af074b20b8bd1515a9a94241f98c602d700d5255
5
5
  SHA512:
6
- metadata.gz: 9252437e39b4a415e2e90ca8a4434540ecd7e45737bdacd788e772a308fbc6c1f231c293eaa11aaaf90310f695f7a44e87b7511cd5af204259949746a6e85013
7
- data.tar.gz: 969b2e55a423d02f241e04ccb27eeb13d352dbe3b0f4394c53dccdf4a3bab8c4d59e19ac7a9c7e455a08b5c22340e49b126581b1be90adcd972faaa79fbb6e11
6
+ metadata.gz: eba8e8d34d8de98fe34bfea5a3b48393e59d04473f81721565bea42fef486f06ab5b01899e16ed9d2517f6349137e05d1df04f851f48bfb0a998a9c23912daf3
7
+ data.tar.gz: 39b6d21b0007e6e14f31903a82d8871f45a47a6d27ecef6df65cff7ba6db95ae2b4e81a8bb84e227b2f65cf7ed2cc6fe06bf1b3c2dc880f3423232a25d39224a
@@ -1,7 +1,6 @@
1
1
  require 'net/https'
2
2
  require 'logger'
3
3
  require 'json'
4
- require 'thread'
5
4
  require 'set'
6
5
  require 'socket'
7
6
  require 'time'
@@ -7,12 +7,6 @@ module Airbrake
7
7
  class AsyncSender
8
8
  include Loggable
9
9
 
10
- # @return [String]
11
- WILL_NOT_DELIVER_MSG =
12
- "%<log_label>s AsyncSender has reached its capacity of %<capacity>s " \
13
- "and the following notice will not be delivered " \
14
- "Error: %<type>s - %<message>s\nBacktrace: %<backtrace>s\n".freeze
15
-
16
10
  def initialize(method = :post)
17
11
  @config = Airbrake::Config.instance
18
12
  @method = method
@@ -20,12 +14,13 @@ module Airbrake
20
14
 
21
15
  # Asynchronously sends a notice to Airbrake.
22
16
  #
23
- # @param [Airbrake::Notice] notice A notice that was generated by the
24
- # library
17
+ # @param [Hash] payload Whatever needs to be sent
25
18
  # @return [Airbrake::Promise]
26
- def send(notice, promise, endpoint = @config.endpoint)
27
- unless thread_pool << [notice, promise, endpoint]
28
- return will_not_deliver(notice, promise)
19
+ def send(payload, promise, endpoint = @config.endpoint)
20
+ unless thread_pool << [payload, promise, endpoint]
21
+ return promise.reject(
22
+ "AsyncSender has reached its capacity of #{@config.queue_size}",
23
+ )
29
24
  end
30
25
 
31
26
  promise
@@ -58,23 +53,5 @@ module Airbrake
58
53
  )
59
54
  end
60
55
  end
61
-
62
- def will_not_deliver(notice, promise)
63
- error = notice[:errors].first
64
-
65
- logger.error(
66
- format(
67
- WILL_NOT_DELIVER_MSG,
68
- log_label: LOG_LABEL,
69
- capacity: @config.queue_size,
70
- type: error[:type],
71
- message: error[:message],
72
- backtrace: error[:backtrace].map do |line|
73
- "#{line[:file]}:#{line[:line]} in `#{line[:function]}'"
74
- end.join("\n"),
75
- ),
76
- )
77
- promise.reject("AsyncSender has reached its capacity of #{@config.queue_size}")
78
- end
79
56
  end
80
57
  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
@@ -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)
@@ -76,7 +76,7 @@ module Airbrake
76
76
  #
77
77
  # @return [Hash{String=>String}, nil]
78
78
  # @api private
79
- def to_json
79
+ def to_json(*_args)
80
80
  loop do
81
81
  begin
82
82
  json = @payload.to_json
@@ -9,7 +9,7 @@ module Airbrake
9
9
  # @return [Array<Class>] filters to be executed first
10
10
  DEFAULT_FILTERS = [
11
11
  Airbrake::Filters::SystemExitFilter,
12
- Airbrake::Filters::GemRootFilter
12
+ Airbrake::Filters::GemRootFilter,
13
13
 
14
14
  # Optional filters (must be included by users):
15
15
  # Airbrake::Filters::ThreadFilter
@@ -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
 
@@ -14,15 +14,13 @@ module Airbrake
14
14
  #
15
15
  # @since v3.2.0
16
16
  class Stat
17
- attr_accessor :count, :sum, :sumsq, :tdigest
17
+ attr_accessor :sum, :sumsq, :tdigest
18
18
 
19
- # @param [Integer] count How many times this stat was incremented
20
19
  # @param [Float] sum The sum of duration in milliseconds
21
20
  # @param [Float] sumsq The squared sum of duration in milliseconds
22
21
  # @param [TDigest::TDigest] tdigest Packed durations. By default,
23
22
  # compression is 20
24
- def initialize(count: 0, sum: 0.0, sumsq: 0.0, tdigest: TDigest.new(0.05))
25
- @count = count
23
+ def initialize(sum: 0.0, sumsq: 0.0, tdigest: TDigest.new(0.05))
26
24
  @sum = sum
27
25
  @sumsq = sumsq
28
26
  @tdigest = tdigest
@@ -33,15 +31,15 @@ module Airbrake
33
31
  def to_h
34
32
  tdigest.compress!
35
33
  {
36
- 'count' => count,
34
+ 'count' => tdigest.size,
37
35
  'sum' => sum,
38
36
  'sumsq' => sumsq,
39
37
  'tdigest' => Base64.strict_encode64(tdigest.as_small_bytes),
40
38
  }
41
39
  end
42
40
 
43
- # Increments count and updates performance with the difference of +end_time+
44
- # and +start_time+.
41
+ # Increments tdigest timings and updates tdigest with the difference between
42
+ # +end_time+ and +start_time+.
45
43
  #
46
44
  # @param [Date] start_time
47
45
  # @param [Date] end_time
@@ -51,13 +49,11 @@ module Airbrake
51
49
  increment_ms((end_time - start_time) * 1000)
52
50
  end
53
51
 
54
- # Increments count and updates performance with given +ms+ value.
52
+ # Increments tdigest timings and updates tdigest with given +ms+ value.
55
53
  #
56
54
  # @param [Float] ms
57
55
  # @return [void]
58
56
  def increment_ms(ms)
59
- self.count += 1
60
-
61
57
  self.sum += ms
62
58
  self.sumsq += ms * ms
63
59
 
@@ -69,7 +65,7 @@ module Airbrake
69
65
  #
70
66
  # @return [String]
71
67
  def inspect
72
- "#<struct Airbrake::Stat count=#{count}, sum=#{sum}, sumsq=#{sumsq}>"
68
+ "#<struct Airbrake::Stat count=#{tdigest.size}, sum=#{sum}, sumsq=#{sumsq}>"
73
69
  end
74
70
  alias pretty_print inspect
75
71
  end
@@ -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
@@ -45,7 +45,15 @@ module Airbrake
45
45
  # @return [Boolean] true if the message was successfully sent to the pool,
46
46
  # false if the queue is full
47
47
  def <<(message)
48
- return false if backlog >= @queue_size
48
+ if backlog >= @queue_size
49
+ logger.error(
50
+ "#{LOG_LABEL} ThreadPool has reached its capacity of " \
51
+ "#{@queue_size} and the following message will not be " \
52
+ "processed: #{message.inspect}",
53
+ )
54
+ return false
55
+ end
56
+
49
57
  @queue << message
50
58
  true
51
59
  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.13.0'.freeze
5
+ AIRBRAKE_RUBY_VERSION = '4.13.2'.freeze
6
6
  end
@@ -58,15 +58,6 @@ RSpec.describe Airbrake::AsyncSender do
58
58
  'error' => "AsyncSender has reached its capacity of 1",
59
59
  )
60
60
  end
61
-
62
- it "logs discarded notice" do
63
- expect(Airbrake::Loggable.instance).to receive(:error).with(
64
- /reached its capacity/,
65
- ).at_least(:once)
66
-
67
- 15.times { subject.send(notice, Airbrake::Promise.new) }
68
- subject.close
69
- end
70
61
  end
71
62
  end
72
63
  end
@@ -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
@@ -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
@@ -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
 
@@ -51,6 +51,15 @@ RSpec.describe Airbrake::ThreadPool do
51
51
 
52
52
  expect(tasks.size).to be_zero
53
53
  end
54
+
55
+ it "logs discarded tasks" do
56
+ expect(Airbrake::Loggable.instance).to receive(:error).with(
57
+ /reached its capacity/,
58
+ ).exactly(15).times
59
+
60
+ 15.times { subject << 1 }
61
+ subject.close
62
+ end
54
63
  end
55
64
  end
56
65
 
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.13.0
4
+ version: 4.13.2
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-27 00:00:00.000000000 Z
11
+ date: 2020-02-21 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