airbrake-ruby 4.15.0 → 6.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (96) hide show
  1. checksums.yaml +4 -4
  2. data/lib/airbrake-ruby/async_sender.rb +4 -2
  3. data/lib/airbrake-ruby/backtrace.rb +6 -5
  4. data/lib/airbrake-ruby/config/processor.rb +77 -0
  5. data/lib/airbrake-ruby/config/validator.rb +6 -0
  6. data/lib/airbrake-ruby/config.rb +44 -35
  7. data/lib/airbrake-ruby/context.rb +51 -0
  8. data/lib/airbrake-ruby/file_cache.rb +1 -1
  9. data/lib/airbrake-ruby/filter_chain.rb +3 -0
  10. data/lib/airbrake-ruby/filters/context_filter.rb +4 -5
  11. data/lib/airbrake-ruby/filters/dependency_filter.rb +1 -0
  12. data/lib/airbrake-ruby/filters/exception_attributes_filter.rb +1 -1
  13. data/lib/airbrake-ruby/filters/gem_root_filter.rb +1 -0
  14. data/lib/airbrake-ruby/filters/git_last_checkout_filter.rb +3 -4
  15. data/lib/airbrake-ruby/filters/git_repository_filter.rb +11 -2
  16. data/lib/airbrake-ruby/filters/git_revision_filter.rb +3 -1
  17. data/lib/airbrake-ruby/filters/keys_filter.rb +23 -15
  18. data/lib/airbrake-ruby/filters/root_directory_filter.rb +1 -0
  19. data/lib/airbrake-ruby/filters/sql_filter.rb +11 -11
  20. data/lib/airbrake-ruby/filters/system_exit_filter.rb +1 -0
  21. data/lib/airbrake-ruby/filters/thread_filter.rb +4 -3
  22. data/lib/airbrake-ruby/grouppable.rb +1 -1
  23. data/lib/airbrake-ruby/ignorable.rb +1 -2
  24. data/lib/airbrake-ruby/mergeable.rb +1 -1
  25. data/lib/airbrake-ruby/monotonic_time.rb +1 -1
  26. data/lib/airbrake-ruby/notice.rb +1 -8
  27. data/lib/airbrake-ruby/notice_notifier.rb +4 -4
  28. data/lib/airbrake-ruby/performance_breakdown.rb +1 -6
  29. data/lib/airbrake-ruby/performance_notifier.rb +40 -54
  30. data/lib/airbrake-ruby/promise.rb +1 -0
  31. data/lib/airbrake-ruby/query.rb +1 -6
  32. data/lib/airbrake-ruby/queue.rb +1 -8
  33. data/lib/airbrake-ruby/remote_settings/callback.rb +44 -0
  34. data/lib/airbrake-ruby/remote_settings/settings_data.rb +116 -0
  35. data/lib/airbrake-ruby/remote_settings.rb +128 -0
  36. data/lib/airbrake-ruby/request.rb +1 -8
  37. data/lib/airbrake-ruby/stat.rb +2 -13
  38. data/lib/airbrake-ruby/sync_sender.rb +3 -2
  39. data/lib/airbrake-ruby/tdigest.rb +12 -9
  40. data/lib/airbrake-ruby/thread_pool.rb +9 -6
  41. data/lib/airbrake-ruby/time_truncate.rb +2 -2
  42. data/lib/airbrake-ruby/timed_trace.rb +1 -3
  43. data/lib/airbrake-ruby/truncator.rb +8 -2
  44. data/lib/airbrake-ruby/version.rb +11 -1
  45. data/lib/airbrake-ruby.rb +44 -54
  46. data/spec/airbrake_spec.rb +178 -92
  47. data/spec/async_sender_spec.rb +10 -8
  48. data/spec/backtrace_spec.rb +39 -36
  49. data/spec/benchmark_spec.rb +7 -5
  50. data/spec/code_hunk_spec.rb +26 -17
  51. data/spec/config/processor_spec.rb +167 -0
  52. data/spec/config/validator_spec.rb +23 -3
  53. data/spec/config_spec.rb +43 -55
  54. data/spec/context_spec.rb +54 -0
  55. data/spec/deploy_notifier_spec.rb +6 -4
  56. data/spec/file_cache_spec.rb +1 -0
  57. data/spec/filter_chain_spec.rb +29 -24
  58. data/spec/filters/context_filter_spec.rb +14 -5
  59. data/spec/filters/dependency_filter_spec.rb +3 -1
  60. data/spec/filters/exception_attributes_filter_spec.rb +5 -3
  61. data/spec/filters/gem_root_filter_spec.rb +9 -6
  62. data/spec/filters/git_last_checkout_filter_spec.rb +10 -12
  63. data/spec/filters/{git_repository_filter.rb → git_repository_filter_spec.rb} +26 -15
  64. data/spec/filters/git_revision_filter_spec.rb +20 -20
  65. data/spec/filters/keys_allowlist_spec.rb +26 -16
  66. data/spec/filters/keys_blocklist_spec.rb +35 -18
  67. data/spec/filters/root_directory_filter_spec.rb +7 -7
  68. data/spec/filters/sql_filter_spec.rb +28 -28
  69. data/spec/filters/system_exit_filter_spec.rb +4 -2
  70. data/spec/filters/thread_filter_spec.rb +16 -14
  71. data/spec/loggable_spec.rb +2 -2
  72. data/spec/monotonic_time_spec.rb +8 -6
  73. data/spec/nested_exception_spec.rb +46 -46
  74. data/spec/notice_notifier/options_spec.rb +25 -15
  75. data/spec/notice_notifier_spec.rb +54 -49
  76. data/spec/notice_spec.rb +7 -3
  77. data/spec/performance_breakdown_spec.rb +0 -12
  78. data/spec/performance_notifier_spec.rb +69 -87
  79. data/spec/promise_spec.rb +38 -32
  80. data/spec/query_spec.rb +1 -11
  81. data/spec/queue_spec.rb +1 -13
  82. data/spec/remote_settings/callback_spec.rb +162 -0
  83. data/spec/remote_settings/settings_data_spec.rb +348 -0
  84. data/spec/remote_settings_spec.rb +201 -0
  85. data/spec/request_spec.rb +1 -13
  86. data/spec/response_spec.rb +34 -12
  87. data/spec/spec_helper.rb +4 -4
  88. data/spec/stashable_spec.rb +5 -5
  89. data/spec/stat_spec.rb +7 -14
  90. data/spec/sync_sender_spec.rb +52 -17
  91. data/spec/tdigest_spec.rb +61 -56
  92. data/spec/thread_pool_spec.rb +67 -58
  93. data/spec/time_truncate_spec.rb +23 -6
  94. data/spec/timed_trace_spec.rb +32 -30
  95. data/spec/truncator_spec.rb +72 -43
  96. metadata +67 -51
