airbrake-ruby 5.0.0 → 5.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e98183a856071ce3d105c36adc9aa22a8ee7071b93958fd70fb673d2735b3977
4
- data.tar.gz: 80464931a1362c4d845ffee737ed7aa1e46693136023b1f08006c49de98e62eb
3
+ metadata.gz: 8102082d97168a2204b3e730fc2085d30d06d4ab501e6207d91ce9d67bd91584
4
+ data.tar.gz: cf2234bd6fc61e439796b0f664163a12fdd4c39c4256a1c469f06dd1f8132de3
5
5
  SHA512:
6
- metadata.gz: 8c32fc631e3af6348a871094dde31452d4a6fdef834596933e179d2acdbbd4935751715007fa7c4dcfce3639ebbd55f61b15f06428015647e4cbcac590ef7aaf
7
- data.tar.gz: 23968940c6f1dfb500be5ab479c1d524cdc9f80e40808b4ff08d7f4b03c97baad603d97504c58a5300ddfa7ec9b1a4b5bd44a24a6a4a15776a82778ef2a0c324
6
+ metadata.gz: d5b353597aa2bf9d59fa454f8a6a87fadcdc71685a7e21ef3ec9f74e57b20268e5d6cad2d6d0fe061bdcdbe02e65cf2e5761d4e92c6370ea1d1316972da9d9af
7
+ data.tar.gz: d13b77d0f959ff3a5326f054b8df67c9402b216038afd25672df8b6803623ac135bb5366739687fc5c234ae2e127d075828f747bb945d9ba4d48f3f354775f17
@@ -13,6 +13,7 @@ require 'airbrake-ruby/grouppable'
13
13
  require 'airbrake-ruby/config'
14
14
  require 'airbrake-ruby/config/validator'
15
15
  require 'airbrake-ruby/config/processor'
16
+ require 'airbrake-ruby/remote_settings/callback'
16
17
  require 'airbrake-ruby/remote_settings/settings_data'
17
18
  require 'airbrake-ruby/remote_settings'
18
19
  require 'airbrake-ruby/promise'
@@ -121,13 +121,21 @@ module Airbrake
121
121
  # @return [Boolean] true if the library should send error reports to
122
122
  # Airbrake, false otherwise
123
123
  # @api public
124
- # @since 5.0.0
124
+ # @since v5.0.0
125
125
  attr_accessor :error_notifications
126
126
 
127
- # @return [String] the host such as which should be used for fetching remote
128
- # configuration options (example: "https://bucket-name.s3.amazonaws.com")
127
+ # @return [String] the host which should be used for fetching remote
128
+ # configuration options
129
+ # @api public
130
+ # @since v5.0.0
129
131
  attr_accessor :remote_config_host
130
132
 
133
+ # @return [String] true if notifier should periodically fetch remote
134
+ # configuration, false otherwise
135
+ # @api public
136
+ # @since v5.2.0
137
+ attr_accessor :remote_config
138
+
131
139
  class << self
132
140
  # @return [Config]
133
141
  attr_writer :instance
@@ -140,7 +148,7 @@ module Airbrake
140
148
 
141
149
  # @param [Hash{Symbol=>Object}] user_config the hash to be used to build the
142
150
  # config
143
- # rubocop:disable Metrics/AbcSize
151
+ # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
144
152
  def initialize(user_config = {})
145
153
  self.proxy = {}
146
154
  self.queue_size = 100
@@ -151,7 +159,7 @@ module Airbrake
151
159
  self.project_key = user_config[:project_key]
152
160
  self.error_host = 'https://api.airbrake.io'
153
161
  self.apm_host = 'https://api.airbrake.io'
154
- self.remote_config_host = 'https://v1-production-notifier-configs.s3.amazonaws.com'
162
+ self.remote_config_host = 'https://notifier-configs.airbrake.io'
155
163
 
156
164
  self.ignore_environments = []
157
165
 
@@ -171,10 +179,11 @@ module Airbrake
171
179
  self.query_stats = true
172
180
  self.job_stats = true
173
181
  self.error_notifications = true
182
+ self.remote_config = true
174
183
 
175
184
  merge(user_config)
176
185
  end
177
- # rubocop:enable Metrics/AbcSize
186
+ # rubocop:enable Metrics/AbcSize, Metrics/MethodLength
178
187
 
179
188
  # The full URL to the Airbrake Notice API. Based on the +:error_host+ option.
180
189
  # @return [URI] the endpoint address
@@ -3,7 +3,7 @@ module Airbrake
3
3
  # Processor is a helper class, which is responsible for setting default
4
4
  # config values, default notifier filters and remote configuration changes.
