airbrake-ruby 4.8.0 → 4.10.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 (61) hide show
  1. checksums.yaml +4 -4
  2. data/lib/airbrake-ruby.rb +84 -5
  3. data/lib/airbrake-ruby/async_sender.rb +3 -3
  4. data/lib/airbrake-ruby/backtrace.rb +2 -2
  5. data/lib/airbrake-ruby/code_hunk.rb +1 -1
  6. data/lib/airbrake-ruby/config.rb +1 -1
  7. data/lib/airbrake-ruby/config/validator.rb +3 -3
  8. data/lib/airbrake-ruby/deploy_notifier.rb +1 -1
  9. data/lib/airbrake-ruby/filters/exception_attributes_filter.rb +2 -2
  10. data/lib/airbrake-ruby/filters/git_last_checkout_filter.rb +2 -2
  11. data/lib/airbrake-ruby/filters/keys_filter.rb +1 -1
  12. data/lib/airbrake-ruby/filters/sql_filter.rb +3 -3
  13. data/lib/airbrake-ruby/filters/thread_filter.rb +1 -1
  14. data/lib/airbrake-ruby/grouppable.rb +12 -0
  15. data/lib/airbrake-ruby/inspectable.rb +2 -2
  16. data/lib/airbrake-ruby/mergeable.rb +12 -0
  17. data/lib/airbrake-ruby/notice.rb +7 -7
  18. data/lib/airbrake-ruby/notice_notifier.rb +1 -1
  19. data/lib/airbrake-ruby/performance_breakdown.rb +2 -1
  20. data/lib/airbrake-ruby/performance_notifier.rb +42 -21
  21. data/lib/airbrake-ruby/query.rb +3 -5
  22. data/lib/airbrake-ruby/queue.rb +52 -0
  23. data/lib/airbrake-ruby/request.rb +3 -5
  24. data/lib/airbrake-ruby/stat.rb +1 -1
  25. data/lib/airbrake-ruby/version.rb +1 -1
  26. data/spec/airbrake_spec.rb +135 -45
  27. data/spec/async_sender_spec.rb +4 -4
  28. data/spec/backtrace_spec.rb +18 -18
  29. data/spec/code_hunk_spec.rb +9 -9
  30. data/spec/config/validator_spec.rb +5 -5
  31. data/spec/config_spec.rb +5 -5
  32. data/spec/deploy_notifier_spec.rb +2 -2
  33. data/spec/filter_chain_spec.rb +1 -1
  34. data/spec/filters/dependency_filter_spec.rb +1 -1
  35. data/spec/filters/gem_root_filter_spec.rb +5 -5
  36. data/spec/filters/git_last_checkout_filter_spec.rb +1 -1
  37. data/spec/filters/git_repository_filter.rb +1 -1
  38. data/spec/filters/git_revision_filter_spec.rb +10 -10
  39. data/spec/filters/keys_blacklist_spec.rb +22 -22
  40. data/spec/filters/keys_whitelist_spec.rb +21 -21
  41. data/spec/filters/root_directory_filter_spec.rb +5 -5
  42. data/spec/filters/sql_filter_spec.rb +53 -53
  43. data/spec/filters/system_exit_filter_spec.rb +1 -1
  44. data/spec/filters/thread_filter_spec.rb +28 -28
  45. data/spec/fixtures/project_root/code.rb +9 -9
  46. data/spec/notice_notifier/options_spec.rb +12 -12
  47. data/spec/notice_notifier_spec.rb +17 -17
  48. data/spec/notice_spec.rb +5 -5
  49. data/spec/performance_notifier_spec.rb +171 -60
  50. data/spec/query_spec.rb +1 -1
  51. data/spec/queue_spec.rb +11 -0
  52. data/spec/request_spec.rb +1 -1
  53. data/spec/response_spec.rb +8 -8
  54. data/spec/spec_helper.rb +2 -2
  55. data/spec/stat_spec.rb +2 -2
  56. data/spec/sync_sender_spec.rb +12 -12
  57. data/spec/tdigest_spec.rb +6 -6
  58. data/spec/thread_pool_spec.rb +5 -5
  59. data/spec/timed_trace_spec.rb +1 -1
  60. data/spec/truncator_spec.rb +12 -12
  61. metadata +7 -2
@@ -116,7 +116,7 @@ module Airbrake
116
116
 
117
117
  logger.warn(
118
118
  "#{LOG_LABEL} falling back to sync delivery because there are no " \
119
- "running async workers"
119
+ "running async workers",
120
120
  )
