airbrake-ruby 5.2.0 → 6.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (72) hide show
  1. checksums.yaml +4 -4
  2. data/lib/airbrake-ruby/async_sender.rb +3 -1
  3. data/lib/airbrake-ruby/config.rb +3 -3
  4. data/lib/airbrake-ruby/context.rb +51 -0
  5. data/lib/airbrake-ruby/filter_chain.rb +2 -0
  6. data/lib/airbrake-ruby/filters/context_filter.rb +4 -5
  7. data/lib/airbrake-ruby/filters/exception_attributes_filter.rb +1 -1
  8. data/lib/airbrake-ruby/filters/git_last_checkout_filter.rb +2 -2
  9. data/lib/airbrake-ruby/filters/git_repository_filter.rb +1 -1
  10. data/lib/airbrake-ruby/filters/git_revision_filter.rb +1 -1
  11. data/lib/airbrake-ruby/filters/keys_filter.rb +2 -2
  12. data/lib/airbrake-ruby/filters/sql_filter.rb +8 -8
  13. data/lib/airbrake-ruby/filters/thread_filter.rb +1 -1
  14. data/lib/airbrake-ruby/ignorable.rb +0 -2
  15. data/lib/airbrake-ruby/monotonic_time.rb +1 -1
  16. data/lib/airbrake-ruby/notice_notifier.rb +3 -4
  17. data/lib/airbrake-ruby/performance_notifier.rb +39 -40
  18. data/lib/airbrake-ruby/remote_settings/settings_data.rb +1 -1
  19. data/lib/airbrake-ruby/remote_settings.rb +26 -3
  20. data/lib/airbrake-ruby/stat.rb +1 -1
  21. data/lib/airbrake-ruby/tdigest.rb +10 -9
  22. data/lib/airbrake-ruby/thread_pool.rb +8 -6
  23. data/lib/airbrake-ruby/time_truncate.rb +2 -2
  24. data/lib/airbrake-ruby/timed_trace.rb +1 -3
  25. data/lib/airbrake-ruby/version.rb +1 -1
  26. data/lib/airbrake-ruby.rb +24 -23
  27. data/spec/airbrake_spec.rb +139 -76
  28. data/spec/async_sender_spec.rb +10 -8
  29. data/spec/backtrace_spec.rb +13 -10
  30. data/spec/benchmark_spec.rb +5 -3
  31. data/spec/code_hunk_spec.rb +24 -15
  32. data/spec/config/processor_spec.rb +12 -4
  33. data/spec/config/validator_spec.rb +5 -2
  34. data/spec/config_spec.rb +28 -20
  35. data/spec/context_spec.rb +54 -0
  36. data/spec/deploy_notifier_spec.rb +6 -4
  37. data/spec/file_cache_spec.rb +1 -0
  38. data/spec/filter_chain_spec.rb +29 -24
  39. data/spec/filters/context_filter_spec.rb +14 -5
  40. data/spec/filters/dependency_filter_spec.rb +3 -1
  41. data/spec/filters/exception_attributes_filter_spec.rb +5 -3
  42. data/spec/filters/gem_root_filter_spec.rb +5 -2
  43. data/spec/filters/git_last_checkout_filter_spec.rb +10 -12
  44. data/spec/filters/git_repository_filter.rb +9 -9
  45. data/spec/filters/git_revision_filter_spec.rb +20 -20
  46. data/spec/filters/keys_allowlist_spec.rb +25 -16
  47. data/spec/filters/keys_blocklist_spec.rb +25 -18
  48. data/spec/filters/root_directory_filter_spec.rb +3 -3
  49. data/spec/filters/sql_filter_spec.rb +26 -26
  50. data/spec/filters/system_exit_filter_spec.rb +4 -2
  51. data/spec/filters/thread_filter_spec.rb +15 -13
  52. data/spec/loggable_spec.rb +2 -2
  53. data/spec/monotonic_time_spec.rb +8 -6
  54. data/spec/nested_exception_spec.rb +46 -46
  55. data/spec/notice_notifier/options_spec.rb +23 -13
  56. data/spec/notice_notifier_spec.rb +52 -47
  57. data/spec/notice_spec.rb +6 -2
  58. data/spec/performance_notifier_spec.rb +69 -62
  59. data/spec/promise_spec.rb +38 -32
  60. data/spec/remote_settings/callback_spec.rb +27 -8
  61. data/spec/remote_settings/settings_data_spec.rb +4 -4
  62. data/spec/remote_settings_spec.rb +23 -9
  63. data/spec/response_spec.rb +34 -12
  64. data/spec/stashable_spec.rb +5 -5
  65. data/spec/stat_spec.rb +7 -5
  66. data/spec/sync_sender_spec.rb +49 -16
  67. data/spec/tdigest_spec.rb +60 -55
  68. data/spec/thread_pool_spec.rb +65 -56
  69. data/spec/time_truncate_spec.rb +23 -6
  70. data/spec/timed_trace_spec.rb +32 -30
  71. data/spec/truncator_spec.rb +72 -43
  72. metadata +54 -50
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8102082d97168a2204b3e730fc2085d30d06d4ab501e6207d91ce9d67bd91584
4
- data.tar.gz: cf2234bd6fc61e439796b0f664163a12fdd4c39c4256a1c469f06dd1f8132de3
3
+ metadata.gz: f0310134480c9c13f2e8ab87a4c616768c213b590f98edd38e9c9abbd539a7ba
4
+ data.tar.gz: 6f5e8d2411eee4e18ff97ef7d89b3c4a1c8ebb3ba5ec14fafe24c25e850672b1
5
5
  SHA512:
6
- metadata.gz: d5b353597aa2bf9d59fa454f8a6a87fadcdc71685a7e21ef3ec9f74e57b20268e5d6cad2d6d0fe061bdcdbe02e65cf2e5761d4e92c6370ea1d1316972da9d9af
7
- data.tar.gz: d13b77d0f959ff3a5326f054b8df67c9402b216038afd25672df8b6803623ac135bb5366739687fc5c234ae2e127d075828f747bb945d9ba4d48f3f354775f17
6
+ metadata.gz: d6e19e37eca3e38af441560c044ac1472ab06a282c4d8b4873a4c8529b9fb9e9a70634a42b4d0d5fe668ba866649ee115942678e5bbfffa1f60373ccd66e964b
7
+ data.tar.gz: f8b22c23d2534b1f4dc12dd1b5d8bf970504b297a61421cda6d496ec2f1143ac9eb031e2c1b033e8d8a24b94b634c2a17ea475f9ef13d482be08a419a54b1d98
@@ -7,9 +7,10 @@ module Airbrake
7
7
  class AsyncSender
8
8
  include Loggable
9
9
 
10
- def initialize(method = :post)
10
+ def initialize(method = :post, name = 'async-sender')
11
11
  @config = Airbrake::Config.instance
12
12
  @method = method
13
+ @name = name
13
14
  end
14
15
 
15
16
  # Asynchronously sends a notice to Airbrake.
@@ -47,6 +48,7 @@ module Airbrake
47
48
  @thread_pool ||= begin
48
49
  sender = SyncSender.new(@method)