5
5
  #
6
- # @since 5.0.0
6
+ # @since v5.0.0
7
7
  # @api private
8
8
  class Processor
9
9
  # @param [Airbrake::Config] config
@@ -18,6 +18,7 @@ module Airbrake
18
18
  @blocklist_keys = @config.blocklist_keys
19
19
  @allowlist_keys = @config.allowlist_keys
20
20
  @project_id = @config.project_id
21
+ @poll_callback = Airbrake::RemoteSettings::Callback.new(config)
21
22
  end
22
23
 
23
24
  # @param [Airbrake::NoticeNotifier] notifier
@@ -40,13 +41,13 @@ module Airbrake
40
41
 
41
42
  # @return [Airbrake::RemoteSettings]
42
43
  def process_remote_configuration
44
+ return unless @config.remote_config
43
45
  return unless @project_id
46
+ return if @config.environment == 'test'
44
47
 
45
- RemoteSettings.poll(
46
- @project_id,
47
- @config.remote_config_host,
48
- &method(:poll_callback)
49
- )
48
+ RemoteSettings.poll(@project_id, @config.remote_config_host) do |data|
49
+ @poll_callback.call(data)
50
+ end
50
51
  end
51
52
 
52
53
  # @param [Airbrake::NoticeNotifier] notifier
@@ -65,20 +66,6 @@ module Airbrake
65
66
  notifier.add_filter(filter.new(@config.root_directory))
66
67
  end
67
68
  end
68
-
69
- # @param [Airbrake::RemoteSettings::SettingsData] data
70
- # @return [void]
71
- def poll_callback(data)
72
- @config.logger.debug(
73
- "#{LOG_LABEL} applying remote settings: #{data.to_h}",
74
- )
75
-
76
- @config.error_host = data.error_host if data.error_host
77
- @config.apm_host = data.apm_host if data.apm_host
78
-
79
- @config.error_notifications = data.error_notifications?
80
- @config.performance_stats = data.performance_stats?
81
- end
82
69
  end
83
70
  end
84
71
  end
@@ -40,7 +40,7 @@ module Airbrake
40
40
  data.empty?
41
41
  end
42
42
 
43
- # @since 4.7.0
43
+ # @since v4.7.0
44
44
  # @return [void]
45
45
  def self.reset
46
46
  @data = {}
@@ -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
 
@@ -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
@@ -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)
@@ -8,22 +8,11 @@ module Airbrake
8
8
  # config.error_notifications = data.error_notifications?
9
9
  # end
10
10
  #
11
- # When {#poll} is called, it will try to load remote settings from disk, so
12
- # that it doesn't wait on the result from the API call.
13
- #
14
- # When {#stop_polling} is called, the current config will be dumped to disk.
15
- #
16
- # @since 5.0.0
11
+ # @since v5.0.0
17
12
  # @api private
18
13
  class RemoteSettings
19
14
  include Airbrake::Loggable
20
15
 
21
- # @return [String] the path to the persistent config
22
- CONFIG_DUMP_PATH = File.join(
23
- File.expand_path(__dir__),
24
- '../../config/config.json',
25
- ).freeze
26
-
27
16
  # @return [Hash{Symbol=>String}] metadata to be attached to every GET
28
17
  # request
29
18
  QUERY_PARAMS = URI.encode_www_form(
@@ -33,6 +22,9 @@ module Airbrake
33
22
  language: "#{RUBY_ENGINE}/#{RUBY_VERSION}".freeze,
34
23
  ).freeze
35
24
 
25
+ # @return [String]
26
+ HTTP_OK = '200'.freeze
27
+
36
28
  # Polls remote config of the given project.
37
29
  #
38
30
  # @param [Integer] project_id
@@ -54,18 +46,11 @@ module Airbrake
54
46
  @poll = nil
55
47
  end
56
48
 
57
- # Polls remote config of the given project in background. Loads local config
58
- # first (if exists).
49
+ # Polls remote config of the given project in background.
59
50
  #
60
51
  # @return [self]
61
52
  def poll
62
53
  @poll ||= Thread.new do
63
- begin
64
- load_config
65
- rescue StandardError => ex
66
- logger.error("#{LOG_LABEL} config loading failed: #{ex}")
67
- end
68
-
69
54
  @block.call(@data)
70
55
 
71
56
  loop do
@@ -77,17 +62,11 @@ module Airbrake
77
62
  self
78
63
  end
79
64
 
80
- # Stops the background poller thread. Dumps current config to disk.
65
+ # Stops the background poller thread.
81
66
  #
82
67
  # @return [void]
83
68
  def stop_polling
