airbrake-ruby 5.0.0 → 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 +4 -4
- data/lib/airbrake-ruby.rb +1 -0
- data/lib/airbrake-ruby/config.rb +15 -6
- data/lib/airbrake-ruby/config/processor.rb +7 -20
- data/lib/airbrake-ruby/file_cache.rb +1 -1
- data/lib/airbrake-ruby/filters/thread_filter.rb +1 -2
- data/lib/airbrake-ruby/grouppable.rb +1 -1
- data/lib/airbrake-ruby/mergeable.rb +1 -1
- data/lib/airbrake-ruby/remote_settings.rb +10 -50
- data/lib/airbrake-ruby/remote_settings/callback.rb +44 -0
- data/lib/airbrake-ruby/remote_settings/settings_data.rb +4 -8
- data/lib/airbrake-ruby/version.rb +2 -2
- data/spec/config/processor_spec.rb +19 -85
- data/spec/config_spec.rb +2 -1
- data/spec/filters/git_last_checkout_filter_spec.rb +1 -1
- data/spec/filters/thread_filter_spec.rb +1 -1
- data/spec/remote_settings/callback_spec.rb +143 -0
- data/spec/remote_settings/settings_data_spec.rb +27 -44
- data/spec/remote_settings_spec.rb +30 -73
- data/spec/tdigest_spec.rb +1 -1
- metadata +5 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8102082d97168a2204b3e730fc2085d30d06d4ab501e6207d91ce9d67bd91584
|
4
|
+
data.tar.gz: cf2234bd6fc61e439796b0f664163a12fdd4c39c4256a1c469f06dd1f8132de3
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d5b353597aa2bf9d59fa454f8a6a87fadcdc71685a7e21ef3ec9f74e57b20268e5d6cad2d6d0fe061bdcdbe02e65cf2e5761d4e92c6370ea1d1316972da9d9af
|
7
|
+
data.tar.gz: d13b77d0f959ff3a5326f054b8df67c9402b216038afd25672df8b6803623ac135bb5366739687fc5c234ae2e127d075828f747bb945d9ba4d48f3f354775f17
|
data/lib/airbrake-ruby.rb
CHANGED
@@ -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'
|
data/lib/airbrake-ruby/config.rb
CHANGED
@@ -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
|
124
|
+
# @since v5.0.0
|
125
125
|
attr_accessor :error_notifications
|
126
126
|
|
127
|
-
# @return [String] the host
|
128
|
-
# configuration options
|
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://
|
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
|
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
|
-
@
|
47
|
-
|
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
|
@@ -8,22 +8,11 @@ module Airbrake
|
|
8
8
|
# config.error_notifications = data.error_notifications?
|
9
9
|
# end
|
10
10
|
#
|
11
|
-
#
|
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.
|
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.
|
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.
|
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
|
-
|
105
|
-
|
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
|
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
|
62
|
-
return
|
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:
|
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.
|
6
|
+
AIRBRAKE_RUBY_VERSION = '5.2.0'.freeze
|
7
7
|
|
8
8
|
# @return [Hash{Symbol=>String}] the information about the notifier library
|
9
|
-
# @since
|
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
|
data/spec/config_spec.rb
CHANGED
@@ -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://
|
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.-]
|
47
|
+
match(/\A\w+[\w.-]*@(\w+\.)*\w+\z/),
|
48
48
|
)
|
49
49
|
end
|
50
50
|
|
@@ -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
|
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) { '
|
63
|
+
let(:host) { 'http://example.com/' }
|
64
64
|
|
65
|
-
context "when
|
66
|
-
|
67
|
-
|
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
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
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
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
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
|
-
|
103
|
-
|
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
|
-
|
106
|
-
|
107
|
-
|
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
|
97
|
+
context "when a config route is specified but is set to an empty string" do
|
114
98
|
let(:data) do
|
115
|
-
{ 'config_route' =>
|
99
|
+
{ 'config_route' => '' }
|
116
100
|
end
|
117
101
|
|
118
|
-
it "returns the route with the
|
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
|
-
|
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(:
|
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
|
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:
|
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
|
-
|
178
|
-
|
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
|
-
|
183
|
-
|
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
|
-
|
187
|
-
|
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 "
|
192
|
-
|
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
|
-
|
209
|
-
|
210
|
-
|
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
|
-
|
213
|
-
|
167
|
+
let(:body) do
|
168
|
+
{ 'config_route' => new_config_route, 'poll_sec' => 0.1 }
|
169
|
+
end
|
214
170
|
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
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
|
data/spec/tdigest_spec.rb
CHANGED
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.
|
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-
|
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
|