49
50
  ThreadPool.new(
51
+ name: @name,
50
52
  worker_size: @config.workers,
51
53
  queue_size: @config.queue_size,
52
54
  block: proc { |args| sender.send(*args) },
@@ -248,14 +248,14 @@ module Airbrake
248
248
 
249
249
  # @return [Promise] resolved promise if neither of the performance options
250
250
  # reject it, false otherwise
251
- def check_performance_options(resource)
251
+ def check_performance_options(metric)
252
252
  promise = Airbrake::Promise.new
253
253
 
254
254
  if !performance_stats
255
255
  promise.reject("The Performance Stats feature is disabled")
256
- elsif resource.is_a?(Airbrake::Query) && !query_stats
256
+ elsif metric.is_a?(Airbrake::Query) && !query_stats
257
257
  promise.reject("The Query Stats feature is disabled")
258
- elsif resource.is_a?(Airbrake::Queue) && !job_stats
258
+ elsif metric.is_a?(Airbrake::Queue) && !job_stats
259
259
  promise.reject("The Job Stats feature is disabled")
260
260
  else
261
261
  promise
@@ -0,0 +1,51 @@
1
+ module Airbrake
2
+ # Represents a thread-safe Airbrake context object, which carries arbitrary
3
+ # information added via {Airbrake.merge_context} calls.
4
+ #
5
+ # @example
6
+ # Airbrake::Context.current.merge!(foo: 'bar')
7
+ #
8
+ # @api private
9
+ # @since v5.2.1
10
+ class Context
11
+ # Returns current, thread-local, context.
12
+ # @return [self]
13
+ def self.current
14
+ Thread.current[:airbrake_context] ||= new
15
+ end
16
+
17
+ def initialize
18
+ @mutex = Mutex.new
19
+ @context = {}
20
+ end
21
+
22
+ # Merges the given context with the current one.
23
+ #
24
+ # @param [Hash{Object=>Object}] other
25
+ # @return [void]
26
+ def merge!(other)
27
+ @mutex.synchronize do
28
+ @context.merge!(other)
29
+ end
30
+ end
31
+
32
+ # @return [Hash] duplicated Hash context
33
+ def to_h
34
+ @mutex.synchronize do
35
+ @context.dup
36
+ end
37
+ end
38
+
39
+ # @return [Hash] clears (resets) the current context
40
+ def clear
41
+ @mutex.synchronize do
42
+ @context.clear
43
+ end
44
+ end
45
+
46
+ # @return [Boolean] checks whether the context has any data
47
+ def empty?
48
+ @context.empty?
49
+ end
50
+ end
51
+ end
@@ -57,7 +57,9 @@ module Airbrake
57
57
  # @return [void]
58
58
  # @since v3.1.0
59
59
  def delete_filter(filter_class)
60
+ # rubocop:disable Style/ClassEqualityComparison
60
61
  index = @filters.index { |f| f.class.name == filter_class.name }
62
+ # rubocop:enable Style/ClassEqualityComparison
61
63
  @filters.delete_at(index) if index
62
64
  end
63
65
 
@@ -9,8 +9,7 @@ module Airbrake
9
9
  # @return [Integer]
10
10
  attr_reader :weight
11
11
 
12
- def initialize(context)
13
- @context = context
12
+ def initialize
14
13
  @weight = 119
15
14
  @mutex = Mutex.new
16
15
  end
@@ -18,10 +17,10 @@ module Airbrake
18
17
  # @macro call_filter
19
18
  def call(notice)
20
19
  @mutex.synchronize do
21
- return if @context.empty?
20
+ return if Airbrake::Context.current.empty?
22
21
 
23
- notice[:params][:airbrake_context] = @context.dup
24
- @context.clear
22
+ notice[:params][:airbrake_context] = Airbrake::Context.current.to_h
23
+ Airbrake::Context.current.clear
25
24
  end
26
25
  end
27
26
  end
@@ -13,7 +13,7 @@ module Airbrake
13
13
  end
14
14
 
15
15
  # @macro call_filter
16
- def call(notice)
16
+ def call(notice) # rubocop:disable Metrics/AbcSize
17
17
  exception = notice.stash[:exception]
18
18
  return unless exception.respond_to?(:to_airbrake)
19
19
 
@@ -50,7 +50,7 @@ module Airbrake
50
50
  def last_checkout
51
51
  return unless (line = last_checkout_line)
52
52
 
53
- parts = line.chomp.split("\t").first.split(' ')
53
+ parts = line.chomp.split("\t").first.split
54
54
  if parts.size < MIN_HEAD_COLS
55
55
  logger.error(
56
56
  "#{LOG_LABEL} Airbrake::#{self.class.name}: can't parse line: #{line}",
@@ -72,7 +72,7 @@ module Airbrake
72
72
  return unless File.exist?(head_path)
73
73
 
74
74
  last_line = nil
75
- IO.foreach(head_path) do |line|
75
+ File.foreach(head_path) do |line|
76
76
  last_line = line if checkout_line?(line)
77
77
  end
78
78
  last_line
@@ -36,7 +36,7 @@ module Airbrake
36
36
  `cd #{@git_path} && git config --get remote.origin.url`.chomp
37
37
  else
38
38
  "`git remote get-url` is unsupported in git #{@git_version}. " \
39
- 'Consider an upgrade to 2.7+'
39
+ 'Consider an upgrade to 2.7+'
40
40
  end
41
41
 
42
42
  return unless @repository
@@ -57,7 +57,7 @@ module Airbrake
57
57
 
58
58
  File.readlines(packed_refs_path).each do |line|
59
59
  next if %w[# ^].include?(line[0])
60
- next unless (parts = line.split(' ')).size == 2
60
+ next unless (parts = line.split).size == 2
61
61
  return parts.first if parts.last == head
62
62
  end
63
63
 
@@ -82,7 +82,7 @@ module Airbrake
82
82
 
83
83
  private
84
84
 
85
- def filter_hash(hash)
85
+ def filter_hash(hash) # rubocop:disable Metrics/AbcSize
86
86
  return hash unless hash.is_a?(Hash)
87
87
 
88
88
  hash_copy = hash.dup
@@ -103,7 +103,7 @@ module Airbrake
103
103
  end
104
104
 
105
105
  def filter_url_params(url)
106
- url.query = Hash[URI.decode_www_form(url.query)].map do |key, val|
106
+ url.query = URI.decode_www_form(url.query).to_h.map do |key, val|
107
107
  should_filter?(key) ? "#{key}=[Filtered]" : "#{key}=#{val}"
108
108
  end.join('&')
109
109
 
@@ -27,13 +27,13 @@ module Airbrake
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'\(.*?(?:\)'|$)/,
36
+ oracle_quoted_strings: /q'\[.*?(?:\]'|$)|q'\{.*?(?:\}'|$)|q'<.*?(?:>'|$)|q'\(.*?(?:\)'|$)/,
37
37
  # rubocop:enable Layout/LineLength