84
69
  @poll.kill if @poll
85
-
86
- begin
87
- dump_config
88
- rescue StandardError => ex
89
- logger.error("#{LOG_LABEL} config dumping failed: #{ex}")
90
- end
91
70
  end
92
71
 
93
72
  private
@@ -95,22 +74,20 @@ module Airbrake
95
74
  def fetch_config
96
75
  response = nil
97
76
  begin
98
- response = Net::HTTP.get(build_config_uri)
77
+ response = Net::HTTP.get_response(build_config_uri)
99
78
  rescue StandardError => ex
100
79
  logger.error(ex)
101
80
  return {}
102
81
  end
103
82
 
104
- # AWS S3 API returns XML when request is not valid. In this case we just
105
- # print the returned body and exit the method.
106
- if response.start_with?('<?xml ')
107
- logger.error(response)
83
+ unless response.code == HTTP_OK
84
+ logger.error(response.body)
108
85
  return {}
109
86
  end
110
87
 
111
88
  json = nil
112
89
  begin
113
- json = JSON.parse(response)
90
+ json = JSON.parse(response.body)
114
91
  rescue JSON::ParserError => ex
115
92
  logger.error(ex)
116
93
  return {}
@@ -124,22 +101,5 @@ module Airbrake
124
101
  uri.query = QUERY_PARAMS
125
102
  uri
126
103
  end
127
-
128
- def load_config
129
- config_dir = File.dirname(CONFIG_DUMP_PATH)
130
- Dir.mkdir(config_dir) unless File.directory?(config_dir)
131
-
132
- return unless File.exist?(CONFIG_DUMP_PATH)
133
-
134
- config = File.read(CONFIG_DUMP_PATH)
135
- @data.merge!(JSON.parse(config))
136
- end
137
-
138
- def dump_config
139
- config_dir = File.dirname(CONFIG_DUMP_PATH)
140
- Dir.mkdir(config_dir) unless File.directory?(config_dir)
141
-
142
- File.write(CONFIG_DUMP_PATH, JSON.dump(@data.to_h))
143
- end
144
104
  end
145
105
  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
@@ -11,7 +11,7 @@ module Airbrake
11
11
  #
12
12
  # settings_data.interval #=> 600
13
13
  #
14
- # @since 5.0.0
14
+ # @since v5.0.0
15
15
  # @api private
16
16
  class SettingsData
17
17
  # @return [Integer] how frequently we should poll the config API
@@ -58,17 +58,13 @@ module Airbrake
58
58
  # @param [String] remote_config_host
59
59
  # @return [String] where the config is stored on S3.
60
60
  def config_route(remote_config_host)
61
- if !@data.key?('config_route') || !@data['config_route']
62
- return format(
63
- CONFIG_ROUTE_PATTERN,
64
- host: remote_config_host.chomp('/'),
65
- project_id: @project_id,
66
- )
61
+ if @data['config_route'] && !@data['config_route'].empty?
62
+ return remote_config_host.chomp('/') + '/' + @data['config_route']
67
63
  end
68
64
 
69
65
  format(
70
66
  CONFIG_ROUTE_PATTERN,
71
- host: @data['config_route'].chomp('/'),
67
+ host: remote_config_host.chomp('/'),
72
68
  project_id: @project_id,
73
69
  )
74
70
  end
@@ -3,10 +3,10 @@
3
3
  module Airbrake
4
4
  # @return [String] the library version
5
5
  # @api public
6
- AIRBRAKE_RUBY_VERSION = '5.0.0'.freeze
6
+ AIRBRAKE_RUBY_VERSION = '5.2.0'.freeze
7
7
 
8
8
  # @return [Hash{Symbol=>String}] the information about the notifier library
9
- # @since 5.0.0
9
+ # @since v5.0.0
10
10
  # @api public
