airbrake-ruby 4.8.0 → 5.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (91) hide show
  1. checksums.yaml +4 -4
  2. data/lib/airbrake-ruby.rb +132 -57
  3. data/lib/airbrake-ruby/async_sender.rb +7 -30
  4. data/lib/airbrake-ruby/backtrace.rb +8 -7
  5. data/lib/airbrake-ruby/benchmark.rb +1 -1
  6. data/lib/airbrake-ruby/code_hunk.rb +1 -1
  7. data/lib/airbrake-ruby/config.rb +59 -15
  8. data/lib/airbrake-ruby/config/processor.rb +71 -0
  9. data/lib/airbrake-ruby/config/validator.rb +9 -3
  10. data/lib/airbrake-ruby/deploy_notifier.rb +1 -1
  11. data/lib/airbrake-ruby/file_cache.rb +1 -1
  12. data/lib/airbrake-ruby/filter_chain.rb +16 -1
  13. data/lib/airbrake-ruby/filters/dependency_filter.rb +1 -0
  14. data/lib/airbrake-ruby/filters/exception_attributes_filter.rb +2 -2
  15. data/lib/airbrake-ruby/filters/gem_root_filter.rb +1 -0
  16. data/lib/airbrake-ruby/filters/git_last_checkout_filter.rb +5 -5
  17. data/lib/airbrake-ruby/filters/git_repository_filter.rb +3 -0
  18. data/lib/airbrake-ruby/filters/git_revision_filter.rb +2 -0
  19. data/lib/airbrake-ruby/filters/{keys_whitelist.rb → keys_allowlist.rb} +3 -3
  20. data/lib/airbrake-ruby/filters/{keys_blacklist.rb → keys_blocklist.rb} +3 -3
  21. data/lib/airbrake-ruby/filters/keys_filter.rb +39 -20
  22. data/lib/airbrake-ruby/filters/root_directory_filter.rb +1 -0
  23. data/lib/airbrake-ruby/filters/sql_filter.rb +7 -7
  24. data/lib/airbrake-ruby/filters/system_exit_filter.rb +1 -0
  25. data/lib/airbrake-ruby/filters/thread_filter.rb +5 -4
  26. data/lib/airbrake-ruby/grouppable.rb +12 -0
  27. data/lib/airbrake-ruby/ignorable.rb +1 -0
  28. data/lib/airbrake-ruby/inspectable.rb +2 -2
  29. data/lib/airbrake-ruby/loggable.rb +1 -1
  30. data/lib/airbrake-ruby/mergeable.rb +12 -0
  31. data/lib/airbrake-ruby/monotonic_time.rb +5 -0
  32. data/lib/airbrake-ruby/notice.rb +7 -14
  33. data/lib/airbrake-ruby/notice_notifier.rb +11 -3
  34. data/lib/airbrake-ruby/performance_breakdown.rb +16 -10
  35. data/lib/airbrake-ruby/performance_notifier.rb +80 -58
  36. data/lib/airbrake-ruby/promise.rb +1 -0
  37. data/lib/airbrake-ruby/query.rb +20 -15
  38. data/lib/airbrake-ruby/queue.rb +65 -0
  39. data/lib/airbrake-ruby/remote_settings.rb +105 -0
  40. data/lib/airbrake-ruby/remote_settings/callback.rb +44 -0
  41. data/lib/airbrake-ruby/remote_settings/settings_data.rb +116 -0
  42. data/lib/airbrake-ruby/request.rb +14 -12
  43. data/lib/airbrake-ruby/stat.rb +26 -33
  44. data/lib/airbrake-ruby/sync_sender.rb +3 -2
  45. data/lib/airbrake-ruby/tdigest.rb +43 -58
  46. data/lib/airbrake-ruby/thread_pool.rb +11 -1
  47. data/lib/airbrake-ruby/truncator.rb +10 -4
  48. data/lib/airbrake-ruby/version.rb +11 -1
  49. data/spec/airbrake_spec.rb +206 -71
  50. data/spec/async_sender_spec.rb +3 -12
  51. data/spec/backtrace_spec.rb +44 -44
  52. data/spec/code_hunk_spec.rb +11 -11
  53. data/spec/config/processor_spec.rb +143 -0
  54. data/spec/config/validator_spec.rb +23 -6
  55. data/spec/config_spec.rb +40 -14
  56. data/spec/deploy_notifier_spec.rb +2 -2
  57. data/spec/filter_chain_spec.rb +28 -1
  58. data/spec/filters/dependency_filter_spec.rb +1 -1
  59. data/spec/filters/gem_root_filter_spec.rb +9 -9
  60. data/spec/filters/git_last_checkout_filter_spec.rb +21 -4
  61. data/spec/filters/git_repository_filter.rb +1 -1
  62. data/spec/filters/git_revision_filter_spec.rb +10 -10
  63. data/spec/filters/{keys_whitelist_spec.rb → keys_allowlist_spec.rb} +29 -28
  64. data/spec/filters/{keys_blacklist_spec.rb → keys_blocklist_spec.rb} +39 -29
  65. data/spec/filters/root_directory_filter_spec.rb +9 -9
  66. data/spec/filters/sql_filter_spec.rb +58 -60
  67. data/spec/filters/system_exit_filter_spec.rb +1 -1
  68. data/spec/filters/thread_filter_spec.rb +32 -30
  69. data/spec/fixtures/project_root/code.rb +9 -9
  70. data/spec/loggable_spec.rb +17 -0
  71. data/spec/monotonic_time_spec.rb +11 -0
  72. data/spec/notice_notifier/options_spec.rb +17 -17
  73. data/spec/notice_notifier_spec.rb +20 -20
  74. data/spec/notice_spec.rb +6 -6
  75. data/spec/performance_breakdown_spec.rb +0 -1
  76. data/spec/performance_notifier_spec.rb +220 -73
  77. data/spec/query_spec.rb +1 -1
  78. data/spec/queue_spec.rb +18 -0
  79. data/spec/remote_settings/callback_spec.rb +143 -0
  80. data/spec/remote_settings/settings_data_spec.rb +348 -0
  81. data/spec/remote_settings_spec.rb +187 -0
  82. data/spec/request_spec.rb +1 -3
  83. data/spec/response_spec.rb +8 -8
  84. data/spec/spec_helper.rb +6 -6
  85. data/spec/stat_spec.rb +2 -12
  86. data/spec/sync_sender_spec.rb +14 -12
  87. data/spec/tdigest_spec.rb +7 -7
  88. data/spec/thread_pool_spec.rb +39 -10
  89. data/spec/timed_trace_spec.rb +1 -1
  90. data/spec/truncator_spec.rb +12 -12
  91. metadata +32 -14