@@ -23,23 +23,23 @@ module Airbrake
23
23
 
24
24
  # @return [Hash{Symbol=>Regexp}] matchers for certain features of SQL
25
25
  ALL_FEATURES = {
26
- # rubocop:disable Metrics/LineLength
26
+ # rubocop:disable Layout/LineLength
27
27
  single_quotes: /'(?:[^']|'')*?(?:\\'.*|'(?!'))/,
28
28
  double_quotes: /"(?:[^"]|"")*?(?:\\".*|"(?!"))/,
29
29
  dollar_quotes: /(\$(?!\d)[^$]*?\$).*?(?:\1|$)/,
30
- uuids: /\{?(?:[0-9a-fA-F]\-*){32}\}?/,
30
+ uuids: /\{?(?:[0-9a-fA-F]-*){32}\}?/,
31
31
  numeric_literals: /\b-?(?:[0-9]+\.)?[0-9]+([eE][+-]?[0-9]+)?\b/,
32
32
  boolean_literals: /\b(?:true|false|null)\b/i,
33
33
  hexadecimal_literals: /0x[0-9a-fA-F]+/,
34
34
  comments: /(?:#|--).*?(?=\r|\n|$)/i,
35
35
  multi_line_comments: %r{/\*(?:[^/]|/[^*])*?(?:\*/|/\*.*)},
36
- oracle_quoted_strings: /q'\[.*?(?:\]'|$)|q'\{.*?(?:\}'|$)|q'\<.*?(?:\>'|$)|q'\(.*?(?:\)'|$)/
37
- # rubocop:enable Metrics/LineLength
36
+ oracle_quoted_strings: /q'\[.*?(?:\]'|$)|q'\{.*?(?:\}'|$)|q'<.*?(?:>'|$)|q'\(.*?(?:\)'|$)/,
37
+ # rubocop:enable Layout/LineLength
38
38
  }.freeze
