airbrake-ruby 4.13.3-java → 5.0.0-java

Sign up to get free protection for your applications and to get access to all the features.
Files changed (66) hide show
  1. checksums.yaml +4 -4
  2. data/lib/airbrake-ruby.rb +23 -32
  3. data/lib/airbrake-ruby/async_sender.rb +1 -1
  4. data/lib/airbrake-ruby/backtrace.rb +6 -5
  5. data/lib/airbrake-ruby/config.rb +37 -13
  6. data/lib/airbrake-ruby/config/processor.rb +84 -0
  7. data/lib/airbrake-ruby/config/validator.rb +6 -0
  8. data/lib/airbrake-ruby/file_cache.rb +1 -1
  9. data/lib/airbrake-ruby/filter_chain.rb +16 -1
  10. data/lib/airbrake-ruby/filters/dependency_filter.rb +1 -0
  11. data/lib/airbrake-ruby/filters/gem_root_filter.rb +1 -0
  12. data/lib/airbrake-ruby/filters/git_last_checkout_filter.rb +3 -3
  13. data/lib/airbrake-ruby/filters/git_repository_filter.rb +3 -0
  14. data/lib/airbrake-ruby/filters/git_revision_filter.rb +2 -0
  15. data/lib/airbrake-ruby/filters/{keys_whitelist.rb → keys_allowlist.rb} +3 -3
  16. data/lib/airbrake-ruby/filters/{keys_blacklist.rb → keys_blocklist.rb} +3 -3
  17. data/lib/airbrake-ruby/filters/keys_filter.rb +26 -18
  18. data/lib/airbrake-ruby/filters/root_directory_filter.rb +1 -0
  19. data/lib/airbrake-ruby/filters/sql_filter.rb +4 -4
  20. data/lib/airbrake-ruby/filters/system_exit_filter.rb +1 -0
  21. data/lib/airbrake-ruby/filters/thread_filter.rb +2 -0
  22. data/lib/airbrake-ruby/ignorable.rb +1 -0
  23. data/lib/airbrake-ruby/notice.rb +1 -8
  24. data/lib/airbrake-ruby/notice_notifier.rb +7 -0
  25. data/lib/airbrake-ruby/performance_breakdown.rb +1 -6
  26. data/lib/airbrake-ruby/performance_notifier.rb +2 -15
  27. data/lib/airbrake-ruby/promise.rb +1 -0
  28. data/lib/airbrake-ruby/query.rb +1 -6
  29. data/lib/airbrake-ruby/queue.rb +1 -8
  30. data/lib/airbrake-ruby/remote_settings.rb +145 -0
  31. data/lib/airbrake-ruby/remote_settings/settings_data.rb +120 -0
  32. data/lib/airbrake-ruby/request.rb +1 -8
  33. data/lib/airbrake-ruby/stat.rb +1 -12
  34. data/lib/airbrake-ruby/sync_sender.rb +3 -2
  35. data/lib/airbrake-ruby/tdigest.rb +2 -0
  36. data/lib/airbrake-ruby/thread_pool.rb +2 -0
  37. data/lib/airbrake-ruby/truncator.rb +8 -2
  38. data/lib/airbrake-ruby/version.rb +11 -1
  39. data/spec/airbrake_spec.rb +71 -36
  40. data/spec/backtrace_spec.rb +26 -26
  41. data/spec/code_hunk_spec.rb +2 -2
  42. data/spec/config/processor_spec.rb +209 -0
  43. data/spec/config/validator_spec.rb +18 -1
  44. data/spec/config_spec.rb +13 -6
  45. data/spec/filter_chain_spec.rb +27 -0
  46. data/spec/filters/gem_root_filter_spec.rb +4 -4
  47. data/spec/filters/git_last_checkout_filter_spec.rb +20 -3
  48. data/spec/filters/{keys_whitelist_spec.rb → keys_allowlist_spec.rb} +11 -10
  49. data/spec/filters/{keys_blacklist_spec.rb → keys_blocklist_spec.rb} +20 -10
  50. data/spec/filters/root_directory_filter_spec.rb +4 -4
  51. data/spec/filters/sql_filter_spec.rb +5 -5
  52. data/spec/notice_notifier/options_spec.rb +6 -6
  53. data/spec/notice_notifier_spec.rb +2 -2
  54. data/spec/notice_spec.rb +1 -1
  55. data/spec/performance_breakdown_spec.rb +0 -12
  56. data/spec/performance_notifier_spec.rb +2 -27
  57. data/spec/query_spec.rb +1 -11
  58. data/spec/queue_spec.rb +1 -13
  59. data/spec/remote_settings/settings_data_spec.rb +365 -0
  60. data/spec/remote_settings_spec.rb +230 -0
  61. data/spec/request_spec.rb +1 -13
  62. data/spec/spec_helper.rb +4 -4
  63. data/spec/stat_spec.rb +0 -9
  64. data/spec/sync_sender_spec.rb +3 -1
  65. data/spec/thread_pool_spec.rb +25 -5
  66. metadata +22 -14
