airbrake-ruby 4.1.0 → 5.0.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 +5 -5
- data/lib/airbrake-ruby/async_sender.rb +22 -96
- data/lib/airbrake-ruby/backtrace.rb +8 -7
- data/lib/airbrake-ruby/benchmark.rb +39 -0
- data/lib/airbrake-ruby/code_hunk.rb +1 -1
- data/lib/airbrake-ruby/config/processor.rb +84 -0
- data/lib/airbrake-ruby/config/validator.rb +9 -3
- data/lib/airbrake-ruby/config.rb +76 -20
- data/lib/airbrake-ruby/deploy_notifier.rb +1 -1
- data/lib/airbrake-ruby/file_cache.rb +6 -0
- 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 +30 -6
- data/lib/airbrake-ruby/filters/system_exit_filter.rb +1 -0
- data/lib/airbrake-ruby/filters/thread_filter.rb +4 -2
- 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 +2 -2
- data/lib/airbrake-ruby/mergeable.rb +12 -0
- data/lib/airbrake-ruby/monotonic_time.rb +48 -0
- data/lib/airbrake-ruby/notice.rb +10 -20
- data/lib/airbrake-ruby/notice_notifier.rb +23 -42
- data/lib/airbrake-ruby/performance_breakdown.rb +52 -0
- data/lib/airbrake-ruby/performance_notifier.rb +126 -49
- data/lib/airbrake-ruby/promise.rb +1 -0
- data/lib/airbrake-ruby/query.rb +26 -11
- data/lib/airbrake-ruby/queue.rb +65 -0
- data/lib/airbrake-ruby/remote_settings/settings_data.rb +120 -0
- data/lib/airbrake-ruby/remote_settings.rb +145 -0
- data/lib/airbrake-ruby/request.rb +20 -6
- data/lib/airbrake-ruby/stashable.rb +15 -0
- data/lib/airbrake-ruby/stat.rb +34 -24
- data/lib/airbrake-ruby/sync_sender.rb +3 -2
- data/lib/airbrake-ruby/tdigest.rb +43 -58
- data/lib/airbrake-ruby/thread_pool.rb +138 -0
- data/lib/airbrake-ruby/timed_trace.rb +58 -0
- data/lib/airbrake-ruby/truncator.rb +10 -4
- data/lib/airbrake-ruby/version.rb +11 -1
- data/lib/airbrake-ruby.rb +219 -53
- data/spec/airbrake_spec.rb +428 -9
- data/spec/async_sender_spec.rb +26 -110
- data/spec/backtrace_spec.rb +44 -44
- data/spec/benchmark_spec.rb +33 -0
- data/spec/code_hunk_spec.rb +11 -11
- data/spec/config/processor_spec.rb +209 -0
- data/spec/config/validator_spec.rb +23 -6
- data/spec/config_spec.rb +77 -7
- data/spec/deploy_notifier_spec.rb +2 -2
- data/spec/{file_cache.rb → file_cache_spec.rb} +2 -4
- 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 +13 -11
- 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 +110 -55
- data/spec/filters/system_exit_filter_spec.rb +1 -1
- data/spec/filters/thread_filter_spec.rb +33 -31
- data/spec/fixtures/project_root/code.rb +9 -9
- data/spec/loggable_spec.rb +17 -0
- data/spec/monotonic_time_spec.rb +23 -0
- data/spec/{notice_notifier_spec → notice_notifier}/options_spec.rb +19 -21
- data/spec/notice_notifier_spec.rb +20 -80
- data/spec/notice_spec.rb +9 -11
- data/spec/performance_breakdown_spec.rb +11 -0
- data/spec/performance_notifier_spec.rb +360 -85
- data/spec/query_spec.rb +11 -0
- data/spec/queue_spec.rb +18 -0
- data/spec/remote_settings/settings_data_spec.rb +365 -0
- data/spec/remote_settings_spec.rb +230 -0
- data/spec/request_spec.rb +9 -0
- data/spec/response_spec.rb +8 -8
- data/spec/spec_helper.rb +9 -13
- data/spec/stashable_spec.rb +23 -0
- data/spec/stat_spec.rb +17 -15
- data/spec/sync_sender_spec.rb +14 -12
- data/spec/tdigest_spec.rb +6 -6
- data/spec/thread_pool_spec.rb +187 -0
- data/spec/timed_trace_spec.rb +125 -0
- data/spec/truncator_spec.rb +12 -12
- metadata +55 -18
@@ -9,7 +9,7 @@ module Airbrake
|
|
9
9
|
# @return [Array<Class>] filters to be executed first
|
10
10
|
DEFAULT_FILTERS = [
|
11
11
|
Airbrake::Filters::SystemExitFilter,
|
12
|
-
Airbrake::Filters::GemRootFilter
|
12
|
+
Airbrake::Filters::GemRootFilter,
|
13
13
|
|
14
14
|
# Optional filters (must be included by users):
|
15
15
|
# Airbrake::Filters::ThreadFilter
|
@@ -25,34 +25,38 @@ module Airbrake
|
|
25
25
|
@async_sender = AsyncSender.new
|
26
26
|
@sync_sender = SyncSender.new
|
27
27
|
|
28
|
-
|
28
|
+
DEFAULT_FILTERS.each { |filter| add_filter(filter.new) }
|
29
|
+
|
30
|
+
add_filter(Airbrake::Filters::ContextFilter.new(@context))
|
31
|
+
add_filter(Airbrake::Filters::ExceptionAttributesFilter.new)
|
29
32
|
end
|
30
33
|
|
31
|
-
# @
|
34
|
+
# @see Airbrake.notify
|
32
35
|
def notify(exception, params = {}, &block)
|
33
36
|
send_notice(exception, params, default_sender, &block)
|
34
37
|
end
|
35
38
|
|
36
|
-
# @
|
39
|
+
# @see Airbrake.notify_sync
|
37
40
|
def notify_sync(exception, params = {}, &block)
|
38
41
|
send_notice(exception, params, @sync_sender, &block).value
|
39
42
|
end
|
40
43
|
|
41
|
-
# @
|
44
|
+
# @see Airbrake.add_filte
|
42
45
|
def add_filter(filter = nil, &block)
|
43
46
|
@filter_chain.add_filter(block_given? ? block : filter)
|
44
47
|
end
|
45
48
|
|
46
|
-
# @
|
49
|
+
# @see Airbrake.delete_filter
|
47
50
|
def delete_filter(filter_class)
|
48
51
|
@filter_chain.delete_filter(filter_class)
|
49
52
|
end
|
50
53
|
|
51
|
-
# @
|
54
|
+
# @see Airbrake.build_notice
|
52
55
|
def build_notice(exception, params = {})
|
53
56
|
if @async_sender.closed?
|
54
57
|
raise Airbrake::Error,
|
55
|
-
"
|
58
|
+
"Airbrake is closed; can't build exception: " \
|
59
|
+
"#{exception.class}: #{exception}"
|
56
60
|
end
|
57
61
|
|
58
62
|
if exception.is_a?(Airbrake::Notice)
|
@@ -63,21 +67,27 @@ module Airbrake
|
|
63
67
|
end
|
64
68
|
end
|
65
69
|
|
66
|
-
# @
|
70
|
+
# @see Airbrake.close
|
67
71
|
def close
|
68
72
|
@async_sender.close
|
69
73
|
end
|
70
74
|
|
71
|
-
# @
|
75
|
+
# @see Airbrake.configured?
|
72
76
|
def configured?
|
73
77
|
@config.valid?
|
74
78
|
end
|
75
79
|
|
76
|
-
# @
|
80
|
+
# @see Airbrake.merge_context
|
77
81
|
def merge_context(context)
|
78
82
|
@context.merge!(context)
|
79
83
|
end
|
80
84
|
|
85
|
+
# @return [Boolean]
|
86
|
+
# @since v4.14.0
|
87
|
+
def has_filter?(filter_class) # rubocop:disable Naming/PredicateName
|
88
|
+
@filter_chain.includes?(filter_class)
|
89
|
+
end
|
90
|
+
|
81
91
|
private
|
82
92
|
|
83
93
|
def convert_to_exception(ex)
|
@@ -113,7 +123,7 @@ module Airbrake
|
|
113
123
|
|
114
124
|
logger.warn(
|
115
125
|
"#{LOG_LABEL} falling back to sync delivery because there are no " \
|
116
|
-
"running async workers"
|
126
|
+
"running async workers",
|
117
127
|
)
|
118
128
|
@sync_sender
|
119
129
|
end
|
@@ -125,37 +135,8 @@ module Airbrake
|
|
125
135
|
# If true, then it's likely an internal library error. In this case return
|
126
136
|
# at least some backtrace to simplify debugging.
|
127
137
|
return caller_copy if clean_bt.empty?
|
128
|
-
clean_bt
|
129
|
-
end
|
130
|
-
|
131
|
-
# rubocop:disable Metrics/AbcSize
|
132
|
-
def add_default_filters
|
133
|
-
DEFAULT_FILTERS.each { |f| add_filter(f.new) }
|
134
|
-
|
135
|
-
if (whitelist_keys = @config.whitelist_keys).any?
|
136
|
-
add_filter(Airbrake::Filters::KeysWhitelist.new(whitelist_keys))
|
137
|
-
end
|
138
138
|
|
139
|
-
|
140
|
-
add_filter(Airbrake::Filters::KeysBlacklist.new(blacklist_keys))
|
141
|
-
end
|
142
|
-
|
143
|
-
add_filter(Airbrake::Filters::ContextFilter.new(@context))
|
144
|
-
add_filter(Airbrake::Filters::ExceptionAttributesFilter.new)
|
145
|
-
|
146
|
-
return unless (root_directory = @config.root_directory)
|
147
|
-
[
|
148
|
-
Airbrake::Filters::RootDirectoryFilter,
|
149
|
-
Airbrake::Filters::GitRevisionFilter,
|
150
|
-
Airbrake::Filters::GitRepositoryFilter
|
151
|
-
].each do |filter|
|
152
|
-
add_filter(filter.new(root_directory))
|
153
|
-
end
|
154
|
-
|
155
|
-
add_filter(
|
156
|
-
Airbrake::Filters::GitLastCheckoutFilter.new(root_directory)
|
157
|
-
)
|
139
|
+
clean_bt
|
158
140
|
end
|
159
|
-
# rubocop:enable Metrics/AbcSize
|
160
141
|
end
|
161
142
|
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
module Airbrake
|
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 Airbrake.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
|
@@ -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,40 +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
|
+
|
20
|
+
@payload = {}.extend(MonitorMixin)
|
21
|
+
@has_payload = @payload.new_cond
|
19
22
|
end
|
20
23
|
|
21
24
|
# @param [Hash] resource
|
22
25
|
# @see Airbrake.notify_query
|
23
26
|
# @see Airbrake.notify_request
|
24
27
|
def notify(resource)
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
promise = Airbrake::Promise.new
|
29
|
-
unless @config.performance_stats
|
30
|
-
return promise.reject("The Performance Stats feature is disabled")
|
31
|
-
end
|
32
|
-
|
33
|
-
@filter_chain.refine(resource)
|
34
|
-
return if resource.ignored?
|
35
|
-
|
36
|
-
@mutex.synchronize do
|
37
|
-
@payload[resource] ||= Airbrake::Stat.new
|
38
|
-
@payload[resource].increment(resource.start_time, resource.end_time)
|
39
|
-
|
40
|
-
if @flush_period > 0
|
41
|
-
schedule_flush(promise)
|
42
|
-
else
|
43
|
-
send(@payload, promise)
|
44
|
-
end
|
28
|
+
@payload.synchronize do
|
29
|
+
send_resource(resource, sync: false)
|
45
30
|
end
|
31
|
+
end
|
46
32
|
|
47
|
-
|
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
|
48
38
|
end
|
49
39
|
|
50
40
|
# @see Airbrake.add_performance_filter
|
@@ -57,44 +47,131 @@ module Airbrake
|
|
57
47
|
@filter_chain.delete_filter(filter_class)
|
58
48
|
end
|
59
49
|
|
50
|
+
def close
|
51
|
+
@payload.synchronize do
|
52
|
+
@schedule_flush.kill if @schedule_flush
|
53
|
+
@async_sender.close
|
54
|
+
logger.debug("#{LOG_LABEL} performance notifier closed")
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
60
58
|
private
|
61
59
|
|
62
|
-
def schedule_flush
|
60
|
+
def schedule_flush
|
63
61
|
@schedule_flush ||= Thread.new do
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
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, Airbrake::Promise.new)
|
76
|
+
@payload.clear
|
77
|
+
end
|
71
78
|
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def send_resource(resource, sync:)
|
83
|
+
promise = check_configuration(resource)
|
84
|
+
return promise if promise.rejected?
|
72
85
|
|
73
|
-
|
86
|
+
@filter_chain.refine(resource)
|
87
|
+
if resource.ignored?
|
88
|
+
return Promise.new.reject("#{resource.class} was ignored by a filter")
|
89
|
+
end
|
90
|
+
|
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
|
74
97
|
end
|
75
98
|
end
|
76
99
|
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
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
|
81
106
|
|
82
|
-
|
107
|
+
@payload[resource][:total].increment_ms(resource.timing)
|
83
108
|
|
84
|
-
|
85
|
-
|
86
|
-
|
109
|
+
resource.groups.each do |name, ms|
|
110
|
+
@payload[resource][name] ||= Airbrake::Stat.new
|
111
|
+
@payload[resource][name].increment_ms(ms)
|
112
|
+
end
|
113
|
+
end
|
87
114
|
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
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?
|
121
|
+
|
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?
|
131
|
+
|
132
|
+
with_grouped_payload(payload) do |resource_hash, destination|
|
133
|
+
url = URI.join(
|
134
|
+
@config.apm_host,
|
135
|
+
"api/v5/projects/#{@config.project_id}/#{destination}",
|
95
136
|
)
|
137
|
+
|
138
|
+
logger.debug do
|
139
|
+
"#{LOG_LABEL} #{self.class.name}##{__method__}: #{resource_hash}"
|
140
|
+
end
|
141
|
+
sender.send(resource_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 |resource, _stats|
|
149
|
+
[resource.cargo, resource.destination]
|
150
|
+
end
|
151
|
+
|
152
|
+
grouped_payload.each do |(cargo, destination), resources|
|
153
|
+
payload = {}
|
154
|
+
payload[cargo] = serialize_resources(resources)
|
155
|
+
payload['environment'] = @config.environment if @config.environment
|
156
|
+
|
157
|
+
yield(payload, destination)
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
def serialize_resources(resources)
|
162
|
+
resources.map do |resource, stats|
|
163
|
+
resource_hash = resource.to_h.merge!(stats[:total].to_h)
|
164
|
+
|
165
|
+
if resource.groups.any?
|
166
|
+
group_stats = stats.reject { |name, _stat| name == :total }
|
167
|
+
resource_hash['groups'] = group_stats.merge(group_stats) do |_name, stat|
|
168
|
+
stat.to_h
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
resource_hash
|
96
173
|
end
|
97
174
|
end
|
98
|
-
# rubocop:enable Metrics/AbcSize
|
99
175
|
end
|
176
|
+
# rubocop:enable Metrics/ClassLength
|
100
177
|
end
|
data/lib/airbrake-ruby/query.rb
CHANGED
@@ -4,12 +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
|
11
|
+
include Stashable
|
12
|
+
include Mergeable
|
13
|
+
include Grouppable
|
14
|
+
|
15
|
+
attr_accessor :method, :route, :query, :func, :file, :line, :timing, :time
|
13
16
|
|
14
17
|
def initialize(
|
15
18
|
method:,
|
@@ -18,13 +21,25 @@ module Airbrake
|
|
18
21
|
func: nil,
|
19
22
|
file: nil,
|
20
23
|
line: nil,
|
21
|
-
|
22
|
-
|
24
|
+
timing: nil,
|
25
|
+
time: Time.now
|
23
26
|
)
|
24
|
-
|
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'
|
25
40
|
end
|
26
41
|
|
27
|
-
def
|
42
|
+
def cargo
|
28
43
|
'queries'
|
29
44
|
end
|
30
45
|
|
@@ -33,12 +48,12 @@ module Airbrake
|
|
33
48
|
'method' => method,
|
34
49
|
'route' => route,
|
35
50
|
'query' => query,
|
36
|
-
'time' =>
|
51
|
+
'time' => @time_utc,
|
37
52
|
'function' => func,
|
38
53
|
'file' => file,
|
39
|
-
'line' => line
|
54
|
+
'line' => line,
|
40
55
|
}.delete_if { |_key, val| val.nil? }
|
41
56
|
end
|
42
|
-
# rubocop:enable Metrics/ParameterLists
|
57
|
+
# rubocop:enable Metrics/ParameterLists
|
43
58
|
end
|
44
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,120 @@
|
|
1
|
+
module Airbrake
|
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 5.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.key?('config_route') || !@data['config_route']
|
62
|
+
return format(
|
63
|
+
CONFIG_ROUTE_PATTERN,
|
64
|
+
host: remote_config_host.chomp('/'),
|
65
|
+
project_id: @project_id,
|
66
|
+
)
|
67
|
+
end
|
68
|
+
|
69
|
+
format(
|
70
|
+
CONFIG_ROUTE_PATTERN,
|
71
|
+
host: @data['config_route'].chomp('/'),
|
72
|
+
project_id: @project_id,
|
73
|
+
)
|
74
|
+
end
|
75
|
+
|
76
|
+
# @return [Boolean] whether error notifications are enabled
|
77
|
+
def error_notifications?
|
78
|
+
return true unless (s = find_setting(SETTINGS[:errors]))
|
79
|
+
|
80
|
+
s['enabled']
|
81
|
+
end
|
82
|
+
|
83
|
+
# @return [Boolean] whether APM is enabled
|
84
|
+
def performance_stats?
|
85
|
+
return true unless (s = find_setting(SETTINGS[:apm]))
|
86
|
+
|
87
|
+
s['enabled']
|
88
|
+
end
|
89
|
+
|
90
|
+
# @return [String, nil] the host, which provides the API endpoint to which
|
91
|
+
# exceptions should be sent
|
92
|
+
def error_host
|
93
|
+
return unless (s = find_setting(SETTINGS[:errors]))
|
94
|
+
|
95
|
+
s['endpoint']
|
96
|
+
end
|
97
|
+
|
98
|
+
# @return [String, nil] the host, which provides the API endpoint to which
|
99
|
+
# APM data should be sent
|
100
|
+
def apm_host
|
101
|
+
return unless (s = find_setting(SETTINGS[:apm]))
|
102
|
+
|
103
|
+
s['endpoint']
|
104
|
+
end
|
105
|
+
|
106
|
+
# @return [Hash{String=>Object}] raw representation of JSON payload
|
107
|
+
def to_h
|
108
|
+
@data.dup
|
109
|
+
end
|
110
|
+
|
111
|
+
private
|
112
|
+
|
113
|
+
def find_setting(name)
|
114
|
+
return unless @data.key?('settings')
|
115
|
+
|
116
|
+
@data['settings'].find { |s| s['name'] == name }
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|