39
39
 
40
40
  # @return [Regexp] the regexp that is applied after the feature regexps
41
41
  # were used
42
- POST_FILTER = /(?<=[values|in ]\().+(?=\))/i
42
+ POST_FILTER = /(?<=[values|in ]\().+(?=\))/i.freeze
43
43
 
44
44
  # @return [Hash{Symbol=>Array<Symbol>}] a set of features that corresponds
45
45
  # to a certain dialect
@@ -108,20 +108,20 @@ module Airbrake
108
108
  @regexp = Regexp.union(features)
109
109
  end
110
110
 
111
- # @param [Airbrake::Query] resource
112
- def call(resource)
113
- return unless resource.respond_to?(:query)
111
+ # @param [Airbrake::Query] metric
112
+ def call(metric)
113
+ return unless metric.respond_to?(:query)
114
114
 
115
- query = resource.query
115
+ query = metric.query
116
116
  if IGNORED_QUERIES.any? { |q| q =~ query }
117
- resource.ignore!
117
+ metric.ignore!
118
118
  return
119
119
  end
120
120
 
121
121
  q = query.gsub(@regexp, FILTERED)
122
122
  q.gsub!(POST_FILTER, FILTERED) if q =~ POST_FILTER
123
123
  q = ERROR_MSG if UNMATCHED_PAIR[@dialect] =~ q
124
- resource.query = q
124
+ metric.query = q
125
125
  end
126
126
  end
127
127
  end
@@ -16,6 +16,7 @@ module Airbrake
16
16
  # @macro call_filter
17
17
  def call(notice)
18
18
  return if notice[:errors].none? { |error| error[:type] == SYSTEM_EXIT_TYPE }
19
+
19
20
  notice.ignore!
20
21
  end
21
22
  end
@@ -41,8 +41,7 @@ module Airbrake
41
41
  thread_info[:fiber_variables] = vars
42
42
  end
43
43
 
44
- # Present in Ruby 2.3+.
45
- if th.respond_to?(:name) && (name = th.name)
44
+ if (name = th.name)
46
45
  thread_info[:name] = name
47
46
  end
48
47
 
@@ -56,6 +55,7 @@ module Airbrake
56
55
  def thread_variables(th)
57
56
  th.thread_variables.map.with_object({}) do |var, h|
58
57
  next if var.to_s.start_with?(IGNORE_PREFIX)
58
+
59
59
  h[var] = sanitize_value(th.thread_variable_get(var))
60
60
  end
61
61
  end
@@ -63,6 +63,7 @@ module Airbrake
63
63
  def fiber_variables(th)
64
64
  th.keys.map.with_object({}) do |key, h|
65
65
  next if key.to_s.start_with?(IGNORE_PREFIX)
66
+
66
67
  h[key] = sanitize_value(th[key])
67
68
  end
68
69
  end
@@ -82,7 +83,7 @@ module Airbrake
82
83
  when Array
83
84
  value = value.map { |elem| sanitize_value(elem) }
84
85
  when Hash
85
- Hash[value.map { |k, v| [k, sanitize_value(v)] }]
86
+ value.transform_values { |v| sanitize_value(v) }
86
87
  else
87
88
  value.to_s
88
89
  end
@@ -2,7 +2,7 @@ module Airbrake
2
2
  # Grouppable adds the `#groups` method, so that we don't need to define it in
3
3
  # all of performance models every time we add a model without groups.
4
4
  #
5
- # @since 4.9.0
5
+ # @since v4.9.0
6
6
  # @api private
