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 +4 -4
- data/lib/airbrake-ruby.rb +15 -8
- data/lib/airbrake-ruby/config.rb +3 -1
- data/lib/airbrake-ruby/filters/thread_filter.rb +1 -1
- data/lib/airbrake-ruby/loggable.rb +1 -1
- data/lib/airbrake-ruby/monotonic_time.rb +5 -0
- data/lib/airbrake-ruby/performance_breakdown.rb +14 -9
- data/lib/airbrake-ruby/performance_notifier.rb +52 -64
- data/lib/airbrake-ruby/query.rb +16 -10
- data/lib/airbrake-ruby/queue.rb +22 -6
- data/lib/airbrake-ruby/request.rb +13 -6
- data/lib/airbrake-ruby/stat.rb +13 -14
- data/lib/airbrake-ruby/tdigest.rb +41 -58
- data/lib/airbrake-ruby/truncator.rb +2 -2
- data/lib/airbrake-ruby/version.rb +1 -1
- data/spec/config_spec.rb +6 -0
- data/spec/filters/thread_filter_spec.rb +3 -1
- data/spec/loggable_spec.rb +17 -0
- data/spec/monotonic_time_spec.rb +11 -0
- data/spec/queue_spec.rb +9 -0
- data/spec/stat_spec.rb +0 -1
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 29f58448d31db594854b99c73fd6574b326f7943215f6c5e89b4407bf254add5
|
4
|
+
data.tar.gz: 2f2d9bd955015731f70abc13a6dafd3a73002567cea3b0b8b6bb0a1ae456a8be
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
data/lib/airbrake-ruby/config.rb
CHANGED
@@ -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
|
75
|
+
thread_info[:safe_level] = th.safe_level if Airbrake::HAS_SAFE_LEVEL
|
76
76
|
end
|
77
77
|
|
78
78
|
def sanitize_value(value)
|
@@ -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/
|
9
|
-
PerformanceBreakdown
|
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
|
-
|
30
|
-
|
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/
|
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
|
-
|
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
|
-
|
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
|
-
@
|
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
|
-
|
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
|
|
data/lib/airbrake-ruby/query.rb
CHANGED
@@ -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
|
8
|
-
Query
|
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
|
-
|
32
|
-
|
33
|
-
|
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
|
62
|
+
# rubocop:enable Metrics/ParameterLists
|
57
63
|
end
|
58
64
|
end
|
data/lib/airbrake-ruby/queue.rb
CHANGED
@@ -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/
|
8
|
-
Queue
|
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
|
-
|
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/
|
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/
|
8
|
-
Request
|
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
|
-
|
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/
|
54
|
+
# rubocop:enable Metrics/ParameterLists
|
48
55
|
end
|
data/lib/airbrake-ruby/stat.rb
CHANGED
@@ -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
|
18
|
-
|
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(
|
24
|
-
|
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' =>
|
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
|
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
|
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=#{
|
68
|
+
"#<struct Airbrake::Stat count=#{tdigest.size}, sum=#{sum}, sumsq=#{sumsq}>"
|
69
69
|
end
|
70
|
-
|
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
|
-
@
|
46
|
-
@
|
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
|
63
|
-
output += @centroids.map
|
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 |
|
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 |
|
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
|
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
|
142
|
-
|
143
|
-
ceil = @centroids.upper_bound(x)
|
144
|
-
floor = @centroids.lower_bound(x)
|
142
|
+
return if size == 0
|
145
143
|
|
146
|
-
|
147
|
-
|
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
|
-
|
150
|
-
|
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
|
-
|
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 / @
|
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 = @
|
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
|
-
@
|
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.
|
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(
|
311
|
-
|
312
|
-
|
313
|
-
|
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
|
-
|
316
|
-
nearest.mean_cumn += n / 2.0
|
317
|
-
nearest.n += n
|
307
|
+
_cumulate(false, true) if centroid.mean_cumn.nil?
|
318
308
|
|
319
|
-
|
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
|
-
(@
|
320
|
+
(@size.to_f / @last_cumulate)
|
329
321
|
end
|
330
|
-
return if @
|
322
|
+
return if @size == @last_cumulate || (!exact && @cx && @cx > factor)
|
331
323
|
end
|
332
324
|
|
333
325
|
cumn = 0
|
334
|
-
@centroids.
|
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
|
-
@
|
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
|
-
@
|
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
|
-
|
349
|
+
@centroids[x] = Centroid.new(x, n, 0)
|
361
350
|
elsif nearest == max
|
362
|
-
|
351
|
+
@centroids[x] = Centroid.new(x, n, @size)
|
363
352
|
else
|
364
|
-
p = nearest.mean_cumn.to_f / @
|
365
|
-
max_n = (4 * @
|
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
|
-
|
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
|
data/spec/config_spec.rb
CHANGED
@@ -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:
|
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
|
data/spec/monotonic_time_spec.rb
CHANGED
@@ -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
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.
|
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-
|
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
|