airbrake-ruby 4.8.0 → 4.11.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (63) hide show
  1. checksums.yaml +4 -4
  2. data/lib/airbrake-ruby.rb +101 -25
  3. data/lib/airbrake-ruby/async_sender.rb +3 -3
  4. data/lib/airbrake-ruby/backtrace.rb +2 -2
  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 +1 -1
  8. data/lib/airbrake-ruby/config/validator.rb +3 -3
  9. data/lib/airbrake-ruby/deploy_notifier.rb +1 -1
  10. data/lib/airbrake-ruby/filters/exception_attributes_filter.rb +2 -2
  11. data/lib/airbrake-ruby/filters/git_last_checkout_filter.rb +2 -2
  12. data/lib/airbrake-ruby/filters/keys_filter.rb +1 -1
  13. data/lib/airbrake-ruby/filters/sql_filter.rb +3 -3
  14. data/lib/airbrake-ruby/filters/thread_filter.rb +1 -1
  15. data/lib/airbrake-ruby/grouppable.rb +12 -0
  16. data/lib/airbrake-ruby/inspectable.rb +2 -2
  17. data/lib/airbrake-ruby/mergeable.rb +12 -0
  18. data/lib/airbrake-ruby/notice.rb +7 -7
  19. data/lib/airbrake-ruby/notice_notifier.rb +3 -2
  20. data/lib/airbrake-ruby/performance_breakdown.rb +12 -6
  21. data/lib/airbrake-ruby/performance_notifier.rb +69 -22
  22. data/lib/airbrake-ruby/query.rb +15 -11
  23. data/lib/airbrake-ruby/queue.rb +56 -0
  24. data/lib/airbrake-ruby/request.rb +14 -12
  25. data/lib/airbrake-ruby/stat.rb +1 -1
  26. data/lib/airbrake-ruby/version.rb +1 -1
  27. data/spec/airbrake_spec.rb +135 -45
  28. data/spec/async_sender_spec.rb +4 -4
  29. data/spec/backtrace_spec.rb +18 -18
  30. data/spec/code_hunk_spec.rb +9 -9
  31. data/spec/config/validator_spec.rb +5 -5
  32. data/spec/config_spec.rb +5 -9
  33. data/spec/deploy_notifier_spec.rb +2 -2
  34. data/spec/filter_chain_spec.rb +1 -1
  35. data/spec/filters/dependency_filter_spec.rb +1 -1
  36. data/spec/filters/gem_root_filter_spec.rb +5 -5
  37. data/spec/filters/git_last_checkout_filter_spec.rb +1 -1
  38. data/spec/filters/git_repository_filter.rb +1 -1
  39. data/spec/filters/git_revision_filter_spec.rb +10 -10
  40. data/spec/filters/keys_blacklist_spec.rb +22 -22
  41. data/spec/filters/keys_whitelist_spec.rb +21 -21
  42. data/spec/filters/root_directory_filter_spec.rb +5 -5
  43. data/spec/filters/sql_filter_spec.rb +53 -55
  44. data/spec/filters/system_exit_filter_spec.rb +1 -1
  45. data/spec/filters/thread_filter_spec.rb +28 -28
  46. data/spec/fixtures/project_root/code.rb +9 -9
  47. data/spec/notice_notifier/options_spec.rb +12 -12
  48. data/spec/notice_notifier_spec.rb +18 -18
  49. data/spec/notice_spec.rb +5 -5
  50. data/spec/performance_breakdown_spec.rb +11 -0
  51. data/spec/performance_notifier_spec.rb +243 -72
  52. data/spec/query_spec.rb +11 -1
  53. data/spec/queue_spec.rb +21 -0
  54. data/spec/request_spec.rb +11 -1
  55. data/spec/response_spec.rb +8 -8
  56. data/spec/spec_helper.rb +2 -2
  57. data/spec/stat_spec.rb +2 -2
  58. data/spec/sync_sender_spec.rb +12 -12
  59. data/spec/tdigest_spec.rb +6 -6
  60. data/spec/thread_pool_spec.rb +5 -5
  61. data/spec/timed_trace_spec.rb +1 -1
  62. data/spec/truncator_spec.rb +12 -12
  63. metadata +7 -2