7
7
  module Grouppable
8
8
  def groups
@@ -18,11 +18,9 @@ module Airbrake
18
18
  # Checks whether the instance was ignored.
19
19
  # @return [Boolean]
20
20
  # @see #ignore!
21
- # rubocop:disable Style/DoubleNegation
22
21
  def ignored?
23
22
  !!ignored
24
23
  end
25
- # rubocop:enable Style/DoubleNegation
26
24
 
27
25
  # Ignores an instance. Ignored instances must never reach the Airbrake
28
26
  # dashboard.
@@ -38,6 +36,7 @@ module Airbrake
38
36
  # @raise [Airbrake::Error] when instance is ignored
39
37
  def raise_if_ignored
40
38
  return unless ignored?
39
+
41
40
  raise Airbrake::Error, "cannot access ignored #{self.class}"
42
41
  end
43
42
  end
@@ -2,7 +2,7 @@ module Airbrake
2
2
  # Mergeable adds the `#merge` method, so that we don't need to define it in
3
3
  # all of performance models every time we add a model.
4
4
  #
5
- # @since 4.9.0
5
+ # @since v4.9.0
6
6
  # @api private
7
7
  module Mergeable
8
8
  def merge(_other)
@@ -39,7 +39,7 @@ module Airbrake
39
39
 
40
40
  def time_in_nanoseconds
41
41
  time = Time.now
42
- time.to_i * (10**9) + time.nsec
42
+ (time.to_i * (10**9)) + time.nsec
43
43
  end
44
44
 
45
45
  end
@@ -4,19 +4,12 @@ module Airbrake
4
4
  #
5
5
  # @since v1.0.0
6
6
  class Notice
7
- # @return [Hash{Symbol=>String}] the information about the notifier library
8
- NOTIFIER = {
9
- name: 'airbrake-ruby'.freeze,
10
- version: Airbrake::AIRBRAKE_RUBY_VERSION,
11
- url: 'https://github.com/airbrake/airbrake-ruby'.freeze,
12
- }.freeze
13
-
14
7
  # @return [Hash{Symbol=>String,Hash}] the information to be displayed in the
15
8
  # Context tab in the dashboard
16
9
  CONTEXT = {
17
10
  os: RUBY_PLATFORM,
18
11
  language: "#{RUBY_ENGINE}/#{RUBY_VERSION}".freeze,
19
- notifier: NOTIFIER,
12
+ notifier: Airbrake::NOTIFIER_INFO,
20
13
  }.freeze
21
14
 
22
15
  # @return [Integer] the maxium size of the JSON payload in bytes
@@ -20,14 +20,13 @@ module Airbrake
20
20
 
21
21
  def initialize
22
22
  @config = Airbrake::Config.instance
23
- @context = {}
24
23
  @filter_chain = FilterChain.new
25
- @async_sender = AsyncSender.new
24
+ @async_sender = AsyncSender.new(:post, self.class.name)
26
25
  @sync_sender = SyncSender.new
27
26
 
28
27
  DEFAULT_FILTERS.each { |filter| add_filter(filter.new) }
29
28
 
30
- add_filter(Airbrake::Filters::ContextFilter.new(@context))
29
+ add_filter(Airbrake::Filters::ContextFilter.new)
31
30
  add_filter(Airbrake::Filters::ExceptionAttributesFilter.new)
32
31
  end
33
32
 
@@ -79,7 +78,7 @@ module Airbrake
79
78
 
80
79
  # @see Airbrake.merge_context
81
80
  def merge_context(context)
82
- @context.merge!(context)
81
+ Airbrake::Context.current.merge!(context)
83
82
  end
84
83
 
85
84
  # @return [Boolean]
@@ -135,6 +134,7 @@ module Airbrake
135
134
  # If true, then it's likely an internal library error. In this case return
136
135
  # at least some backtrace to simplify debugging.
137
136
  return caller_copy if clean_bt.empty?
137
+
138
138
  clean_bt
139
139
  end
140
140
  end
@@ -12,16 +12,13 @@ module Airbrake
12
12
  include Stashable
13
13
  include Mergeable
