airbrake-ruby 4.15.0 → 6.0.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.
- checksums.yaml +4 -4
- data/lib/airbrake-ruby/async_sender.rb +4 -2
- data/lib/airbrake-ruby/backtrace.rb +6 -5
- data/lib/airbrake-ruby/config/processor.rb +71 -0
- data/lib/airbrake-ruby/config/validator.rb +6 -0
- data/lib/airbrake-ruby/config.rb +44 -35
- data/lib/airbrake-ruby/context.rb +51 -0
- data/lib/airbrake-ruby/file_cache.rb +1 -1
- data/lib/airbrake-ruby/filter_chain.rb +3 -0
- data/lib/airbrake-ruby/filters/context_filter.rb +4 -5
- data/lib/airbrake-ruby/filters/dependency_filter.rb +1 -0
- data/lib/airbrake-ruby/filters/exception_attributes_filter.rb +1 -1
- data/lib/airbrake-ruby/filters/gem_root_filter.rb +1 -0
- data/lib/airbrake-ruby/filters/git_last_checkout_filter.rb +3 -4
- data/lib/airbrake-ruby/filters/git_repository_filter.rb +4 -1
- data/lib/airbrake-ruby/filters/git_revision_filter.rb +3 -1
- data/lib/airbrake-ruby/filters/keys_filter.rb +23 -15
- data/lib/airbrake-ruby/filters/root_directory_filter.rb +1 -0
- data/lib/airbrake-ruby/filters/sql_filter.rb +11 -11
- data/lib/airbrake-ruby/filters/system_exit_filter.rb +1 -0
- data/lib/airbrake-ruby/filters/thread_filter.rb +4 -3
- data/lib/airbrake-ruby/grouppable.rb +1 -1
- data/lib/airbrake-ruby/ignorable.rb +1 -2
- data/lib/airbrake-ruby/mergeable.rb +1 -1
- data/lib/airbrake-ruby/monotonic_time.rb +1 -1
- data/lib/airbrake-ruby/notice.rb +1 -8
- data/lib/airbrake-ruby/notice_notifier.rb +4 -4
- data/lib/airbrake-ruby/performance_breakdown.rb +1 -6
- data/lib/airbrake-ruby/performance_notifier.rb +40 -54
- data/lib/airbrake-ruby/promise.rb +1 -0
- data/lib/airbrake-ruby/query.rb +1 -6
- data/lib/airbrake-ruby/queue.rb +1 -8
- data/lib/airbrake-ruby/remote_settings/callback.rb +44 -0
- data/lib/airbrake-ruby/remote_settings/settings_data.rb +116 -0
- data/lib/airbrake-ruby/remote_settings.rb +128 -0
- data/lib/airbrake-ruby/request.rb +1 -8
- data/lib/airbrake-ruby/stat.rb +2 -13
- data/lib/airbrake-ruby/sync_sender.rb +3 -2
- data/lib/airbrake-ruby/tdigest.rb +12 -9
- data/lib/airbrake-ruby/thread_pool.rb +9 -6
- data/lib/airbrake-ruby/time_truncate.rb +2 -2
- data/lib/airbrake-ruby/timed_trace.rb +1 -3
- data/lib/airbrake-ruby/truncator.rb +8 -2
- data/lib/airbrake-ruby/version.rb +11 -1
- data/lib/airbrake-ruby.rb +44 -54
- data/spec/airbrake_spec.rb +178 -92
- data/spec/async_sender_spec.rb +10 -8
- data/spec/backtrace_spec.rb +39 -36
- data/spec/benchmark_spec.rb +5 -3
- data/spec/code_hunk_spec.rb +26 -17
- data/spec/config/processor_spec.rb +151 -0
- data/spec/config/validator_spec.rb +23 -3
- data/spec/config_spec.rb +40 -52
- data/spec/context_spec.rb +54 -0
- data/spec/deploy_notifier_spec.rb +6 -4
- data/spec/file_cache_spec.rb +1 -0
- data/spec/filter_chain_spec.rb +29 -24
- data/spec/filters/context_filter_spec.rb +14 -5
- data/spec/filters/dependency_filter_spec.rb +3 -1
- data/spec/filters/exception_attributes_filter_spec.rb +5 -3
- data/spec/filters/gem_root_filter_spec.rb +9 -6
- data/spec/filters/git_last_checkout_filter_spec.rb +10 -12
- data/spec/filters/git_repository_filter.rb +9 -9
- data/spec/filters/git_revision_filter_spec.rb +20 -20
- data/spec/filters/keys_allowlist_spec.rb +26 -16
- data/spec/filters/keys_blocklist_spec.rb +35 -18
- data/spec/filters/root_directory_filter_spec.rb +7 -7
- data/spec/filters/sql_filter_spec.rb +28 -28
- data/spec/filters/system_exit_filter_spec.rb +4 -2
- data/spec/filters/thread_filter_spec.rb +16 -14
- data/spec/loggable_spec.rb +2 -2
- data/spec/monotonic_time_spec.rb +8 -6
- data/spec/nested_exception_spec.rb +46 -46
- data/spec/notice_notifier/options_spec.rb +25 -15
- data/spec/notice_notifier_spec.rb +54 -49
- data/spec/notice_spec.rb +7 -3
- data/spec/performance_breakdown_spec.rb +0 -12
- data/spec/performance_notifier_spec.rb +69 -87
- data/spec/promise_spec.rb +38 -32
- data/spec/query_spec.rb +1 -11
- data/spec/queue_spec.rb +1 -13
- data/spec/remote_settings/callback_spec.rb +162 -0
- data/spec/remote_settings/settings_data_spec.rb +348 -0
- data/spec/remote_settings_spec.rb +201 -0
- data/spec/request_spec.rb +1 -13
- data/spec/response_spec.rb +34 -12
- data/spec/spec_helper.rb +4 -4
- data/spec/stashable_spec.rb +5 -5
- data/spec/stat_spec.rb +7 -14
- data/spec/sync_sender_spec.rb +52 -17
- data/spec/tdigest_spec.rb +61 -56
- data/spec/thread_pool_spec.rb +65 -56
- data/spec/time_truncate_spec.rb +23 -6
- data/spec/timed_trace_spec.rb +32 -30
- data/spec/truncator_spec.rb +72 -43
- metadata +66 -50
@@ -0,0 +1,128 @@
|
|
1
|
+
module Airbrake
|
2
|
+
# RemoteSettings polls the remote config of the passed project at fixed
|
3
|
+
# intervals. The fetched config is yielded as a callback parameter so that the
|
4
|
+
# invoker can define read config values. Supports proxies.
|
5
|
+
#
|
6
|
+
# @example Disable/enable error notifications based on the remote value
|
7
|
+
# RemoteSettings.poll do |data|
|
8
|
+
# config.error_notifications = data.error_notifications?
|
9
|
+
# end
|
10
|
+
#
|
11
|
+
# @since v5.0.0
|
12
|
+
# @api private
|
13
|
+
class RemoteSettings
|
14
|
+
include Airbrake::Loggable
|
15
|
+
|
16
|
+
# @return [Hash{Symbol=>String}] metadata to be attached to every GET
|
17
|
+
# request
|
18
|
+
QUERY_PARAMS = URI.encode_www_form(
|
19
|
+
notifier_name: Airbrake::NOTIFIER_INFO[:name],
|
20
|
+
notifier_version: Airbrake::NOTIFIER_INFO[:version],
|
21
|
+
os: RUBY_PLATFORM,
|
22
|
+
language: "#{RUBY_ENGINE}/#{RUBY_VERSION}".freeze,
|
23
|
+
).freeze
|
24
|
+
|
25
|
+
# @return [String]
|
26
|
+
HTTP_OK = '200'.freeze
|
27
|
+
|
28
|
+
# Polls remote config of the given project.
|
29
|
+
#
|
30
|
+
# @param [Integer] project_id
|
31
|
+
# @param [String] host
|
32
|
+
# @yield [data]
|
33
|
+
# @yieldparam data [Airbrake::RemoteSettings::SettingsData]
|
34
|
+
# @return [Airbrake::RemoteSettings]
|
35
|
+
def self.poll(project_id, host, &block)
|
36
|
+
new(project_id, host, &block).poll
|
37
|
+
end
|
38
|
+
|
39
|
+
# @param [Integer] project_id
|
40
|
+
# @yield [data]
|
41
|
+
# @yieldparam data [Airbrake::RemoteSettings::SettingsData]
|
42
|
+
def initialize(project_id, host, &block)
|
43
|
+
@data = SettingsData.new(project_id, {})
|
44
|
+
@host = host
|
45
|
+
@block = block
|
46
|
+
@config = Airbrake::Config.instance
|
47
|
+
@poll = nil
|
48
|
+
end
|
49
|
+
|
50
|
+
# Polls remote config of the given project in background.
|
51
|
+
#
|
52
|
+
# @return [self]
|
53
|
+
def poll
|
54
|
+
@poll ||= Thread.new do
|
55
|
+
@block.call(@data)
|
56
|
+
|
57
|
+
loop do
|
58
|
+
@block.call(@data.merge!(fetch_config))
|
59
|
+
sleep(@data.interval)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
self
|
64
|
+
end
|
65
|
+
|
66
|
+
# Stops the background poller thread.
|
67
|
+
#
|
68
|
+
# @return [void]
|
69
|
+
def stop_polling
|
70
|
+
@poll.kill if @poll
|
71
|
+
end
|
72
|
+
|
73
|
+
private
|
74
|
+
|
75
|
+
def fetch_config
|
76
|
+
uri = build_config_uri
|
77
|
+
https = build_https(uri)
|
78
|
+
req = Net::HTTP::Get.new(uri.request_uri)
|
79
|
+
response = nil
|
80
|
+
|
81
|
+
begin
|
82
|
+
response = https.request(req)
|
83
|
+
rescue StandardError => ex
|
84
|
+
reason = "#{LOG_LABEL} HTTP error: #{ex}"
|
85
|
+
logger.error(reason)
|
86
|
+
return {}
|
87
|
+
end
|
88
|
+
|
89
|
+
unless response.code == HTTP_OK
|
90
|
+
logger.error(response.body)
|
91
|
+
return {}
|
92
|
+
end
|
93
|
+
|
94
|
+
json = nil
|
95
|
+
begin
|
96
|
+
json = JSON.parse(response.body)
|
97
|
+
rescue JSON::ParserError => ex
|
98
|
+
logger.error(ex)
|
99
|
+
return {}
|
100
|
+
end
|
101
|
+
|
102
|
+
json
|
103
|
+
end
|
104
|
+
|
105
|
+
def build_config_uri
|
106
|
+
uri = URI(@data.config_route(@host))
|
107
|
+
uri.query = QUERY_PARAMS
|
108
|
+
uri
|
109
|
+
end
|
110
|
+
|
111
|
+
def build_https(uri)
|
112
|
+
Net::HTTP.new(uri.host, uri.port, *proxy_params).tap do |https|
|
113
|
+
https.use_ssl = uri.is_a?(URI::HTTPS)
|
114
|
+
if @config.timeout
|
115
|
+
https.open_timeout = @config.timeout
|
116
|
+
https.read_timeout = @config.timeout
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
def proxy_params
|
122
|
+
return unless @config.proxy.key?(:host)
|
123
|
+
|
124
|
+
[@config.proxy[:host], @config.proxy[:port], @config.proxy[:user],
|
125
|
+
@config.proxy[:password]]
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
@@ -4,7 +4,6 @@ module Airbrake
|
|
4
4
|
# @see Airbrake.notify_request
|
5
5
|
# @api public
|
6
6
|
# @since v3.2.0
|
7
|
-
# rubocop:disable Metrics/ParameterLists
|
8
7
|
class Request
|
9
8
|
include HashKeyable
|
10
9
|
include Ignorable
|
@@ -12,15 +11,12 @@ module Airbrake
|
|
12
11
|
include Mergeable
|
13
12
|
include Grouppable
|
14
13
|
|
15
|
-
attr_accessor :method, :route, :status_code, :
|
16
|
-
:timing, :time
|
14
|
+
attr_accessor :method, :route, :status_code, :timing, :time
|
17
15
|
|
18
16
|
def initialize(
|
19
17
|
method:,
|
20
18
|
route:,
|
21
19
|
status_code:,
|
22
|
-
start_time: Time.now,
|
23
|
-
end_time: start_time + 1,
|
24
20
|
timing: nil,
|
25
21
|
time: Time.now
|
26
22
|
)
|
@@ -28,8 +24,6 @@ module Airbrake
|
|
28
24
|
@method = method
|
29
25
|
@route = route
|
30
26
|
@status_code = status_code
|
31
|
-
@start_time = start_time
|
32
|
-
@end_time = end_time
|
33
27
|
@timing = timing
|
34
28
|
@time = time
|
35
29
|
end
|
@@ -51,5 +45,4 @@ module Airbrake
|
|
51
45
|
}.delete_if { |_key, val| val.nil? }
|
52
46
|
end
|
53
47
|
end
|
54
|
-
# rubocop:enable Metrics/ParameterLists
|
55
48
|
end
|
data/lib/airbrake-ruby/stat.rb
CHANGED
@@ -4,12 +4,12 @@ module Airbrake
|
|
4
4
|
# Stat is a data structure that allows accumulating performance data (route
|
5
5
|
# performance, SQL query performance and such). It's powered by TDigests.
|
6
6
|
#
|
7
|
-
# Usually, one Stat corresponds to one
|
7
|
+
# Usually, one Stat corresponds to one metric (route or query,
|
8
8
|
# etc.). Incrementing a stat means pushing new performance statistics.
|
9
9
|
#
|
10
10
|
# @example
|
11
11
|
# stat = Airbrake::Stat.new
|
12
|
-
# stat.
|
12
|
+
# stat.increment_ms(2000)
|
13
13
|
# stat.to_h # Pack and serialize data so it can be transmitted.
|
14
14
|
#
|
15
15
|
# @since v3.2.0
|
@@ -41,17 +41,6 @@ module Airbrake
|
|
41
41
|
end
|
42
42
|
end
|
43
43
|
|
44
|
-
# Increments tdigest timings and updates tdigest with the difference between
|
45
|
-
# +end_time+ and +start_time+.
|
46
|
-
#
|
47
|
-
# @param [Date] start_time
|
48
|
-
# @param [Date] end_time
|
49
|
-
# @return [void]
|
50
|
-
def increment(start_time, end_time = nil)
|
51
|
-
end_time ||= Time.new
|
52
|
-
increment_ms((end_time - start_time) * 1000)
|
53
|
-
end
|
54
|
-
|
55
44
|
# Increments tdigest timings and updates tdigest with given +ms+ value.
|
56
45
|
#
|
57
46
|
# @param [Float] ms
|
@@ -23,7 +23,7 @@ module Airbrake
|
|
23
23
|
# @param [#to_json] data
|
24
24
|
# @param [URI::HTTPS] endpoint
|
25
25
|
# @return [Hash{String=>String}] the parsed HTTP response
|
26
|
-
def send(data, promise, endpoint = @config.
|
26
|
+
def send(data, promise, endpoint = @config.error_endpoint)
|
27
27
|
return promise if rate_limited_ip?(promise)
|
28
28
|
|
29
29
|
response = nil
|
@@ -47,6 +47,7 @@ module Airbrake
|
|
47
47
|
end
|
48
48
|
|
49
49
|
return promise.reject(parsed_resp['error']) if parsed_resp.key?('error')
|
50
|
+
|
50
51
|
promise.resolve(parsed_resp)
|
51
52
|
end
|
52
53
|
|
@@ -79,7 +80,7 @@ module Airbrake
|
|
79
80
|
req['Authorization'] = "Bearer #{@config.project_key}"
|
80
81
|
req['Content-Type'] = CONTENT_TYPE
|
81
82
|
req['User-Agent'] =
|
82
|
-
"#{Airbrake::
|
83
|
+
"#{Airbrake::NOTIFIER_INFO[:name]}/#{Airbrake::AIRBRAKE_RUBY_VERSION}" \
|
83
84
|
" Ruby/#{RUBY_VERSION}"
|
84
85
|
|
85
86
|
req
|
@@ -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
|
@@ -130,7 +131,7 @@ module Airbrake
|
|
130
131
|
points = to_a
|
131
132
|
reset!
|
132
133
|
push_centroid(points.shuffle)
|
133
|
-
_cumulate(true, true)
|
134
|
+
_cumulate(exact: true, force: true)
|
134
135
|
nil
|
135
136
|
end
|
136
137
|
|
@@ -175,7 +176,7 @@ module Airbrake
|
|
175
176
|
elsif item > max[1].mean
|
176
177
|
1.0
|
177
178
|
else
|
178
|
-
_cumulate(true)
|
179
|
+
_cumulate(exact: true)
|
179
180
|
bound = bound_mean(item)
|
180
181
|
lower, upper = bound
|
181
182
|
mean_cumn = lower.mean_cumn
|
@@ -200,10 +201,11 @@ module Airbrake
|
|
200
201
|
unless (0..1).cover?(item)
|
201
202
|
raise ArgumentError, "p should be in [0,1], got #{item}"
|
202
203
|
end
|
204
|
+
|
203
205
|
if size == 0
|
204
206
|
nil
|
205
207
|
else
|
206
|
-
_cumulate(true)
|
208
|
+
_cumulate(exact: true)
|
207
209
|
h = @size * item
|
208
210
|
lower, upper = bound_mean_cumn(h)
|
209
211
|
if lower.nil? && upper.nil?
|
@@ -254,7 +256,7 @@ module Airbrake
|
|
254
256
|
array = bytes[start_idx..-1].unpack("G#{size}N#{size}")
|
255
257
|
means, counts = array.each_slice(size).to_a if array.any?
|
256
258
|
when SMALL_ENCODING
|
257
|
-
means = bytes[start_idx..(start_idx + 4 * size)].unpack("g#{size}")
|
259
|
+
means = bytes[start_idx..(start_idx + (4 * size))].unpack("g#{size}")
|
258
260
|
# Decode delta encoding of means
|
259
261
|
x = 0
|
260
262
|
means.map! do |m|
|
@@ -262,7 +264,7 @@ module Airbrake
|
|
262
264
|
x = m
|
263
265
|
m
|
264
266
|
end
|
265
|
-
counts_bytes = bytes[(start_idx + 4 * size)..-1].unpack('C*')
|
267
|
+
counts_bytes = bytes[(start_idx + (4 * size))..-1].unpack('C*')
|
266
268
|
counts = []
|
267
269
|
# Decode variable length integer bytes
|
268
270
|
size.times do
|
@@ -271,6 +273,7 @@ module Airbrake
|
|
271
273
|
shift = 7
|
272
274
|
while (v & 0x80) != 0
|
273
275
|
raise 'Shift too large in decode' if shift > 28
|
276
|
+
|
274
277
|
v = counts_bytes.shift || 0
|
275
278
|
z += (v & 0x7f) << shift
|
276
279
|
shift += 7
|
@@ -304,7 +307,7 @@ module Airbrake
|
|
304
307
|
centroid.mean += n * (x - centroid.mean) / (centroid.n + n)
|
305
308
|
end
|
306
309
|
|
307
|
-
_cumulate(false, true) if centroid.mean_cumn.nil?
|
310
|
+
_cumulate(exact: false, force: true) if centroid.mean_cumn.nil?
|
308
311
|
|
309
312
|
centroid.cumn += n
|
310
313
|
centroid.mean_cumn += n / 2.0
|
@@ -312,7 +315,7 @@ module Airbrake
|
|
312
315
|
end
|
313
316
|
|
314
317
|
# rubocop:disable Metrics/PerceivedComplexity, Metrics/CyclomaticComplexity
|
315
|
-
def _cumulate(exact
|
318
|
+
def _cumulate(exact: false, force: false)
|
316
319
|
unless force
|
317
320
|
factor = if @last_cumulate == 0
|
318
321
|
Float::INFINITY
|
@@ -324,7 +327,7 @@ module Airbrake
|
|
324
327
|
|
325
328
|
cumn = 0
|
326
329
|
@centroids.each_value do |c|
|
327
|
-
c.mean_cumn = cumn + c.n / 2.0
|
330
|
+
c.mean_cumn = cumn + (c.n / 2.0)
|
328
331
|
cumn = c.cumn = cumn + c.n
|
329
332
|
end
|
330
333
|
@size = @last_cumulate = cumn
|
@@ -359,7 +362,7 @@ module Airbrake
|
|
359
362
|
end
|
360
363
|
end
|
361
364
|
|
362
|
-
_cumulate(false)
|
365
|
+
_cumulate(exact: false)
|
363
366
|
|
364
367
|
# If the number of centroids has grown to a very large size,
|
365
368
|
# it may be due to values being inserted in sorted order.
|
@@ -6,6 +6,7 @@ module Airbrake
|
|
6
6
|
# # Initialize a new thread pool with 5 workers and a queue size of 100. Set
|
7
7
|
# # the block to be run concurrently.
|
8
8
|
# thread_pool = ThreadPool.new(
|
9
|
+
# name: 'performance-notifier',
|
9
10
|
# worker_size: 5,
|
10
11
|
# queue_size: 100,
|
11
12
|
# block: proc { |message| print "ECHO: #{message}..."}
|
@@ -24,7 +25,8 @@ module Airbrake
|
|
24
25
|
# @note This is exposed for eaiser unit testing
|
25
26
|
attr_reader :workers
|
26
27
|
|
27
|
-
def initialize(worker_size:, queue_size:, block:)
|
28
|
+
def initialize(worker_size:, queue_size:, block:, name: nil)
|
29
|
+
@name = name
|
28
30
|
@worker_size = worker_size
|
29
31
|
@queue_size = queue_size
|
30
32
|
@block = block
|
@@ -46,11 +48,11 @@ module Airbrake
|
|
46
48
|
# false if the queue is full
|
47
49
|
def <<(message)
|
48
50
|
if backlog >= @queue_size
|
49
|
-
logger.
|
51
|
+
logger.info do
|
50
52
|
"#{LOG_LABEL} ThreadPool has reached its capacity of " \
|
51
53
|
"#{@queue_size} and the following message will not be " \
|
52
|
-
"processed: #{message.inspect}"
|
53
|
-
|
54
|
+
"processed: #{message.inspect}"
|
55
|
+
end
|
54
56
|
return false
|
55
57
|
end
|
56
58
|
|
@@ -102,7 +104,7 @@ module Airbrake
|
|
102
104
|
|
103
105
|
unless @queue.empty?
|
104
106
|
msg = "#{LOG_LABEL} waiting to process #{@queue.size} task(s)..."
|
105
|
-
logger.debug(msg
|
107
|
+
logger.debug("#{msg} (Ctrl-C to abort)")
|
106
108
|
end
|
107
109
|
|
108
110
|
@worker_size.times { @queue << :stop }
|
@@ -111,7 +113,7 @@ module Airbrake
|
|
111
113
|
end
|
112
114
|
|
113
115
|
threads.each(&:join)
|
114
|
-
logger.debug("#{LOG_LABEL} thread pool closed")
|
116
|
+
logger.debug("#{LOG_LABEL} #{@name} thread pool closed")
|
115
117
|
end
|
116
118
|
|
117
119
|
def closed?
|
@@ -129,6 +131,7 @@ module Airbrake
|
|
129
131
|
Thread.new do
|
130
132
|
while (message = @queue.pop)
|
131
133
|
break if message == :stop
|
134
|
+
|
132
135
|
@block.call(message)
|
133
136
|
end
|
134
137
|
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
|
@@ -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,8 +110,7 @@ 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
|
-
|
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
|
@@ -2,5 +2,15 @@
|
|
2
2
|
# More information: http://semver.org/
|
3
3
|
module Airbrake
|
4
4
|
# @return [String] the library version
|
5
|
-
|
5
|
+
# @api public
|
6
|
+
AIRBRAKE_RUBY_VERSION = '6.0.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
|
data/lib/airbrake-ruby.rb
CHANGED
@@ -12,6 +12,10 @@ require 'airbrake-ruby/mergeable'
|
|
12
12
|
require 'airbrake-ruby/grouppable'
|
13
13
|
require 'airbrake-ruby/config'
|
14
14
|
require 'airbrake-ruby/config/validator'
|
15
|
+
require 'airbrake-ruby/config/processor'
|
16
|
+
require 'airbrake-ruby/remote_settings/callback'
|
17
|
+
require 'airbrake-ruby/remote_settings/settings_data'
|
18
|
+
require 'airbrake-ruby/remote_settings'
|
15
19
|
require 'airbrake-ruby/promise'
|
16
20
|
require 'airbrake-ruby/thread_pool'
|
17
21
|
require 'airbrake-ruby/sync_sender'
|
@@ -54,6 +58,7 @@ require 'airbrake-ruby/benchmark'
|
|
54
58
|
require 'airbrake-ruby/monotonic_time'
|
55
59
|
require 'airbrake-ruby/timed_trace'
|
56
60
|
require 'airbrake-ruby/queue'
|
61
|
+
require 'airbrake-ruby/context'
|
57
62
|
|
58
63
|
# Airbrake is a thin wrapper around instances of the notifier classes (such as
|
59
64
|
# notice, performance & deploy notifiers). It creates a way to access them via a
|
@@ -117,7 +122,15 @@ module Airbrake
|
|
117
122
|
def configure
|
118
123
|
yield config = Airbrake::Config.instance
|
119
124
|
Airbrake::Loggable.instance = config.logger
|
120
|
-
|
125
|
+
|
126
|
+
config_processor = Airbrake::Config::Processor.new(config)
|
127
|
+
|
128
|
+
config_processor.process_blocklist(notice_notifier)
|
129
|
+
config_processor.process_allowlist(notice_notifier)
|
130
|
+
|
131
|
+
@remote_settings ||= config_processor.process_remote_configuration
|
132
|
+
|
133
|
+
config_processor.add_filters(notice_notifier)
|
121
134
|
end
|
122
135
|
|
123
136
|
# @since v4.2.3
|
@@ -260,8 +273,8 @@ module Airbrake
|
|
260
273
|
# Airbrake.close
|
261
274
|
# Airbrake.notify('App crashed!') #=> raises Airbrake::Error
|
262
275
|
#
|
263
|
-
# @return [
|
264
|
-
# rubocop:disable Style/
|
276
|
+
# @return [nil]
|
277
|
+
# rubocop:disable Style/IfUnlessModifier
|
265
278
|
def close
|
266
279
|
if defined?(@notice_notifier) && @notice_notifier
|
267
280
|
@notice_notifier.close
|
@@ -270,8 +283,14 @@ module Airbrake
|
|
270
283
|
if defined?(@performance_notifier) && @performance_notifier
|
271
284
|
@performance_notifier.close
|
272
285
|
end
|
286
|
+
|
287
|
+
if defined?(@remote_settings) && @remote_settings
|
288
|
+
@remote_settings.stop_polling
|
289
|
+
end
|
290
|
+
|
291
|
+
nil
|
273
292
|
end
|
274
|
-
# rubocop:enable Style/
|
293
|
+
# rubocop:enable Style/IfUnlessModifier
|
275
294
|
|
276
295
|
# Pings the Airbrake Deploy API endpoint about the occurred deploy.
|
277
296
|
#
|
@@ -345,23 +364,14 @@ module Airbrake
|
|
345
364
|
# method: 'POST',
|
346
365
|
# route: '/thing/:id/create',
|
347
366
|
# status_code: 200,
|
348
|
-
# func: 'do_stuff',
|
349
|
-
# file: 'app/models/foo.rb',
|
350
|
-
# line: 452,
|
351
367
|
# timing: 123.45 # ms
|
352
368
|
# )
|
353
369
|
#
|
354
370
|
# @param [Hash{Symbol=>Object}] request_info
|
355
371
|
# @option request_info [String] :method The HTTP method that was invoked
|
356
372
|
# @option request_info [String] :route The route that was invoked
|
357
|
-
# @option request_info [Integer] :status_code The
|
373
|
+
# @option request_info [Integer] :status_code The response code that the
|
358
374
|
# route returned
|
359
|
-
# @option request_info [String] :func The function that called the query
|
360
|
-
# (optional)
|
361
|
-
# @option request_info [String] :file The file that has the function that
|
362
|
-
# called the query (optional)
|
363
|
-
# @option request_info [Integer] :line The line that executes the query
|
364
|
-
# (optional)
|
365
375
|
# @option request_info [Float] :timing How much time it took to process the
|
366
376
|
# request (in ms)
|
367
377
|
# @param [Hash] stash What needs to be appeneded to the stash, so it's
|
@@ -396,6 +406,9 @@ module Airbrake
|
|
396
406
|
# method: 'GET',
|
397
407
|
# route: '/things',
|
398
408
|
# query: 'SELECT * FROM things',
|
409
|
+
# func: 'do_stuff',
|
410
|
+
# file: 'app/models/foo.rb',
|
411
|
+
# line: 452,
|
399
412
|
# timing: 123.45 # ms
|
400
413
|
# )
|
401
414
|
#
|
@@ -405,6 +418,12 @@ module Airbrake
|
|
405
418
|
# @option query_info [String] :route The route that triggered this SQL
|
406
419
|
# query (optional)
|
407
420
|
# @option query_info [String] :query The query that was executed
|
421
|
+
# @option request_info [String] :func The function that called the query
|
422
|
+
# (optional)
|
423
|
+
# @option request_info [String] :file The file that has the function that
|
424
|
+
# called the query (optional)
|
425
|
+
# @option request_info [Integer] :line The line that executes the query
|
426
|
+
# (optional)
|
408
427
|
# @option query_info [Float] :timing How much time it took to process the
|
409
428
|
# query (in ms)
|
410
429
|
# @param [Hash] stash What needs to be appeneded to the stash, so it's
|
@@ -432,7 +451,7 @@ module Airbrake
|
|
432
451
|
# Increments performance breakdown statistics of a certain route.
|
433
452
|
#
|
434
453
|
# @example
|
435
|
-
# Airbrake.
|
454
|
+
# Airbrake.notify_performance_breakdown(
|
436
455
|
# method: 'POST',
|
437
456
|
# route: '/thing/:id/create',
|
438
457
|
# response_type: 'json',
|
@@ -504,24 +523,24 @@ module Airbrake
|
|
504
523
|
performance_notifier.notify_sync(queue)
|
505
524
|
end
|
506
525
|
|
507
|
-
# Runs a callback before {.notify_request}
|
508
|
-
# is useful if you want to
|
509
|
-
#
|
526
|
+
# Runs a callback before {.notify_request}, {.notify_query}, {.notify_queue}
|
527
|
+
# or {.notify_performance_breakdown} kicks in. This is useful if you want to
|
528
|
+
# ignore specific metrics or filter the data the metric contains.
|
510
529
|
#
|
511
|
-
# @example Ignore all
|
530
|
+
# @example Ignore all metrics
|
512
531
|
# Airbrake.add_performance_filter(&:ignore!)
|
513
532
|
# @example Filter sensitive data
|
514
|
-
# Airbrake.add_performance_filter do |
|
515
|
-
# case
|
533
|
+
# Airbrake.add_performance_filter do |metric|
|
534
|
+
# case metric
|
516
535
|
# when Airbrake::Query
|
517
|
-
#
|
536
|
+
# metric.route = '[Filtered]'
|
518
537
|
# when Airbrake::Request
|
519
|
-
#
|
538
|
+
# metric.query = '[Filtered]'
|
520
539
|
# end
|
521
540
|
# end
|
522
541
|
# @example Filter with help of a class
|
523
542
|
# class MyFilter
|
524
|
-
# def call(
|
543
|
+
# def call(metric)
|
525
544
|
# # ...
|
526
545
|
# end
|
527
546
|
# end
|
@@ -529,7 +548,7 @@ module Airbrake
|
|
529
548
|
# Airbrake.add_performance_filter(MyFilter.new)
|
530
549
|
#
|
531
550
|
# @param [#call] filter The filter object
|
532
|
-
# @yield [
|
551
|
+
# @yield [metric] The metric to filter
|
533
552
|
# @yieldparam [Airbrake::Query, Airbrake::Request]
|
534
553
|
# @yieldreturn [void]
|
535
554
|
# @return [void]
|
@@ -567,35 +586,6 @@ module Airbrake
|
|
567
586
|
self.notice_notifier = NoticeNotifier.new
|
568
587
|
self.deploy_notifier = DeployNotifier.new
|
569
588
|
end
|
570
|
-
|
571
|
-
private
|
572
|
-
|
573
|
-
# rubocop:disable Metrics/AbcSize
|
574
|
-
def process_config_options(config)
|
575
|
-
if config.blocklist_keys.any?
|
576
|
-
blocklist = Airbrake::Filters::KeysBlocklist.new(config.blocklist_keys)
|
577
|
-
notice_notifier.add_filter(blocklist)
|
578
|
-
end
|
579
|
-
|
580
|
-
if config.allowlist_keys.any?
|
581
|
-
allowlist = Airbrake::Filters::KeysAllowlist.new(config.allowlist_keys)
|
582
|
-
notice_notifier.add_filter(allowlist)
|
583
|
-
end
|
584
|
-
|
585
|
-
return unless config.root_directory
|
586
|
-
|
587
|
-
[
|
588
|
-
Airbrake::Filters::RootDirectoryFilter,
|
589
|
-
Airbrake::Filters::GitRevisionFilter,
|
590
|
-
Airbrake::Filters::GitRepositoryFilter,
|
591
|
-
Airbrake::Filters::GitLastCheckoutFilter,
|
592
|
-
].each do |filter|
|
593
|
-
next if notice_notifier.has_filter?(filter)
|
594
|
-
|
595
|
-
notice_notifier.add_filter(filter.new(config.root_directory))
|
596
|
-
end
|
597
|
-
end
|
598
|
-
# rubocop:enable Metrics/AbcSize
|
599
589
|
end
|
600
590
|
end
|
601
591
|
# rubocop:enable Metrics/ModuleLength
|