airbrake-ruby 5.0.2 → 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: 43ac92e6db1b50d6cba741b6e3d2270e101bed02fa1779967f142a9a64b7bbb4
4
- data.tar.gz: 9f0debfc7e2eb97bd2dd0e263d1e40f81e3c8132d009dfc18a9cfcabf66fe31f
3
+ metadata.gz: 8102082d97168a2204b3e730fc2085d30d06d4ab501e6207d91ce9d67bd91584
4
+ data.tar.gz: cf2234bd6fc61e439796b0f664163a12fdd4c39c4256a1c469f06dd1f8132de3
5
5
  SHA512:
6
- metadata.gz: 8287f643e7ba4af54d5f6a4ee74b03c412c0a8a8fb2b8b7f8b9e2599cc1a1d38081c85877b554cb75a5e040d06c2d415af293d41f7692d4c1bfc29aef2df4285
7
- data.tar.gz: dc8eacdeda076da7adffa1a469ed2ffe65e3d78a810cbf38b2e5a55ba584144f343a9cfa9a3c2b1cc036eabe79198045c4dc5e8239dd948c7292f3fba1a3e3fc
6
+ metadata.gz: d5b353597aa2bf9d59fa454f8a6a87fadcdc71685a7e21ef3ec9f74e57b20268e5d6cad2d6d0fe061bdcdbe02e65cf2e5761d4e92c6370ea1d1316972da9d9af
7
+ data.tar.gz: d13b77d0f959ff3a5326f054b8df67c9402b216038afd25672df8b6803623ac135bb5366739687fc5c234ae2e127d075828f747bb945d9ba4d48f3f354775f17
@@ -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
@@ -41,7 +41,9 @@ module Airbrake
41
41
 
42
42
  # @return [Airbrake::RemoteSettings]
43
43
  def process_remote_configuration
44
+ return unless @config.remote_config
44
45
  return unless @project_id
46
+ return if @config.environment == 'test'
45
47
 
46
48
  RemoteSettings.poll(@project_id, @config.remote_config_host) do |data|
47
49
  @poll_callback.call(data)
@@ -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
@@ -4,7 +4,7 @@ module Airbrake
4
4
  # updates the local config according to the data.
5
5
  #
6
6
  # @api private
7
- # @since 5.0.2
7
+ # @since v5.0.2
8
8
  class Callback
9
9
  def initialize(config)
10
10
  @config = config
@@ -15,9 +15,9 @@ module Airbrake
15
15
  # @param [Airbrake::RemoteSettings::SettingsData] data
16
16
  # @return [void]
17
17
  def call(data)
18
- @config.logger.debug(
19
- "#{LOG_LABEL} applying remote settings: #{data.to_h}",
20
- )
18
+ @config.logger.debug do
19
+ "#{LOG_LABEL} applying remote settings: #{data.to_h}"
20
+ end
21
21
 
22
22
  @config.error_host = data.error_host if data.error_host
23
23
  @config.apm_host = data.apm_host if data.apm_host
@@ -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
@@ -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.2'.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
@@ -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)
@@ -22,7 +22,9 @@ RSpec.describe Airbrake::RemoteSettings::Callback do
22
22
  end
23
23
 
24
24
  it "logs given data" do
25
- expect(logger).to receive(:debug).with(/applying remote settings/)
25
+ expect(logger).to receive(:debug) do |&block|
26
+ expect(block.call).to match(/applying remote settings/)
27
+ end
26
28
  described_class.new(config).call(data)
27
29
  end
28
30
 
@@ -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,6 +134,29 @@ 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
137
+
138
+ it "logs error" do
139
+ expect(Airbrake::Loggable.instance).to receive(:error).with(body.to_json)
140
+
141
+ remote_settings = described_class.poll(project_id, host) {}
142
+ sleep(0.1)
143
+ remote_settings.stop_polling
144
+ end
145
+ end
146
+
147
+ context "when API returns a 200 response" do
148
+ let!(:stub) do
149
+ stub_request(:get, Regexp.new(endpoint))
150
+ .to_return(status: 200, body: body.to_json)
151
+ end
152
+
153
+ it "doesn't log errors" do
154
+ expect(Airbrake::Loggable.instance).not_to receive(:error)
155
+
156
+ remote_settings = described_class.poll(project_id, host) {}
157
+ sleep(0.1)
158
+ remote_settings.stop_polling
159
+ end
175
160
  end
176
161
 
177
162
  context "when a config route is specified in the returned data" do
@@ -199,32 +184,4 @@ RSpec.describe Airbrake::RemoteSettings do
199
184
  end
200
185
  end
201
186
  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
-
208
- remote_settings = described_class.poll(project_id, host) {}
209
- sleep(0.2)
210
- remote_settings.stop_polling
211
-
212
- expect(stub).to have_been_requested.once
213
- end
214
-
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
- )
221
-
222
- remote_settings = described_class.poll(project_id, host) {}
223
- sleep(0.2)
224
- remote_settings.stop_polling
225
-
226
- expect(stub).to have_been_requested.once
227
- end
228
- end
229
- end
230
187
  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.2
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-18 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