14
14
 
15
- attr_accessor :method, :route, :response_type, :groups, :start_time,
16
- :end_time, :timing, :time
15
+ attr_accessor :method, :route, :response_type, :groups, :timing, :time
17
16
 
18
17
  def initialize(
19
18
  method:,
20
19
  route:,
21
20
  response_type:,
22
21
  groups:,
23
- start_time: Time.now,
24
- end_time: start_time + 1,
25
22
  timing: nil,
26
23
  time: Time.now
27
24
  )
@@ -30,8 +27,6 @@ module Airbrake
30
27
  @route = route
31
28
  @response_type = response_type
32
29
  @groups = groups
33
- @start_time = start_time
34
- @end_time = end_time
35
30
  @timing = timing
36
31
  @time = time
37
32
  end
@@ -1,6 +1,6 @@
1
1
  module Airbrake
2
- # QueryNotifier aggregates information about SQL queries and periodically sends
3
- # collected data to Airbrake.
2
+ # PerformanceNotifier aggregates performance data and periodically sends it to
3
+ # Airbrake.
4
4
  #
5
5
  # @api public
6
6
  # @since v3.2.0
@@ -12,7 +12,7 @@ module Airbrake
12
12
  def initialize
13
13
  @config = Airbrake::Config.instance
14
14
  @flush_period = Airbrake::Config.instance.performance_stats_flush_period
15
- @async_sender = AsyncSender.new(:put)
15
+ @async_sender = AsyncSender.new(:put, self.class.name)
16
16
  @sync_sender = SyncSender.new(:put)
17
17
  @schedule_flush = nil
18
18
  @filter_chain = FilterChain.new
@@ -21,20 +21,20 @@ module Airbrake
21
21
  @has_payload = @payload.new_cond
22
22
  end
23
23
 
24
- # @param [Hash] resource
24
+ # @param [Hash] metric
25
25
  # @see Airbrake.notify_query
26
26
  # @see Airbrake.notify_request
27
- def notify(resource)
27
+ def notify(metric)
28
28
  @payload.synchronize do
29
- send_resource(resource, sync: false)
29
+ send_metric(metric, sync: false)
30
30
  end
31
31
  end
32
32
 
33
- # @param [Hash] resource
33
+ # @param [Hash] metric
34
34
  # @since v4.10.0
35
35
  # @see Airbrake.notify_queue_sync
36
- def notify_sync(resource)
37
- send_resource(resource, sync: true).value
36
+ def notify_sync(metric)
37
+ send_metric(metric, sync: true).value
38
38
  end
39
39
 
40
40
  # @see Airbrake.add_performance_filter
@@ -51,7 +51,6 @@ module Airbrake
51
51
  @payload.synchronize do
52
52
  @schedule_flush.kill if @schedule_flush
53
53
  @async_sender.close
54
- logger.debug("#{LOG_LABEL} performance notifier closed")
55
54
  end
56
55
  end
57
56
 
@@ -79,16 +78,16 @@ module Airbrake
79
78
  end
80
79
  end
81
80
 
82
- def send_resource(resource, sync:)
83
- promise = check_configuration(resource)
81
+ def send_metric(metric, sync:)
82
+ promise = check_configuration(metric)
84
83
  return promise if promise.rejected?
85
84
 
86
- @filter_chain.refine(resource)
87
- if resource.ignored?
88
- return Promise.new.reject("#{resource.class} was ignored by a filter")
85
+ @filter_chain.refine(metric)
86
+ if metric.ignored?
87
+ return Promise.new.reject("#{metric.class} was ignored by a filter")
89
88
  end
90
89
 
91
- update_payload(resource)
90
+ update_payload(metric)
92
91
  if sync || @flush_period == 0
93
92
  send(@sync_sender, @payload, promise)
94
93
  else
@@ -97,42 +96,29 @@ module Airbrake
97
96
  end
98
97
  end
99
98
 
100
- def update_payload(resource)
101
- if (total_stat = @payload[resource])
102
- @payload.key(total_stat).merge(resource)
99
+ def update_payload(metric)
100
+ if (total_stat = @payload[metric])
101
+ @payload.key(total_stat).merge(metric)
103
102
  else
