airbrake-ruby 3.2.6-java → 4.0.0-java
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/airbrake-ruby.rb +31 -138
- data/lib/airbrake-ruby/async_sender.rb +20 -8
- data/lib/airbrake-ruby/backtrace.rb +15 -13
- data/lib/airbrake-ruby/code_hunk.rb +2 -4
- data/lib/airbrake-ruby/config.rb +8 -38
- data/lib/airbrake-ruby/deploy_notifier.rb +4 -17
- data/lib/airbrake-ruby/filters/exception_attributes_filter.rb +5 -4
- data/lib/airbrake-ruby/filters/git_last_checkout_filter.rb +6 -4
- data/lib/airbrake-ruby/filters/keys_blacklist.rb +0 -1
- data/lib/airbrake-ruby/filters/keys_filter.rb +4 -4
- data/lib/airbrake-ruby/filters/keys_whitelist.rb +0 -1
- data/lib/airbrake-ruby/loggable.rb +31 -0
- data/lib/airbrake-ruby/nested_exception.rb +2 -3
- data/lib/airbrake-ruby/notice.rb +6 -6
- data/lib/airbrake-ruby/notice_notifier.rb +11 -47
- data/lib/airbrake-ruby/performance_notifier.rb +6 -18
- data/lib/airbrake-ruby/response.rb +5 -2
- data/lib/airbrake-ruby/sync_sender.rb +8 -6
- data/lib/airbrake-ruby/version.rb +1 -1
- data/spec/airbrake_spec.rb +1 -143
- data/spec/async_sender_spec.rb +83 -90
- data/spec/backtrace_spec.rb +36 -47
- data/spec/code_hunk_spec.rb +12 -15
- data/spec/config_spec.rb +79 -96
- data/spec/deploy_notifier_spec.rb +3 -7
- data/spec/filter_chain_spec.rb +1 -3
- data/spec/filters/context_filter_spec.rb +1 -3
- data/spec/filters/dependency_filter_spec.rb +1 -3
- data/spec/filters/exception_attributes_filter_spec.rb +1 -14
- data/spec/filters/gem_root_filter_spec.rb +1 -4
- data/spec/filters/git_last_checkout_filter_spec.rb +3 -5
- data/spec/filters/git_revision_filter_spec.rb +1 -3
- data/spec/filters/keys_blacklist_spec.rb +14 -25
- data/spec/filters/keys_whitelist_spec.rb +14 -25
- data/spec/filters/root_directory_filter_spec.rb +1 -4
- data/spec/filters/system_exit_filter_spec.rb +2 -2
- data/spec/filters/thread_filter_spec.rb +1 -3
- data/spec/nested_exception_spec.rb +3 -5
- data/spec/notice_notifier_spec.rb +23 -20
- data/spec/notice_notifier_spec/options_spec.rb +20 -25
- data/spec/notice_spec.rb +13 -12
- data/spec/performance_notifier_spec.rb +19 -31
- data/spec/response_spec.rb +23 -17
- data/spec/sync_sender_spec.rb +26 -33
- metadata +2 -1
@@ -6,24 +6,12 @@ module Airbrake
|
|
6
6
|
# @since v3.2.0
|
7
7
|
class PerformanceNotifier
|
8
8
|
include Inspectable
|
9
|
+
include Loggable
|
9
10
|
|
10
|
-
|
11
|
-
|
12
|
-
@
|
13
|
-
|
14
|
-
config
|
15
|
-
else
|
16
|
-
loc = caller_locations(1..1).first
|
17
|
-
signature = "#{self.class.name}##{__method__}"
|
18
|
-
warn(
|
19
|
-
"#{loc.path}:#{loc.lineno}: warning: passing a Hash to #{signature} " \
|
20
|
-
'is deprecated. Pass `Airbrake::Config` instead'
|
21
|
-
)
|
22
|
-
Config.new(config)
|
23
|
-
end
|
24
|
-
|
25
|
-
@flush_period = @config.performance_stats_flush_period
|
26
|
-
@sender = SyncSender.new(@config, :put)
|
11
|
+
def initialize
|
12
|
+
@config = Airbrake::Config.instance
|
13
|
+
@flush_period = Airbrake::Config.instance.performance_stats_flush_period
|
14
|
+
@sender = SyncSender.new(:put)
|
27
15
|
@payload = {}
|
28
16
|
@schedule_flush = nil
|
29
17
|
@mutex = Mutex.new
|
@@ -92,7 +80,7 @@ module Airbrake
|
|
92
80
|
signature = "#{self.class.name}##{__method__}"
|
93
81
|
raise "#{signature}: payload (#{payload}) cannot be empty. Race?" if payload.none?
|
94
82
|
|
95
|
-
|
83
|
+
logger.debug("#{LOG_LABEL} #{signature}: #{payload}")
|
96
84
|
|
97
85
|
payload.group_by { |k, _v| k.name }.each do |resource_name, data|
|
98
86
|
data = { resource_name => data.map { |k, v| k.to_h.merge!(v.to_h) } }
|
@@ -11,13 +11,16 @@ module Airbrake
|
|
11
11
|
# @return [Integer] HTTP code returned when an IP sends over 10k/min notices
|
12
12
|
TOO_MANY_REQUESTS = 429
|
13
13
|
|
14
|
+
class << self
|
15
|
+
include Loggable
|
16
|
+
end
|
17
|
+
|
14
18
|
# Parses HTTP responses from the Airbrake API.
|
15
19
|
#
|
16
20
|
# @param [Net::HTTPResponse] response
|
17
|
-
# @param [Logger] logger
|
18
21
|
# @return [Hash{String=>String}] parsed response
|
19
22
|
# rubocop:disable Metrics/MethodLength, Metrics/AbcSize
|
20
|
-
def self.parse(response
|
23
|
+
def self.parse(response)
|
21
24
|
code = response.code.to_i
|
22
25
|
body = response.body
|
23
26
|
|
@@ -9,9 +9,11 @@ module Airbrake
|
|
9
9
|
# @return [String] body for HTTP requests
|
10
10
|
CONTENT_TYPE = 'application/json'.freeze
|
11
11
|
|
12
|
-
|
13
|
-
|
14
|
-
|
12
|
+
include Loggable
|
13
|
+
|
14
|
+
# @param [Symbol] method HTTP method to use to send payload
|
15
|
+
def initialize(method = :post)
|
16
|
+
@config = Airbrake::Config.instance
|
15
17
|
@method = method
|
16
18
|
@rate_limit_reset = Time.now
|
17
19
|
end
|
@@ -35,11 +37,11 @@ module Airbrake
|
|
35
37
|
response = https.request(req)
|
36
38
|
rescue StandardError => ex
|
37
39
|
reason = "#{LOG_LABEL} HTTP error: #{ex}"
|
38
|
-
|
40
|
+
logger.error(reason)
|
39
41
|
return promise.reject(reason)
|
40
42
|
end
|
41
43
|
|
42
|
-
parsed_resp = Response.parse(response
|
44
|
+
parsed_resp = Response.parse(response)
|
43
45
|
if parsed_resp.key?('rate_limit_reset')
|
44
46
|
@rate_limit_reset = parsed_resp['rate_limit_reset']
|
45
47
|
end
|
@@ -101,7 +103,7 @@ module Airbrake
|
|
101
103
|
|
102
104
|
if missing
|
103
105
|
reason = "#{LOG_LABEL} data was not sent because of missing body"
|
104
|
-
|
106
|
+
logger.error(reason)
|
105
107
|
promise.reject(reason)
|
106
108
|
end
|
107
109
|
|
data/spec/airbrake_spec.rb
CHANGED
@@ -1,31 +1,7 @@
|
|
1
1
|
RSpec.describe Airbrake do
|
2
|
-
|
3
|
-
it "returns a NilNotifier" do
|
4
|
-
expect(described_class[:test]).to be_an(Airbrake::NilNoticeNotifier)
|
5
|
-
end
|
6
|
-
end
|
7
|
-
|
8
|
-
describe ".notifiers" do
|
9
|
-
it "returns a Hash of notifiers" do
|
10
|
-
expect(described_class.notifiers).to eq(
|
11
|
-
notice: {}, performance: {}, deploy: {}
|
12
|
-
)
|
13
|
-
end
|
14
|
-
end
|
15
|
-
|
16
|
-
let(:default_notifier) do
|
17
|
-
described_class[:default]
|
18
|
-
end
|
2
|
+
before { Airbrake::Config.instance = Airbrake::Config.new }
|
19
3
|
|
20
4
|
describe ".configure" do
|
21
|
-
let(:config_params) { { project_id: 1, project_key: 'abc' } }
|
22
|
-
|
23
|
-
after do
|
24
|
-
described_class.instance_variable_get(:@notice_notifiers).clear
|
25
|
-
described_class.instance_variable_get(:@performance_notifiers).clear
|
26
|
-
described_class.instance_variable_get(:@deploy_notifiers).clear
|
27
|
-
end
|
28
|
-
|
29
5
|
it "yields the config" do
|
30
6
|
expect do |b|
|
31
7
|
begin
|
@@ -36,31 +12,6 @@ RSpec.describe Airbrake do
|
|
36
12
|
end.to yield_with_args(Airbrake::Config)
|
37
13
|
end
|
38
14
|
|
39
|
-
context "when invoked with a notice notifier name" do
|
40
|
-
it "sets notice notifier name to the provided name" do
|
41
|
-
described_class.configure(:test) { |c| c.merge(config_params) }
|
42
|
-
expect(described_class[:test]).to be_an(Airbrake::NoticeNotifier)
|
43
|
-
end
|
44
|
-
end
|
45
|
-
|
46
|
-
context "when invoked without a notifier name" do
|
47
|
-
it "defaults to the :default notifier name" do
|
48
|
-
described_class.configure { |c| c.merge(config_params) }
|
49
|
-
expect(described_class[:default]).to be_an(Airbrake::NoticeNotifier)
|
50
|
-
end
|
51
|
-
end
|
52
|
-
|
53
|
-
context "when invoked twice with the same notifier name" do
|
54
|
-
it "raises Airbrake::Error" do
|
55
|
-
described_class.configure { |c| c.merge(config_params) }
|
56
|
-
expect do
|
57
|
-
described_class.configure { |c| c.merge(config_params) }
|
58
|
-
end.to raise_error(
|
59
|
-
Airbrake::Error, "the 'default' notifier was already configured"
|
60
|
-
)
|
61
|
-
end
|
62
|
-
end
|
63
|
-
|
64
15
|
context "when user config doesn't contain a project id" do
|
65
16
|
it "raises error" do
|
66
17
|
expect { described_class.configure { |c| c.project_key = '1' } }.
|
@@ -75,97 +26,4 @@ RSpec.describe Airbrake do
|
|
75
26
|
end
|
76
27
|
end
|
77
28
|
end
|
78
|
-
|
79
|
-
describe ".configured?" do
|
80
|
-
it "forwards 'configured?' to the notifier" do
|
81
|
-
expect(default_notifier).to receive(:configured?)
|
82
|
-
described_class.configured?
|
83
|
-
end
|
84
|
-
end
|
85
|
-
|
86
|
-
describe ".notify" do
|
87
|
-
it "forwards 'notify' to the notifier" do
|
88
|
-
block = proc {}
|
89
|
-
expect(default_notifier).to receive(:notify).with('ex', foo: 'bar', &block)
|
90
|
-
described_class.notify('ex', foo: 'bar', &block)
|
91
|
-
end
|
92
|
-
end
|
93
|
-
|
94
|
-
describe ".notify_sync" do
|
95
|
-
it "forwards 'notify_sync' to the notifier" do
|
96
|
-
block = proc {}
|
97
|
-
expect(default_notifier).to receive(:notify).with('ex', foo: 'bar', &block)
|
98
|
-
described_class.notify('ex', foo: 'bar', &block)
|
99
|
-
end
|
100
|
-
end
|
101
|
-
|
102
|
-
describe ".add_filter" do
|
103
|
-
it "forwards 'add_filter' to the notifier" do
|
104
|
-
block = proc {}
|
105
|
-
expect(default_notifier).to receive(:add_filter).with(nil, &block)
|
106
|
-
described_class.add_filter(&block)
|
107
|
-
end
|
108
|
-
end
|
109
|
-
|
110
|
-
describe ".build_notice" do
|
111
|
-
it "forwards 'build_notice' to the notifier" do
|
112
|
-
expect(default_notifier).to receive(:build_notice).with('ex', foo: 'bar')
|
113
|
-
described_class.build_notice('ex', foo: 'bar')
|
114
|
-
end
|
115
|
-
end
|
116
|
-
|
117
|
-
describe ".close" do
|
118
|
-
it "forwards 'close' to the notifier" do
|
119
|
-
expect(default_notifier).to receive(:close)
|
120
|
-
described_class.close
|
121
|
-
end
|
122
|
-
end
|
123
|
-
|
124
|
-
describe ".notify_deploy" do
|
125
|
-
let(:default_notifier) { described_class.notifiers[:deploy][:default] }
|
126
|
-
|
127
|
-
it "calls 'notify' on the deploy notifier" do
|
128
|
-
expect(default_notifier).to receive(:notify).with(foo: 'bar')
|
129
|
-
described_class.notify_deploy(foo: 'bar')
|
130
|
-
end
|
131
|
-
end
|
132
|
-
|
133
|
-
describe ".merge_context" do
|
134
|
-
it "forwards 'merge_context' to the notifier" do
|
135
|
-
expect(default_notifier).to receive(:merge_context).with(foo: 'bar')
|
136
|
-
described_class.merge_context(foo: 'bar')
|
137
|
-
end
|
138
|
-
end
|
139
|
-
|
140
|
-
describe ".notify_request" do
|
141
|
-
let(:default_notifier) { described_class.notifiers[:performance][:default] }
|
142
|
-
|
143
|
-
it "calls 'notify' on the route notifier" do
|
144
|
-
params = {
|
145
|
-
method: 'GET',
|
146
|
-
route: '/foo',
|
147
|
-
status_code: 200,
|
148
|
-
start_time: Time.new(2018, 1, 1, 0, 20, 0, 0),
|
149
|
-
end_time: Time.new(2018, 1, 1, 0, 19, 0, 0)
|
150
|
-
}
|
151
|
-
expect(default_notifier).to receive(:notify).with(Airbrake::Request.new(params))
|
152
|
-
described_class.notify_request(params)
|
153
|
-
end
|
154
|
-
end
|
155
|
-
|
156
|
-
describe ".notify_query" do
|
157
|
-
let(:default_notifier) { described_class.notifiers[:performance][:default] }
|
158
|
-
|
159
|
-
it "calls 'notify' on the query notifier" do
|
160
|
-
params = {
|
161
|
-
method: 'GET',
|
162
|
-
route: '/foo',
|
163
|
-
query: 'SELECT * FROM foos',
|
164
|
-
start_time: Time.new(2018, 1, 1, 0, 20, 0, 0),
|
165
|
-
end_time: Time.new(2018, 1, 1, 0, 19, 0, 0)
|
166
|
-
}
|
167
|
-
expect(default_notifier).to receive(:notify).with(Airbrake::Query.new(params))
|
168
|
-
described_class.notify_query(params)
|
169
|
-
end
|
170
|
-
end
|
171
29
|
end
|
data/spec/async_sender_spec.rb
CHANGED
@@ -1,154 +1,147 @@
|
|
1
1
|
RSpec.describe Airbrake::AsyncSender do
|
2
|
+
let(:endpoint) { 'https://api.airbrake.io/api/v3/projects/1/notices' }
|
3
|
+
let(:queue_size) { 10 }
|
4
|
+
let(:notice) { Airbrake::Notice.new(AirbrakeTestError.new) }
|
5
|
+
|
2
6
|
before do
|
3
|
-
stub_request(:post,
|
7
|
+
stub_request(:post, endpoint).to_return(status: 201, body: '{}')
|
8
|
+
Airbrake::Config.instance = Airbrake::Config.new(
|
9
|
+
project_id: '1',
|
10
|
+
workers: 3,
|
11
|
+
queue_size: queue_size
|
12
|
+
)
|
13
|
+
|
14
|
+
allow(Airbrake::Loggable.instance).to receive(:debug)
|
15
|
+
expect(subject).to have_workers
|
4
16
|
end
|
5
17
|
|
6
18
|
describe "#send" do
|
7
|
-
it "
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
)
|
14
|
-
sender = described_class.new(config)
|
15
|
-
expect(sender).to have_workers
|
16
|
-
|
17
|
-
notice = Airbrake::Notice.new(config, AirbrakeTestError.new)
|
18
|
-
notices_count.times { sender.send(notice, Airbrake::Promise.new) }
|
19
|
-
sender.close
|
20
|
-
|
21
|
-
log = stdout.string.split("\n")
|
22
|
-
notices_sent = log.grep(/\*\*Airbrake: Airbrake::Response \(201\): \{\}/).size
|
23
|
-
notices_dropped = log.grep(/\*\*Airbrake:.*not.*delivered/).size
|
24
|
-
expect(notices_sent).to be >= queue_size
|
25
|
-
expect(notices_sent + notices_dropped).to eq(notices_count)
|
19
|
+
it "sends payload to Airbrake" do
|
20
|
+
2.times do
|
21
|
+
subject.send(notice, Airbrake::Promise.new)
|
22
|
+
end
|
23
|
+
subject.close
|
24
|
+
|
25
|
+
expect(a_request(:post, endpoint)).to have_been_made.twice
|
26
26
|
end
|
27
|
-
end
|
28
27
|
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
28
|
+
context "when the queue is full" do
|
29
|
+
before do
|
30
|
+
allow(subject.unsent).to receive(:size).and_return(queue_size)
|
31
|
+
end
|
32
|
+
|
33
|
+
it "discards payload" do
|
34
|
+
200.times do
|
35
|
+
subject.send(notice, Airbrake::Promise.new)
|
36
|
+
end
|
37
|
+
subject.close
|
38
|
+
|
39
|
+
expect(a_request(:post, endpoint)).not_to have_been_made
|
40
|
+
end
|
41
|
+
|
42
|
+
it "logs discarded payload" do
|
43
|
+
expect(Airbrake::Loggable.instance).to receive(:error).with(
|
44
|
+
/reached its capacity/
|
45
|
+
).exactly(15).times
|
46
|
+
|
47
|
+
15.times do
|
48
|
+
subject.send(notice, Airbrake::Promise.new)
|
49
|
+
end
|
50
|
+
subject.close
|
51
|
+
end
|
35
52
|
end
|
53
|
+
end
|
36
54
|
|
55
|
+
describe "#close" do
|
37
56
|
context "when there are no unsent notices" do
|
38
57
|
it "joins the spawned thread" do
|
39
|
-
workers =
|
40
|
-
|
58
|
+
workers = subject.workers.list
|
41
59
|
expect(workers).to all(be_alive)
|
42
|
-
|
60
|
+
|
61
|
+
subject.close
|
43
62
|
expect(workers).to all(be_stop)
|
44
63
|
end
|
45
64
|
end
|
46
65
|
|
47
66
|
context "when there are some unsent notices" do
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
expect(@stderr.string).to match(/waiting to send \d+ unsent notice/)
|
57
|
-
end
|
58
|
-
|
59
|
-
it "prints the correct number of log messages" do
|
60
|
-
log = @stderr.string.split("\n")
|
61
|
-
notices_sent = log.grep(/\*\*Airbrake: Airbrake::Response \(201\): \{\}/).size
|
62
|
-
notices_dropped = log.grep(/\*\*Airbrake:.*not.*delivered/).size
|
63
|
-
expect(notices_sent).to be >= @sender.instance_variable_get(:@unsent).max
|
64
|
-
expect(notices_sent + notices_dropped).to eq(300)
|
67
|
+
it "logs how many notices are left to send" do
|
68
|
+
expect(Airbrake::Loggable.instance).to receive(:debug).with(
|
69
|
+
/waiting to send \d+ unsent notice\(s\)/
|
70
|
+
)
|
71
|
+
expect(Airbrake::Loggable.instance).to receive(:debug).with(/closed/)
|
72
|
+
|
73
|
+
300.times { subject.send(notice, Airbrake::Promise.new) }
|
74
|
+
subject.close
|
65
75
|
end
|
66
76
|
|
67
77
|
it "waits until the unsent notices queue is empty" do
|
68
|
-
|
78
|
+
subject.close
|
79
|
+
expect(subject.unsent.size).to be_zero
|
69
80
|
end
|
70
81
|
end
|
71
82
|
|
72
83
|
context "when it was already closed" do
|
73
84
|
it "doesn't increase the unsent queue size" do
|
74
85
|
begin
|
75
|
-
|
86
|
+
subject.close
|
76
87
|
rescue Airbrake::Error
|
77
88
|
nil
|
78
89
|
end
|
79
90
|
|
80
|
-
expect(
|
91
|
+
expect(subject.unsent.size).to be_zero
|
81
92
|
end
|
82
93
|
|
83
94
|
it "raises error" do
|
84
|
-
|
95
|
+
subject.close
|
85
96
|
|
86
|
-
expect(
|
87
|
-
expect {
|
88
|
-
|
97
|
+
expect(subject).to be_closed
|
98
|
+
expect { subject.close }.to raise_error(
|
99
|
+
Airbrake::Error, 'attempted to close already closed sender'
|
100
|
+
)
|
89
101
|
end
|
90
102
|
end
|
91
103
|
|
92
104
|
context "when workers were not spawned" do
|
93
105
|
it "correctly closes the notifier nevertheless" do
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
expect(sender).to be_closed
|
106
|
+
subject.close
|
107
|
+
expect(subject).to be_closed
|
98
108
|
end
|
99
109
|
end
|
100
110
|
end
|
101
111
|
|
102
112
|
describe "#has_workers?" do
|
103
|
-
before do
|
104
|
-
@sender = described_class.new(Airbrake::Config.new)
|
105
|
-
expect(@sender).to have_workers
|
106
|
-
end
|
107
|
-
|
108
113
|
it "returns false when the sender is not closed, but has 0 workers" do
|
109
|
-
|
110
|
-
|
111
|
-
|
114
|
+
subject.workers.list.each do |worker|
|
115
|
+
worker.kill.join
|
116
|
+
end
|
117
|
+
expect(subject).not_to have_workers
|
112
118
|
end
|
113
119
|
|
114
120
|
it "returns false when the sender is closed" do
|
115
|
-
|
116
|
-
expect(
|
121
|
+
subject.close
|
122
|
+
expect(subject).not_to have_workers
|
117
123
|
end
|
118
124
|
|
119
125
|
it "respawns workers on fork()", skip: %w[jruby rbx].include?(RUBY_ENGINE) do
|
120
|
-
pid = fork
|
121
|
-
expect(@sender).to have_workers
|
122
|
-
end
|
126
|
+
pid = fork { expect(subject).to have_workers }
|
123
127
|
Process.wait(pid)
|
124
|
-
|
125
|
-
expect(
|
128
|
+
subject.close
|
129
|
+
expect(subject).not_to have_workers
|
126
130
|
end
|
127
131
|
end
|
128
132
|
|
129
133
|
describe "#spawn_workers" do
|
130
134
|
it "spawns alive threads in an enclosed ThreadGroup" do
|
131
|
-
|
132
|
-
expect(
|
135
|
+
expect(subject.workers).to be_a(ThreadGroup)
|
136
|
+
expect(subject.workers.list).to all(be_alive)
|
137
|
+
expect(subject.workers).to be_enclosed
|
133
138
|
|
134
|
-
|
135
|
-
|
136
|
-
expect(workers).to be_a(ThreadGroup)
|
137
|
-
expect(workers.list).to all(be_alive)
|
138
|
-
expect(workers).to be_enclosed
|
139
|
-
|
140
|
-
sender.close
|
139
|
+
subject.close
|
141
140
|
end
|
142
141
|
|
143
142
|
it "spawns exactly config.workers workers" do
|
144
|
-
|
145
|
-
|
146
|
-
expect(sender).to have_workers
|
147
|
-
|
148
|
-
workers = sender.instance_variable_get(:@workers)
|
149
|
-
|
150
|
-
expect(workers.list.size).to eq(workers_count)
|
151
|
-
sender.close
|
143
|
+
expect(subject.workers.list.size).to eq(Airbrake::Config.instance.workers)
|
144
|
+
subject.close
|
152
145
|
end
|
153
146
|
end
|
154
147
|
end
|