@@ -21,7 +21,7 @@ module Airbrake
21
21
  project_id: @config.project_id,
22
22
  project_key: @config.project_key,
23
23
  host: @config.host,
24
- filter_chain: @filter_chain.inspect
24
+ filter_chain: @filter_chain.inspect,
25
25
  )
26
26
  end
27
27
 
@@ -30,7 +30,7 @@ module Airbrake
30
30
  q.text("#<#{self.class}:0x#{(object_id << 1).to_s(16).rjust(16, '0')} ")
31
31
  q.text(
32
32
  "project_id=\"#{@config.project_id}\" project_key=\"#{@config.project_key}\" " \
33
- "host=\"#{@config.host}\" filter_chain="
33
+ "host=\"#{@config.host}\" filter_chain=",
34
34
  )
35
35
  q.pp(@filter_chain)
36
36
  q.text('>')
@@ -0,0 +1,12 @@
1
+ module Airbrake
2
+ # Mergeable adds the `#merge` method, so that we don't need to define it in
3
+ # all of performance models every time we add a model.
4
+ #
5
+ # @since 4.9.0
6
+ # @api private
7
+ module Mergeable
8
+ def merge(_other)
9
+ nil
10
+ end
11
+ end
12
+ end
@@ -8,7 +8,7 @@ module Airbrake
8
8
  NOTIFIER = {
9
9
  name: 'airbrake-ruby'.freeze,
10
10
  version: Airbrake::AIRBRAKE_RUBY_VERSION,
11
- url: 'https://github.com/airbrake/airbrake-ruby'.freeze
11
+ url: 'https://github.com/airbrake/airbrake-ruby'.freeze,
12
12
  }.freeze
13
13
 
14
14
  # @return [Hash{Symbol=>String,Hash}] the information to be displayed in the
@@ -16,7 +16,7 @@ module Airbrake
16
16
  CONTEXT = {
17
17
  os: RUBY_PLATFORM,
18
18
  language: "#{RUBY_ENGINE}/#{RUBY_VERSION}".freeze,
19
- notifier: NOTIFIER
19
+ notifier: NOTIFIER,
20
20
  }.freeze
21
21
 
22
22
  # @return [Integer] the maxium size of the JSON payload in bytes
@@ -32,7 +32,7 @@ module Airbrake
32
32
  IOError,
33
33
  NotImplementedError,
34
34
  JSON::GeneratorError,
35
- Encoding::UndefinedConversionError
35
+ Encoding::UndefinedConversionError,
36
36
  ].freeze
37
37
 
38
38
  # @return [Array<Symbol>] the list of keys that can be be overwritten with
@@ -60,10 +60,10 @@ module Airbrake
60
60
  errors: NestedException.new(exception).as_json,
61
61
  context: context,
62
62
  environment: {
63
- program_name: $PROGRAM_NAME
63
+ program_name: $PROGRAM_NAME,
64
64
  },
65
65
  session: {},
66
- params: params
66
+ params: params,
67
67
  }
68
68
  @truncator = Airbrake::Truncator.new(PAYLOAD_MAX_SIZE)
69
69
 
@@ -138,7 +138,7 @@ module Airbrake
138
138
  # Make sure we always send hostname.
139
139
  hostname: HOSTNAME,
140
140
 
141
- severity: DEFAULT_SEVERITY
141
+ severity: DEFAULT_SEVERITY,
142
142
  }.merge(CONTEXT).delete_if { |_key, val| val.nil? || val.empty? }
143
143
  end
144
144
 