104
- @payload[resource] = { total: Airbrake::Stat.new }
103
+ @payload[metric] = { total: Airbrake::Stat.new }
105
104
  end
106
105
 
107
- update_total(resource, @payload[resource][:total])
106
+ @payload[metric][:total].increment_ms(metric.timing)
108
107
 
109
- resource.groups.each do |name, ms|
110
- @payload[resource][name] ||= Airbrake::Stat.new
111
- @payload[resource][name].increment_ms(ms)
108
+ metric.groups.each do |name, ms|
109
+ @payload[metric][name] ||= Airbrake::Stat.new
110
+ @payload[metric][name].increment_ms(ms)
112
111
  end
113
112
  end
114
113
 
115
- def update_total(resource, total)
116
- if resource.timing
117
- total.increment_ms(resource.timing)
118
- else
119
- loc = caller_locations(6..6).first
120
- Kernel.warn(
121
- "#{loc.path}:#{loc.lineno}: warning: :start_time and :end_time are " \
122
- "deprecated. Use :timing & :time instead",
123
- )
124
- total.increment(resource.start_time, resource.end_time)
125
- end
126
- end
127
-
128
- def check_configuration(resource)
114
+ def check_configuration(metric)
129
115
  promise = @config.check_configuration
130
116
  return promise if promise.rejected?
131
117
 
132
- promise = @config.check_performance_options(resource)
118
+ promise = @config.check_performance_options(metric)
133
119
  return promise if promise.rejected?
134
120
 
135
- if resource.timing && resource.timing == 0
121
+ if metric.timing && metric.timing == 0
136
122
  return Promise.new.reject(':timing cannot be zero')
137
123
  end
138
124
 
@@ -142,47 +128,47 @@ module Airbrake
142
128
  def send(sender, payload, promise)
143
129
  raise "payload cannot be empty. Race?" if payload.none?
144
130
 
145
- with_grouped_payload(payload) do |resource_hash, destination|
131
+ with_grouped_payload(payload) do |metric_hash, destination|
146
132
  url = URI.join(
147
- @config.host,
133
+ @config.apm_host,
148
134
  "api/v5/projects/#{@config.project_id}/#{destination}",
149
135
  )
150
136
 
151
137
  logger.debug do
152
- "#{LOG_LABEL} #{self.class.name}##{__method__}: #{resource_hash}"
138
+ "#{LOG_LABEL} #{self.class.name}##{__method__}: #{metric_hash}"
153
139
  end
154
- sender.send(resource_hash, promise, url)
140
+ sender.send(metric_hash, promise, url)
155
141
  end
156
142
 
157
143
  promise
158
144
  end
159
145
 
160
146
  def with_grouped_payload(raw_payload)
161
- grouped_payload = raw_payload.group_by do |resource, _stats|
162
- [resource.cargo, resource.destination]
147
+ grouped_payload = raw_payload.group_by do |metric, _stats|
148
+ [metric.cargo, metric.destination]
163
149
  end
164
150
 
165
- grouped_payload.each do |(cargo, destination), resources|
151
+ grouped_payload.each do |(cargo, destination), metrics|
166
152
  payload = {}
167
- payload[cargo] = serialize_resources(resources)
153
+ payload[cargo] = serialize_metrics(metrics)
168
154
  payload['environment'] = @config.environment if @config.environment
169
155
 
170
156
  yield(payload, destination)
171
157
  end
172
158
  end
173
159
 
174
- def serialize_resources(resources)
175
- resources.map do |resource, stats|
176
- resource_hash = resource.to_h.merge!(stats[:total].to_h)
160
+ def serialize_metrics(metrics)
161
+ metrics.map do |metric, stats|
162
+ metric_hash = metric.to_h.merge!(stats[:total].to_h)
177
163
 
178
- if resource.groups.any?
164
+ if metric.groups.any?
179
165
  group_stats = stats.reject { |name, _stat| name == :total }
