airbrake-ruby 4.8.0 → 5.2.0

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