11
11
  NOTIFIER_INFO = {
12
12
  name: 'airbrake-ruby'.freeze,
@@ -53,9 +53,18 @@ RSpec.describe Airbrake::Config::Processor do
53
53
  end
54
54
  end
55
55
 
56
+ context "when the config sets environment to 'test'" do
57
+ let(:config) { Airbrake::Config.new(project_id: 123, environment: 'test') }
58
+
59
+ it "doesn't set remote settings" do
60
+ expect(Airbrake::RemoteSettings).not_to receive(:poll)
61
+ described_class.new(config).process_remote_configuration
62
+ end
63
+ end
64
+
56
65
  context "when the config defines a project_id" do
57
66
  let(:config) do
58
- Airbrake::Config.new(project_id: 123)
67
+ Airbrake::Config.new(project_id: 123, environment: 'not-test')
59
68
  end
60
69
 
61
70
  it "sets remote settings" do
@@ -63,6 +72,15 @@ RSpec.describe Airbrake::Config::Processor do
63
72
  described_class.new(config).process_remote_configuration
64
73
  end
65
74
  end
75
+
76
+ context "when the config disables the remote_config option" do
77
+ let(:config) { Airbrake::Config.new(project_id: 123, remote_config: false) }
78
+
79
+ it "doesn't set remote settings" do
80
+ expect(Airbrake::RemoteSettings).not_to receive(:poll)
81
+ described_class.new(config).process_remote_configuration
82
+ end
83
+ end
66
84
  end
67
85
 
68
86
  describe "#add_filters" do
@@ -122,88 +140,4 @@ RSpec.describe Airbrake::Config::Processor do
122
140
  end
123
141
  end
124
142
  end
125
-
126
- describe "#poll_callback" do
127
- let(:logger) { Logger.new(File::NULL) }
128
-
129
- let(:config) do
130
- Airbrake::Config.new(
131
- project_id: 123,
132
- logger: logger,
133
- )
134
- end
135
-
136
- let(:data) do
137
- instance_double(Airbrake::RemoteSettings::SettingsData)
138
- end
139
-
140
- before do
141
- allow(data).to receive(:to_h)
142
- allow(data).to receive(:error_host)
143
- allow(data).to receive(:apm_host)
144
- allow(data).to receive(:error_notifications?)
145
- allow(data).to receive(:performance_stats?)
146
- end
147
-
148
- it "logs given data" do
149
- expect(logger).to receive(:debug).with(/applying remote settings/)
150
- described_class.new(config).poll_callback(data)
151
- end
152
-
153
- it "sets the error_notifications option" do
154
- config.error_notifications = false
155
- expect(data).to receive(:error_notifications?).and_return(true)
156
-
157
- described_class.new(config).poll_callback(data)
158
- expect(config.error_notifications).to eq(true)
159
- end
160
-
161
- it "sets the performance_stats option" do
162
- config.performance_stats = false
163
- expect(data).to receive(:performance_stats?).and_return(true)
164
-
165
- described_class.new(config).poll_callback(data)
166
- expect(config.performance_stats).to eq(true)
167
- end
168
-
169
- context "when error_host returns a value" do
170
- it "sets the error_host option" do
171
- config.error_host = 'http://api.airbrake.io'
172
- allow(data).to receive(:error_host).and_return('https://api.example.com')
173
-
174
- described_class.new(config).poll_callback(data)
175
- expect(config.error_host).to eq('https://api.example.com')
176
- end
177
- end
178
-
179
- context "when error_host returns nil" do
180
- it "doesn't modify the error_host option" do
181
- config.error_host = 'http://api.airbrake.io'
182
- allow(data).to receive(:error_host).and_return(nil)
183
-
184
- described_class.new(config).poll_callback(data)
185
- expect(config.error_host).to eq('http://api.airbrake.io')
186
- end
187
- end
188
-
189
- context "when apm_host returns a value" do
190
- it "sets the apm_host option" do
191
- config.apm_host = 'http://api.airbrake.io'
192
- allow(data).to receive(:apm_host).and_return('https://api.example.com')
193
-
194
- described_class.new(config).poll_callback(data)
195
- expect(config.apm_host).to eq('https://api.example.com')
196
- end
197
- end
198
-
199
- context "when apm_host returns nil" do
200
- it "doesn't modify the apm_host option" do
201
- config.apm_host = 'http://api.airbrake.io'
202
- allow(data).to receive(:apm_host).and_return(nil)
203
-
204
- described_class.new(config).poll_callback(data)
205
- expect(config.apm_host).to eq('http://api.airbrake.io')
206
- end
207
- end
208
- end
209
143
  end
@@ -26,9 +26,10 @@ RSpec.describe Airbrake::Config do
26
26
  its(:query_stats) { is_expected.to eq(true) }
27
27
  its(:job_stats) { is_expected.to eq(true) }
28
28
  its(:error_notifications) { is_expected.to eq(true) }
29
+ its(:remote_config) { is_expected.to eq(true) }
29
30
 
30
31
  its(:remote_config_host) do
31
- is_expected.to eq('https://v1-production-notifier-configs.s3.amazonaws.com')
32
+ is_expected.to eq('https://notifier-configs.airbrake.io')
32
33
  end
33
34
 
34
35
  describe "#new" do
@@ -44,7 +44,7 @@ RSpec.describe Airbrake::Filters::GitLastCheckoutFilter do
44
44
  it "attaches last checkouted email" do
45
45
  subject.call(notice)
46
46
  expect(notice[:context][:lastCheckout][:email]).to(
47
- match(/\A\w+[\w.-]*@\w+\.?\w+?\z/),
47
+ match(/\A\w+[\w.-]*@(\w+\.)*\w+\z/),
48
48
  )
49
49
  end
50
50
 
@@ -234,7 +234,7 @@ RSpec.describe Airbrake::Filters::ThreadFilter do
234
234
  end
235
235
  end
236
236
 
237
- it "appends name", skip: !Thread.current.respond_to?(:name) do
237
+ it "appends name" do
238
238
  new_thread do |th|
239
239
  th.name = 'bingo'
240
240
  subject.call(notice)
@@ -0,0 +1,143 @@
1
+ RSpec.describe Airbrake::RemoteSettings::Callback do
2
+ describe "#call" do
3
+ let(:logger) { Logger.new(File::NULL) }
4
+
5
+ let(:config) do
6
+ Airbrake::Config.new(
7
+ project_id: 123,
8
+ logger: logger,
9
+ )
10
+ end
11
+
12
+ let(:data) do
13
+ instance_double(Airbrake::RemoteSettings::SettingsData)
14
+ end
15
+
16
+ before do
17
+ allow(data).to receive(:to_h)
18
+ allow(data).to receive(:error_host)
19
+ allow(data).to receive(:apm_host)
20
+ allow(data).to receive(:error_notifications?)
21
+ allow(data).to receive(:performance_stats?)
22
+ end
23
+
24
+ it "logs given data" do
25
+ expect(logger).to receive(:debug) do |&block|
26
+ expect(block.call).to match(/applying remote settings/)
27
+ end
28
+ described_class.new(config).call(data)
29
+ end
30
+
31
+ context "when the config disables error notifications" do
32
+ before do
33
+ config.error_notifications = false
34
+ allow(data).to receive(:error_notifications?).and_return(true)
35
+ end
36
+
37
+ it "keeps the option disabled forever" do
38
+ callback = described_class.new(config)
39
+
40
+ callback.call(data)
41
+ expect(config.error_notifications).to eq(false)
42
+
43
+ callback.call(data)
44
+ expect(config.error_notifications).to eq(false)
45
+
46
+ callback.call(data)
47
+ expect(config.error_notifications).to eq(false)
48
+ end
49
+ end
50
+
51
+ context "when the config enables error notifications" do
52
+ before { config.error_notifications = true }
53
+
54
+ it "can disable and enable error notifications" do
55
+ expect(data).to receive(:error_notifications?).and_return(false)
56
+
57
+ callback = described_class.new(config)
58
+ callback.call(data)
59
+ expect(config.error_notifications).to eq(false)
60
+
61
+ expect(data).to receive(:error_notifications?).and_return(true)
62
+ callback.call(data)
63
+ expect(config.error_notifications).to eq(true)
64
+ end
65
+ end
66
+
67
+ context "when the config disables performance_stats" do
68
+ before do
69
+ config.performance_stats = false
70
+ allow(data).to receive(:performance_stats?).and_return(true)
71
+ end
72
+
73
+ it "keeps the option disabled forever" do
74
+ callback = described_class.new(config)
75
+
76
+ callback.call(data)
77
+ expect(config.performance_stats).to eq(false)
78
+
79
+ callback.call(data)
80
+ expect(config.performance_stats).to eq(false)
81
+
82
+ callback.call(data)
83
+ expect(config.performance_stats).to eq(false)
84
+ end
85
+ end
86
+
87
+ context "when the config enables performance stats" do
88
+ before { config.performance_stats = true }
89
+
90
+ it "can disable and enable performance_stats" do
91
+ expect(data).to receive(:performance_stats?).and_return(false)
92
+
93
+ callback = described_class.new(config)
94
+ callback.call(data)
95
+ expect(config.performance_stats).to eq(false)
96
+
97
+ expect(data).to receive(:performance_stats?).and_return(true)
98
+ callback.call(data)
99
+ expect(config.performance_stats).to eq(true)
100
+ end
101
+ end
102
+
103
+ context "when error_host returns a value" do
104
+ it "sets the error_host option" do
105
+ config.error_host = 'http://api.airbrake.io'
106
+ allow(data).to receive(:error_host).and_return('https://api.example.com')
107
+
108
+ described_class.new(config).call(data)
109
+ expect(config.error_host).to eq('https://api.example.com')
110
+ end
111
+ end
112
+
113
+ context "when error_host returns nil" do
114
+ it "doesn't modify the error_host option" do
115
+ config.error_host = 'http://api.airbrake.io'
116
+ allow(data).to receive(:error_host).and_return(nil)
117
+
118
+ described_class.new(config).call(data)
119
+ expect(config.error_host).to eq('http://api.airbrake.io')
120
+ end
121
+ end
122
+
123
+ context "when apm_host returns a value" do
124
+ it "sets the apm_host option" do
125
+ config.apm_host = 'http://api.airbrake.io'
126
+ allow(data).to receive(:apm_host).and_return('https://api.example.com')
127
+
128
+ described_class.new(config).call(data)
129
+ expect(config.apm_host).to eq('https://api.example.com')
130
+ end
131
+ end
132
+
133
+ context "when apm_host returns nil" do
134
+ it "doesn't modify the apm_host option" do
135
+ config.apm_host = 'http://api.airbrake.io'
136
+ allow(data).to receive(:apm_host).and_return(nil)
137
+
138
+ described_class.new(config).call(data)
139
+ expect(config.apm_host).to eq('http://api.airbrake.io')
140
+ end
141
+ end
142
+ end
143
+ end
@@ -13,7 +13,7 @@ RSpec.describe Airbrake::RemoteSettings::SettingsData do
13
13
 
14
14
  expect(settings_data.interval).to eq(123)
15
15
  expect(settings_data.config_route(''))
16
- .to eq('abc/2020-06-18/config/123/config.json')
16
+ .to eq('/abc')
17
17
  end
18
18
  end
19
19
 
@@ -60,65 +60,48 @@ RSpec.describe Airbrake::RemoteSettings::SettingsData do
60
60
  end
61
61
 
62
62
  describe "#config_route" do
63
- let(:host) { 'https://v1-production-notifier-configs.s3.amazonaws.com' }
63
+ let(:host) { 'http://example.com/' }
64
64
 
65
- context "when given a remote host through the remote config" do
66
- context "and when the remote host ends with a slash" do
67
- let(:data) do
68
- { 'config_route' => 'http://example.com/' }
69
- end
70
-
71
- it "returns the route with the host" do
72
- expect(described_class.new(project_id, data).config_route(host)).to eq(
73
- "http://example.com/2020-06-18/config/#{project_id}/config.json",
74
- )
75
- end
65
+ context "when remote config specifies a config route" do
66
+ let(:data) do
67
+ { 'config_route' => '123/cfg/321/cfg.json' }
76
68
  end
77
69
 
78
- context "and when the remote host doesn't with a slash" do
79
- let(:data) do
80
- { 'config_route' => 'http://example.com' }
81
- end
82
-
83
- it "returns the route with the host" do
84
- expect(described_class.new(project_id, data).config_route(host)).to eq(
85
- "http://example.com/2020-06-18/config/#{project_id}/config.json",
86
- )
87
- end
70
+ it "returns the config route with the provided location" do
71
+ expect(described_class.new(project_id, data).config_route(host)).to eq(
72
+ 'http://example.com/123/cfg/321/cfg.json',
73
+ )
88
74
  end
89
75
  end
90
76
 
91
- context "when given a remote host through local configuration" do
92
- context "and when the remote host ends with a slash" do
93
- let(:host) { 'http://example.com/' }
94
-
95
- it "returns the route with the host" do
96
- expect(described_class.new(project_id, {}).config_route(host)).to eq(
97
- "http://example.com/2020-06-18/config/#{project_id}/config.json",
98
- )
99
- end
77
+ context "when remote config DOES NOT specify a config route" do
78
+ it "returns the config route with the default location" do
79
+ expect(described_class.new(project_id, {}).config_route(host)).to eq(
80
+ "http://example.com/2020-06-18/config/#{project_id}/config.json",
81
+ )
100
82
  end
83
+ end
101
84
 
102
- context "and when the remote host doesn't with a slash" do
103
- let(:host) { 'http://example.com' }
85
+ context "when a config route is specified but is set to nil" do
86
+ let(:data) do
87
+ { 'config_route' => nil }
88
+ end
104
89
 
105
- it "returns the route with the host" do
106
- expect(described_class.new(project_id, {}).config_route(host)).to eq(
107
- "http://example.com/2020-06-18/config/#{project_id}/config.json",
108
- )
109
- end
90
+ it "returns the config route with the default location" do
91
+ expect(described_class.new(project_id, data).config_route(host)).to eq(
92
+ "http://example.com/2020-06-18/config/#{project_id}/config.json",
93
+ )
110
94
  end
111
95
  end
112
96
 
113
- context "when the given remote host in the remote config is nil" do
97
+ context "when a config route is specified but is set to an empty string" do
114
98
  let(:data) do
115
- { 'config_route' => nil }
99
+ { 'config_route' => '' }
116
100
  end
117
101
 
118
- it "returns the route with the given host instead" do
102
+ it "returns the route with the default instead" do
119
103
  expect(described_class.new(project_id, data).config_route(host)).to eq(
120
- 'https://v1-production-notifier-configs.s3.amazonaws.com/' \
121
- "2020-06-18/config/#{project_id}/config.json",
104
+ "http://example.com/2020-06-18/config/#{project_id}/config.json",
122
105
  )
123
106
  end
124
107
  end
@@ -22,42 +22,19 @@ RSpec.describe Airbrake::RemoteSettings do
22
22
  }
