airbrake-ruby 4.13.3-java → 5.0.0-java

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.
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