airbrake-ruby 4.8.0 → 5.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/airbrake-ruby.rb +132 -57
- data/lib/airbrake-ruby/async_sender.rb +7 -30
- data/lib/airbrake-ruby/backtrace.rb +8 -7
- data/lib/airbrake-ruby/benchmark.rb +1 -1
- data/lib/airbrake-ruby/code_hunk.rb +1 -1
- data/lib/airbrake-ruby/config.rb +59 -15
- data/lib/airbrake-ruby/config/processor.rb +71 -0
- data/lib/airbrake-ruby/config/validator.rb +9 -3
- data/lib/airbrake-ruby/deploy_notifier.rb +1 -1
- data/lib/airbrake-ruby/file_cache.rb +1 -1
- data/lib/airbrake-ruby/filter_chain.rb +16 -1
- data/lib/airbrake-ruby/filters/dependency_filter.rb +1 -0
- data/lib/airbrake-ruby/filters/exception_attributes_filter.rb +2 -2
- data/lib/airbrake-ruby/filters/gem_root_filter.rb +1 -0
- data/lib/airbrake-ruby/filters/git_last_checkout_filter.rb +5 -5
- data/lib/airbrake-ruby/filters/git_repository_filter.rb +3 -0
- data/lib/airbrake-ruby/filters/git_revision_filter.rb +2 -0
- data/lib/airbrake-ruby/filters/{keys_whitelist.rb → keys_allowlist.rb} +3 -3
- data/lib/airbrake-ruby/filters/{keys_blacklist.rb → keys_blocklist.rb} +3 -3
- data/lib/airbrake-ruby/filters/keys_filter.rb +39 -20
- data/lib/airbrake-ruby/filters/root_directory_filter.rb +1 -0
- data/lib/airbrake-ruby/filters/sql_filter.rb +7 -7
- data/lib/airbrake-ruby/filters/system_exit_filter.rb +1 -0
- data/lib/airbrake-ruby/filters/thread_filter.rb +5 -4
- data/lib/airbrake-ruby/grouppable.rb +12 -0
- data/lib/airbrake-ruby/ignorable.rb +1 -0
- data/lib/airbrake-ruby/inspectable.rb +2 -2
- data/lib/airbrake-ruby/loggable.rb +1 -1
- data/lib/airbrake-ruby/mergeable.rb +12 -0
- data/lib/airbrake-ruby/monotonic_time.rb +5 -0
- data/lib/airbrake-ruby/notice.rb +7 -14
- data/lib/airbrake-ruby/notice_notifier.rb +11 -3
- data/lib/airbrake-ruby/performance_breakdown.rb +16 -10
- data/lib/airbrake-ruby/performance_notifier.rb +80 -58
- data/lib/airbrake-ruby/promise.rb +1 -0
- data/lib/airbrake-ruby/query.rb +20 -15
- data/lib/airbrake-ruby/queue.rb +65 -0
- data/lib/airbrake-ruby/remote_settings.rb +105 -0
- data/lib/airbrake-ruby/remote_settings/callback.rb +44 -0
- data/lib/airbrake-ruby/remote_settings/settings_data.rb +116 -0
- data/lib/airbrake-ruby/request.rb +14 -12
- data/lib/airbrake-ruby/stat.rb +26 -33
- data/lib/airbrake-ruby/sync_sender.rb +3 -2
- data/lib/airbrake-ruby/tdigest.rb +43 -58
- data/lib/airbrake-ruby/thread_pool.rb +11 -1
- data/lib/airbrake-ruby/truncator.rb +10 -4
- data/lib/airbrake-ruby/version.rb +11 -1
- data/spec/airbrake_spec.rb +206 -71
- data/spec/async_sender_spec.rb +3 -12
- data/spec/backtrace_spec.rb +44 -44
- data/spec/code_hunk_spec.rb +11 -11
- data/spec/config/processor_spec.rb +143 -0
- data/spec/config/validator_spec.rb +23 -6
- data/spec/config_spec.rb +40 -14
- data/spec/deploy_notifier_spec.rb +2 -2
- data/spec/filter_chain_spec.rb +28 -1
- data/spec/filters/dependency_filter_spec.rb +1 -1
- data/spec/filters/gem_root_filter_spec.rb +9 -9
- data/spec/filters/git_last_checkout_filter_spec.rb +21 -4
- data/spec/filters/git_repository_filter.rb +1 -1
- data/spec/filters/git_revision_filter_spec.rb +10 -10
- data/spec/filters/{keys_whitelist_spec.rb → keys_allowlist_spec.rb} +29 -28
- data/spec/filters/{keys_blacklist_spec.rb → keys_blocklist_spec.rb} +39 -29
- data/spec/filters/root_directory_filter_spec.rb +9 -9
- data/spec/filters/sql_filter_spec.rb +58 -60
- data/spec/filters/system_exit_filter_spec.rb +1 -1
- data/spec/filters/thread_filter_spec.rb +32 -30
- data/spec/fixtures/project_root/code.rb +9 -9
- data/spec/loggable_spec.rb +17 -0
- data/spec/monotonic_time_spec.rb +11 -0
- data/spec/notice_notifier/options_spec.rb +17 -17
- data/spec/notice_notifier_spec.rb +20 -20
- data/spec/notice_spec.rb +6 -6
- data/spec/performance_breakdown_spec.rb +0 -1
- data/spec/performance_notifier_spec.rb +220 -73
- data/spec/query_spec.rb +1 -1
- data/spec/queue_spec.rb +18 -0
- data/spec/remote_settings/callback_spec.rb +143 -0
- data/spec/remote_settings/settings_data_spec.rb +348 -0
- data/spec/remote_settings_spec.rb +187 -0
- data/spec/request_spec.rb +1 -3
- data/spec/response_spec.rb +8 -8
- data/spec/spec_helper.rb +6 -6
- data/spec/stat_spec.rb +2 -12
- data/spec/sync_sender_spec.rb +14 -12
- data/spec/tdigest_spec.rb +7 -7
- data/spec/thread_pool_spec.rb +39 -10
- data/spec/timed_trace_spec.rb +1 -1
- data/spec/truncator_spec.rb +12 -12
- metadata +32 -14
@@ -5,24 +5,30 @@ module Airbrake
|
|
5
5
|
# @see Airbrake.notify_breakdown
|
6
6
|
# @api public
|
7
7
|
# @since v4.2.0
|
8
|
-
# rubocop:disable Metrics/
|
9
|
-
PerformanceBreakdown
|
10
|
-
:method, :route, :response_type, :groups, :start_time, :end_time
|
11
|
-
) do
|
8
|
+
# rubocop:disable Metrics/ParameterLists
|
9
|
+
class PerformanceBreakdown
|
12
10
|
include HashKeyable
|
13
11
|
include Ignorable
|
14
12
|
include Stashable
|
13
|
+
include Mergeable
|
14
|
+
|
15
|
+
attr_accessor :method, :route, :response_type, :groups, :timing, :time
|
15
16
|
|
16
17
|
def initialize(
|
17
18
|
method:,
|
18
19
|
route:,
|
19
20
|
response_type:,
|
20
21
|
groups:,
|
21
|
-
|
22
|
-
|
22
|
+
timing: nil,
|
23
|
+
time: Time.now
|
23
24
|
)
|
24
|
-
@
|
25
|
-
|
25
|
+
@time_utc = TimeTruncate.utc_truncate_minutes(time)
|
26
|
+
@method = method
|
27
|
+
@route = route
|
28
|
+
@response_type = response_type
|
29
|
+
@groups = groups
|
30
|
+
@timing = timing
|
31
|
+
@time = time
|
26
32
|
end
|
27
33
|
|
28
34
|
def destination
|
@@ -38,9 +44,9 @@ module Airbrake
|
|
38
44
|
'method' => method,
|
39
45
|
'route' => route,
|
40
46
|
'responseType' => response_type,
|
41
|
-
'time' => @
|
47
|
+
'time' => @time_utc,
|
42
48
|
}.delete_if { |_key, val| val.nil? }
|
43
49
|
end
|
44
50
|
end
|
45
|
-
# rubocop:enable Metrics/
|
51
|
+
# rubocop:enable Metrics/ParameterLists
|
46
52
|
end
|
@@ -4,6 +4,7 @@ module Airbrake
|
|
4
4
|
#
|
5
5
|
# @api public
|
6
6
|
# @since v3.2.0
|
7
|
+
# rubocop:disable Metrics/ClassLength
|
7
8
|
class PerformanceNotifier
|
8
9
|
include Inspectable
|
9
10
|
include Loggable
|
@@ -11,33 +12,29 @@ module Airbrake
|
|
11
12
|
def initialize
|
12
13
|
@config = Airbrake::Config.instance
|
13
14
|
@flush_period = Airbrake::Config.instance.performance_stats_flush_period
|
14
|
-
@
|
15
|
-
@
|
15
|
+
@async_sender = AsyncSender.new(:put)
|
16
|
+
@sync_sender = SyncSender.new(:put)
|
16
17
|
@schedule_flush = nil
|
17
|
-
@mutex = Mutex.new
|
18
18
|
@filter_chain = FilterChain.new
|
19
|
-
|
19
|
+
|
20
|
+
@payload = {}.extend(MonitorMixin)
|
21
|
+
@has_payload = @payload.new_cond
|
20
22
|
end
|
21
23
|
|
22
24
|
# @param [Hash] resource
|
23
25
|
# @see Airbrake.notify_query
|
24
26
|
# @see Airbrake.notify_request
|
25
27
|
def notify(resource)
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
promise = @config.check_performance_options(resource)
|
30
|
-
return promise if promise.rejected?
|
31
|
-
|
32
|
-
@filter_chain.refine(resource)
|
33
|
-
return if resource.ignored?
|
34
|
-
|
35
|
-
@mutex.synchronize do
|
36
|
-
update_payload(resource)
|
37
|
-
@flush_period > 0 ? schedule_flush : send(@payload, promise)
|
28
|
+
@payload.synchronize do
|
29
|
+
send_resource(resource, sync: false)
|
38
30
|
end
|
31
|
+
end
|
39
32
|
|
40
|
-
|
33
|
+
# @param [Hash] resource
|
34
|
+
# @since v4.10.0
|
35
|
+
# @see Airbrake.notify_queue_sync
|
36
|
+
def notify_sync(resource)
|
37
|
+
send_resource(resource, sync: true).value
|
41
38
|
end
|
42
39
|
|
43
40
|
# @see Airbrake.add_performance_filter
|
@@ -51,73 +48,97 @@ module Airbrake
|
|
51
48
|
end
|
52
49
|
|
53
50
|
def close
|
54
|
-
@
|
51
|
+
@payload.synchronize do
|
55
52
|
@schedule_flush.kill if @schedule_flush
|
56
|
-
@
|
53
|
+
@async_sender.close
|
57
54
|
logger.debug("#{LOG_LABEL} performance notifier closed")
|
58
55
|
end
|
59
56
|
end
|
60
57
|
|
61
58
|
private
|
62
59
|
|
63
|
-
def
|
64
|
-
@
|
65
|
-
|
60
|
+
def schedule_flush
|
61
|
+
@schedule_flush ||= Thread.new do
|
62
|
+
loop do
|
63
|
+
@payload.synchronize do
|
64
|
+
@last_flush_time ||= MonotonicTime.time_in_s
|
66
65
|
|
67
|
-
|
68
|
-
|
69
|
-
|
66
|
+
while (MonotonicTime.time_in_s - @last_flush_time) < @flush_period
|
67
|
+
@has_payload.wait(@flush_period)
|
68
|
+
end
|
69
|
+
|
70
|
+
if @payload.none?
|
71
|
+
@last_flush_time = nil
|
72
|
+
next
|
73
|
+
end
|
74
|
+
|
75
|
+
send(@async_sender, @payload, Airbrake::Promise.new)
|
76
|
+
@payload.clear
|
77
|
+
end
|
78
|
+
end
|
70
79
|
end
|
71
80
|
end
|
72
81
|
|
73
|
-
def
|
74
|
-
|
82
|
+
def send_resource(resource, sync:)
|
83
|
+
promise = check_configuration(resource)
|
84
|
+
return promise if promise.rejected?
|
75
85
|
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
rescue ThreadError => exception
|
80
|
-
logger.error("#{LOG_LABEL}: error occurred while flushing: #{exception}")
|
81
|
-
end
|
86
|
+
@filter_chain.refine(resource)
|
87
|
+
if resource.ignored?
|
88
|
+
return Promise.new.reject("#{resource.class} was ignored by a filter")
|
82
89
|
end
|
83
90
|
|
84
|
-
|
91
|
+
update_payload(resource)
|
92
|
+
if sync || @flush_period == 0
|
93
|
+
send(@sync_sender, @payload, promise)
|
94
|
+
else
|
95
|
+
@has_payload.signal
|
96
|
+
schedule_flush
|
97
|
+
end
|
85
98
|
end
|
86
99
|
|
87
|
-
def
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
@waiting = false
|
94
|
-
end
|
95
|
-
|
96
|
-
sleep(@flush_period)
|
100
|
+
def update_payload(resource)
|
101
|
+
if (total_stat = @payload[resource])
|
102
|
+
@payload.key(total_stat).merge(resource)
|
103
|
+
else
|
104
|
+
@payload[resource] = { total: Airbrake::Stat.new }
|
105
|
+
end
|
97
106
|
|
98
|
-
|
99
|
-
@mutex.synchronize do
|
100
|
-
payload = @payload
|
101
|
-
@payload = {}
|
102
|
-
end
|
107
|
+
@payload[resource][:total].increment_ms(resource.timing)
|
103
108
|
|
104
|
-
|
105
|
-
|
109
|
+
resource.groups.each do |name, ms|
|
110
|
+
@payload[resource][name] ||= Airbrake::Stat.new
|
111
|
+
@payload[resource][name].increment_ms(ms)
|
106
112
|
end
|
107
113
|
end
|
108
114
|
|
109
|
-
def
|
110
|
-
|
111
|
-
|
115
|
+
def check_configuration(resource)
|
116
|
+
promise = @config.check_configuration
|
117
|
+
return promise if promise.rejected?
|
118
|
+
|
119
|
+
promise = @config.check_performance_options(resource)
|
120
|
+
return promise if promise.rejected?
|
112
121
|
|
113
|
-
|
122
|
+
if resource.timing && resource.timing == 0
|
123
|
+
return Promise.new.reject(':timing cannot be zero')
|
124
|
+
end
|
125
|
+
|
126
|
+
Promise.new
|
127
|
+
end
|
128
|
+
|
129
|
+
def send(sender, payload, promise)
|
130
|
+
raise "payload cannot be empty. Race?" if payload.none?
|
114
131
|
|
115
132
|
with_grouped_payload(payload) do |resource_hash, destination|
|
116
133
|
url = URI.join(
|
117
|
-
@config.
|
118
|
-
"api/v5/projects/#{@config.project_id}/#{destination}"
|
134
|
+
@config.apm_host,
|
135
|
+
"api/v5/projects/#{@config.project_id}/#{destination}",
|
119
136
|
)
|
120
|
-
|
137
|
+
|
138
|
+
logger.debug do
|
139
|
+
"#{LOG_LABEL} #{self.class.name}##{__method__}: #{resource_hash}"
|
140
|
+
end
|
141
|
+
sender.send(resource_hash, promise, url)
|
121
142
|
end
|
122
143
|
|
123
144
|
promise
|
@@ -152,4 +173,5 @@ module Airbrake
|
|
152
173
|
end
|
153
174
|
end
|
154
175
|
end
|
176
|
+
# rubocop:enable Metrics/ClassLength
|
155
177
|
end
|
data/lib/airbrake-ruby/query.rb
CHANGED
@@ -4,13 +4,15 @@ module Airbrake
|
|
4
4
|
# @see Airbrake.notify_query
|
5
5
|
# @api public
|
6
6
|
# @since v3.2.0
|
7
|
-
# rubocop:disable Metrics/ParameterLists
|
8
|
-
Query
|
9
|
-
:method, :route, :query, :func, :file, :line, :start_time, :end_time
|
10
|
-
) do
|
7
|
+
# rubocop:disable Metrics/ParameterLists
|
8
|
+
class Query
|
11
9
|
include HashKeyable
|
12
10
|
include Ignorable
|
13
11
|
include Stashable
|
12
|
+
include Mergeable
|
13
|
+
include Grouppable
|
14
|
+
|
15
|
+
attr_accessor :method, :route, :query, :func, :file, :line, :timing, :time
|
14
16
|
|
15
17
|
def initialize(
|
16
18
|
method:,
|
@@ -19,11 +21,18 @@ module Airbrake
|
|
19
21
|
func: nil,
|
20
22
|
file: nil,
|
21
23
|
line: nil,
|
22
|
-
|
23
|
-
|
24
|
+
timing: nil,
|
25
|
+
time: Time.now
|
24
26
|
)
|
25
|
-
@
|
26
|
-
|
27
|
+
@time_utc = TimeTruncate.utc_truncate_minutes(time)
|
28
|
+
@method = method
|
29
|
+
@route = route
|
30
|
+
@query = query
|
31
|
+
@func = func
|
32
|
+
@file = file
|
33
|
+
@line = line
|
34
|
+
@timing = timing
|
35
|
+
@time = time
|
27
36
|
end
|
28
37
|
|
29
38
|
def destination
|
@@ -34,21 +43,17 @@ module Airbrake
|
|
34
43
|
'queries'
|
35
44
|
end
|
36
45
|
|
37
|
-
def groups
|
38
|
-
{}
|
39
|
-
end
|
40
|
-
|
41
46
|
def to_h
|
42
47
|
{
|
43
48
|
'method' => method,
|
44
49
|
'route' => route,
|
45
50
|
'query' => query,
|
46
|
-
'time' => @
|
51
|
+
'time' => @time_utc,
|
47
52
|
'function' => func,
|
48
53
|
'file' => file,
|
49
|
-
'line' => line
|
54
|
+
'line' => line,
|
50
55
|
}.delete_if { |_key, val| val.nil? }
|
51
56
|
end
|
52
|
-
# rubocop:enable Metrics/ParameterLists
|
57
|
+
# rubocop:enable Metrics/ParameterLists
|
53
58
|
end
|
54
59
|
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
module Airbrake
|
2
|
+
# Queue represents a queue (worker).
|
3
|
+
#
|
4
|
+
# @see Airbrake.notify_queue
|
5
|
+
# @api public
|
6
|
+
# @since v4.9.0
|
7
|
+
class Queue
|
8
|
+
include HashKeyable
|
9
|
+
include Ignorable
|
10
|
+
include Stashable
|
11
|
+
|
12
|
+
attr_accessor :queue, :error_count, :groups, :timing, :time
|
13
|
+
|
14
|
+
def initialize(
|
15
|
+
queue:,
|
16
|
+
error_count:,
|
17
|
+
groups: {},
|
18
|
+
timing: nil,
|
19
|
+
time: Time.now
|
20
|
+
)
|
21
|
+
@time_utc = TimeTruncate.utc_truncate_minutes(time)
|
22
|
+
@queue = queue
|
23
|
+
@error_count = error_count
|
24
|
+
@groups = groups
|
25
|
+
@timing = timing
|
26
|
+
@time = time
|
27
|
+
end
|
28
|
+
|
29
|
+
def destination
|
30
|
+
'queues-stats'
|
31
|
+
end
|
32
|
+
|
33
|
+
def cargo
|
34
|
+
'queues'
|
35
|
+
end
|
36
|
+
|
37
|
+
def to_h
|
38
|
+
{
|
39
|
+
'queue' => queue,
|
40
|
+
'errorCount' => error_count,
|
41
|
+
'time' => @time_utc,
|
42
|
+
}
|
43
|
+
end
|
44
|
+
|
45
|
+
def hash
|
46
|
+
{
|
47
|
+
'queue' => queue,
|
48
|
+
'time' => @time_utc,
|
49
|
+
}.hash
|
50
|
+
end
|
51
|
+
|
52
|
+
def merge(other)
|
53
|
+
self.error_count += other.error_count
|
54
|
+
end
|
55
|
+
|
56
|
+
# Queues don't have routes, but we want to define this to make sure our
|
57
|
+
# filter API is consistent (other models define this property)
|
58
|
+
#
|
59
|
+
# @return [String] empty route
|
60
|
+
# @see https://github.com/airbrake/airbrake-ruby/pull/537
|
61
|
+
def route
|
62
|
+
''
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,105 @@
|
|
1
|
+
module Airbrake
|
2
|
+
# RemoteSettings polls the remote config of the passed project at fixed
|
3
|
+
# intervals. The fetched config is yielded as a callback parameter so that the
|
4
|
+
# invoker can define read config values.
|
5
|
+
#
|
6
|
+
# @example Disable/enable error notifications based on the remote value
|
7
|
+
# RemoteSettings.poll do |data|
|
8
|
+
# config.error_notifications = data.error_notifications?
|
9
|
+
# end
|
10
|
+
#
|
11
|
+
# @since v5.0.0
|
12
|
+
# @api private
|
13
|
+
class RemoteSettings
|
14
|
+
include Airbrake::Loggable
|
15
|
+
|
16
|
+
# @return [Hash{Symbol=>String}] metadata to be attached to every GET
|
17
|
+
# request
|
18
|
+
QUERY_PARAMS = URI.encode_www_form(
|
19
|
+
notifier_name: Airbrake::NOTIFIER_INFO[:name],
|
20
|
+
notifier_version: Airbrake::NOTIFIER_INFO[:version],
|
21
|
+
os: RUBY_PLATFORM,
|
22
|
+
language: "#{RUBY_ENGINE}/#{RUBY_VERSION}".freeze,
|
23
|
+
).freeze
|
24
|
+
|
25
|
+
# @return [String]
|
26
|
+
HTTP_OK = '200'.freeze
|
27
|
+
|
28
|
+
# Polls remote config of the given project.
|
29
|
+
#
|
30
|
+
# @param [Integer] project_id
|
31
|
+
# @param [String] host
|
32
|
+
# @yield [data]
|
33
|
+
# @yieldparam data [Airbrake::RemoteSettings::SettingsData]
|
34
|
+
# @return [Airbrake::RemoteSettings]
|
35
|
+
def self.poll(project_id, host, &block)
|
36
|
+
new(project_id, host, &block).poll
|
37
|
+
end
|
38
|
+
|
39
|
+
# @param [Integer] project_id
|
40
|
+
# @yield [data]
|
41
|
+
# @yieldparam data [Airbrake::RemoteSettings::SettingsData]
|
42
|
+
def initialize(project_id, host, &block)
|
43
|
+
@data = SettingsData.new(project_id, {})
|
44
|
+
@host = host
|
45
|
+
@block = block
|
46
|
+
@poll = nil
|
47
|
+
end
|
48
|
+
|
49
|
+
# Polls remote config of the given project in background.
|
50
|
+
#
|
51
|
+
# @return [self]
|
52
|
+
def poll
|
53
|
+
@poll ||= Thread.new do
|
54
|
+
@block.call(@data)
|
55
|
+
|
56
|
+
loop do
|
57
|
+
@block.call(@data.merge!(fetch_config))
|
58
|
+
sleep(@data.interval)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
self
|
63
|
+
end
|
64
|
+
|
65
|
+
# Stops the background poller thread.
|
66
|
+
#
|
67
|
+
# @return [void]
|
68
|
+
def stop_polling
|
69
|
+
@poll.kill if @poll
|
70
|
+
end
|
71
|
+
|
72
|
+
private
|
73
|
+
|
74
|
+
def fetch_config
|
75
|
+
response = nil
|
76
|
+
begin
|
77
|
+
response = Net::HTTP.get_response(build_config_uri)
|
78
|
+
rescue StandardError => ex
|
79
|
+
logger.error(ex)
|
80
|
+
return {}
|
81
|
+
end
|
82
|
+
|
83
|
+
unless response.code == HTTP_OK
|
84
|
+
logger.error(response.body)
|
85
|
+
return {}
|
86
|
+
end
|
87
|
+
|
88
|
+
json = nil
|
89
|
+
begin
|
90
|
+
json = JSON.parse(response.body)
|
91
|
+
rescue JSON::ParserError => ex
|
92
|
+
logger.error(ex)
|
93
|
+
return {}
|
94
|
+
end
|
95
|
+
|
96
|
+
json
|
97
|
+
end
|
98
|
+
|
99
|
+
def build_config_uri
|
100
|
+
uri = URI(@data.config_route(@host))
|
101
|
+
uri.query = QUERY_PARAMS
|
102
|
+
uri
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|