121
121
  @sync_sender
122
122
  end
@@ -12,6 +12,7 @@ module Airbrake
12
12
  include HashKeyable
13
13
  include Ignorable
14
14
  include Stashable
15
+ include Mergeable
15
16
 
16
17
  def initialize(
17
18
  method:,
@@ -38,7 +39,7 @@ module Airbrake
38
39
  'method' => method,
39
40
  'route' => route,
40
41
  'responseType' => response_type,
41
- 'time' => @start_time_utc
42
+ 'time' => @start_time_utc,
42
43
  }.delete_if { |_key, val| val.nil? }
43
44
  end
44
45
  end
@@ -4,6 +4,7 @@ module Airbrake
4
4
  #
5
5
  # @api public
6
6
  # @since v3.2.0
7
+ # rubocop:disable Metrics/ClassLength
7
8
  class PerformanceNotifier
8
9
  include Inspectable
9
10
  include Loggable
@@ -11,7 +12,8 @@ module Airbrake
11
12
  def initialize
12
13
  @config = Airbrake::Config.instance
13
14
  @flush_period = Airbrake::Config.instance.performance_stats_flush_period
14
- @sender = AsyncSender.new(:put)
15
+ @async_sender = AsyncSender.new(:put)
16
+ @sync_sender = SyncSender.new(:put)
15
17
  @payload = {}
16
18
  @schedule_flush = nil
17
19
  @mutex = Mutex.new
@@ -23,21 +25,14 @@ module Airbrake
23
25
  # @see Airbrake.notify_query
24
26
  # @see Airbrake.notify_request
25
27
  def notify(resource)
26
- promise = @config.check_configuration
27
- return promise if promise.rejected?
28
-
29
- promise = @config.check_performance_options(resource)
30
- return promise if promise.rejected?
31
-
32
- @filter_chain.refine(resource)
33
- return if resource.ignored?
34
-
35
- @mutex.synchronize do
36
- update_payload(resource)
37
- @flush_period > 0 ? schedule_flush : send(@payload, promise)
38
- end
28
+ send_resource(resource, sync: false)
29
+ end
39
30
 
40
- promise.resolve(:success)
31
+ # @param [Hash] resource
32
+ # @since v4.10.0
33
+ # @see Airbrake.notify_queue_sync
34
+ def notify_sync(resource)
35
+ send_resource(resource, sync: true).value
41
36
  end
42
37
 
43
38
  # @see Airbrake.add_performance_filter
@@ -53,7 +48,7 @@ module Airbrake
53
48
  def close
54
49
  @mutex.synchronize do
55
50
  @schedule_flush.kill if @schedule_flush
56
- @sender.close
51
+ @async_sender.close
57
52
  logger.debug("#{LOG_LABEL} performance notifier closed")
58
53
  end
59
54
  end
@@ -61,7 +56,12 @@ module Airbrake
61
56
  private
62
57
 
63
58
  def update_payload(resource)
64
- @payload[resource] ||= { total: Airbrake::Stat.new }
59
+ if (total_stat = @payload[resource])
60
+ @payload.key(total_stat).merge(resource)
61
+ else
62
+ @payload[resource] = { total: Airbrake::Stat.new }
63
+ end
64
+
65
65
  @payload[resource][:total].increment(resource.start_time, resource.end_time)
66
66
 
67
67
  resource.groups.each do |name, ms|
@@ -101,12 +101,32 @@ module Airbrake
101
101
  @payload = {}
102
102
  end
103
103
 
104
- send(payload, Airbrake::Promise.new)
104
+ send(@async_sender, payload, Airbrake::Promise.new)
105
+ end
106
+ end
107
+ end
108
+
109
+ def send_resource(resource, sync:)
110
+ promise = @config.check_configuration
111
+ return promise if promise.rejected?
112
+
113
+ promise = @config.check_performance_options(resource)
114
+ return promise if promise.rejected?
115
+
116
+ @filter_chain.refine(resource)
117
+ return if resource.ignored?
118
+
119
+ @mutex.synchronize do
120
+ update_payload(resource)
121
+ if sync || @flush_period == 0
122
+ send(@sync_sender, @payload, promise)
123
+ else
124
+ schedule_flush
105
125
  end
106
126
  end
107
127
  end
108
128
 
109
- def send(payload, promise)
129
+ def send(sender, payload, promise)
110
130
  signature = "#{self.class.name}##{__method__}"
