airbrake-ruby 4.6.0 → 6.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +5 -5
- data/lib/airbrake-ruby/async_sender.rb +24 -109
- data/lib/airbrake-ruby/backlog.rb +123 -0
- data/lib/airbrake-ruby/backtrace.rb +8 -7
- data/lib/airbrake-ruby/benchmark.rb +1 -1
- data/lib/airbrake-ruby/code_hunk.rb +1 -1
- data/lib/airbrake-ruby/config/processor.rb +77 -0
- data/lib/airbrake-ruby/config/validator.rb +9 -3
- data/lib/airbrake-ruby/config.rb +78 -19
- data/lib/airbrake-ruby/context.rb +51 -0
- data/lib/airbrake-ruby/deploy_notifier.rb +1 -1
- data/lib/airbrake-ruby/file_cache.rb +6 -0
- data/lib/airbrake-ruby/filter_chain.rb +18 -1
- 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 +3 -3
- data/lib/airbrake-ruby/filters/gem_root_filter.rb +1 -0
- data/lib/airbrake-ruby/filters/git_last_checkout_filter.rb +7 -7
- data/lib/airbrake-ruby/filters/git_repository_filter.rb +11 -2
- data/lib/airbrake-ruby/filters/git_revision_filter.rb +3 -1
- data/lib/airbrake-ruby/filters/{keys_whitelist.rb → keys_allowlist.rb} +3 -3
- data/lib/airbrake-ruby/filters/{keys_blacklist.rb → keys_blocklist.rb} +3 -3
- data/lib/airbrake-ruby/filters/keys_filter.rb +41 -22
- data/lib/airbrake-ruby/filters/root_directory_filter.rb +1 -0
- data/lib/airbrake-ruby/filters/sql_filter.rb +35 -11
- data/lib/airbrake-ruby/filters/system_exit_filter.rb +1 -0
- data/lib/airbrake-ruby/filters/thread_filter.rb +6 -5
- data/lib/airbrake-ruby/grouppable.rb +12 -0
- data/lib/airbrake-ruby/ignorable.rb +1 -2
- data/lib/airbrake-ruby/inspectable.rb +2 -2
- data/lib/airbrake-ruby/loggable.rb +1 -1
- data/lib/airbrake-ruby/mergeable.rb +12 -0
- data/lib/airbrake-ruby/monotonic_time.rb +6 -1
- data/lib/airbrake-ruby/nested_exception.rb +22 -1
- data/lib/airbrake-ruby/notice.rb +12 -17
- data/lib/airbrake-ruby/notice_notifier.rb +15 -7
- data/lib/airbrake-ruby/performance_breakdown.rb +16 -9
- data/lib/airbrake-ruby/performance_notifier.rb +106 -54
- data/lib/airbrake-ruby/promise.rb +1 -0
- data/lib/airbrake-ruby/query.rb +20 -14
- data/lib/airbrake-ruby/queue.rb +65 -0
- 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 +14 -11
- data/lib/airbrake-ruby/response.rb +56 -5
- data/lib/airbrake-ruby/stat.rb +27 -34
- data/lib/airbrake-ruby/sync_sender.rb +43 -11
- data/lib/airbrake-ruby/tdigest.rb +52 -66
- data/lib/airbrake-ruby/thread_pool.rb +139 -0
- data/lib/airbrake-ruby/time_truncate.rb +2 -2
- data/lib/airbrake-ruby/timed_trace.rb +1 -3
- data/lib/airbrake-ruby/truncator.rb +10 -4
- data/lib/airbrake-ruby/version.rb +11 -1
- data/lib/airbrake-ruby.rb +157 -78
- metadata +122 -116
- data/spec/airbrake_spec.rb +0 -324
- data/spec/async_sender_spec.rb +0 -155
- data/spec/backtrace_spec.rb +0 -427
- data/spec/benchmark_spec.rb +0 -33
- data/spec/code_hunk_spec.rb +0 -115
- data/spec/config/validator_spec.rb +0 -184
- data/spec/config_spec.rb +0 -154
- data/spec/deploy_notifier_spec.rb +0 -48
- data/spec/file_cache.rb +0 -36
- data/spec/filter_chain_spec.rb +0 -92
- data/spec/filters/context_filter_spec.rb +0 -23
- data/spec/filters/dependency_filter_spec.rb +0 -12
- data/spec/filters/exception_attributes_filter_spec.rb +0 -50
- data/spec/filters/gem_root_filter_spec.rb +0 -41
- data/spec/filters/git_last_checkout_filter_spec.rb +0 -46
- data/spec/filters/git_repository_filter.rb +0 -61
- data/spec/filters/git_revision_filter_spec.rb +0 -126
- data/spec/filters/keys_blacklist_spec.rb +0 -225
- data/spec/filters/keys_whitelist_spec.rb +0 -194
- data/spec/filters/root_directory_filter_spec.rb +0 -39
- data/spec/filters/sql_filter_spec.rb +0 -219
- data/spec/filters/system_exit_filter_spec.rb +0 -14
- data/spec/filters/thread_filter_spec.rb +0 -277
- data/spec/fixtures/notroot.txt +0 -7
- data/spec/fixtures/project_root/code.rb +0 -221
- data/spec/fixtures/project_root/empty_file.rb +0 -0
- data/spec/fixtures/project_root/long_line.txt +0 -1
- data/spec/fixtures/project_root/short_file.rb +0 -3
- data/spec/fixtures/project_root/vendor/bundle/ignored_file.rb +0 -5
- data/spec/helpers.rb +0 -9
- data/spec/ignorable_spec.rb +0 -14
- data/spec/inspectable_spec.rb +0 -45
- data/spec/monotonic_time_spec.rb +0 -12
- data/spec/nested_exception_spec.rb +0 -73
- data/spec/notice_notifier_spec/options_spec.rb +0 -259
- data/spec/notice_notifier_spec.rb +0 -356
- data/spec/notice_spec.rb +0 -296
- data/spec/performance_breakdown_spec.rb +0 -12
- data/spec/performance_notifier_spec.rb +0 -435
- data/spec/promise_spec.rb +0 -197
- data/spec/query_spec.rb +0 -11
- data/spec/request_spec.rb +0 -11
- data/spec/response_spec.rb +0 -88
- data/spec/spec_helper.rb +0 -100
- data/spec/stashable_spec.rb +0 -23
- data/spec/stat_spec.rb +0 -47
- data/spec/sync_sender_spec.rb +0 -133
- data/spec/tdigest_spec.rb +0 -230
- data/spec/time_truncate_spec.rb +0 -13
- data/spec/timed_trace_spec.rb +0 -125
- data/spec/truncator_spec.rb +0 -238
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
|
-
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 0c1f0e8b950ee1f9b9e6270071d3513508fd2c342673d206a50951ecca0281e0
|
|
4
|
+
data.tar.gz: 31fb69c96a4aede059904726542f925df9066365f864765254896d89c67a7e30
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: e6f2d8369dbc2b58b257ab7e06e2abf1f893b90570eb1ec2012e2ecd35f98f6b03b44130605a689dc57f3367400b220821a511428bdd3dfa4841c2955a0ff9af
|
|
7
|
+
data.tar.gz: 217107fd6982ccea8b0f4158546688d78d6d2415b627f908016de3f036c4cbc1ccae6c2148b670c4eee5fc93a45f8fdf62d37431a492cc0f20d3ca662a8ae5de
|
|
@@ -1,142 +1,57 @@
|
|
|
1
1
|
module Airbrake
|
|
2
|
-
# Responsible for sending notices to Airbrake asynchronously.
|
|
3
|
-
# supports an unlimited number of worker threads and an unlimited queue size
|
|
4
|
-
# (both values are configurable).
|
|
2
|
+
# Responsible for sending notices to Airbrake asynchronously.
|
|
5
3
|
#
|
|
6
4
|
# @see SyncSender
|
|
7
5
|
# @api private
|
|
8
6
|
# @since v1.0.0
|
|
9
7
|
class AsyncSender
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
# @return [String]
|
|
13
|
-
WILL_NOT_DELIVER_MSG =
|
|
14
|
-
"%<log_label>s AsyncSender has reached its capacity of %<capacity>s " \
|
|
15
|
-
"and the following notice will not be delivered " \
|
|
16
|
-
"Error: %<type>s - %<message>s\nBacktrace: %<backtrace>s\n".freeze
|
|
17
|
-
|
|
18
|
-
# @return [ThreadGroup] the list of workers
|
|
19
|
-
# @note This is exposed for eaiser unit testing
|
|
20
|
-
# @since v4.0.0
|
|
21
|
-
attr_reader :workers
|
|
22
|
-
|
|
23
|
-
# @return [Array<[Airbrake::Notice,Airbrake::Promise]>] the list of unsent
|
|
24
|
-
# payload
|
|
25
|
-
# @note This is exposed for eaiser unit testing
|
|
26
|
-
# @since v4.0.0
|
|
27
|
-
attr_reader :unsent
|
|
28
|
-
|
|
29
|
-
def initialize
|
|
8
|
+
def initialize(method = :post, name = 'async-sender')
|
|
30
9
|
@config = Airbrake::Config.instance
|
|
31
|
-
@
|
|
32
|
-
@
|
|
33
|
-
@closed = false
|
|
34
|
-
@workers = ThreadGroup.new
|
|
35
|
-
@mutex = Mutex.new
|
|
36
|
-
@pid = nil
|
|
10
|
+
@sync_sender = SyncSender.new(method)
|
|
11
|
+
@name = name
|
|
37
12
|
end
|
|
38
13
|
|
|
39
14
|
# Asynchronously sends a notice to Airbrake.
|
|
40
15
|
#
|
|
41
|
-
# @param [Airbrake::Notice]
|
|
42
|
-
#
|
|
16
|
+
# @param [Airbrake::Notice] data Whatever needs to be sent
|
|
17
|
+
# @param [Airbrake::Promise] promise
|
|
18
|
+
# @param [URI] endpoint Where to send +data+
|
|
43
19
|
# @return [Airbrake::Promise]
|
|
44
|
-
def send(
|
|
45
|
-
|
|
20
|
+
def send(data, promise, endpoint = @config.error_endpoint)
|
|
21
|
+
unless thread_pool << [data, promise, endpoint]
|
|
22
|
+
return promise.reject(
|
|
23
|
+
"AsyncSender has reached its capacity of #{@config.queue_size}",
|
|
24
|
+
)
|
|
25
|
+
end
|
|
46
26
|
|
|
47
|
-
@unsent << [notice, promise]
|
|
48
27
|
promise
|
|
49
28
|
end
|
|
50
29
|
|
|
51
|
-
# Closes the instance making it a no-op (it shut downs all worker
|
|
52
|
-
# threads). Before closing, waits on all unsent notices to be sent.
|
|
53
|
-
#
|
|
54
30
|
# @return [void]
|
|
55
|
-
# @raise [Airbrake::Error] when invoked more than one time
|
|
56
31
|
def close
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
unless @unsent.empty?
|
|
61
|
-
msg = "#{LOG_LABEL} waiting to send #{@unsent.size} unsent notice(s)..."
|
|
62
|
-
logger.debug(msg + ' (Ctrl-C to abort)')
|
|
63
|
-
end
|
|
64
|
-
|
|
65
|
-
@config.workers.times { @unsent << [:stop, Airbrake::Promise.new] }
|
|
66
|
-
@closed = true
|
|
67
|
-
@workers.list.dup
|
|
68
|
-
end
|
|
69
|
-
|
|
70
|
-
threads.each(&:join)
|
|
71
|
-
logger.debug("#{LOG_LABEL} closed")
|
|
32
|
+
@sync_sender.close
|
|
33
|
+
thread_pool.close
|
|
72
34
|
end
|
|
73
35
|
|
|
74
|
-
# Checks whether the sender is closed and thus usable.
|
|
75
36
|
# @return [Boolean]
|
|
76
37
|
def closed?
|
|
77
|
-
|
|
38
|
+
thread_pool.closed?
|
|
78
39
|
end
|
|
79
40
|
|
|
80
|
-
#
|
|
81
|
-
# workers only in two cases: when it was closed or when all workers
|
|
82
|
-
# crashed. An *active* sender doesn't have any workers only when something
|
|
83
|
-
# went wrong.
|
|
84
|
-
#
|
|
85
|
-
# Workers are expected to crash when you +fork+ the process the workers are
|
|
86
|
-
# living in. In this case we detect a +fork+ and try to revive them here.
|
|
87
|
-
#
|
|
88
|
-
# Another possible scenario that crashes workers is when you close the
|
|
89
|
-
# instance on +at_exit+, but some other +at_exit+ hook prevents the process
|
|
90
|
-
# from exiting.
|
|
91
|
-
#
|
|
92
|
-
# @return [Boolean] true if an instance wasn't closed, but has no workers
|
|
93
|
-
# @see https://goo.gl/oydz8h Example of at_exit that prevents exit
|
|
41
|
+
# @return [Boolean]
|
|
94
42
|
def has_workers?
|
|
95
|
-
|
|
96
|
-
return false if @closed
|
|
97
|
-
|
|
98
|
-
if @pid != Process.pid && @workers.list.empty?
|
|
99
|
-
@pid = Process.pid
|
|
100
|
-
spawn_workers
|
|
101
|
-
end
|
|
102
|
-
|
|
103
|
-
!@closed && @workers.list.any?
|
|
104
|
-
end
|
|
43
|
+
thread_pool.has_workers?
|
|
105
44
|
end
|
|
106
45
|
|
|
107
46
|
private
|
|
108
47
|
|
|
109
|
-
def
|
|
110
|
-
@
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
def spawn_worker
|
|
116
|
-
Thread.new do
|
|
117
|
-
while (message = @unsent.pop)
|
|
118
|
-
break if message.first == :stop
|
|
119
|
-
@sender.send(*message)
|
|
120
|
-
end
|
|
121
|
-
end
|
|
122
|
-
end
|
|
123
|
-
|
|
124
|
-
def will_not_deliver(notice, promise)
|
|
125
|
-
error = notice[:errors].first
|
|
126
|
-
|
|
127
|
-
logger.error(
|
|
128
|
-
format(
|
|
129
|
-
WILL_NOT_DELIVER_MSG,
|
|
130
|
-
log_label: LOG_LABEL,
|
|
131
|
-
capacity: @unsent.max,
|
|
132
|
-
type: error[:type],
|
|
133
|
-
message: error[:message],
|
|
134
|
-
backtrace: error[:backtrace].map do |line|
|
|
135
|
-
"#{line[:file]}:#{line[:line]} in `#{line[:function]}'"
|
|
136
|
-
end.join("\n")
|
|
137
|
-
)
|
|
48
|
+
def thread_pool
|
|
49
|
+
@thread_pool ||= ThreadPool.new(
|
|
50
|
+
name: @name,
|
|
51
|
+
worker_size: @config.workers,
|
|
52
|
+
queue_size: @config.queue_size,
|
|
53
|
+
block: proc { |args| @sync_sender.send(*args) },
|
|
138
54
|
)
|
|
139
|
-
promise.reject("AsyncSender has reached its capacity of #{@unsent.max}")
|
|
140
55
|
end
|
|
141
56
|
end
|
|
142
57
|
end
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
module Airbrake
|
|
2
|
+
# Backlog accepts notices and APM events and synchronously sends them in the
|
|
3
|
+
# background at regular intervals. The backlog is a queue of data that failed
|
|
4
|
+
# to be sent due to some error. In a nutshell, it's a retry mechanism.
|
|
5
|
+
#
|
|
6
|
+
# @api private
|
|
7
|
+
# @since v6.2.0
|
|
8
|
+
class Backlog
|
|
9
|
+
include Loggable
|
|
10
|
+
|
|
11
|
+
# @return [Integer] how many records to keep in the backlog
|
|
12
|
+
BACKLOG_SIZE = 100
|
|
13
|
+
|
|
14
|
+
# @return [Integer] flush period in seconds
|
|
15
|
+
TWO_MINUTES = 60 * 2
|
|
16
|
+
|
|
17
|
+
def initialize(sync_sender, flush_period = TWO_MINUTES)
|
|
18
|
+
@sync_sender = sync_sender
|
|
19
|
+
@flush_period = flush_period
|
|
20
|
+
@queue = SizedQueue.new(BACKLOG_SIZE).extend(MonitorMixin)
|
|
21
|
+
@has_backlog_data = @queue.new_cond
|
|
22
|
+
@schedule_flush = nil
|
|
23
|
+
|
|
24
|
+
@seen = Set.new
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# Appends data to the backlog. Once appended, the flush schedule will
|
|
28
|
+
# start. Chainable.
|
|
29
|
+
#
|
|
30
|
+
# @example
|
|
31
|
+
# backlog << [{ 'data' => 1 }, 'https://airbrake.io/api']
|
|
32
|
+
#
|
|
33
|
+
# @param [Array<#to_json, String>] data An array of two elements, where the
|
|
34
|
+
# first element is the data we are sending and the second element is the
|
|
35
|
+
# URL that we are sending to
|
|
36
|
+
# @return [self]
|
|
37
|
+
def <<(data)
|
|
38
|
+
@queue.synchronize do
|
|
39
|
+
return self if @seen.include?(data)
|
|
40
|
+
|
|
41
|
+
@seen << data
|
|
42
|
+
|
|
43
|
+
begin
|
|
44
|
+
@queue.push(data, true)
|
|
45
|
+
rescue ThreadError
|
|
46
|
+
logger.error("#{LOG_LABEL} Airbrake::Backlog full")
|
|
47
|
+
return self
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
@has_backlog_data.signal
|
|
51
|
+
schedule_flush
|
|
52
|
+
|
|
53
|
+
self
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# Closes all the resources that this sender has allocated.
|
|
58
|
+
#
|
|
59
|
+
# @return [void]
|
|
60
|
+
# @since v6.2.0
|
|
61
|
+
def close
|
|
62
|
+
@queue.synchronize do
|
|
63
|
+
if @schedule_flush
|
|
64
|
+
@schedule_flush.kill
|
|
65
|
+
logger.debug("#{LOG_LABEL} Airbrake::Backlog closed")
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
private
|
|
71
|
+
|
|
72
|
+
def schedule_flush
|
|
73
|
+
@schedule_flush ||= Thread.new do
|
|
74
|
+
loop do
|
|
75
|
+
@queue.synchronize do
|
|
76
|
+
wait
|
|
77
|
+
next if @queue.empty?
|
|
78
|
+
|
|
79
|
+
flush
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def wait
|
|
86
|
+
@has_backlog_data.wait(@flush_period) while time_elapsed < @flush_period
|
|
87
|
+
@last_flush = nil
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def time_elapsed
|
|
91
|
+
MonotonicTime.time_in_s - last_flush
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def last_flush
|
|
95
|
+
@last_flush ||= MonotonicTime.time_in_s
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def flush
|
|
99
|
+
unless @queue.empty?
|
|
100
|
+
logger.debug(
|
|
101
|
+
"#{LOG_LABEL} Airbrake::Backlog flushing #{@queue.size} messages",
|
|
102
|
+
)
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
failed = 0
|
|
106
|
+
|
|
107
|
+
until @queue.empty?
|
|
108
|
+
data, endpoint = @queue.pop
|
|
109
|
+
promise = Airbrake::Promise.new
|
|
110
|
+
@sync_sender.send(data, promise, endpoint)
|
|
111
|
+
failed += 1 if promise.rejected?
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
if failed > 0
|
|
115
|
+
logger.debug(
|
|
116
|
+
"#{LOG_LABEL} Airbrake::Backlog #{failed} messages were not flushed",
|
|
117
|
+
)
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
@seen.clear
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
end
|
|
@@ -22,7 +22,7 @@ module Airbrake
|
|
|
22
22
|
(?<line>\d+) # Matches '43'
|
|
23
23
|
:in\s
|
|
24
24
|
`(?<function>.*)' # Matches "`block (3 levels) in <top (required)>'"
|
|
25
|
-
\z}x
|
|
25
|
+
\z}x.freeze
|
|
26
26
|
|
|
27
27
|
# @return [Regexp] the pattern that matches JRuby Java stack frames, such
|
|
28
28
|
# as org.jruby.ast.NewlineNode.interpret(NewlineNode.java:105)
|
|
@@ -39,7 +39,7 @@ module Airbrake
|
|
|
39
39
|
:?
|
|
40
40
|
(?<line>\d+)? # Matches '105'
|
|
41
41
|
\)
|
|
42
|
-
\z}x
|
|
42
|
+
\z}x.freeze
|
|
43
43
|
|
|
44
44
|
# @return [Regexp] the pattern that tries to assume what a generic stack
|
|
45
45
|
# frame might look like, when exception's backtrace is set manually.
|
|
@@ -53,7 +53,7 @@ module Airbrake
|
|
|
53
53
|
|
|
|
54
54
|
:in\s(?<function>.+) # Matches ":in func"
|
|
55
55
|
)? # ... or nothing
|
|
56
|
-
\z}x
|
|
56
|
+
\z}x.freeze
|
|
57
57
|
|
|
58
58
|
# @return [Regexp] the pattern that matches exceptions from PL/SQL such as
|
|
59
59
|
# ORA-06512: at "STORE.LI_LICENSES_PACK", line 1945
|
|
@@ -67,7 +67,7 @@ module Airbrake
|
|
|
67
67
|
|
|
|
68
68
|
#{GENERIC}
|
|
69
69
|
)
|
|
70
|
-
\z/x
|
|
70
|
+
\z/x.freeze
|
|
71
71
|
|
|
72
72
|
# @return [Regexp] the pattern that matches CoffeeScript backtraces
|
|
73
73
|
# usually coming from Rails & ExecJS
|
|
@@ -82,7 +82,7 @@ module Airbrake
|
|
|
82
82
|
# Matches the Ruby part of the backtrace
|
|
83
83
|
#{RUBY}
|
|
84
84
|
)
|
|
85
|
-
\z/x
|
|
85
|
+
\z/x.freeze
|
|
86
86
|
end
|
|
87
87
|
|
|
88
88
|
# @return [Integer] how many first frames should include code hunks
|
|
@@ -95,6 +95,7 @@ module Airbrake
|
|
|
95
95
|
# @return [Array<Hash{Symbol=>String,Integer}>] the parsed backtrace
|
|
96
96
|
def self.parse(exception)
|
|
97
97
|
return [] if exception.backtrace.nil? || exception.backtrace.none?
|
|
98
|
+
|
|
98
99
|
parse_backtrace(exception)
|
|
99
100
|
end
|
|
100
101
|
|
|
@@ -147,13 +148,13 @@ module Airbrake
|
|
|
147
148
|
return {
|
|
148
149
|
file: match[:file],
|
|
149
150
|
line: (Integer(match[:line]) if match[:line]),
|
|
150
|
-
function: match[:function]
|
|
151
|
+
function: match[:function],
|
|
151
152
|
}
|
|
152
153
|
end
|
|
153
154
|
|
|
154
155
|
logger.error(
|
|
155
156
|
"can't parse '#{stackframe}' (please file an issue so we can fix " \
|
|
156
|
-
"it: https://github.com/airbrake/airbrake-ruby/issues/new)"
|
|
157
|
+
"it: https://github.com/airbrake/airbrake-ruby/issues/new)",
|
|
157
158
|
)
|
|
158
159
|
{ file: nil, line: nil, function: stackframe }
|
|
159
160
|
end
|
|
@@ -30,7 +30,7 @@ module Airbrake
|
|
|
30
30
|
Airbrake::FileCache[file] ||= File.foreach(file)
|
|
31
31
|
rescue StandardError => ex
|
|
32
32
|
logger.error(
|
|
33
|
-
"#{self.class.name}: can't read code hunk for #{file}: #{ex}"
|
|
33
|
+
"#{self.class.name}: can't read code hunk for #{file}: #{ex}",
|
|
34
34
|
)
|
|
35
35
|
nil
|
|
36
36
|
end
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
module Airbrake
|
|
2
|
+
class Config
|
|
3
|
+
# Processor is a helper class, which is responsible for setting default
|
|
4
|
+
# config values, default notifier filters and remote configuration changes.
|
|
5
|
+
#
|
|
6
|
+
# @since v5.0.0
|
|
7
|
+
# @api private
|
|
8
|
+
class Processor
|
|
9
|
+
# @param [Airbrake::Config] config
|
|
10
|
+
# @return [Airbrake::Config::Processor]
|
|
11
|
+
def self.process(config)
|
|
12
|
+
new(config).process
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# @param [Airbrake::Config] config
|
|
16
|
+
def initialize(config)
|
|
17
|
+
@config = config
|
|
18
|
+
@blocklist_keys = @config.blocklist_keys
|
|
19
|
+
@allowlist_keys = @config.allowlist_keys
|
|
20
|
+
@project_id = @config.project_id
|
|
21
|
+
@poll_callback = Airbrake::RemoteSettings::Callback.new(config)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# @param [Airbrake::NoticeNotifier] notifier
|
|
25
|
+
# @return [void]
|
|
26
|
+
def process_blocklist(notifier)
|
|
27
|
+
return if @blocklist_keys.none?
|
|
28
|
+
|
|
29
|
+
blocklist = Airbrake::Filters::KeysBlocklist.new(@blocklist_keys)
|
|
30
|
+
notifier.add_filter(blocklist)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# @param [Airbrake::NoticeNotifier] notifier
|
|
34
|
+
# @return [void]
|
|
35
|
+
def process_allowlist(notifier)
|
|
36
|
+
return if @allowlist_keys.none?
|
|
37
|
+
|
|
38
|
+
allowlist = Airbrake::Filters::KeysAllowlist.new(@allowlist_keys)
|
|
39
|
+
notifier.add_filter(allowlist)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# @return [Airbrake::RemoteSettings]
|
|
43
|
+
def process_remote_configuration
|
|
44
|
+
return unless @config.remote_config
|
|
45
|
+
return unless @project_id
|
|
46
|
+
|
|
47
|
+
# Never poll remote configuration in the test environment.
|
|
48
|
+
return if @config.environment == 'test'
|
|
49
|
+
|
|
50
|
+
# If the current environment is ignored, don't try to poll remote
|
|
51
|
+
# configuration.
|
|
52
|
+
return if @config.ignore_environments.include?(@config.environment)
|
|
53
|
+
|
|
54
|
+
RemoteSettings.poll(@project_id, @config.remote_config_host) do |data|
|
|
55
|
+
@poll_callback.call(data)
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# @param [Airbrake::NoticeNotifier] notifier
|
|
60
|
+
# @return [void]
|
|
61
|
+
def add_filters(notifier)
|
|
62
|
+
return unless @config.root_directory
|
|
63
|
+
|
|
64
|
+
[
|
|
65
|
+
Airbrake::Filters::RootDirectoryFilter,
|
|
66
|
+
Airbrake::Filters::GitRevisionFilter,
|
|
67
|
+
Airbrake::Filters::GitRepositoryFilter,
|
|
68
|
+
Airbrake::Filters::GitLastCheckoutFilter,
|
|
69
|
+
].each do |filter|
|
|
70
|
+
next if notifier.has_filter?(filter)
|
|
71
|
+
|
|
72
|
+
notifier.add_filter(filter.new(@config.root_directory))
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
@@ -29,7 +29,7 @@ module Airbrake
|
|
|
29
29
|
return promise.reject(
|
|
30
30
|
"the 'environment' option must be configured " \
|
|
31
31
|
"with a Symbol (or String), but '#{config.environment.class}' was " \
|
|
32
|
-
"provided: #{config.environment}"
|
|
32
|
+
"provided: #{config.environment}",
|
|
33
33
|
)
|
|
34
34
|
end
|
|
35
35
|
|
|
@@ -44,9 +44,13 @@ module Airbrake
|
|
|
44
44
|
def check_notify_ability(config)
|
|
45
45
|
promise = Airbrake::Promise.new
|
|
46
46
|
|
|
47
|
+
unless config.error_notifications
|
|
48
|
+
return promise.reject('error notifications are disabled')
|
|
49
|
+
end
|
|
50
|
+
|
|
47
51
|
if ignored_environment?(config)
|
|
48
52
|
return promise.reject(
|
|
49
|
-
"current environment '#{config.environment}' is ignored"
|
|
53
|
+
"current environment '#{config.environment}' is ignored",
|
|
50
54
|
)
|
|
51
55
|
end
|
|
52
56
|
|
|
@@ -57,12 +61,14 @@ module Airbrake
|
|
|
57
61
|
|
|
58
62
|
def valid_project_id?(config)
|
|
59
63
|
return true if config.project_id.to_i > 0
|
|
64
|
+
|
|
60
65
|
false
|
|
61
66
|
end
|
|
62
67
|
|
|
63
68
|
def valid_project_key?(config)
|
|
64
69
|
return false unless config.project_key.is_a?(String)
|
|
65
70
|
return false if config.project_key.empty?
|
|
71
|
+
|
|
66
72
|
true
|
|
67
73
|
end
|
|
68
74
|
|
|
@@ -74,7 +80,7 @@ module Airbrake
|
|
|
74
80
|
if config.ignore_environments.any? && config.environment.nil?
|
|
75
81
|
config.logger.warn(
|
|
76
82
|
"#{LOG_LABEL} the 'environment' option is not set, " \
|
|
77
|
-
"'ignore_environments' has no effect"
|
|
83
|
+
"'ignore_environments' has no effect",
|
|
78
84
|
)
|
|
79
85
|
end
|
|
80
86
|
|
data/lib/airbrake-ruby/config.rb
CHANGED
|
@@ -44,7 +44,14 @@ module Airbrake
|
|
|
44
44
|
# @return [String] the host, which provides the API endpoint to which
|
|
45
45
|
# exceptions should be sent
|
|
46
46
|
# @api public
|
|
47
|
-
|
|
47
|
+
# @since v5.0.0
|
|
48
|
+
attr_accessor :error_host
|
|
49
|
+
|
|
50
|
+
# @return [String] the host, which provides the API endpoint to which
|
|
51
|
+
# APM data should be sent
|
|
52
|
+
# @api public
|
|
53
|
+
# @since v5.0.0
|
|
54
|
+
attr_accessor :apm_host
|
|
48
55
|
|
|
49
56
|
# @return [String, Pathname] the working directory of your project
|
|
50
57
|
# @api public
|
|
@@ -68,14 +75,14 @@ module Airbrake
|
|
|
68
75
|
# @return [Array<String, Symbol, Regexp>] the keys, which should be
|
|
69
76
|
# filtered
|
|
70
77
|
# @api public
|
|
71
|
-
# @since
|
|
72
|
-
attr_accessor :
|
|
78
|
+
# @since v4.15.0
|
|
79
|
+
attr_accessor :allowlist_keys
|
|
73
80
|
|
|
74
|
-
# @return [Array<String, Symbol, Regexp>] the keys, which
|
|
81
|
+
# @return [Array<String, Symbol, Regexp>] the keys, which should be
|
|
75
82
|
# filtered
|
|
76
83
|
# @api public
|
|
77
|
-
# @since
|
|
78
|
-
attr_accessor :
|
|
84
|
+
# @since v4.15.0
|
|
85
|
+
attr_accessor :blocklist_keys
|
|
79
86
|
|
|
80
87
|
# @return [Boolean] true if the library should attach code hunks to each
|
|
81
88
|
# frame in a backtrace, false otherwise
|
|
@@ -101,6 +108,36 @@ module Airbrake
|
|
|
101
108
|
# @since v4.6.0
|
|
102
109
|
attr_accessor :query_stats
|
|
103
110
|
|
|
111
|
+
# @return [Boolean] true if the library should send job/queue/worker stats
|
|
112
|
+
# to Airbrake, false otherwise
|
|
113
|
+
# @api public
|
|
114
|
+
# @since v4.12.0
|
|
115
|
+
attr_accessor :job_stats
|
|
116
|
+
|
|
117
|
+
# @return [Boolean] true if the library should send error reports to
|
|
118
|
+
# Airbrake, false otherwise
|
|
119
|
+
# @api public
|
|
120
|
+
# @since v5.0.0
|
|
121
|
+
attr_accessor :error_notifications
|
|
122
|
+
|
|
123
|
+
# @return [String] the host which should be used for fetching remote
|
|
124
|
+
# configuration options
|
|
125
|
+
# @api public
|
|
126
|
+
# @since v5.0.0
|
|
127
|
+
attr_accessor :remote_config_host
|
|
128
|
+
|
|
129
|
+
# @return [String] true if notifier should periodically fetch remote
|
|
130
|
+
# configuration, false otherwise
|
|
131
|
+
# @api public
|
|
132
|
+
# @since v5.2.0
|
|
133
|
+
attr_accessor :remote_config
|
|
134
|
+
|
|
135
|
+
# @return [Boolean] true if the library should keep a backlog of failed
|
|
136
|
+
# notices or APM events and retry them after an interval, false otherwise
|
|
137
|
+
# @api public
|
|
138
|
+
# @since v6.2.0
|
|
139
|
+
attr_accessor :backlog
|
|
140
|
+
|
|
104
141
|
class << self
|
|
105
142
|
# @return [Config]
|
|
106
143
|
attr_writer :instance
|
|
@@ -113,44 +150,51 @@ module Airbrake
|
|
|
113
150
|
|
|
114
151
|
# @param [Hash{Symbol=>Object}] user_config the hash to be used to build the
|
|
115
152
|
# config
|
|
153
|
+
# rubocop:disable Metrics/AbcSize, Metrics/MethodLength
|
|
116
154
|
def initialize(user_config = {})
|
|
117
155
|
self.proxy = {}
|
|
118
156
|
self.queue_size = 100
|
|
119
157
|
self.workers = 1
|
|
120
158
|
self.code_hunks = true
|
|
121
|
-
self.logger = ::Logger.new(File::NULL)
|
|
159
|
+
self.logger = ::Logger.new(File::NULL).tap { |l| l.level = Logger::WARN }
|
|
122
160
|
self.project_id = user_config[:project_id]
|
|
123
161
|
self.project_key = user_config[:project_key]
|
|
124
|
-
self.
|
|
162
|
+
self.error_host = self.apm_host = 'https://api.airbrake.io'
|
|
163
|
+
self.remote_config_host = 'https://notifier-configs.airbrake.io'
|
|
125
164
|
|
|
126
165
|
self.ignore_environments = []
|
|
127
166
|
|
|
128
167
|
self.timeout = user_config[:timeout]
|
|
129
168
|
|
|
130
|
-
self.
|
|
131
|
-
self.
|
|
169
|
+
self.blocklist_keys = []
|
|
170
|
+
self.allowlist_keys = []
|
|
132
171
|
|
|
133
172
|
self.root_directory = File.realpath(
|
|
134
173
|
(defined?(Bundler) && Bundler.root) ||
|
|
135
|
-
Dir.pwd
|
|
174
|
+
Dir.pwd,
|
|
136
175
|
)
|
|
137
176
|
|
|
138
177
|
self.versions = {}
|
|
139
178
|
self.performance_stats = true
|
|
140
179
|
self.performance_stats_flush_period = 15
|
|
141
|
-
self.query_stats =
|
|
180
|
+
self.query_stats = true
|
|
181
|
+
self.job_stats = true
|
|
182
|
+
self.error_notifications = true
|
|
183
|
+
self.remote_config = true
|
|
184
|
+
self.backlog = true
|
|
142
185
|
|
|
143
186
|
merge(user_config)
|
|
144
187
|
end
|
|
188
|
+
# rubocop:enable Metrics/AbcSize, Metrics/MethodLength
|
|
145
189
|
|
|
146
|
-
# The full URL to the Airbrake Notice API. Based on the +:
|
|
190
|
+
# The full URL to the Airbrake Notice API. Based on the +:error_host+ option.
|
|
147
191
|
# @return [URI] the endpoint address
|
|
148
|
-
def
|
|
149
|
-
@
|
|
192
|
+
def error_endpoint
|
|
193
|
+
@error_endpoint ||=
|
|
150
194
|
begin
|
|
151
|
-
self.
|
|
195
|
+
self.error_host = ('https://' << error_host) if error_host !~ %r{\Ahttps?://}
|
|
152
196
|
api = "api/v3/projects/#{project_id}/notices"
|
|
153
|
-
URI.join(
|
|
197
|
+
URI.join(error_host, api)
|
|
154
198
|
end
|
|
155
199
|
end
|
|
156
200
|
|
|
@@ -206,18 +250,33 @@ module Airbrake
|
|
|
206
250
|
|
|
207
251
|
# @return [Promise] resolved promise if neither of the performance options
|
|
208
252
|
# reject it, false otherwise
|
|
209
|
-
def check_performance_options(
|
|
253
|
+
def check_performance_options(metric)
|
|
210
254
|
promise = Airbrake::Promise.new
|
|
211
255
|
|
|
212
256
|
if !performance_stats
|
|
213
257
|
promise.reject("The Performance Stats feature is disabled")
|
|
214
|
-
elsif
|
|
258
|
+
elsif metric.is_a?(Airbrake::Query) && !query_stats
|
|
215
259
|
promise.reject("The Query Stats feature is disabled")
|
|
260
|
+
elsif metric.is_a?(Airbrake::Queue) && !job_stats
|
|
261
|
+
promise.reject("The Job Stats feature is disabled")
|
|
216
262
|
else
|
|
217
263
|
promise
|
|
218
264
|
end
|
|
219
265
|
end
|
|
220
266
|
|
|
267
|
+
HOST_DEPRECATION_MSG = "**Airbrake: the 'host' option is deprecated. Use " \
|
|
268
|
+
"'error_host' instead".freeze
|
|
269
|
+
|
|
270
|
+
def host
|
|
271
|
+
logger.warn(HOST_DEPRECATION_MSG)
|
|
272
|
+
@error_host
|
|
273
|
+
end
|
|
274
|
+
|
|
275
|
+
def host=(value)
|
|
276
|
+
logger.warn(HOST_DEPRECATION_MSG)
|
|
277
|
+
@error_host = value
|
|
278
|
+
end
|
|
279
|
+
|
|
221
280
|
private
|
|
222
281
|
|
|
223
282
|
def set_option(option, value)
|