airbrake-ruby 4.1.0 → 5.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|