180
- resource_hash['groups'] = group_stats.merge(group_stats) do |_name, stat|
166
+ metric_hash['groups'] = group_stats.merge(group_stats) do |_name, stat|
181
167
  stat.to_h
182
168
  end
183
169
  end
184
170
 
185
- resource_hash
171
+ metric_hash
186
172
  end
187
173
  end
188
174
  end
@@ -103,6 +103,7 @@ module Airbrake
103
103
  # needed for compatibility but it shouldn't exist in the future
104
104
  def value
105
105
  return @value['ok'] if resolved?
106
+
106
107
  @value
107
108
  end
108
109
  end
@@ -12,8 +12,7 @@ module Airbrake
12
12
  include Mergeable
13
13
  include Grouppable
14
14
 
15
- attr_accessor :method, :route, :query, :func, :file, :line, :start_time,
16
- :end_time, :timing, :time
15
+ attr_accessor :method, :route, :query, :func, :file, :line, :timing, :time
17
16
 
18
17
  def initialize(
19
18
  method:,
@@ -22,8 +21,6 @@ module Airbrake
22
21
  func: nil,
23
22
  file: nil,
24
23
  line: nil,
25
- start_time: Time.now,
26
- end_time: start_time + 1,
27
24
  timing: nil,
28
25
  time: Time.now
29
26
  )
@@ -34,8 +31,6 @@ module Airbrake
34
31
  @func = func
35
32
  @file = file
36
33
  @line = line
37
- @start_time = start_time
38
- @end_time = end_time
39
34
  @timing = timing
40
35
  @time = time
41
36
  end
@@ -4,21 +4,17 @@ module Airbrake
4
4
  # @see Airbrake.notify_queue
5
5
  # @api public
6
6
  # @since v4.9.0
7
- # rubocop:disable Metrics/ParameterLists
8
7
  class Queue
9
8
  include HashKeyable
10
9
  include Ignorable
11
10
  include Stashable
12
11
 
13
- attr_accessor :queue, :error_count, :groups, :start_time, :end_time,
14
- :timing, :time
12
+ attr_accessor :queue, :error_count, :groups, :timing, :time
15
13
 
16
14
  def initialize(
17
15
  queue:,
18
16
  error_count:,
19
17
  groups: {},
20
- start_time: Time.now,
21
- end_time: start_time + 1,
22
18
  timing: nil,
23
19
  time: Time.now
24
20
  )
@@ -26,8 +22,6 @@ module Airbrake
26
22
  @queue = queue
27
23
  @error_count = error_count
28
24
  @groups = groups
29
- @start_time = start_time
30
- @end_time = end_time
31
25
  @timing = timing
32
26
  @time = time
33
27
  end
@@ -68,5 +62,4 @@ module Airbrake
68
62
  ''
69
63
  end
70
64
  end
71
- # rubocop:enable Metrics/ParameterLists
72
65
  end