23
23
  end
24
24
 
25
- let(:config_path) { described_class::CONFIG_DUMP_PATH }
26
- let(:config_dir) { File.dirname(config_path) }
27
-
28
25
  let!(:stub) do
29
26
  stub_request(:get, Regexp.new(endpoint))
30
27
  .to_return(status: 200, body: body.to_json)
31
28
  end
32
29
 
33
- before do
34
- # Do not create config dumps on disk.
35
- allow(Dir).to receive(:mkdir).with(config_dir)
36
- allow(File).to receive(:write).with(config_path, anything)
37
- end
38
-
39
30
  describe ".poll" do
40
31
  describe "config loading" do
41
32
  let(:settings_data) { described_class::SettingsData.new(project_id, body) }
42
33
 
43
34
  before do
44
- allow(File).to receive(:exist?).with(config_path).and_return(true)
45
- allow(File).to receive(:read).with(config_path).and_return(body.to_json)
46
-
47
35
  allow(described_class::SettingsData).to receive(:new).and_return(settings_data)
48
36
  end
49
37
 
50
- it "loads the config from disk" do
51
- expect(File).to receive(:read).with(config_path)
52
- expect(settings_data).to receive(:merge!).with(body).twice
53
-
54
- remote_settings = described_class.poll(project_id, host) {}
55
- sleep(0.2)
56
- remote_settings.stop_polling
57
-
58
- expect(stub).to have_been_requested.once
59
- end
60
-
61
38
  it "yields the config to the block twice" do