38
38
  }.freeze
39
39
 
@@ -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
@@ -83,7 +83,7 @@ module Airbrake
83
83
  when Array
84
84
  value = value.map { |elem| sanitize_value(elem) }
85
85
  when Hash
86
- Hash[value.map { |k, v| [k, sanitize_value(v)] }]
86
+ value.transform_values { |v| sanitize_value(v) }
87
87
  else
88
88
  value.to_s
89
89
  end
@@ -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.
@@ -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
@@ -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]
@@ -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,29 +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
- @payload[resource][:total].increment_ms(resource.timing)
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 check_configuration(resource)
114
+ def check_configuration(metric)
116
115
  promise = @config.check_configuration
117
116
  return promise if promise.rejected?
118
117
 
119
- promise = @config.check_performance_options(resource)
118
+ promise = @config.check_performance_options(metric)
120
119
  return promise if promise.rejected?
121
120
 
122
- if resource.timing && resource.timing == 0
121
+ if metric.timing && metric.timing == 0
123
122
  return Promise.new.reject(':timing cannot be zero')
124
123
  end
125
124
 
@@ -129,47 +128,47 @@ module Airbrake
129
128
  def send(sender, payload, promise)
130
129
  raise "payload cannot be empty. Race?" if payload.none?
131
130
 
132
- with_grouped_payload(payload) do |resource_hash, destination|
131
+ with_grouped_payload(payload) do |metric_hash, destination|
133
132
  url = URI.join(
134
133
  @config.apm_host,
135
134
  "api/v5/projects/#{@config.project_id}/#{destination}",
136
135
  )
137
136
 
138
137
  logger.debug do
139
- "#{LOG_LABEL} #{self.class.name}##{__method__}: #{resource_hash}"
138
+ "#{LOG_LABEL} #{self.class.name}##{__method__}: #{metric_hash}"
140
139
  end
141
- sender.send(resource_hash, promise, url)
140
+ sender.send(metric_hash, promise, url)
142
141
  end
143
142
 
144
143
  promise
145
144
  end
146
145
 
147
146
  def with_grouped_payload(raw_payload)