@@ -0,0 +1,44 @@
1
+ module Airbrake
2
+ class RemoteSettings
3
+ # Callback is a class that provides a callback for the config poller, which
4
+ # updates the local config according to the data.
5
+ #
6
+ # @api private
7
+ # @since v5.0.2
8
+ class Callback
9
+ def initialize(config)
10
+ @config = config
11
+ @orig_error_notifications = config.error_notifications
12
+ @orig_performance_stats = config.performance_stats
13
+ end
14
+
15
+ # @param [Airbrake::RemoteSettings::SettingsData] data
16
+ # @return [void]
17
+ def call(data)
18
+ @config.logger.debug do
19
+ "#{LOG_LABEL} applying remote settings: #{data.to_h}"
20
+ end
21
+
22
+ @config.error_host = data.error_host if data.error_host
23
+ @config.apm_host = data.apm_host if data.apm_host
24
+
25
+ process_error_notifications(data)
26
+ process_performance_stats(data)
27
+ end
28
+
29
+ private
30
+
31
+ def process_error_notifications(data)
32
+ return unless @orig_error_notifications
33
+
34
+ @config.error_notifications = data.error_notifications?
35
+ end
36
+
37
+ def process_performance_stats(data)
38
+ return unless @orig_performance_stats
39
+
40
+ @config.performance_stats = data.performance_stats?
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,116 @@
1
+ module Airbrake
2
+ class RemoteSettings
3
+ # SettingsData is a container, which wraps JSON payload returned by the
4
+ # remote settings API. It exposes the payload via convenient methods and
5
+ # also ensures that in case some data from the payload is missing, a default
6
+ # value would be returned instead.
7
+ #
8
+ # @example
9
+ # # Create the object and pass initial data (empty hash).
10
+ # settings_data = SettingsData.new({})
11
+ #
12
+ # settings_data.interval #=> 600
13
+ #
14
+ # @since v5.0.0
15
+ # @api private
16
+ class SettingsData
17
+ # @return [Integer] how frequently we should poll the config API
18
+ DEFAULT_INTERVAL = 600
19
+
20
+ # @return [String] API version of the S3 API to poll
21
+ API_VER = '2020-06-18'.freeze
22
+
23
+ # @return [String] what path to poll
24
+ CONFIG_ROUTE_PATTERN =
25
+ "%<host>s/#{API_VER}/config/%<project_id>s/config.json".freeze
26
+
27
+ # @return [Hash{Symbol=>String}] the hash of all supported settings where
28
+ # the value is the name of the setting returned by the API
29
+ SETTINGS = {
30
+ errors: 'errors'.freeze,
31
+ apm: 'apm'.freeze,
32
+ }.freeze
33
+
34
+ # @param [Integer] project_id
35
+ # @param [Hash{String=>Object}] data
36
+ def initialize(project_id, data)
37
+ @project_id = project_id
38
+ @data = data
39
+ end
40
+
41
+ # Merges the given +hash+ with internal data.
42
+ #
43
+ # @param [Hash{String=>Object}] hash
44
+ # @return [self]
45
+ def merge!(hash)
46
+ @data.merge!(hash)
47
+
48
+ self
49
+ end
50
+
51
+ # @return [Integer] how frequently we should poll for the config
52
+ def interval
53
+ return DEFAULT_INTERVAL if !@data.key?('poll_sec') || !@data['poll_sec']
54
+
55
+ @data['poll_sec'] > 0 ? @data['poll_sec'] : DEFAULT_INTERVAL
56
+ end
57
+
58
+ # @param [String] remote_config_host
59
+ # @return [String] where the config is stored on S3.
60
+ def config_route(remote_config_host)
61
+ if @data['config_route'] && !@data['config_route'].empty?
62
+ return "#{remote_config_host.chomp('/')}/#{@data['config_route']}"
63
+ end
64
+
65
+ format(
66
+ CONFIG_ROUTE_PATTERN,
67
+ host: remote_config_host.chomp('/'),
68
+ project_id: @project_id,
69
+ )
70
+ end
71
+
72
+ # @return [Boolean] whether error notifications are enabled
73
+ def error_notifications?
74
+ return true unless (s = find_setting(SETTINGS[:errors]))
75
+
76
+ s['enabled']
77
+ end
78
+
79
+ # @return [Boolean] whether APM is enabled
80
+ def performance_stats?
81
+ return true unless (s = find_setting(SETTINGS[:apm]))
82
+
83
+ s['enabled']
84
+ end
85
+
86
+ # @return [String, nil] the host, which provides the API endpoint to which
87
+ # exceptions should be sent
88
+ def error_host
89
+ return unless (s = find_setting(SETTINGS[:errors]))
90
+
91
+ s['endpoint']
92
+ end
93
+
94
+ # @return [String, nil] the host, which provides the API endpoint to which
95
+ # APM data should be sent
96
+ def apm_host
97
+ return unless (s = find_setting(SETTINGS[:apm]))
98
+
99
+ s['endpoint']
100
+ end
101
+
102
+ # @return [Hash{String=>Object}] raw representation of JSON payload
103
+ def to_h
104
+ @data.dup
105
+ end
106
+
107
+ private
108
+
109
+ def find_setting(name)
110
+ return unless @data.key?('settings')
111
+
112
+ @data['settings'].find { |s| s['name'] == name }
113
+ end
114
+ end
115
+ end
116
+ end