@@ -152,7 +152,7 @@ module Airbrake
152
152
  logger.error(
153
153
  "#{LOG_LABEL} truncation failed. File an issue at " \
154
154
  "https://github.com/airbrake/airbrake-ruby " \
155
- "and attach the following payload: #{@payload}"
155
+ "and attach the following payload: #{@payload}",
156
156
  )
157
157
  end
158
158
 
@@ -55,7 +55,8 @@ module Airbrake
55
55
  def build_notice(exception, params = {})
56
56
  if @async_sender.closed?
57
57
  raise Airbrake::Error,
58
- "attempted to build #{exception} with closed Airbrake instance"
58
+ "Airbrake is closed; can't build exception: " \
59
+ "#{exception.class}: #{exception}"
59
60
  end
60
61
 
61
62
  if exception.is_a?(Airbrake::Notice)
@@ -116,7 +117,7 @@ module Airbrake
116
117
 
117
118
  logger.warn(
118
119
  "#{LOG_LABEL} falling back to sync delivery because there are no " \
119
- "running async workers"
120
+ "running async workers",
120
121
  )
121
122
  @sync_sender
122
123
  end
@@ -7,22 +7,28 @@ module Airbrake
7
7
  # @since v4.2.0
8
8
  # rubocop:disable Metrics/BlockLength, Metrics/ParameterLists
9
9
  PerformanceBreakdown = Struct.new(
10
- :method, :route, :response_type, :groups, :start_time, :end_time
10
+ :method, :route, :response_type, :groups, :start_time, :end_time, :timing,
11
+ :time
11
12
  ) do
12
13
  include HashKeyable
13
14
  include Ignorable
14
15
  include Stashable
16
+ include Mergeable
15
17
 
16
18
  def initialize(
17
19
  method:,
18
20
  route:,
19
21
  response_type:,
20
22
  groups:,
21
- start_time:,
22
- end_time: Time.now
23
+ start_time: Time.now,
24
+ end_time: start_time + 1,
25
+ timing: nil,
26
+ time: Time.now
23
27
  )
24
- @start_time_utc = TimeTruncate.utc_truncate_minutes(start_time)
25
- super(method, route, response_type, groups, start_time, end_time)
28
+ @time_utc = TimeTruncate.utc_truncate_minutes(time)
29
+ super(
30
+ method, route, response_type, groups, start_time, end_time, timing, time
31
+ )
26
32
  end
27
33
 
28
34
  def destination
@@ -38,7 +44,7 @@ module Airbrake
38
44
  'method' => method,
39
45
  'route' => route,
40
46
  'responseType' => response_type,
41
- 'time' => @start_time_utc
47
+ 'time' => @time_utc,
42
48
  }.delete_if { |_key, val| val.nil? }
43
49
  end
44
50
  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,8 +56,13 @@ module Airbrake
61
56
  private
62
57
 
63
58
  def update_payload(resource)
64
- @payload[resource] ||= { total: Airbrake::Stat.new }
65
- @payload[resource][:total].increment(resource.start_time, resource.end_time)
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
+ update_total(resource, @payload[resource][:total])
66
66
 
67
67
  resource.groups.each do |name, ms|
68
68
  @payload[resource][name] ||= Airbrake::Stat.new
@@ -70,6 +70,19 @@ module Airbrake
70
70
  end
71
71
  end
72
72
 
73
+ def update_total(resource, total)
74
+ if resource.timing
75
+ total.increment_ms(resource.timing)
76
+ else
77
+ loc = caller_locations(6..6).first
78
+ Kernel.warn(
79
+ "#{loc.path}:#{loc.lineno}: warning: :start_time and :end_time are " \
80
+ "deprecated. Use :timing & :time instead",
81
+ )
82
+ total.increment(resource.start_time, resource.end_time)
83
+ end
84
+ end
85
+
73
86
  def schedule_flush
74
87
  return if @payload.empty?
75
88
 