148
- grouped_payload = raw_payload.group_by do |resource, _stats|
149
- [resource.cargo, resource.destination]
147
+ grouped_payload = raw_payload.group_by do |metric, _stats|
148
+ [metric.cargo, metric.destination]
150
149
  end
151
150
 
152
- grouped_payload.each do |(cargo, destination), resources|
151
+ grouped_payload.each do |(cargo, destination), metrics|
153
152
  payload = {}
154
- payload[cargo] = serialize_resources(resources)
153
+ payload[cargo] = serialize_metrics(metrics)
155
154
  payload['environment'] = @config.environment if @config.environment
156
155
 
157
156
  yield(payload, destination)
158
157
  end
159
158
  end
160
159
 
161
- def serialize_resources(resources)
162
- resources.map do |resource, stats|
163
- 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)
164
163
 
165
- if resource.groups.any?
164
+ if metric.groups.any?
166
165
  group_stats = stats.reject { |name, _stat| name == :total }
167
- resource_hash['groups'] = group_stats.merge(group_stats) do |_name, stat|
166
+ metric_hash['groups'] = group_stats.merge(group_stats) do |_name, stat|
168
167
  stat.to_h
169
168
  end
170
169
  end
171
170
 
172
- resource_hash
171
+ metric_hash
173
172
  end
174
173
  end
175
174
  end
@@ -59,7 +59,7 @@ module Airbrake
59
59
  # @return [String] where the config is stored on S3.
60
60
  def config_route(remote_config_host)
61
61
  if @data['config_route'] && !@data['config_route'].empty?
62
- return remote_config_host.chomp('/') + '/' + @data['config_route']
62
+ return "#{remote_config_host.chomp('/')}/#{@data['config_route']}"
63
63
  end
64
64
 
