celerbrake-ruby 0.1.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 +7 -0
- data/lib/celerbrake-ruby/async_sender.rb +57 -0
- data/lib/celerbrake-ruby/backlog.rb +123 -0
- data/lib/celerbrake-ruby/backtrace.rb +197 -0
- data/lib/celerbrake-ruby/benchmark.rb +39 -0
- data/lib/celerbrake-ruby/code_hunk.rb +51 -0
- data/lib/celerbrake-ruby/config/processor.rb +77 -0
- data/lib/celerbrake-ruby/config/validator.rb +97 -0
- data/lib/celerbrake-ruby/config.rb +291 -0
- data/lib/celerbrake-ruby/context.rb +51 -0
- data/lib/celerbrake-ruby/deploy_notifier.rb +36 -0
- data/lib/celerbrake-ruby/file_cache.rb +54 -0
- data/lib/celerbrake-ruby/filter_chain.rb +112 -0
- data/lib/celerbrake-ruby/filters/context_filter.rb +28 -0
- data/lib/celerbrake-ruby/filters/dependency_filter.rb +32 -0
- data/lib/celerbrake-ruby/filters/exception_attributes_filter.rb +46 -0
- data/lib/celerbrake-ruby/filters/gem_root_filter.rb +34 -0
- data/lib/celerbrake-ruby/filters/git_last_checkout_filter.rb +92 -0
- data/lib/celerbrake-ruby/filters/git_repository_filter.rb +73 -0
- data/lib/celerbrake-ruby/filters/git_revision_filter.rb +68 -0
- data/lib/celerbrake-ruby/filters/keys_allowlist.rb +48 -0
- data/lib/celerbrake-ruby/filters/keys_blocklist.rb +49 -0
- data/lib/celerbrake-ruby/filters/keys_filter.rb +159 -0
- data/lib/celerbrake-ruby/filters/root_directory_filter.rb +29 -0
- data/lib/celerbrake-ruby/filters/sql_filter.rb +128 -0
- data/lib/celerbrake-ruby/filters/system_exit_filter.rb +24 -0
- data/lib/celerbrake-ruby/filters/thread_filter.rb +93 -0
- data/lib/celerbrake-ruby/grouppable.rb +12 -0
- data/lib/celerbrake-ruby/hash_keyable.rb +37 -0
- data/lib/celerbrake-ruby/ignorable.rb +43 -0
- data/lib/celerbrake-ruby/inspectable.rb +39 -0
- data/lib/celerbrake-ruby/loggable.rb +34 -0
- data/lib/celerbrake-ruby/mergeable.rb +12 -0
- data/lib/celerbrake-ruby/monotonic_time.rb +48 -0
- data/lib/celerbrake-ruby/nested_exception.rb +59 -0
- data/lib/celerbrake-ruby/notice.rb +157 -0
- data/lib/celerbrake-ruby/notice_notifier.rb +142 -0
- data/lib/celerbrake-ruby/performance_breakdown.rb +52 -0
- data/lib/celerbrake-ruby/performance_notifier.rb +177 -0
- data/lib/celerbrake-ruby/promise.rb +110 -0
- data/lib/celerbrake-ruby/query.rb +59 -0
- data/lib/celerbrake-ruby/queue.rb +65 -0
- data/lib/celerbrake-ruby/remote_settings/callback.rb +44 -0
- data/lib/celerbrake-ruby/remote_settings/settings_data.rb +116 -0
- data/lib/celerbrake-ruby/remote_settings.rb +128 -0
- data/lib/celerbrake-ruby/request.rb +48 -0
- data/lib/celerbrake-ruby/response.rb +125 -0
- data/lib/celerbrake-ruby/stashable.rb +15 -0
- data/lib/celerbrake-ruby/stat.rb +66 -0
- data/lib/celerbrake-ruby/sync_sender.rb +145 -0
- data/lib/celerbrake-ruby/tdigest.rb +379 -0
- data/lib/celerbrake-ruby/thread_pool.rb +139 -0
- data/lib/celerbrake-ruby/time_truncate.rb +17 -0
- data/lib/celerbrake-ruby/timed_trace.rb +56 -0
- data/lib/celerbrake-ruby/truncator.rb +121 -0
- data/lib/celerbrake-ruby/version.rb +16 -0
- data/lib/celerbrake-ruby.rb +592 -0
- metadata +251 -0
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
module Celerbrake
|
|
2
|
+
# PerformanceBreakdown holds data that shows how much time a request spent
|
|
3
|
+
# doing certaing subtasks such as (DB querying, view rendering, etc).
|
|
4
|
+
#
|
|
5
|
+
# @see Celerbrake.notify_breakdown
|
|
6
|
+
# @api public
|
|
7
|
+
# @since v4.2.0
|
|
8
|
+
# rubocop:disable Metrics/ParameterLists
|
|
9
|
+
class PerformanceBreakdown
|
|
10
|
+
include HashKeyable
|
|
11
|
+
include Ignorable
|
|
12
|
+
include Stashable
|
|
13
|
+
include Mergeable
|
|
14
|
+
|
|
15
|
+
attr_accessor :method, :route, :response_type, :groups, :timing, :time
|
|
16
|
+
|
|
17
|
+
def initialize(
|
|
18
|
+
method:,
|
|
19
|
+
route:,
|
|
20
|
+
response_type:,
|
|
21
|
+
groups:,
|
|
22
|
+
timing: nil,
|
|
23
|
+
time: Time.now
|
|
24
|
+
)
|
|
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
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def destination
|
|
35
|
+
'routes-breakdowns'
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def cargo
|
|
39
|
+
'routes'
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def to_h
|
|
43
|
+
{
|
|
44
|
+
'method' => method,
|
|
45
|
+
'route' => route,
|
|
46
|
+
'responseType' => response_type,
|
|
47
|
+
'time' => @time_utc,
|
|
48
|
+
}.delete_if { |_key, val| val.nil? }
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
# rubocop:enable Metrics/ParameterLists
|
|
52
|
+
end
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
module Celerbrake
|
|
2
|
+
# PerformanceNotifier aggregates performance data and periodically sends it to
|
|
3
|
+
# Celerbrake.
|
|
4
|
+
#
|
|
5
|
+
# @api public
|
|
6
|
+
# @since v3.2.0
|
|
7
|
+
# rubocop:disable Metrics/ClassLength
|
|
8
|
+
class PerformanceNotifier
|
|
9
|
+
include Inspectable
|
|
10
|
+
include Loggable
|
|
11
|
+
|
|
12
|
+
def initialize
|
|
13
|
+
@config = Celerbrake::Config.instance
|
|
14
|
+
@flush_period = Celerbrake::Config.instance.performance_stats_flush_period
|
|
15
|
+
@async_sender = AsyncSender.new(:put, self.class.name)
|
|
16
|
+
@sync_sender = SyncSender.new(:put)
|
|
17
|
+
@schedule_flush = nil
|
|
18
|
+
@filter_chain = FilterChain.new
|
|
19
|
+
|
|
20
|
+
@payload = {}.extend(MonitorMixin)
|
|
21
|
+
@has_payload = @payload.new_cond
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# @param [Hash] metric
|
|
25
|
+
# @see Celerbrake.notify_query
|
|
26
|
+
# @see Celerbrake.notify_request
|
|
27
|
+
def notify(metric)
|
|
28
|
+
@payload.synchronize do
|
|
29
|
+
send_metric(metric, sync: false)
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# @param [Hash] metric
|
|
34
|
+
# @since v4.10.0
|
|
35
|
+
# @see Celerbrake.notify_queue_sync
|
|
36
|
+
def notify_sync(metric)
|
|
37
|
+
send_metric(metric, sync: true).value
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# @see Celerbrake.add_performance_filter
|
|
41
|
+
def add_filter(filter = nil, &block)
|
|
42
|
+
@filter_chain.add_filter(block_given? ? block : filter)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# @see Celerbrake.delete_performance_filter
|
|
46
|
+
def delete_filter(filter_class)
|
|
47
|
+
@filter_chain.delete_filter(filter_class)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def close
|
|
51
|
+
@payload.synchronize do
|
|
52
|
+
@schedule_flush.kill if @schedule_flush
|
|
53
|
+
@sync_sender.close
|
|
54
|
+
@async_sender.close
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
private
|
|
59
|
+
|
|
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
|
|
65
|
+
|
|
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, Celerbrake::Promise.new)
|
|
76
|
+
@payload.clear
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def send_metric(metric, sync:)
|
|
83
|
+
promise = check_configuration(metric)
|
|
84
|
+
return promise if promise.rejected?
|
|
85
|
+
|
|
86
|
+
@filter_chain.refine(metric)
|
|
87
|
+
if metric.ignored?
|
|
88
|
+
return Promise.new.reject("#{metric.class} was ignored by a filter")
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
update_payload(metric)
|
|
92
|
+
if sync || @flush_period == 0
|
|
93
|
+
send(@sync_sender, @payload, promise)
|
|
94
|
+
else
|
|
95
|
+
@has_payload.signal
|
|
96
|
+
schedule_flush
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def update_payload(metric)
|
|
101
|
+
if (total_stat = @payload[metric])
|
|
102
|
+
@payload.key(total_stat).merge(metric)
|
|
103
|
+
else
|
|
104
|
+
@payload[metric] = { total: Celerbrake::Stat.new }
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
@payload[metric][:total].increment_ms(metric.timing)
|
|
108
|
+
|
|
109
|
+
metric.groups.each do |name, ms|
|
|
110
|
+
@payload[metric][name] ||= Celerbrake::Stat.new
|
|
111
|
+
@payload[metric][name].increment_ms(ms)
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def check_configuration(metric)
|
|
116
|
+
promise = @config.check_configuration
|
|
117
|
+
return promise if promise.rejected?
|
|
118
|
+
|
|
119
|
+
promise = @config.check_performance_options(metric)
|
|
120
|
+
return promise if promise.rejected?
|
|
121
|
+
|
|
122
|
+
if metric.timing && metric.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?
|
|
131
|
+
|
|
132
|
+
with_grouped_payload(payload) do |metric_hash, destination|
|
|
133
|
+
url = URI.join(
|
|
134
|
+
@config.apm_host,
|
|
135
|
+
"api/v5/projects/#{@config.project_id}/#{destination}",
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
logger.debug do
|
|
139
|
+
"#{LOG_LABEL} #{self.class.name}##{__method__}: #{metric_hash}"
|
|
140
|
+
end
|
|
141
|
+
sender.send(metric_hash, promise, url)
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
promise
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
def with_grouped_payload(raw_payload)
|
|
148
|
+
grouped_payload = raw_payload.group_by do |metric, _stats|
|
|
149
|
+
[metric.cargo, metric.destination]
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
grouped_payload.each do |(cargo, destination), metrics|
|
|
153
|
+
payload = {}
|
|
154
|
+
payload[cargo] = serialize_metrics(metrics)
|
|
155
|
+
payload['environment'] = @config.environment if @config.environment
|
|
156
|
+
|
|
157
|
+
yield(payload, destination)
|
|
158
|
+
end
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
def serialize_metrics(metrics)
|
|
162
|
+
metrics.map do |metric, stats|
|
|
163
|
+
metric_hash = metric.to_h.merge!(stats[:total].to_h)
|
|
164
|
+
|
|
165
|
+
if metric.groups.any?
|
|
166
|
+
group_stats = stats.reject { |name, _stat| name == :total }
|
|
167
|
+
metric_hash['groups'] = group_stats.merge(group_stats) do |_name, stat|
|
|
168
|
+
stat.to_h
|
|
169
|
+
end
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
metric_hash
|
|
173
|
+
end
|
|
174
|
+
end
|
|
175
|
+
end
|
|
176
|
+
# rubocop:enable Metrics/ClassLength
|
|
177
|
+
end
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
module Celerbrake
|
|
2
|
+
# Represents a simplified promise object (similar to promises found in
|
|
3
|
+
# JavaScript), which allows chaining callbacks that are executed when the
|
|
4
|
+
# promise is either resolved or rejected.
|
|
5
|
+
#
|
|
6
|
+
# @see https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Promise
|
|
7
|
+
# @see https://github.com/ruby-concurrency/concurrent-ruby/blob/master/lib/concurrent/promise.rb
|
|
8
|
+
# @since v1.7.0
|
|
9
|
+
class Promise
|
|
10
|
+
def initialize
|
|
11
|
+
@on_resolved = []
|
|
12
|
+
@on_rejected = []
|
|
13
|
+
@value = {}
|
|
14
|
+
@mutex = Mutex.new
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# Attaches a callback to be executed when the promise is resolved.
|
|
18
|
+
#
|
|
19
|
+
# @example
|
|
20
|
+
# Celerbrake::Promise.new.then { |response| puts response }
|
|
21
|
+
# #=> {"id"=>"00054415-8201-e9c6-65d6-fc4d231d2871",
|
|
22
|
+
# # "url"=>"http://localhost/locate/00054415-8201-e9c6-65d6-fc4d231d2871"}
|
|
23
|
+
#
|
|
24
|
+
# @yield [response]
|
|
25
|
+
# @yieldparam response [Hash<String,String>] Contains the `id` & `url` keys
|
|
26
|
+
# @return [self]
|
|
27
|
+
def then(&block)
|
|
28
|
+
@mutex.synchronize do
|
|
29
|
+
if @value.key?('ok')
|
|
30
|
+
yield(@value['ok'])
|
|
31
|
+
return self
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
@on_resolved << block
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
self
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Attaches a callback to be executed when the promise is rejected.
|
|
41
|
+
#
|
|
42
|
+
# @example
|
|
43
|
+
# Celerbrake::Promise.new.rescue { |error| raise error }
|
|
44
|
+
#
|
|
45
|
+
# @yield [error] The error message from the API
|
|
46
|
+
# @yieldparam error [String]
|
|
47
|
+
# @return [self]
|
|
48
|
+
def rescue(&block)
|
|
49
|
+
@mutex.synchronize do
|
|
50
|
+
if @value.key?('error')
|
|
51
|
+
yield(@value['error'])
|
|
52
|
+
return self
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
@on_rejected << block
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
self
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# @example
|
|
62
|
+
# Celerbrake::Promise.new.resolve('id' => '123')
|
|
63
|
+
#
|
|
64
|
+
# @param reason [Object]
|
|
65
|
+
# @return [self]
|
|
66
|
+
def resolve(reason = 'resolved')
|
|
67
|
+
@mutex.synchronize do
|
|
68
|
+
@value['ok'] = reason
|
|
69
|
+
@on_resolved.each { |callback| callback.call(reason) }
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
self
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# @example
|
|
76
|
+
# Celerbrake::Promise.new.reject('Something went wrong')
|
|
77
|
+
#
|
|
78
|
+
# @param reason [String]
|
|
79
|
+
# @return [self]
|
|
80
|
+
def reject(reason = 'rejected')
|
|
81
|
+
@mutex.synchronize do
|
|
82
|
+
@value['error'] = reason
|
|
83
|
+
@on_rejected.each { |callback| callback.call(reason) }
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
self
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# @return [Boolean]
|
|
90
|
+
def rejected?
|
|
91
|
+
@value.key?('error')
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# @return [Boolean]
|
|
95
|
+
def resolved?
|
|
96
|
+
@value.key?('ok')
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# @return [Hash<String,String>] either successful response containing the
|
|
100
|
+
# +id+ key or unsuccessful response containing the +error+ key
|
|
101
|
+
# @note This is a non-blocking call!
|
|
102
|
+
# @todo Get rid of this method and use an accessor. The resolved guard is
|
|
103
|
+
# needed for compatibility but it shouldn't exist in the future
|
|
104
|
+
def value
|
|
105
|
+
return @value['ok'] if resolved?
|
|
106
|
+
|
|
107
|
+
@value
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
end
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
module Celerbrake
|
|
2
|
+
# Query holds SQL query data that powers SQL query collection.
|
|
3
|
+
#
|
|
4
|
+
# @see Celerbrake.notify_query
|
|
5
|
+
# @api public
|
|
6
|
+
# @since v3.2.0
|
|
7
|
+
# rubocop:disable Metrics/ParameterLists
|
|
8
|
+
class Query
|
|
9
|
+
include HashKeyable
|
|
10
|
+
include Ignorable
|
|
11
|
+
include Stashable
|
|
12
|
+
include Mergeable
|
|
13
|
+
include Grouppable
|
|
14
|
+
|
|
15
|
+
attr_accessor :method, :route, :query, :func, :file, :line, :timing, :time
|
|
16
|
+
|
|
17
|
+
def initialize(
|
|
18
|
+
method:,
|
|
19
|
+
route:,
|
|
20
|
+
query:,
|
|
21
|
+
func: nil,
|
|
22
|
+
file: nil,
|
|
23
|
+
line: nil,
|
|
24
|
+
timing: nil,
|
|
25
|
+
time: Time.now
|
|
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
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def destination
|
|
39
|
+
'queries-stats'
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def cargo
|
|
43
|
+
'queries'
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def to_h
|
|
47
|
+
{
|
|
48
|
+
'method' => method,
|
|
49
|
+
'route' => route,
|
|
50
|
+
'query' => query,
|
|
51
|
+
'time' => @time_utc,
|
|
52
|
+
'function' => func,
|
|
53
|
+
'file' => file,
|
|
54
|
+
'line' => line,
|
|
55
|
+
}.delete_if { |_key, val| val.nil? }
|
|
56
|
+
end
|
|
57
|
+
# rubocop:enable Metrics/ParameterLists
|
|
58
|
+
end
|
|
59
|
+
end
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
module Celerbrake
|
|
2
|
+
# Queue represents a queue (worker).
|
|
3
|
+
#
|
|
4
|
+
# @see Celerbrake.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/celerbrake/celerbrake-ruby/pull/537
|
|
61
|
+
def route
|
|
62
|
+
''
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
module Celerbrake
|
|
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 [Celerbrake::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
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
module Celerbrake
|
|
2
|
+
class RemoteSettings
|
|
3
|
+
# SettingsData is a container, which wraps JSON payload returned by the
|
|
4
|
+
# remote settings API. It exposes the payload via convenient methods and
|
|
5
|
+
# also ensures that in case some data from the payload is missing, a default
|
|
6
|
+
# value would be returned instead.
|
|
7
|
+
#
|
|
8
|
+
# @example
|
|
9
|
+
# # Create the object and pass initial data (empty hash).
|
|
10
|
+
# settings_data = SettingsData.new({})
|
|
11
|
+
#
|
|
12
|
+
# settings_data.interval #=> 600
|
|
13
|
+
#
|
|
14
|
+
# @since v5.0.0
|
|
15
|
+
# @api private
|
|
16
|
+
class SettingsData
|
|
17
|
+
# @return [Integer] how frequently we should poll the config API
|
|
18
|
+
DEFAULT_INTERVAL = 600
|
|
19
|
+
|
|
20
|
+
# @return [String] API version of the S3 API to poll
|
|
21
|
+
API_VER = '2020-06-18'.freeze
|
|
22
|
+
|
|
23
|
+
# @return [String] what path to poll
|
|
24
|
+
CONFIG_ROUTE_PATTERN =
|
|
25
|
+
"%<host>s/#{API_VER}/config/%<project_id>s/config.json".freeze
|
|
26
|
+
|
|
27
|
+
# @return [Hash{Symbol=>String}] the hash of all supported settings where
|
|
28
|
+
# the value is the name of the setting returned by the API
|
|
29
|
+
SETTINGS = {
|
|
30
|
+
errors: 'errors'.freeze,
|
|
31
|
+
apm: 'apm'.freeze,
|
|
32
|
+
}.freeze
|
|
33
|
+
|
|
34
|
+
# @param [Integer] project_id
|
|
35
|
+
# @param [Hash{String=>Object}] data
|
|
36
|
+
def initialize(project_id, data)
|
|
37
|
+
@project_id = project_id
|
|
38
|
+
@data = data
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Merges the given +hash+ with internal data.
|
|
42
|
+
#
|
|
43
|
+
# @param [Hash{String=>Object}] hash
|
|
44
|
+
# @return [self]
|
|
45
|
+
def merge!(hash)
|
|
46
|
+
@data.merge!(hash)
|
|
47
|
+
|
|
48
|
+
self
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# @return [Integer] how frequently we should poll for the config
|
|
52
|
+
def interval
|
|
53
|
+
return DEFAULT_INTERVAL if !@data.key?('poll_sec') || !@data['poll_sec']
|
|
54
|
+
|
|
55
|
+
@data['poll_sec'] > 0 ? @data['poll_sec'] : DEFAULT_INTERVAL
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# @param [String] remote_config_host
|
|
59
|
+
# @return [String] where the config is stored on S3.
|
|
60
|
+
def config_route(remote_config_host)
|
|
61
|
+
if @data['config_route'] && !@data['config_route'].empty?
|
|
62
|
+
return "#{remote_config_host.chomp('/')}/#{@data['config_route']}"
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
format(
|
|
66
|
+
CONFIG_ROUTE_PATTERN,
|
|
67
|
+
host: remote_config_host.chomp('/'),
|
|
68
|
+
project_id: @project_id,
|
|
69
|
+
)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# @return [Boolean] whether error notifications are enabled
|
|
73
|
+
def error_notifications?
|
|
74
|
+
return true unless (s = find_setting(SETTINGS[:errors]))
|
|
75
|
+
|
|
76
|
+
s['enabled']
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# @return [Boolean] whether APM is enabled
|
|
80
|
+
def performance_stats?
|
|
81
|
+
return true unless (s = find_setting(SETTINGS[:apm]))
|
|
82
|
+
|
|
83
|
+
s['enabled']
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# @return [String, nil] the host, which provides the API endpoint to which
|
|
87
|
+
# exceptions should be sent
|
|
88
|
+
def error_host
|
|
89
|
+
return unless (s = find_setting(SETTINGS[:errors]))
|
|
90
|
+
|
|
91
|
+
s['endpoint']
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# @return [String, nil] the host, which provides the API endpoint to which
|
|
95
|
+
# APM data should be sent
|
|
96
|
+
def apm_host
|
|
97
|
+
return unless (s = find_setting(SETTINGS[:apm]))
|
|
98
|
+
|
|
99
|
+
s['endpoint']
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# @return [Hash{String=>Object}] raw representation of JSON payload
|
|
103
|
+
def to_h
|
|
104
|
+
@data.dup
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
private
|
|
108
|
+
|
|
109
|
+
def find_setting(name)
|
|
110
|
+
return unless @data.key?('settings')
|
|
111
|
+
|
|
112
|
+
@data['settings'].find { |s| s['name'] == name }
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
end
|