airbrake-ruby 4.15.0 → 6.0.2

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 (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 +71 -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 +4 -1
  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 +5 -3
  50. data/spec/code_hunk_spec.rb +26 -17
  51. data/spec/config/processor_spec.rb +151 -0
  52. data/spec/config/validator_spec.rb +23 -3
  53. data/spec/config_spec.rb +40 -52
  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 +9 -9
  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 +65 -56
  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 +66 -50
@@ -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