62
39
  block = proc {}
63
40
  expect(block).to receive(:call).twice
@@ -68,21 +45,6 @@ RSpec.describe Airbrake::RemoteSettings do
68
45
 
69
46
  expect(stub).to have_been_requested.once
70
47
  end
71
-
72
- context "when config loading fails" do
73
- it "logs an error" do
74
- expect(File).to receive(:read).and_raise(StandardError)
75
- expect(Airbrake::Loggable.instance).to receive(:error).with(
76
- '**Airbrake: config loading failed: StandardError',
77
- )
78
-
79
- remote_settings = described_class.poll(project_id, host) {}
80
- sleep(0.2)
81
- remote_settings.stop_polling
82
-
83
- expect(stub).to have_been_requested.once
84
- end
85
- end
86
48
  end
87
49
 
88
50
  context "when no errors are raised" do
@@ -121,7 +83,7 @@ RSpec.describe Airbrake::RemoteSettings do
121
83
 
122
84
  context "when an error is raised while making a HTTP request" do
123
85
  before do
124
- allow(Net::HTTP).to receive(:get).and_raise(StandardError)
86
+ allow(Net::HTTP).to receive(:get_response).and_raise(StandardError)
125
87
  end
126
88
 
127
89
  it "doesn't fetch remote settings" do