@@ -9,21 +9,9 @@ RSpec.describe Airbrake::Queue do
9
9
  it { is_expected.to respond_to(:stash) }
10
10
  end
11
11
 
12
- describe "#end_time" do
13
- it "is always equal to start_time + 1 second by default" do
14
- time = Time.now
15
- queue = described_class.new(
16
- queue: 'bananas', error_count: 0, start_time: time,
17
- )
18
- expect(queue.end_time).to eq(time + 1)
19
- end
20
- end
21
-
22
12
  describe "#route" do
23
13
  it "always returns an empty route" do
24
- queue = described_class.new(
25
- queue: 'a', error_count: 0, start_time: Time.now,
26
- )
14
+ queue = described_class.new(queue: 'a', error_count: 0)
27
15
  expect(queue.route).to be_empty
28
16
  end
29
17
  end
@@ -0,0 +1,365 @@
1
+ RSpec.describe Airbrake::RemoteSettings::SettingsData do
2
+ let(:project_id) { 123 }
3
+
4
+ describe "#merge!" do
5
+ it "returns self" do
6
+ settings_data = described_class.new(project_id, {})
7
+ expect(settings_data.merge!({})).to eql(settings_data)
8
+ end
9
+
10
+ it "merges the given hash with the data" do
11
+ settings_data = described_class.new(project_id, {})
12
+ settings_data.merge!('poll_sec' => 123, 'config_route' => 'abc')
13
+
14
+ expect(settings_data.interval).to eq(123)
15
+ expect(settings_data.config_route(''))
16
+ .to eq('abc/2020-06-18/config/123/config.json')
17
+ end
18
+ end
19
+
20
+ describe "#interval" do
21
+ context "when given data has zero interval" do
22
+ let(:data) do
23
+ { 'poll_sec' => 0 }
24
+ end
25
+
26
+ it "returns the default interval" do
27
+ expect(described_class.new(project_id, data).interval).to eq(600)
28
+ end
29
+ end
30
+
31
+ context "when given data has negative interval" do
32
+ let(:data) do
33
+ { 'poll_sec' => -1 }
34
+ end
35
+
36
+ it "returns the default interval" do
37
+ expect(described_class.new(project_id, data).interval).to eq(600)
38
+ end
39
+ end
40
+
41
+ context "when given data has nil interval" do
42
+ let(:data) do
43
+ { 'poll_sec' => nil }
44
+ end
45
+
46
+ it "returns the default interval" do
47
+ expect(described_class.new(project_id, data).interval).to eq(600)
48
+ end
49
+ end
50
+
51
+ context "when given data has a positive interval" do
52
+ let(:data) do
53
+ { 'poll_sec' => 123 }
54
+ end
55
+
56
+ it "returns the interval from data" do
57
+ expect(described_class.new(project_id, data).interval).to eq(123)
58
+ end
59
+ end
60
+ end
61
+
62
+ describe "#config_route" do
63
+ let(:host) { 'https://v1-production-notifier-configs.s3.amazonaws.com' }
64
+
65
+ context "when given a remote host through the remote config" do
66
+ context "and when the remote host ends with a slash" do
67
+ let(:data) do
68
+ { 'config_route' => 'http://example.com/' }
69
+ end
70
+
71
+ it "returns the route with the host" do
72
+ expect(described_class.new(project_id, data).config_route(host)).to eq(
73
+ "http://example.com/2020-06-18/config/#{project_id}/config.json",
74
+ )
75
+ end
76
+ end
77
+
78
+ context "and when the remote host doesn't with a slash" do
79
+ let(:data) do
80
+ { 'config_route' => 'http://example.com' }
81
+ end
82
+
83
+ it "returns the route with the host" do
84
+ expect(described_class.new(project_id, data).config_route(host)).to eq(
85
+ "http://example.com/2020-06-18/config/#{project_id}/config.json",
86
+ )
87
+ end
88
+ end
89
+ end
90
+
91
+ context "when given a remote host through local configuration" do
92
+ context "and when the remote host ends with a slash" do
93
+ let(:host) { 'http://example.com/' }
94
+
95
+ it "returns the route with the host" do
96
+ expect(described_class.new(project_id, {}).config_route(host)).to eq(
97
+ "http://example.com/2020-06-18/config/#{project_id}/config.json",
98
+ )
99
+ end
100
+ end
101
+
102
+ context "and when the remote host doesn't with a slash" do
103
+ let(:host) { 'http://example.com' }
104
+
105
+ it "returns the route with the host" do
106
+ expect(described_class.new(project_id, {}).config_route(host)).to eq(
107
+ "http://example.com/2020-06-18/config/#{project_id}/config.json",
108
+ )
109
+ end
110
+ end
111
+ end
112
+
113
+ context "when the given remote host in the remote config is nil" do
114
+ let(:data) do
115
+ { 'config_route' => nil }
116
+ end
117
+
118
+ it "returns the route with the given host instead" do
119
+ expect(described_class.new(project_id, data).config_route(host)).to eq(
120
+ 'https://v1-production-notifier-configs.s3.amazonaws.com/' \
121
+ "2020-06-18/config/#{project_id}/config.json",
122
+ )
123
+ end
124
+ end
125
+ end
126
+
127
+ describe "#error_notifications?" do
128
+ context "when the 'errors' setting is present" do
129
+ context "and when it is enabled" do
130
+ let(:data) do
131
+ {
132
+ 'settings' => [
133
+ {
134
+ 'name' => 'errors',
135
+ 'enabled' => true,
136
+ },
137
+ ],
138
+ }
139
+ end
140
+
141
+ it "returns true" do
142
+ expect(described_class.new(project_id, data).error_notifications?)
143
+ .to eq(true)
144
+ end
145
+ end
146
+
147
+ context "and when it is disabled" do
148
+ let(:data) do
149
+ {
150
+ 'settings' => [
151
+ {
152
+ 'name' => 'errors',
153
+ 'enabled' => false,
154
+ },
155
+ ],
156
+ }
157
+ end
158
+
159
+ it "returns false" do
160
+ expect(described_class.new(project_id, data).error_notifications?)
161
+ .to eq(false)
162
+ end
163
+ end
164
+ end
165
+
166
+ context "when the 'errors' setting is missing" do
167
+ let(:data) do
168
+ { 'settings' => [] }
169
+ end
170
+
171
+ it "returns true" do
172
+ expect(described_class.new(project_id, data).error_notifications?)
173
+ .to eq(true)
174
+ end
175
+ end
176
+ end
177
+
178
+ describe "#performance_stats?" do
179
+ context "when the 'apm' setting is present" do
180
+ context "and when it is enabled" do
181
+ let(:data) do
182
+ {
183
+ 'settings' => [
184
+ {
185
+ 'name' => 'apm',
186
+ 'enabled' => true,
187
+ },
188
+ ],
189
+ }
190
+ end
191
+
192
+ it "returns true" do
193
+ expect(described_class.new(project_id, data).performance_stats?)
194
+ .to eq(true)
195
+ end
196
+ end
197
+
198
+ context "and when it is disabled" do
199
+ let(:data) do
200
+ {
201
+ 'settings' => [
202
+ {
203
+ 'name' => 'apm',
204
+ 'enabled' => false,
205
+ },
206
+ ],
207
+ }
208
+ end
209
+
210
+ it "returns false" do
211
+ expect(described_class.new(project_id, data).performance_stats?)
212
+ .to eq(false)
213
+ end
214
+ end
215
+ end
216
+
217
+ context "when the 'apm' setting is missing" do
218
+ let(:data) do
219
+ { 'settings' => [] }
220
+ end
221
+
222
+ it "returns true" do
223
+ expect(described_class.new(project_id, data).performance_stats?)
224
+ .to eq(true)
225
+ end
226
+ end
227
+ end
228
+
229
+ describe "#error_host" do
230
+ context "when the 'errors' setting is present" do
231
+ context "and when 'endpoint' is specified" do
232
+ let(:endpoint) { 'https://api.example.com/' }
233
+
234
+ let(:data) do
235
+ {
236
+ 'settings' => [
237
+ {
238
+ 'name' => 'errors',
239
+ 'enabled' => true,
240
+ 'endpoint' => endpoint,
241
+ },
242
+ ],
243
+ }
244
+ end
245
+
246
+ it "returns the endpoint" do
247
+ expect(described_class.new(project_id, data).error_host).to eq(endpoint)
248
+ end
249
+ end
250
+
251
+ context "and when an endpoint is NOT specified" do
252
+ let(:data) do
253
+ {
254
+ 'settings' => [
255
+ {
256
+ 'name' => 'errors',
257
+ 'enabled' => true,
258
+ },
259
+ ],
260
+ }
261
+ end
262
+
263
+ it "returns nil" do
264
+ expect(described_class.new(project_id, data).error_host).to be_nil
265
+ end
266
+ end
267
+ end
268
+
269
+ context "when the 'errors' setting is missing" do
270
+ let(:data) do
271
+ { 'settings' => [] }
272
+ end
273
+
274
+ it "returns nil" do
275
+ expect(described_class.new(project_id, data).error_host).to be_nil
276
+ end
277
+ end
278
+ end
279
+
280
+ describe "#apm_host" do
281
+ context "when the 'apm' setting is present" do
282
+ context "and when 'endpoint' is specified" do
283
+ let(:endpoint) { 'https://api.example.com/' }
284
+
285
+ let(:data) do
286
+ {
287
+ 'settings' => [
288
+ {
289
+ 'name' => 'apm',
290
+ 'enabled' => true,
291
+ 'endpoint' => endpoint,
292
+ },
293
+ ],
294
+ }
295
+ end
296
+
297
+ it "returns the endpoint" do
298
+ expect(described_class.new(project_id, data).apm_host).to eq(endpoint)
299
+ end
300
+ end
301
+
302
+ context "and when an endpoint is NOT specified" do
303
+ let(:data) do
304
+ {
305
+ 'settings' => [
306
+ {
307
+ 'name' => 'apm',
308
+ 'enabled' => true,
309
+ },
310
+ ],
311
+ }
312
+ end
313
+
314
+ it "returns nil" do
315
+ expect(described_class.new(project_id, data).apm_host).to be_nil
316
+ end
317
+ end
318
+ end
319
+
320
+ context "when the 'apm' setting is missing" do
321
+ let(:data) do
322
+ { 'settings' => [] }
323
+ end
324
+
325
+ it "returns nil" do
326
+ expect(described_class.new(project_id, data).apm_host).to be_nil
327
+ end
328
+ end
329
+ end
330
+
331
+ describe "#to_h" do
332
+ let(:data) do
333
+ {
334
+ 'poll_sec' => 123,
335
+ 'settings' => [
336
+ {
337
+ 'name' => 'apm',
338
+ 'enabled' => false,
339
+ },
340
+ ],
341
+ }
342
+ end
343
+
344
+ subject { described_class.new(project_id, data) }
345
+
346
+ it "returns a hash representation of settings" do
347
+ expect(described_class.new(project_id, data).to_h).to eq(data)
348
+ end
349
+
350
+ it "doesn't allow mutation of the original data object" do
351
+ hash = subject.to_h
352
+ hash['poll_sec'] = 0
353
+
354
+ expect(subject.to_h).to eq(
355
+ 'poll_sec' => 123,
356
+ 'settings' => [
357
+ {
358
+ 'name' => 'apm',
359
+ 'enabled' => false,
360
+ },
361
+ ],
362
+ )
363
+ end
364
+ end
365
+ end
@@ -0,0 +1,230 @@
1
+ RSpec.describe Airbrake::RemoteSettings do
2
+ let(:project_id) { 123 }
3
+ let(:host) { 'https://v1-production-notifier-configs.s3.amazonaws.com' }
4
+
5
+ let(:endpoint) do
6
+ "#{host}/2020-06-18/config/#{project_id}/config.json"
7
+ end
8
+
9
+ let(:body) do
10
+ {
11
+ 'poll_sec' => 1,
12
+ 'settings' => [
13
+ {
14
+ 'name' => 'apm',
15
+ 'enabled' => false,
16
+ },
17
+ {
18
+ 'name' => 'errors',
19
+ 'enabled' => true,
20
+ },
21
+ ],
22
+ }
23
+ end
24
+
25
+ let(:config_path) { described_class::CONFIG_DUMP_PATH }
26
+ let(:config_dir) { File.dirname(config_path) }
27
+
28
+ let!(:stub) do
29
+ stub_request(:get, Regexp.new(endpoint))
30
+ .to_return(status: 200, body: body.to_json)
31
+ end
32
+
33
+ before do
34
+ # Do not create config dumps on disk.
35
+ allow(Dir).to receive(:mkdir).with(config_dir)
36
+ allow(File).to receive(:write).with(config_path, anything)
37
+ end
38
+
39
+ describe ".poll" do
40
+ describe "config loading" do
41
+ let(:settings_data) { described_class::SettingsData.new(project_id, body) }
42
+
43
+ before do
44
+ allow(File).to receive(:exist?).with(config_path).and_return(true)
45
+ allow(File).to receive(:read).with(config_path).and_return(body.to_json)
46
+
47
+ allow(described_class::SettingsData).to receive(:new).and_return(settings_data)
48
+ end
49
+
50
+ it "loads the config from disk" do
51
+ expect(File).to receive(:read).with(config_path)
52
+ expect(settings_data).to receive(:merge!).with(body).twice
53
+
54
+ remote_settings = described_class.poll(project_id, host) {}
55
+ sleep(0.2)
56
+ remote_settings.stop_polling
57
+
58
+ expect(stub).to have_been_requested.once
59
+ end
60
+
61
+ it "yields the config to the block twice" do
62
+ block = proc {}
63
+ expect(block).to receive(:call).twice
64
+
65
+ remote_settings = described_class.poll(project_id, host, &block)
66
+ sleep(0.2)
67
+ remote_settings.stop_polling
68
+
69
+ expect(stub).to have_been_requested.once
70
+ end
71
+
72
+ context "when config loading fails" do
73
+ it "logs an error" do
74
+ expect(File).to receive(:read).and_raise(StandardError)
75
+ expect(Airbrake::Loggable.instance).to receive(:error).with(
76
+ '**Airbrake: config loading failed: StandardError',
77
+ )
78
+
79
+ remote_settings = described_class.poll(project_id, host) {}
80
+ sleep(0.2)
81
+ remote_settings.stop_polling
82
+
83
+ expect(stub).to have_been_requested.once
84
+ end
85
+ end
86
+ end
87
+
88
+ context "when no errors are raised" do
89
+ it "makes a request to AWS S3" do
90
+ remote_settings = described_class.poll(project_id, host) {}
91
+ sleep(0.1)
92
+ remote_settings.stop_polling
93
+
94
+ expect(stub).to have_been_requested.at_least_once
95
+ end
96
+
97
+ it "sends params about the environment with the request" do
98
+ remote_settings = described_class.poll(project_id, host) {}
99
+ sleep(0.1)
100
+ remote_settings.stop_polling
101
+
102
+ stub_with_query_params = stub.with(
103
+ query: URI.decode_www_form(described_class::QUERY_PARAMS).to_h,
104
+ )
105
+ expect(stub_with_query_params).to have_been_requested.at_least_once
106
+ end
107
+
108
+ it "fetches remote settings" do
109
+ settings = nil
110
+ remote_settings = described_class.poll(project_id, host) do |data|
111
+ settings = data
112
+ end
113
+ sleep(0.1)
114
+ remote_settings.stop_polling
115
+
116
+ expect(settings.error_notifications?).to eq(true)
117
+ expect(settings.performance_stats?).to eq(false)
118
+ expect(settings.interval).to eq(1)
119
+ end
120
+ end
121
+
122
+ context "when an error is raised while making a HTTP request" do
123
+ before do
124
+ allow(Net::HTTP).to receive(:get).and_raise(StandardError)
125
+ end
126
+
127
+ it "doesn't fetch remote settings" do
128
+ settings = nil
129
+ remote_settings = described_class.poll(project_id, host) do |data|
130
+ settings = data
131
+ end
132
+ sleep(0.1)
133
+ remote_settings.stop_polling
134
+
135
+ expect(stub).not_to have_been_requested
136
+ expect(settings.interval).to eq(600)
137
+ end
138
+ end
139
+
140
+ context "when an error is raised while parsing returned JSON" do
141
+ before do
142
+ allow(JSON).to receive(:parse).and_raise(JSON::ParserError)
143
+ end
144
+
145
+ it "doesn't update settings data" do
146
+ settings = nil
147
+ remote_settings = described_class.poll(project_id, host) do |data|
148
+ settings = data
149
+ end
150
+ sleep(0.1)
151
+ remote_settings.stop_polling
152
+
153
+ expect(stub).to have_been_requested.once
154
+ expect(settings.interval).to eq(600)
155
+ end
156
+ end
157
+
158
+ context "when API returns an XML response" do
159
+ let!(:stub) do
160
+ stub_request(:get, Regexp.new(endpoint))
161
+ .to_return(status: 200, body: '<?xml ...')
162
+ end
163
+
164
+ it "doesn't update settings data" do
165
+ settings = nil
166
+ remote_settings = described_class.poll(project_id, host) do |data|
167
+ settings = data
168
+ end
169
+ sleep(0.1)
170
+ remote_settings.stop_polling
171
+
172
+ expect(stub).to have_been_requested.once
173
+ expect(settings.interval).to eq(600)
174
+ end
175
+ end
176
+
177
+ context "when a config route is specified in the returned data" do
178
+ let(:new_endpoint) do
179
+ "http://example.com"
180
+ end
181
+
182
+ let(:body) do
183
+ { 'config_route' => new_endpoint, 'poll_sec' => 0.1 }
184
+ end
185
+
186
+ let!(:new_stub) do
187
+ stub_request(:get, Regexp.new(new_endpoint))
188
+ .to_return(status: 200, body: body.to_json)
189
+ end
190
+
191
+ it "makes the next request to the specified config route" do
192
+ remote_settings = described_class.poll(project_id, host) {}
193
+ sleep(0.2)
194
+
195
+ remote_settings.stop_polling
196
+
197
+ expect(stub).to have_been_requested.once
198
+ expect(new_stub).to have_been_requested.once
199
+ end
200
+ end
201
+ end
202
+
203
+ describe "#stop_polling" do
204
+ it "dumps config data to disk" do
205
+ expect(Dir).to receive(:mkdir).with(config_dir)
206
+ expect(File).to receive(:write).with(config_path, body.to_json)
207
+
208
+ remote_settings = described_class.poll(project_id, host) {}
209
+ sleep(0.2)
210
+ remote_settings.stop_polling
211
+
212
+ expect(stub).to have_been_requested.once
213
+ end
214
+
215
+ context "when config dumping fails" do
216
+ it "logs an error" do
217
+ expect(File).to receive(:write).and_raise(StandardError)
218
+ expect(Airbrake::Loggable.instance).to receive(:error).with(
219
+ '**Airbrake: config dumping failed: StandardError',
220
+ )
221
+
222
+ remote_settings = described_class.poll(project_id, host) {}
223
+ sleep(0.2)
224
+ remote_settings.stop_polling
225
+
226
+ expect(stub).to have_been_requested.once
227
+ end
228
+ end
229
+ end
230
+ end