111
131
  raise "#{signature}: payload (#{payload}) cannot be empty. Race?" if payload.none?
112
132
 
@@ -115,9 +135,9 @@ module Airbrake
115
135
  with_grouped_payload(payload) do |resource_hash, destination|
116
136
  url = URI.join(
117
137
  @config.host,
118
- "api/v5/projects/#{@config.project_id}/#{destination}"
138
+ "api/v5/projects/#{@config.project_id}/#{destination}",
119
139
  )
120
- @sender.send(resource_hash, promise, url)
140
+ sender.send(resource_hash, promise, url)
121
141
  end
122
142
 
123
143
  promise
@@ -152,4 +172,5 @@ module Airbrake
152
172
  end
153
173
  end
154
174
  end
175
+ # rubocop:enable Metrics/ClassLength
155
176
  end
@@ -11,6 +11,8 @@ module Airbrake
11
11
  include HashKeyable
12
12
  include Ignorable
13
13
  include Stashable
14
+ include Mergeable
15
+ include Grouppable
14
16
 
15
17
  def initialize(
16
18
  method:,
@@ -34,10 +36,6 @@ module Airbrake
34
36
  'queries'
35
37
  end
36
38
 
37
- def groups
38
- {}
39
- end
40
-
41
39
  def to_h
42
40
  {
43
41
  'method' => method,
@@ -46,7 +44,7 @@ module Airbrake
46
44
  'time' => @start_time_utc,
47
45
  'function' => func,
48
46
  'file' => file,
49
- 'line' => line
47
+ 'line' => line,
50
48
  }.delete_if { |_key, val| val.nil? }
51
49
  end
52
50
  # rubocop:enable Metrics/ParameterLists, Metrics/BlockLength
@@ -0,0 +1,52 @@
1
+ module Airbrake
2
+ # Queue represents a queue (worker).
3
+ #
4
+ # @see Airbrake.notify_queue
5
+ # @api public
6
+ # @since v4.9.0
7
+ # rubocop:disable Metrics/BlockLength
8
+ Queue = Struct.new(:queue, :error_count, :groups, :start_time, :end_time) do
9
+ include HashKeyable
10
+ include Ignorable
11
+ include Stashable
12
+
13
+ def initialize(
14
+ queue:,
15
+ error_count:,
16
+ groups: {},
17
+ start_time: Time.now,
18
+ end_time: Time.now
19
+ )
20
+ @start_time_utc = TimeTruncate.utc_truncate_minutes(start_time)
21
+ super(queue, error_count, groups, start_time, end_time)
22
+ end
23
+
24
+ def destination
25
+ 'queues-stats'
26
+ end
27
+
28
+ def cargo
29
+ 'queues'
30
+ end
31
+
32
+ def to_h
33
+ {
34
+ 'queue' => queue,
35
+ 'errorCount' => error_count,
36
+ 'time' => @start_time_utc,
37
+ }
38
+ end
39
+
40
+ def hash
41
+ {
42
+ 'queue' => queue,
43
+ 'time' => @start_time_utc,
44
+ }.hash
45
+ end
46
+
47
+ def merge(other)
48
+ self.error_count += other.error_count
49
+ end
50
+ end
51
+ # rubocop:enable Metrics/BlockLength
52
+ end
@@ -9,6 +9,8 @@ module Airbrake
9
9
  include HashKeyable
10
10
  include Ignorable
11
11
  include Stashable
12
+ include Mergeable
13
+ include Grouppable
12
14
 
13
15
  def initialize(
14
16
  method:,
@@ -29,16 +31,12 @@ module Airbrake
29
31
  'routes'
30
32
  end
31
33
 
32
- def groups
33
- {}
34
- end
35
-
36
34
  def to_h
37
35
  {
38
36
  'method' => method,
39
37
  'route' => route,
40
38
  'statusCode' => status_code,
41
- 'time' => @start_time_utc
39
+ 'time' => @start_time_utc,
42
40
  }.delete_if { |_key, val| val.nil? }
43
41
  end
44
42
  end
@@ -32,7 +32,7 @@ module Airbrake
32
32
  'count' => count,
33
33
  'sum' => sum,
34
34
  'sumsq' => sumsq,
35
- 'tdigest' => Base64.strict_encode64(tdigest.as_small_bytes)
35
+ 'tdigest' => Base64.strict_encode64(tdigest.as_small_bytes),
36
36
  }
37
37
  end
38
38
 
@@ -2,5 +2,5 @@
2
2
  # More information: http://semver.org/
3
3
  module Airbrake
4
4
  # @return [String] the library version
5
- AIRBRAKE_RUBY_VERSION = '4.8.0'.freeze
5
+ AIRBRAKE_RUBY_VERSION = '4.10.0'.freeze
6
6
  end
@@ -51,28 +51,6 @@ RSpec.describe Airbrake do
51
51
  expect(described_class).to be_configured
52
52
  end
53
53
 
54
- context "when a notifier was configured" do
55
- before do
56
- expect(described_class).to receive(:configured?).and_return(true)
57
- end
58
-
59
- it "closes previously configured notice notifier" do
60
- expect(described_class).to receive(:close)
61
- described_class.configure {}
62
- end
63
- end
64
-
65
- context "when a notifier wasn't configured" do
66
- before do
67
- expect(described_class).to receive(:configured?).and_return(false)
68
- end
69
-
70
- it "doesn't close previously configured notice notifier" do
71
- expect(described_class).not_to receive(:close)
72
- described_class.configure {}
73
- end
74
- end
75
-
76
54
  context "when called multiple times" do
77
55
  it "doesn't overwrite performance notifier" do
78
56
  described_class.configure {}
@@ -182,19 +160,6 @@ RSpec.describe Airbrake do
182
160
  end
183
161
  end
184
162
 
185
- describe "#reset" do
186
- context "when Airbrake was previously configured" do
187
- before do
188
- expect(described_class).to receive(:configured?).and_return(true)
189
- end
190
-
191
- it "closes notice notifier" do
192
- expect(described_class).to receive(:close)
193
- subject.reset
194
- end
195
- end
196
- end
197
-
198
163
  describe "#notify_request" do
199
164
  context "when :stash key is not provided" do
200
165
  it "doesn't add anything to the stash of the request" do
@@ -206,7 +171,7 @@ RSpec.describe Airbrake do
206
171
  method: 'GET',
207
172
  route: '/',
208
173
  status_code: 200,
209
- start_time: Time.now
174
+ start_time: Time.now,
210
175
  )