65
65
  format(
@@ -1,7 +1,7 @@
1
1
  module Airbrake
2
2
  # RemoteSettings polls the remote config of the passed project at fixed
3
3
  # intervals. The fetched config is yielded as a callback parameter so that the
4
- # invoker can define read config values.
4
+ # invoker can define read config values. Supports proxies.
5
5
  #
6
6
  # @example Disable/enable error notifications based on the remote value
7
7
  # RemoteSettings.poll do |data|
@@ -43,6 +43,7 @@ module Airbrake
43
43
  @data = SettingsData.new(project_id, {})
44
44
  @host = host
45
45
  @block = block
46
+ @config = Airbrake::Config.instance
46
47
  @poll = nil
47
48
  end
48
49
 
@@ -72,11 +73,16 @@ module Airbrake
72
73
  private
73
74
 
74
75
  def fetch_config
76
+ uri = build_config_uri
77
+ https = build_https(uri)
78
+ req = Net::HTTP::Get.new(uri.request_uri)
75
79
  response = nil
80
+
76
81
  begin
77
- response = Net::HTTP.get_response(build_config_uri)
82
+ response = https.request(req)
78
83
  rescue StandardError => ex
79
- logger.error(ex)
84
+ reason = "#{LOG_LABEL} HTTP error: #{ex}"
85
+ logger.error(reason)
80
86
  return {}
81
87
  end
82
88
 
@@ -101,5 +107,22 @@ module Airbrake
101
107
  uri.query = QUERY_PARAMS
102
108
  uri
103
109
  end
110
+
111
+ def build_https(uri)
112
+ Net::HTTP.new(uri.host, uri.port, *proxy_params).tap do |https|
113
+ https.use_ssl = uri.is_a?(URI::HTTPS)
114
+ if @config.timeout
115
+ https.open_timeout = @config.timeout
116
+ https.read_timeout = @config.timeout
117
+ end
118
+ end
119
+ end
120
+
121
+ def proxy_params
122
+ return unless @config.proxy.key?(:host)
123
+
124
+ [@config.proxy[:host], @config.proxy[:port], @config.proxy[:user],
125
+ @config.proxy[:password]]
126
+ end
104
127
  end
105
128
  end
@@ -4,7 +4,7 @@ module Airbrake
4
4
  # Stat is a data structure that allows accumulating performance data (route
5
5
  # performance, SQL query performance and such). It's powered by TDigests.
6
6
  #
7
- # Usually, one Stat corresponds to one resource (route or query,
7
+ # Usually, one Stat corresponds to one metric (route or query,
8
8
  # etc.). Incrementing a stat means pushing new performance statistics.
9
9
  #
10
10
  # @example
@@ -24,6 +24,7 @@ module Airbrake
24
24
  # @since v3.2.0
25
25
  class Centroid
26
26
  attr_accessor :mean, :n, :cumn, :mean_cumn
27
+
27
28
  def initialize(mean, n, cumn, mean_cumn = nil)
28
29
  @mean = mean
29
30
  @n = n
@@ -130,7 +131,7 @@ module Airbrake
130
131
  points = to_a
131
132
  reset!
132
133
  push_centroid(points.shuffle)
133
- _cumulate(true, true)
134
+ _cumulate(exact: true, force: true)
134
135
  nil
135
136
  end
136
137
 
@@ -175,7 +176,7 @@ module Airbrake
175
176
  elsif item > max[1].mean
176
177
  1.0
177
178
  else
178
- _cumulate(true)
179
+ _cumulate(exact: true)
179
180
  bound = bound_mean(item)
180
181
  lower, upper = bound
181
182
  mean_cumn = lower.mean_cumn
@@ -204,7 +205,7 @@ module Airbrake
204
205
  if size == 0
205
206
  nil
206
207
  else
207
- _cumulate(true)
208
+ _cumulate(exact: true)
208
209
  h = @size * item
209
210
  lower, upper = bound_mean_cumn(h)
210
211
  if lower.nil? && upper.nil?
@@ -255,7 +256,7 @@ module Airbrake
255
256
  array = bytes[start_idx..-1].unpack("G#{size}N#{size}")
256
257
  means, counts = array.each_slice(size).to_a if array.any?
257
258
  when SMALL_ENCODING
258
- means = bytes[start_idx..(start_idx + 4 * size)].unpack("g#{size}")
259
+ means = bytes[start_idx..(start_idx + (4 * size))].unpack("g#{size}")
259
260
  # Decode delta encoding of means
260
261
  x = 0
261
262
  means.map! do |m|
@@ -263,7 +264,7 @@ module Airbrake
263
264
  x = m
264
265
  m
265
266
  end
266
- counts_bytes = bytes[(start_idx + 4 * size)..-1].unpack('C*')
267
+ counts_bytes = bytes[(start_idx + (4 * size))..-1].unpack('C*')
267
268
  counts = []
268
269
  # Decode variable length integer bytes
269
270
  size.times do
@@ -306,7 +307,7 @@ module Airbrake
306
307
  centroid.mean += n * (x - centroid.mean) / (centroid.n + n)
307
308
  end
308
309
 
309
- _cumulate(false, true) if centroid.mean_cumn.nil?
310
+ _cumulate(exact: false, force: true) if centroid.mean_cumn.nil?
310
311
 
311
312
  centroid.cumn += n
312
313
  centroid.mean_cumn += n / 2.0
@@ -314,7 +315,7 @@ module Airbrake
314
315
  end
315
316
 
316
317
  # rubocop:disable Metrics/PerceivedComplexity, Metrics/CyclomaticComplexity
317
- def _cumulate(exact = false, force = false)
318
+ def _cumulate(exact: false, force: false)
318
319
  unless force
319
320
  factor = if @last_cumulate == 0
320
321
  Float::INFINITY
@@ -326,7 +327,7 @@ module Airbrake
326
327
 
327
328
  cumn = 0
328
329
  @centroids.each_value do |c|
329
- c.mean_cumn = cumn + c.n / 2.0
330
+ c.mean_cumn = cumn + (c.n / 2.0)
330
331
  cumn = c.cumn = cumn + c.n
331
332
  end
332
333
  @size = @last_cumulate = cumn
@@ -361,7 +362,7 @@ module Airbrake
361
362
  end
362
363
  end
363
364
 
364
- _cumulate(false)
365
+ _cumulate(exact: false)
365
366
 
366
367
  # If the number of centroids has grown to a very large size,
367
368
  # it may be due to values being inserted in sorted order.