@@ -101,12 +114,45 @@ module Airbrake
101
114
  @payload = {}
102
115
  end
103
116
 
104
- send(payload, Airbrake::Promise.new)
117
+ send(@async_sender, payload, Airbrake::Promise.new)
118
+ end
119
+ end
120
+ end
121
+
122
+ def send_resource(resource, sync:)
123
+ promise = check_configuration(resource)
124
+ return promise if promise.rejected?
125
+
126
+ @filter_chain.refine(resource)
127
+ if resource.ignored?
128
+ return Promise.new.reject("#{resource.class} was ignored by a filter")
129
+ end
130
+
131
+ @mutex.synchronize do
132
+ update_payload(resource)
133
+ if sync || @flush_period == 0
134
+ send(@sync_sender, @payload, promise)
135
+ else
136
+ schedule_flush
105
137
  end
106
138
  end
107
139
  end
108
140
 
109
- def send(payload, promise)
141
+ def check_configuration(resource)
142
+ promise = @config.check_configuration
143
+ return promise if promise.rejected?
144
+
145
+ promise = @config.check_performance_options(resource)
146
+ return promise if promise.rejected?
147
+
148
+ if resource.timing && resource.timing == 0
149
+ return Promise.new.reject(':timing cannot be zero')
150
+ end
151
+
152
+ Promise.new
153
+ end
154
+
155
+ def send(sender, payload, promise)
110
156
  signature = "#{self.class.name}##{__method__}"
111
157
  raise "#{signature}: payload (#{payload}) cannot be empty. Race?" if payload.none?
112
158
 
@@ -115,9 +161,9 @@ module Airbrake
115
161
  with_grouped_payload(payload) do |resource_hash, destination|
116
162
  url = URI.join(
117
163
  @config.host,
118
- "api/v5/projects/#{@config.project_id}/#{destination}"
164
+ "api/v5/projects/#{@config.project_id}/#{destination}",
119
165
  )
120
- @sender.send(resource_hash, promise, url)
166
+ sender.send(resource_hash, promise, url)
121
167
  end
122
168
 
123
169
  promise
@@ -152,4 +198,5 @@ module Airbrake
152
198
  end
153
199
  end
154
200
  end
201
+ # rubocop:enable Metrics/ClassLength
155
202
  end
@@ -6,11 +6,14 @@ module Airbrake
6
6
  # @since v3.2.0
7
7
  # rubocop:disable Metrics/ParameterLists, Metrics/BlockLength
8
8
  Query = Struct.new(
9
- :method, :route, :query, :func, :file, :line, :start_time, :end_time
9
+ :method, :route, :query, :func, :file, :line, :start_time, :end_time,
10
+ :timing, :time
10
11
  ) do
11
12
  include HashKeyable
12
13
  include Ignorable
13
14
  include Stashable
15
+ include Mergeable
16
+ include Grouppable
14
17
 
15
18
  def initialize(
16
19
  method:,
@@ -19,11 +22,16 @@ module Airbrake
19
22
  func: nil,
20
23
  file: nil,
21
24
  line: nil,
22
- start_time:,
23
- end_time: Time.now
25
+ start_time: Time.now,
26
+ end_time: start_time + 1,
27
+ timing: nil,
28
+ time: Time.now
24
29
  )
25
- @start_time_utc = TimeTruncate.utc_truncate_minutes(start_time)
26
- super(method, route, query, func, file, line, start_time, end_time)
30
+ @time_utc = TimeTruncate.utc_truncate_minutes(time)
31
+ super(
32
+ method, route, query, func, file, line, start_time, end_time, timing,
33
+ time
34
+ )
27
35
  end
28
36
 
29
37
  def destination
@@ -34,19 +42,15 @@ module Airbrake
34
42
  'queries'
35
43
  end
36
44
 
37
- def groups
38
- {}
39
- end
40
-
41
45
  def to_h