@@ -155,10 +117,10 @@ RSpec.describe Airbrake::RemoteSettings do
155
117
  end
156
118
  end
157
119
 
158
- context "when API returns an XML response" do
120
+ context "when API returns a non-200 response" do
159
121
  let!(:stub) do
160
122
  stub_request(:get, Regexp.new(endpoint))
161
- .to_return(status: 200, body: '<?xml ...')
123
+ .to_return(status: 201, body: body.to_json)
162
124
  end
163
125
 
164
126
  it "doesn't update settings data" do
@@ -172,58 +134,53 @@ RSpec.describe Airbrake::RemoteSettings do
172
134
  expect(stub).to have_been_requested.once
173
135
  expect(settings.interval).to eq(600)
174
136
  end
175
- end
176
137
 
177
- context "when a config route is specified in the returned data" do
178
- let(:new_endpoint) do
179
- "http://example.com"
180
- end
138
+ it "logs error" do
139
+ expect(Airbrake::Loggable.instance).to receive(:error).with(body.to_json)
181
140
 
182
- let(:body) do
183
- { 'config_route' => new_endpoint, 'poll_sec' => 0.1 }
141
+ remote_settings = described_class.poll(project_id, host) {}
142
+ sleep(0.1)
143
+ remote_settings.stop_polling
184
144
  end