@@ -2,7 +2,7 @@ RSpec.describe Airbrake::Query do
2
2
  describe "#stash" do
3
3
  subject do
4
4
  described_class.new(
5
- method: 'GET', route: '/', query: '', start_time: Time.now
5
+ method: 'GET', route: '/', query: '',
6
6
  )
7
7
  end
8
8
 
@@ -0,0 +1,18 @@
1
+ RSpec.describe Airbrake::Queue do
2
+ subject { described_class.new(queue: 'bananas', error_count: 0) }
3
+
4
+ describe "#ignore" do
5
+ it { is_expected.to respond_to(:ignore!) }
6
+ end
7
+
8
+ describe "#stash" do
9
+ it { is_expected.to respond_to(:stash) }
10
+ end
11
+
12
+ describe "#route" do
13
+ it "always returns an empty route" do
14
+ queue = described_class.new(queue: 'a', error_count: 0)
15
+ expect(queue.route).to be_empty
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,143 @@
1
+ RSpec.describe Airbrake::RemoteSettings::Callback do
2
+ describe "#call" do
3
+ let(:logger) { Logger.new(File::NULL) }
4
+
5
+ let(:config) do
6
+ Airbrake::Config.new(
7
+ project_id: 123,
8
+ logger: logger,
9
+ )
10
+ end
11
+
12
+ let(:data) do
13
+ instance_double(Airbrake::RemoteSettings::SettingsData)
14
+ end
15
+
16
+ before do
17
+ allow(data).to receive(:to_h)
18
+ allow(data).to receive(:error_host)
19
+ allow(data).to receive(:apm_host)
20
+ allow(data).to receive(:error_notifications?)
21
+ allow(data).to receive(:performance_stats?)
22
+ end
23
+
24
+ it "logs given data" do
25
+ expect(logger).to receive(:debug) do |&block|
26
+ expect(block.call).to match(/applying remote settings/)
27
+ end
28
+ described_class.new(config).call(data)
29
+ end
30
+
31
+ context "when the config disables error notifications" do
32
+ before do
33
+ config.error_notifications = false
34
+ allow(data).to receive(:error_notifications?).and_return(true)
35
+ end
36
+
37
+ it "keeps the option disabled forever" do
38
+ callback = described_class.new(config)
39
+
40
+ callback.call(data)
41
+ expect(config.error_notifications).to eq(false)
42
+
43
+ callback.call(data)
44
+ expect(config.error_notifications).to eq(false)
45
+
46
+ callback.call(data)
47
+ expect(config.error_notifications).to eq(false)
48
+ end
49
+ end
50
+
51
+ context "when the config enables error notifications" do
52
+ before { config.error_notifications = true }
53
+
54
+ it "can disable and enable error notifications" do
55
+ expect(data).to receive(:error_notifications?).and_return(false)
56
+
57
+ callback = described_class.new(config)
58
+ callback.call(data)
59
+ expect(config.error_notifications).to eq(false)
60
+
61
+ expect(data).to receive(:error_notifications?).and_return(true)
62
+ callback.call(data)
63
+ expect(config.error_notifications).to eq(true)
64
+ end
65
+ end
66
+
67
+ context "when the config disables performance_stats" do
68
+ before do
69
+ config.performance_stats = false
70
+ allow(data).to receive(:performance_stats?).and_return(true)
71
+ end
72
+
73
+ it "keeps the option disabled forever" do
74
+ callback = described_class.new(config)
75
+
76
+ callback.call(data)
77
+ expect(config.performance_stats).to eq(false)
78
+
79
+ callback.call(data)
80
+ expect(config.performance_stats).to eq(false)
81
+
82
+ callback.call(data)
83
+ expect(config.performance_stats).to eq(false)
84
+ end
85
+ end
86
+
87
+ context "when the config enables performance stats" do
88
+ before { config.performance_stats = true }
89
+
90
+ it "can disable and enable performance_stats" do
91
+ expect(data).to receive(:performance_stats?).and_return(false)
92
+
93
+ callback = described_class.new(config)
94
+ callback.call(data)
95
+ expect(config.performance_stats).to eq(false)
96
+
97
+ expect(data).to receive(:performance_stats?).and_return(true)
98
+ callback.call(data)
99
+ expect(config.performance_stats).to eq(true)
100
+ end
101
+ end
102
+
103
+ context "when error_host returns a value" do
104
+ it "sets the error_host option" do
105
+ config.error_host = 'http://api.airbrake.io'
106
+ allow(data).to receive(:error_host).and_return('https://api.example.com')
107
+
108
+ described_class.new(config).call(data)
109
+ expect(config.error_host).to eq('https://api.example.com')
110
+ end
111
+ end
112
+
113
+ context "when error_host returns nil" do
114
+ it "doesn't modify the error_host option" do
115
+ config.error_host = 'http://api.airbrake.io'
116
+ allow(data).to receive(:error_host).and_return(nil)
117
+
118
+ described_class.new(config).call(data)
119
+ expect(config.error_host).to eq('http://api.airbrake.io')
120
+ end
121
+ end
122
+
123
+ context "when apm_host returns a value" do
124
+ it "sets the apm_host option" do
125
+ config.apm_host = 'http://api.airbrake.io'
126
+ allow(data).to receive(:apm_host).and_return('https://api.example.com')
127
+
128
+ described_class.new(config).call(data)
129
+ expect(config.apm_host).to eq('https://api.example.com')
130
+ end
131
+ end
132
+
133
+ context "when apm_host returns nil" do
134
+ it "doesn't modify the apm_host option" do
135
+ config.apm_host = 'http://api.airbrake.io'
136
+ allow(data).to receive(:apm_host).and_return(nil)
137
+
138
+ described_class.new(config).call(data)
139
+ expect(config.apm_host).to eq('http://api.airbrake.io')
140
+ end
141
+ end
142
+ end
143
+ end
@@ -0,0 +1,348 @@
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')
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) { 'http://example.com/' }
64
+
65
+ context "when remote config specifies a config route" do
66
+ let(:data) do
67
+ { 'config_route' => '123/cfg/321/cfg.json' }
68
+ end
69
+
70
+ it "returns the config route with the provided location" do
71
+ expect(described_class.new(project_id, data).config_route(host)).to eq(
72
+ 'http://example.com/123/cfg/321/cfg.json',
73
+ )
74
+ end
75
+ end
76
+
77
+ context "when remote config DOES NOT specify a config route" do
78
+ it "returns the config route with the default location" do
79
+ expect(described_class.new(project_id, {}).config_route(host)).to eq(
80
+ "http://example.com/2020-06-18/config/#{project_id}/config.json",
81
+ )
82
+ end
83
+ end
84
+
85
+ context "when a config route is specified but is set to nil" do
86
+ let(:data) do
87
+ { 'config_route' => nil }
88
+ end
89
+
90
+ it "returns the config route with the default location" do
91
+ expect(described_class.new(project_id, data).config_route(host)).to eq(
92
+ "http://example.com/2020-06-18/config/#{project_id}/config.json",
93
+ )
94
+ end
95
+ end
96
+
97
+ context "when a config route is specified but is set to an empty string" do
98
+ let(:data) do
99
+ { 'config_route' => '' }
100
+ end
101
+
102
+ it "returns the route with the default instead" do
103
+ expect(described_class.new(project_id, data).config_route(host)).to eq(
104
+ "http://example.com/2020-06-18/config/#{project_id}/config.json",
105
+ )
106
+ end
107
+ end
108
+ end
109
+
110
+ describe "#error_notifications?" do
111
+ context "when the 'errors' setting is present" do
112
+ context "and when it is enabled" do
113
+ let(:data) do
114
+ {
115
+ 'settings' => [
116
+ {
117
+ 'name' => 'errors',
118
+ 'enabled' => true,
119
+ },
120
+ ],
121
+ }
122
+ end
123
+
124
+ it "returns true" do
125
+ expect(described_class.new(project_id, data).error_notifications?)
126
+ .to eq(true)
127
+ end
128
+ end
129
+
130
+ context "and when it is disabled" do
131
+ let(:data) do
132
+ {
133
+ 'settings' => [
134
+ {
135
+ 'name' => 'errors',
136
+ 'enabled' => false,
137
+ },
138
+ ],
139
+ }
140
+ end
141
+
142
+ it "returns false" do
143
+ expect(described_class.new(project_id, data).error_notifications?)
144
+ .to eq(false)
145
+ end
146
+ end
147
+ end
148
+
149
+ context "when the 'errors' setting is missing" do
150
+ let(:data) do
151
+ { 'settings' => [] }
152
+ end
153
+
154
+ it "returns true" do
155
+ expect(described_class.new(project_id, data).error_notifications?)
156
+ .to eq(true)
157
+ end
158
+ end
159
+ end
160
+
161
+ describe "#performance_stats?" do
162
+ context "when the 'apm' setting is present" do
163
+ context "and when it is enabled" do
164
+ let(:data) do
165
+ {
166
+ 'settings' => [
167
+ {
168
+ 'name' => 'apm',
169
+ 'enabled' => true,
170
+ },
171
+ ],
172
+ }
173
+ end
174
+
175
+ it "returns true" do
176
+ expect(described_class.new(project_id, data).performance_stats?)
177
+ .to eq(true)
178
+ end
179
+ end
180
+
181
+ context "and when it is disabled" do
182
+ let(:data) do
183
+ {
184
+ 'settings' => [
185
+ {
186
+ 'name' => 'apm',
187
+ 'enabled' => false,
188
+ },
189
+ ],
190
+ }
191
+ end
192
+
193
+ it "returns false" do
194
+ expect(described_class.new(project_id, data).performance_stats?)
195
+ .to eq(false)
196
+ end
197
+ end
198
+ end
199
+
200
+ context "when the 'apm' setting is missing" do
201
+ let(:data) do
202
+ { 'settings' => [] }
203
+ end
204
+
205
+ it "returns true" do
206
+ expect(described_class.new(project_id, data).performance_stats?)
207
+ .to eq(true)
208
+ end
209
+ end
210
+ end
211
+
212
+ describe "#error_host" do
213
+ context "when the 'errors' setting is present" do
214
+ context "and when 'endpoint' is specified" do
215
+ let(:endpoint) { 'https://api.example.com/' }
216
+
217
+ let(:data) do
218
+ {
219
+ 'settings' => [
220
+ {
221
+ 'name' => 'errors',
222
+ 'enabled' => true,
223
+ 'endpoint' => endpoint,
224
+ },
225
+ ],
226
+ }
227
+ end
228
+
229
+ it "returns the endpoint" do
230
+ expect(described_class.new(project_id, data).error_host).to eq(endpoint)
231
+ end
232
+ end
233
+
234
+ context "and when an endpoint is NOT specified" do
235
+ let(:data) do
236
+ {
237
+ 'settings' => [
238
+ {
239
+ 'name' => 'errors',
240
+ 'enabled' => true,
241
+ },
242
+ ],
243
+ }
244
+ end
245
+
246
+ it "returns nil" do
247
+ expect(described_class.new(project_id, data).error_host).to be_nil
248
+ end
249
+ end
250
+ end
251
+
252
+ context "when the 'errors' setting is missing" do
253
+ let(:data) do
254
+ { 'settings' => [] }
255
+ end
256
+
257
+ it "returns nil" do
258
+ expect(described_class.new(project_id, data).error_host).to be_nil
259
+ end
260
+ end
261
+ end
262
+
263
+ describe "#apm_host" do
264
+ context "when the 'apm' setting is present" do
265
+ context "and when 'endpoint' is specified" do
266
+ let(:endpoint) { 'https://api.example.com/' }
267
+
268
+ let(:data) do
269
+ {
270
+ 'settings' => [
271
+ {
272
+ 'name' => 'apm',
273
+ 'enabled' => true,
274
+ 'endpoint' => endpoint,
275
+ },
276
+ ],
277
+ }
278
+ end
279
+
280
+ it "returns the endpoint" do
281
+ expect(described_class.new(project_id, data).apm_host).to eq(endpoint)
282
+ end
283
+ end
284
+
285
+ context "and when an endpoint is NOT specified" do
286
+ let(:data) do
287
+ {
288
+ 'settings' => [
289
+ {
290
+ 'name' => 'apm',
291
+ 'enabled' => true,
292
+ },
293
+ ],
294
+ }
295
+ end
296
+
297
+ it "returns nil" do
298
+ expect(described_class.new(project_id, data).apm_host).to be_nil
299
+ end
300
+ end
301
+ end
302
+
303
+ context "when the 'apm' setting is missing" do
304
+ let(:data) do
305
+ { 'settings' => [] }
306
+ end
307
+
308
+ it "returns nil" do
309
+ expect(described_class.new(project_id, data).apm_host).to be_nil
310
+ end
311
+ end
312
+ end
313
+
314
+ describe "#to_h" do
315
+ let(:data) do
316
+ {
317
+ 'poll_sec' => 123,
318
+ 'settings' => [
319
+ {
320
+ 'name' => 'apm',
321
+ 'enabled' => false,
322
+ },
323
+ ],
324
+ }
325
+ end
326
+
327
+ subject { described_class.new(project_id, data) }
328
+
329
+ it "returns a hash representation of settings" do
330
+ expect(described_class.new(project_id, data).to_h).to eq(data)
331
+ end
332
+
333
+ it "doesn't allow mutation of the original data object" do
334
+ hash = subject.to_h
335
+ hash['poll_sec'] = 0
336
+
337
+ expect(subject.to_h).to eq(
338
+ 'poll_sec' => 123,
339
+ 'settings' => [
340
+ {
341
+ 'name' => 'apm',
342
+ 'enabled' => false,
343
+ },
344
+ ],
345
+ )
346
+ end
347
+ end
348
+ end