211
176
  end
212
177
  end
@@ -222,14 +187,30 @@ RSpec.describe Airbrake do
222
187
  method: 'GET',
223
188
  route: '/',
224
189
  status_code: 200,
225
- start_time: Time.now
190
+ start_time: Time.now,
226
191
  },
227
- request_id: 1
192
+ request_id: 1,
228
193
  )
229
194
  end
230
195
  end
231
196
  end
232
197
 
198
+ describe "#notify_request_sync" do
199
+ it "notifies request synchronously" do
200
+ expect(described_class.performance_notifier).to receive(:notify_sync)
201
+
202
+ described_class.notify_request_sync(
203
+ {
204
+ method: 'GET',
205
+ route: '/',
206
+ status_code: 200,
207
+ start_time: Time.now,
208
+ },
209
+ request_id: 1,
210
+ )
211
+ end
212
+ end
213
+
233
214
  describe "#notify_query" do
234
215
  context "when :stash key is not provided" do
235
216
  it "doesn't add anything to the stash of the query" do
@@ -241,7 +222,7 @@ RSpec.describe Airbrake do
241
222
  method: 'GET',
242
223
  route: '/',
243
224
  query: '',
244
- start_time: Time.now
225
+ start_time: Time.now,
245
226
  )
246
227
  end
247
228
  end
@@ -257,14 +238,30 @@ RSpec.describe Airbrake do
257
238
  method: 'GET',
258
239
  route: '/',
259
240
  query: '',
260
- start_time: Time.now
241
+ start_time: Time.now,
261
242
  },
262
- request_id: 1
243
+ request_id: 1,
263
244
  )
264
245
  end
265
246
  end
266
247
  end
267
248
 
249
+ describe "#notify_query_sync" do
250
+ it "notifies query synchronously" do
251
+ expect(described_class.performance_notifier).to receive(:notify_sync)
252
+
253
+ described_class.notify_query_sync(
254
+ {
255
+ method: 'GET',
256
+ route: '/',
257
+ query: '',
258
+ start_time: Time.now,
259
+ },
260
+ request_id: 1,
261
+ )
262
+ end
263
+ end
264
+
268
265
  describe "#notify_performance_breakdown" do
269
266
  context "when :stash key is not provided" do
270
267
  it "doesn't add anything to the stash of the performance breakdown" do
@@ -276,7 +273,7 @@ RSpec.describe Airbrake do
276
273
  method: 'GET',