145
+ end
185
146
 
186
- let!(:new_stub) do
187
- stub_request(:get, Regexp.new(new_endpoint))
147
+ context "when API returns a 200 response" do
148
+ let!(:stub) do
149
+ stub_request(:get, Regexp.new(endpoint))
188
150
  .to_return(status: 200, body: body.to_json)
189
151
  end
190
152
 
191
- it "makes the next request to the specified config route" do
192
- remote_settings = described_class.poll(project_id, host) {}
193
- sleep(0.2)
153
+ it "doesn't log errors" do
154
+ expect(Airbrake::Loggable.instance).not_to receive(:error)
194
155
 
156
+ remote_settings = described_class.poll(project_id, host) {}
157
+ sleep(0.1)
195
158
  remote_settings.stop_polling
196
-
197
- expect(stub).to have_been_requested.once
198
- expect(new_stub).to have_been_requested.once
199
159
  end
200
160
  end
201
- end
202
-
203
- describe "#stop_polling" do
204
- it "dumps config data to disk" do
205
- expect(Dir).to receive(:mkdir).with(config_dir)
206
- expect(File).to receive(:write).with(config_path, body.to_json)
207
161
 
208
- remote_settings = described_class.poll(project_id, host) {}
209
- sleep(0.2)
210
- remote_settings.stop_polling
162
+ context "when a config route is specified in the returned data" do
163
+ let(:new_config_route) do
164
+ '213/config/111/config.json'
165
+ end
211
166
 
212
- expect(stub).to have_been_requested.once
213
- end
167
+ let(:body) do
168
+ { 'config_route' => new_config_route, 'poll_sec' => 0.1 }
169
+ end
214
170
 
215
- context "when config dumping fails" do
216
- it "logs an error" do
217
- expect(File).to receive(:write).and_raise(StandardError)
218
- expect(Airbrake::Loggable.instance).to receive(:error).with(
219
- '**Airbrake: config dumping failed: StandardError',
220
- )
171
+ let!(:new_stub) do
172
+ stub_request(:get, Regexp.new(new_config_route))
173
+ .to_return(status: 200, body: body.to_json)
174
+ end
221
175
 
176
+ it "makes the next request to the specified config route" do
222
177
  remote_settings = described_class.poll(project_id, host) {}
223
178
  sleep(0.2)
179
+
224
180
  remote_settings.stop_polling
225
181
 
226
182
  expect(stub).to have_been_requested.once
183
+ expect(new_stub).to have_been_requested.once
227
184
  end
228
185
  end
229
186
  end
@@ -86,7 +86,7 @@ RSpec.describe Airbrake::TDigest do
86
86
  maxerr = [maxerr, (i - q).abs].max
87
87
  end
88
88
 
89
- expect(maxerr).to be < 0.01
89
+ expect(maxerr).to be < 0.02
90
90
  end
91
91
  end
92
92
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: airbrake-ruby
3
3
  version: !ruby/object:Gem::Version
4
- version: 5.0.0
4
+ version: 5.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Airbrake Technologies, Inc.
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-08-17 00:00:00.000000000 Z
11
+ date: 2020-12-04 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rbtree3
@@ -79,6 +79,7 @@ files:
79
79
  - lib/airbrake-ruby/query.rb
80
80
  - lib/airbrake-ruby/queue.rb
81
81
  - lib/airbrake-ruby/remote_settings.rb
82
+ - lib/airbrake-ruby/remote_settings/callback.rb
82
83
  - lib/airbrake-ruby/remote_settings/settings_data.rb
83
84
  - lib/airbrake-ruby/request.rb
84
85
  - lib/airbrake-ruby/response.rb
@@ -135,6 +136,7 @@ files:
135
136
  - spec/promise_spec.rb
136
137
  - spec/query_spec.rb
137
138
  - spec/queue_spec.rb
139
+ - spec/remote_settings/callback_spec.rb
138
140
  - spec/remote_settings/settings_data_spec.rb
139
141
  - spec/remote_settings_spec.rb
140
142
  - spec/request_spec.rb
@@ -215,6 +217,7 @@ test_files:
215
217
  - spec/notice_notifier/options_spec.rb
216
218
  - spec/filter_chain_spec.rb
217
219
  - spec/remote_settings/settings_data_spec.rb
220
+ - spec/remote_settings/callback_spec.rb
218
221
  - spec/response_spec.rb
219
222
  - spec/queue_spec.rb
220
223
  - spec/code_hunk_spec.rb