42
46
  {
43
47
  'method' => method,
44
48
  'route' => route,
45
49
  'query' => query,
46
- 'time' => @start_time_utc,
50
+ 'time' => @time_utc,
47
51
  'function' => func,
48
52
  'file' => file,
49
- 'line' => line
53
+ 'line' => line,
50
54
  }.delete_if { |_key, val| val.nil? }
51
55
  end
52
56
  # rubocop:enable Metrics/ParameterLists, Metrics/BlockLength
@@ -0,0 +1,56 @@
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, Metrics/ParameterLists
8
+ Queue = Struct.new(
9
+ :queue, :error_count, :groups, :start_time, :end_time, :timing, :time
10
+ ) do
11
+ include HashKeyable
12
+ include Ignorable
13
+ include Stashable
14
+
15
+ def initialize(
16
+ queue:,
17
+ error_count:,
18
+ groups: {},
19
+ start_time: Time.now,
20
+ end_time: start_time + 1,
21
+ timing: nil,
22
+ time: Time.now
23
+ )
24
+ @time_utc = TimeTruncate.utc_truncate_minutes(time)
25
+ super(queue, error_count, groups, start_time, end_time, timing, time)
26
+ end
27
+
28
+ def destination
29
+ 'queues-stats'
30
+ end
31
+
32
+ def cargo
33
+ 'queues'
34
+ end
35
+
36
+ def to_h
37
+ {
38
+ 'queue' => queue,
39
+ 'errorCount' => error_count,
40
+ 'time' => @time_utc,
41
+ }
42
+ end
43
+
44
+ def hash
45
+ {
46
+ 'queue' => queue,
47
+ 'time' => @time_utc,
48
+ }.hash
49
+ end
50
+
51
+ def merge(other)
52
+ self.error_count += other.error_count
53
+ end
54
+ end
55
+ # rubocop:enable Metrics/BlockLength, Metrics/ParameterLists
56
+ end
@@ -4,21 +4,27 @@ module Airbrake
4
4
  # @see Airbrake.notify_request
5
5
  # @api public
6
6
  # @since v3.2.0
7
- # rubocop:disable Metrics/BlockLength
8
- Request = Struct.new(:method, :route, :status_code, :start_time, :end_time) do
7
+ # rubocop:disable Metrics/BlockLength, Metrics/ParameterLists
8
+ Request = Struct.new(
9
+ :method, :route, :status_code, :start_time, :end_time, :timing, :time
10
+ ) do
9
11
  include HashKeyable
10
12
  include Ignorable
11
13
  include Stashable
14
+ include Mergeable
15
+ include Grouppable
12
16
 
13
17
  def initialize(
14
18
  method:,
15
19
  route:,
16
20
  status_code:,
17
- start_time:,
18
- end_time: Time.now
21
+ start_time: Time.now,
22
+ end_time: start_time + 1,
23
+ timing: nil,
24
+ time: Time.now
19
25
  )
20
- @start_time_utc = TimeTruncate.utc_truncate_minutes(start_time)
21
- super(method, route, status_code, start_time, end_time)
26
+ @time_utc = TimeTruncate.utc_truncate_minutes(time)
27
+ super(method, route, status_code, start_time, end_time, timing, time)
22
28
  end
23
29
 
24
30
  def destination
@@ -29,18 +35,14 @@ module Airbrake
29
35
  'routes'
30
36
  end
31
37
 
32
- def groups
33
- {}
34
- end
35
-
36
38
  def to_h
37
39
  {
38
40
  'method' => method,
39
41
  'route' => route,
40
42
  'statusCode' => status_code,
41
- 'time' => @start_time_utc
43
+ 'time' => @time_utc,
42
44
  }.delete_if { |_key, val| val.nil? }
43
45
  end
44
46
  end
45
- # rubocop:enable Metrics/BlockLength
47
+ # rubocop:enable Metrics/BlockLength, Metrics/ParameterLists
46
48
  end