277
274
  route: '/',
278
275
  query: '',
279
- start_time: Time.now
276
+ start_time: Time.now,
280
277
  )
281
278
  end
282
279
  end
@@ -284,7 +281,7 @@ RSpec.describe Airbrake do
284
281
  context "when :stash key is provided" do
285
282
  it "adds the value as the stash of the performance breakdown" do
286
283
  expect(
287
- described_class.performance_notifier
284
+ described_class.performance_notifier,
288
285
  ).to receive(:notify) do |performance_breakdown|
289
286
  expect(performance_breakdown.stash).to eq(request_id: 1)
290
287
  end
@@ -295,14 +292,76 @@ RSpec.describe Airbrake do
295
292
  route: '/',
296
293
  response_type: :html,
297
294
  groups: {},
298
- start_time: Time.now
295
+ start_time: Time.now,
296
+ },
297
+ request_id: 1,
298
+ )
299
+ end
300
+ end
301
+ end
302
+
303
+ describe "#notify_performance_breakdown_sync" do
304
+ it "notifies performance breakdown synchronously" do
305
+ expect(described_class.performance_notifier).to receive(:notify_sync)
306
+
307
+ described_class.notify_performance_breakdown_sync(
308
+ {
309
+ method: 'GET',
310
+ route: '/',
311
+ response_type: :html,
312
+ groups: {},
313
+ start_time: Time.now,
314
+ },
315
+ request_id: 1,
316
+ )
317
+ end
318
+ end
319
+
320
+ describe "#notify_queue" do
321
+ context "when :stash key is not provided" do
322
+ it "doesn't add anything to the stash of the queue" do
323
+ expect(described_class.performance_notifier).to receive(:notify) do |queue|
324
+ expect(queue.stash).to be_empty
325
+ end
326
+
327
+ described_class.notify_queue(
328
+ queue: 'bananas',
329
+ error_count: 10,
330
+ )
331
+ end
332
+ end
333
+
334
+ context "when :stash key is provided" do
335
+ it "adds the value as the stash of the queue" do
336
+ expect(described_class.performance_notifier).to receive(:notify) do |queue|
337
+ expect(queue.stash).to eq(request_id: 1)
338
+ end
339
+
340
+ described_class.notify_queue(
341
+ {
342
+ queue: 'bananas',
343
+ error_count: 10,
299
344
  },
300
- request_id: 1
345
+ request_id: 1,
301
346
  )
302
347
  end
303
348
  end
304
349
  end
305
350
 
351
+ describe "#notify_queue_sync" do
352
+ it "notifies queue synchronously" do
353
+ expect(described_class.performance_notifier).to receive(:notify_sync)
354
+
355
+ described_class.notify_queue_sync(
356
+ {
357
+ queue: 'bananas',
358
+ error_count: 10,
359
+ },
360
+ request_id: 1,
361
+ )
362
+ end
363
+ end
364
+
306
365
  describe ".performance_notifier" do
307
366
  it "returns a performance notifier" do
308
367
  expect(described_class.performance_notifier)
@@ -321,4 +380,35 @@ RSpec.describe Airbrake do
321
380
  expect(described_class.deploy_notifier).to be_an(Airbrake::DeployNotifier)
322
381
  end
323
382
  end
383
+
384
+ describe ".close" do
385
+ after { Airbrake.reset }
386
+
387
+ context "when notice_notifier is defined" do
388
+ it "gets closed" do
389
+ expect(Airbrake.notice_notifier).to receive(:close)
390
+ end
391
+ end
392
+
393
+ context "when notice_notifier is undefined" do
394
+ it "doesn't get closed (because it wasn't initialized)" do
395
+ Airbrake.instance_variable_set(:@notice_notifier, nil)
396
+ expect_any_instance_of(Airbrake::NoticeNotifier).not_to receive(:close)
397
+ end
398
+ end
399
+
400
+ context "when performance_notifier is defined" do
401
+ it "gets closed" do
402
+ expect(Airbrake.performance_notifier).to receive(:close)
403
+ end
404
+ end
405
+
406
+ context "when perforance_notifier is undefined" do
407
+ it "doesn't get closed (because it wasn't initialized)" do
408
+ Airbrake.instance_variable_set(:@performance_notifier, nil)
409
+ expect_any_instance_of(Airbrake::PerformanceNotifier)
410
+ .not_to receive(:close)
411
+ end
412
+ end
413
+ end
324
414
  end