airbrake-ruby 5.0.2 → 5.2.0

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.
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