airbrake-ruby 4.8.0 → 4.10.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (62) 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 +3 -2
  20. data/lib/airbrake-ruby/performance_notifier.rb +42 -21
  21. data/lib/airbrake-ruby/query.rb +4 -6
  22. data/lib/airbrake-ruby/queue.rb +52 -0
  23. data/lib/airbrake-ruby/request.rb +4 -6
  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_breakdown_spec.rb +11 -0
  50. data/spec/performance_notifier_spec.rb +171 -60
  51. data/spec/query_spec.rb +11 -1
  52. data/spec/queue_spec.rb +21 -0
  53. data/spec/request_spec.rb +11 -1
  54. data/spec/response_spec.rb +8 -8
  55. data/spec/spec_helper.rb +2 -2
  56. data/spec/stat_spec.rb +2 -2
  57. data/spec/sync_sender_spec.rb +12 -12
  58. data/spec/tdigest_spec.rb +6 -6
  59. data/spec/thread_pool_spec.rb +5 -5
  60. data/spec/timed_trace_spec.rb +1 -1
  61. data/spec/truncator_spec.rb +12 -12
  62. 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:,
@@ -19,7 +20,7 @@ module Airbrake
19
20
  response_type:,
20
21
  groups:,
21
22
  start_time:,
22
- end_time: Time.now
23
+ end_time: start_time + 1
23
24
  )
24
25
  @start_time_utc = TimeTruncate.utc_truncate_minutes(start_time)
25
26
  super(method, route, response_type, groups, start_time, end_time)
@@ -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:,
@@ -20,7 +22,7 @@ module Airbrake
20
22
  file: nil,
21
23
  line: nil,
22
24
  start_time:,
23
- end_time: Time.now
25
+ end_time: start_time + 1
24
26
  )
25
27
  @start_time_utc = TimeTruncate.utc_truncate_minutes(start_time)
26
28
  super(method, route, query, func, file, line, start_time, end_time)
@@ -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: start_time + 1
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,13 +9,15 @@ 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:,
15
17
  route:,
16
18
  status_code:,
17
19
  start_time:,
18
- end_time: Time.now
20
+ end_time: start_time + 1
19
21
  )
20
22
  @start_time_utc = TimeTruncate.utc_truncate_minutes(start_time)
21
23
  super(method, route, status_code, start_time, end_time)
@@ -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.1'.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