airbrake-ruby 4.6.0 → 6.2.2
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 +24 -109
- data/lib/airbrake-ruby/backlog.rb +123 -0
- 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/processor.rb +77 -0
- data/lib/airbrake-ruby/config/validator.rb +9 -3
- data/lib/airbrake-ruby/config.rb +78 -19
- data/lib/airbrake-ruby/context.rb +51 -0
- 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 +18 -1
- data/lib/airbrake-ruby/filters/context_filter.rb +4 -5
- data/lib/airbrake-ruby/filters/dependency_filter.rb +1 -0
- data/lib/airbrake-ruby/filters/exception_attributes_filter.rb +3 -3
- data/lib/airbrake-ruby/filters/gem_root_filter.rb +1 -0
- data/lib/airbrake-ruby/filters/git_last_checkout_filter.rb +7 -7
- data/lib/airbrake-ruby/filters/git_repository_filter.rb +11 -2
- data/lib/airbrake-ruby/filters/git_revision_filter.rb +3 -1
- 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 +41 -22
- data/lib/airbrake-ruby/filters/root_directory_filter.rb +1 -0
- data/lib/airbrake-ruby/filters/sql_filter.rb +35 -11
- data/lib/airbrake-ruby/filters/system_exit_filter.rb +1 -0
- data/lib/airbrake-ruby/filters/thread_filter.rb +6 -5
- data/lib/airbrake-ruby/grouppable.rb +12 -0
- data/lib/airbrake-ruby/ignorable.rb +1 -2
- 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 +6 -1
- data/lib/airbrake-ruby/nested_exception.rb +22 -1
- data/lib/airbrake-ruby/notice.rb +12 -17
- data/lib/airbrake-ruby/notice_notifier.rb +15 -7
- data/lib/airbrake-ruby/performance_breakdown.rb +16 -9
- data/lib/airbrake-ruby/performance_notifier.rb +106 -54
- data/lib/airbrake-ruby/promise.rb +1 -0
- data/lib/airbrake-ruby/query.rb +20 -14
- data/lib/airbrake-ruby/queue.rb +65 -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/remote_settings.rb +128 -0
- data/lib/airbrake-ruby/request.rb +14 -11
- data/lib/airbrake-ruby/response.rb +56 -5
- data/lib/airbrake-ruby/stat.rb +27 -34
- data/lib/airbrake-ruby/sync_sender.rb +43 -11
- data/lib/airbrake-ruby/tdigest.rb +52 -66
- data/lib/airbrake-ruby/thread_pool.rb +139 -0
- data/lib/airbrake-ruby/time_truncate.rb +2 -2
- data/lib/airbrake-ruby/timed_trace.rb +1 -3
- data/lib/airbrake-ruby/truncator.rb +10 -4
- data/lib/airbrake-ruby/version.rb +11 -1
- data/lib/airbrake-ruby.rb +157 -78
- metadata +122 -116
- data/spec/airbrake_spec.rb +0 -324
- data/spec/async_sender_spec.rb +0 -155
- data/spec/backtrace_spec.rb +0 -427
- data/spec/benchmark_spec.rb +0 -33
- data/spec/code_hunk_spec.rb +0 -115
- data/spec/config/validator_spec.rb +0 -184
- data/spec/config_spec.rb +0 -154
- data/spec/deploy_notifier_spec.rb +0 -48
- data/spec/file_cache.rb +0 -36
- data/spec/filter_chain_spec.rb +0 -92
- data/spec/filters/context_filter_spec.rb +0 -23
- data/spec/filters/dependency_filter_spec.rb +0 -12
- data/spec/filters/exception_attributes_filter_spec.rb +0 -50
- data/spec/filters/gem_root_filter_spec.rb +0 -41
- data/spec/filters/git_last_checkout_filter_spec.rb +0 -46
- data/spec/filters/git_repository_filter.rb +0 -61
- data/spec/filters/git_revision_filter_spec.rb +0 -126
- data/spec/filters/keys_blacklist_spec.rb +0 -225
- data/spec/filters/keys_whitelist_spec.rb +0 -194
- data/spec/filters/root_directory_filter_spec.rb +0 -39
- data/spec/filters/sql_filter_spec.rb +0 -219
- data/spec/filters/system_exit_filter_spec.rb +0 -14
- data/spec/filters/thread_filter_spec.rb +0 -277
- data/spec/fixtures/notroot.txt +0 -7
- data/spec/fixtures/project_root/code.rb +0 -221
- data/spec/fixtures/project_root/empty_file.rb +0 -0
- data/spec/fixtures/project_root/long_line.txt +0 -1
- data/spec/fixtures/project_root/short_file.rb +0 -3
- data/spec/fixtures/project_root/vendor/bundle/ignored_file.rb +0 -5
- data/spec/helpers.rb +0 -9
- data/spec/ignorable_spec.rb +0 -14
- data/spec/inspectable_spec.rb +0 -45
- data/spec/monotonic_time_spec.rb +0 -12
- data/spec/nested_exception_spec.rb +0 -73
- data/spec/notice_notifier_spec/options_spec.rb +0 -259
- data/spec/notice_notifier_spec.rb +0 -356
- data/spec/notice_spec.rb +0 -296
- data/spec/performance_breakdown_spec.rb +0 -12
- data/spec/performance_notifier_spec.rb +0 -435
- data/spec/promise_spec.rb +0 -197
- data/spec/query_spec.rb +0 -11
- data/spec/request_spec.rb +0 -11
- data/spec/response_spec.rb +0 -88
- data/spec/spec_helper.rb +0 -100
- data/spec/stashable_spec.rb +0 -23
- data/spec/stat_spec.rb +0 -47
- data/spec/sync_sender_spec.rb +0 -133
- data/spec/tdigest_spec.rb +0 -230
- data/spec/time_truncate_spec.rb +0 -13
- data/spec/timed_trace_spec.rb +0 -125
- data/spec/truncator_spec.rb +0 -238
|
@@ -21,7 +21,7 @@ module Airbrake
|
|
|
21
21
|
project_id: @config.project_id,
|
|
22
22
|
project_key: @config.project_key,
|
|
23
23
|
host: @config.host,
|
|
24
|
-
filter_chain: @filter_chain.inspect
|
|
24
|
+
filter_chain: @filter_chain.inspect,
|
|
25
25
|
)
|
|
26
26
|
end
|
|
27
27
|
|
|
@@ -30,7 +30,7 @@ module Airbrake
|
|
|
30
30
|
q.text("#<#{self.class}:0x#{(object_id << 1).to_s(16).rjust(16, '0')} ")
|
|
31
31
|
q.text(
|
|
32
32
|
"project_id=\"#{@config.project_id}\" project_key=\"#{@config.project_key}\" " \
|
|
33
|
-
"host=\"#{@config.host}\" filter_chain="
|
|
33
|
+
"host=\"#{@config.host}\" filter_chain=",
|
|
34
34
|
)
|
|
35
35
|
q.pp(@filter_chain)
|
|
36
36
|
q.text('>')
|
|
@@ -16,6 +16,11 @@ module Airbrake
|
|
|
16
16
|
time_in_nanoseconds / (10.0**6)
|
|
17
17
|
end
|
|
18
18
|
|
|
19
|
+
# @return [Integer] current monotonic time in seconds
|
|
20
|
+
def time_in_s
|
|
21
|
+
time_in_nanoseconds / (10.0**9)
|
|
22
|
+
end
|
|
23
|
+
|
|
19
24
|
private
|
|
20
25
|
|
|
21
26
|
if defined?(Process::CLOCK_MONOTONIC)
|
|
@@ -34,7 +39,7 @@ module Airbrake
|
|
|
34
39
|
|
|
35
40
|
def time_in_nanoseconds
|
|
36
41
|
time = Time.now
|
|
37
|
-
time.to_i * (10**9) + time.nsec
|
|
42
|
+
(time.to_i * (10**9)) + time.nsec
|
|
38
43
|
end
|
|
39
44
|
|
|
40
45
|
end
|
|
@@ -9,6 +9,18 @@ module Airbrake
|
|
|
9
9
|
# can unwrap. Exceptions that have a longer cause chain will be ignored
|
|
10
10
|
MAX_NESTED_EXCEPTIONS = 3
|
|
11
11
|
|
|
12
|
+
# On Ruby 3.1+, the error highlighting gem can produce messages that can
|
|
13
|
+
# span multiple lines. We don't display multiline error messages in the
|
|
14
|
+
# title of the notice in the Airbrake dashboard. Therefore, we want to strip
|
|
15
|
+
# out the higlighting part so that the errors look consistent. The full
|
|
16
|
+
# message with the exception will be attached to the notice body.
|
|
17
|
+
#
|
|
18
|
+
# @return [String]
|
|
19
|
+
RUBY_31_ERROR_HIGHLIGHTING_DIVIDER = "\n\n".freeze
|
|
20
|
+
|
|
21
|
+
# @return [Hash] the options for +String#encode+
|
|
22
|
+
ENCODING_OPTIONS = { invalid: :replace, undef: :replace }.freeze
|
|
23
|
+
|
|
12
24
|
def initialize(exception)
|
|
13
25
|
@exception = exception
|
|
14
26
|
end
|
|
@@ -16,7 +28,7 @@ module Airbrake
|
|
|
16
28
|
def as_json
|
|
17
29
|
unwind_exceptions.map do |exception|
|
|
18
30
|
{ type: exception.class.name,
|
|
19
|
-
message: exception
|
|
31
|
+
message: message(exception),
|
|
20
32
|
backtrace: Backtrace.parse(exception) }
|
|
21
33
|
end
|
|
22
34
|
end
|
|
@@ -34,5 +46,14 @@ module Airbrake
|
|
|
34
46
|
|
|
35
47
|
exception_list
|
|
36
48
|
end
|
|
49
|
+
|
|
50
|
+
def message(exception)
|
|
51
|
+
return unless (msg = exception.message)
|
|
52
|
+
|
|
53
|
+
msg
|
|
54
|
+
.encode(Encoding::UTF_8, **ENCODING_OPTIONS)
|
|
55
|
+
.split(RUBY_31_ERROR_HIGHLIGHTING_DIVIDER)
|
|
56
|
+
.first
|
|
57
|
+
end
|
|
37
58
|
end
|
|
38
59
|
end
|
data/lib/airbrake-ruby/notice.rb
CHANGED
|
@@ -4,19 +4,12 @@ module Airbrake
|
|
|
4
4
|
#
|
|
5
5
|
# @since v1.0.0
|
|
6
6
|
class Notice
|
|
7
|
-
# @return [Hash{Symbol=>String}] the information about the notifier library
|
|
8
|
-
NOTIFIER = {
|
|
9
|
-
name: 'airbrake-ruby'.freeze,
|
|
10
|
-
version: Airbrake::AIRBRAKE_RUBY_VERSION,
|
|
11
|
-
url: 'https://github.com/airbrake/airbrake-ruby'.freeze
|
|
12
|
-
}.freeze
|
|
13
|
-
|
|
14
7
|
# @return [Hash{Symbol=>String,Hash}] the information to be displayed in the
|
|
15
8
|
# Context tab in the dashboard
|
|
16
9
|
CONTEXT = {
|
|
17
10
|
os: RUBY_PLATFORM,
|
|
18
11
|
language: "#{RUBY_ENGINE}/#{RUBY_VERSION}".freeze,
|
|
19
|
-
notifier:
|
|
12
|
+
notifier: Airbrake::NOTIFIER_INFO,
|
|
20
13
|
}.freeze
|
|
21
14
|
|
|
22
15
|
# @return [Integer] the maxium size of the JSON payload in bytes
|
|
@@ -32,7 +25,7 @@ module Airbrake
|
|
|
32
25
|
IOError,
|
|
33
26
|
NotImplementedError,
|
|
34
27
|
JSON::GeneratorError,
|
|
35
|
-
Encoding::UndefinedConversionError
|
|
28
|
+
Encoding::UndefinedConversionError,
|
|
36
29
|
].freeze
|
|
37
30
|
|
|
38
31
|
# @return [Array<Symbol>] the list of keys that can be be overwritten with
|
|
@@ -56,16 +49,17 @@ module Airbrake
|
|
|
56
49
|
# @api private
|
|
57
50
|
def initialize(exception, params = {})
|
|
58
51
|
@config = Airbrake::Config.instance
|
|
52
|
+
@truncator = Airbrake::Truncator.new(PAYLOAD_MAX_SIZE)
|
|
53
|
+
|
|
59
54
|
@payload = {
|
|
60
55
|
errors: NestedException.new(exception).as_json,
|
|
61
|
-
context: context,
|
|
56
|
+
context: context(exception),
|
|
62
57
|
environment: {
|
|
63
|
-
program_name: $PROGRAM_NAME
|
|
58
|
+
program_name: $PROGRAM_NAME,
|
|
64
59
|
},
|
|
65
60
|
session: {},
|
|
66
|
-
params: params
|
|
61
|
+
params: params,
|
|
67
62
|
}
|
|
68
|
-
@truncator = Airbrake::Truncator.new(PAYLOAD_MAX_SIZE)
|
|
69
63
|
|
|
70
64
|
stash[:exception] = exception
|
|
71
65
|
end
|
|
@@ -76,7 +70,7 @@ module Airbrake
|
|
|
76
70
|
#
|
|
77
71
|
# @return [Hash{String=>String}, nil]
|
|
78
72
|
# @api private
|
|
79
|
-
def to_json
|
|
73
|
+
def to_json(*_args)
|
|
80
74
|
loop do
|
|
81
75
|
begin
|
|
82
76
|
json = @payload.to_json
|
|
@@ -125,7 +119,7 @@ module Airbrake
|
|
|
125
119
|
|
|
126
120
|
private
|
|
127
121
|
|
|
128
|
-
def context
|
|
122
|
+
def context(exception)
|
|
129
123
|
{
|
|
130
124
|
version: @config.app_version,
|
|
131
125
|
versions: @config.versions,
|
|
@@ -138,7 +132,8 @@ module Airbrake
|
|
|
138
132
|
# Make sure we always send hostname.
|
|
139
133
|
hostname: HOSTNAME,
|
|
140
134
|
|
|
141
|
-
severity: DEFAULT_SEVERITY
|
|
135
|
+
severity: DEFAULT_SEVERITY,
|
|
136
|
+
error_message: @truncator.truncate(exception.message),
|
|
142
137
|
}.merge(CONTEXT).delete_if { |_key, val| val.nil? || val.empty? }
|
|
143
138
|
end
|
|
144
139
|
|
|
@@ -152,7 +147,7 @@ module Airbrake
|
|
|
152
147
|
logger.error(
|
|
153
148
|
"#{LOG_LABEL} truncation failed. File an issue at " \
|
|
154
149
|
"https://github.com/airbrake/airbrake-ruby " \
|
|
155
|
-
"and attach the following payload: #{@payload}"
|
|
150
|
+
"and attach the following payload: #{@payload}",
|
|
156
151
|
)
|
|
157
152
|
end
|
|
158
153
|
|
|
@@ -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
|
|
@@ -20,14 +20,13 @@ module Airbrake
|
|
|
20
20
|
|
|
21
21
|
def initialize
|
|
22
22
|
@config = Airbrake::Config.instance
|
|
23
|
-
@context = {}
|
|
24
23
|
@filter_chain = FilterChain.new
|
|
25
|
-
@async_sender = AsyncSender.new
|
|
24
|
+
@async_sender = AsyncSender.new(:post, self.class.name)
|
|
26
25
|
@sync_sender = SyncSender.new
|
|
27
26
|
|
|
28
27
|
DEFAULT_FILTERS.each { |filter| add_filter(filter.new) }
|
|
29
28
|
|
|
30
|
-
add_filter(Airbrake::Filters::ContextFilter.new
|
|
29
|
+
add_filter(Airbrake::Filters::ContextFilter.new)
|
|
31
30
|
add_filter(Airbrake::Filters::ExceptionAttributesFilter.new)
|
|
32
31
|
end
|
|
33
32
|
|
|
@@ -55,7 +54,8 @@ module Airbrake
|
|
|
55
54
|
def build_notice(exception, params = {})
|
|
56
55
|
if @async_sender.closed?
|
|
57
56
|
raise Airbrake::Error,
|
|
58
|
-
"
|
|
57
|
+
"Airbrake is closed; can't build exception: " \
|
|
58
|
+
"#{exception.class}: #{exception}"
|
|
59
59
|
end
|
|
60
60
|
|
|
61
61
|
if exception.is_a?(Airbrake::Notice)
|
|
@@ -68,6 +68,7 @@ module Airbrake
|
|
|
68
68
|
|
|
69
69
|
# @see Airbrake.close
|
|
70
70
|
def close
|
|
71
|
+
@sync_sender.close
|
|
71
72
|
@async_sender.close
|
|
72
73
|
end
|
|
73
74
|
|
|
@@ -78,7 +79,13 @@ module Airbrake
|
|
|
78
79
|
|
|
79
80
|
# @see Airbrake.merge_context
|
|
80
81
|
def merge_context(context)
|
|
81
|
-
|
|
82
|
+
Airbrake::Context.current.merge!(context)
|
|
83
|
+
end
|
|
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)
|
|
82
89
|
end
|
|
83
90
|
|
|
84
91
|
private
|
|
@@ -116,7 +123,7 @@ module Airbrake
|
|
|
116
123
|
|
|
117
124
|
logger.warn(
|
|
118
125
|
"#{LOG_LABEL} falling back to sync delivery because there are no " \
|
|
119
|
-
"running async workers"
|
|
126
|
+
"running async workers",
|
|
120
127
|
)
|
|
121
128
|
@sync_sender
|
|
122
129
|
end
|
|
@@ -128,6 +135,7 @@ module Airbrake
|
|
|
128
135
|
# If true, then it's likely an internal library error. In this case return
|
|
129
136
|
# at least some backtrace to simplify debugging.
|
|
130
137
|
return caller_copy if clean_bt.empty?
|
|
138
|
+
|
|
131
139
|
clean_bt
|
|
132
140
|
end
|
|
133
141
|
end
|
|
@@ -5,23 +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
|
+
@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
|
|
25
32
|
end
|
|
26
33
|
|
|
27
34
|
def destination
|
|
@@ -37,9 +44,9 @@ module Airbrake
|
|
|
37
44
|
'method' => method,
|
|
38
45
|
'route' => route,
|
|
39
46
|
'responseType' => response_type,
|
|
40
|
-
'time' =>
|
|
47
|
+
'time' => @time_utc,
|
|
41
48
|
}.delete_if { |_key, val| val.nil? }
|
|
42
49
|
end
|
|
43
50
|
end
|
|
44
|
-
# rubocop:enable Metrics/
|
|
51
|
+
# rubocop:enable Metrics/ParameterLists
|
|
45
52
|
end
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
module Airbrake
|
|
2
|
-
#
|
|
3
|
-
#
|
|
2
|
+
# PerformanceNotifier aggregates performance data and periodically sends it to
|
|
3
|
+
# 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,32 +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, self.class.name)
|
|
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
|
-
# @param [Hash]
|
|
24
|
+
# @param [Hash] metric
|
|
22
25
|
# @see Airbrake.notify_query
|
|
23
26
|
# @see Airbrake.notify_request
|
|
24
|
-
def notify(
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
promise = @config.check_performance_options(resource)
|
|
29
|
-
return promise if promise.rejected?
|
|
30
|
-
|
|
31
|
-
@filter_chain.refine(resource)
|
|
32
|
-
return if resource.ignored?
|
|
33
|
-
|
|
34
|
-
@mutex.synchronize do
|
|
35
|
-
update_payload(resource)
|
|
36
|
-
@flush_period > 0 ? schedule_flush(promise) : send(@payload, promise)
|
|
27
|
+
def notify(metric)
|
|
28
|
+
@payload.synchronize do
|
|
29
|
+
send_metric(metric, sync: false)
|
|
37
30
|
end
|
|
31
|
+
end
|
|
38
32
|
|
|
39
|
-
|
|
33
|
+
# @param [Hash] metric
|
|
34
|
+
# @since v4.10.0
|
|
35
|
+
# @see Airbrake.notify_queue_sync
|
|
36
|
+
def notify_sync(metric)
|
|
37
|
+
send_metric(metric, sync: true).value
|
|
40
38
|
end
|
|
41
39
|
|
|
42
40
|
# @see Airbrake.add_performance_filter
|
|
@@ -49,77 +47,131 @@ module Airbrake
|
|
|
49
47
|
@filter_chain.delete_filter(filter_class)
|
|
50
48
|
end
|
|
51
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
|
+
|
|
52
58
|
private
|
|
53
59
|
|
|
54
|
-
def
|
|
55
|
-
@
|
|
56
|
-
|
|
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
|
|
57
74
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
75
|
+
send(@async_sender, @payload, Airbrake::Promise.new)
|
|
76
|
+
@payload.clear
|
|
77
|
+
end
|
|
78
|
+
end
|
|
61
79
|
end
|
|
62
80
|
end
|
|
63
81
|
|
|
64
|
-
def
|
|
65
|
-
|
|
66
|
-
|
|
82
|
+
def send_metric(metric, sync:)
|
|
83
|
+
promise = check_configuration(metric)
|
|
84
|
+
return promise if promise.rejected?
|
|
67
85
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
@schedule_flush = nil
|
|
73
|
-
end
|
|
86
|
+
@filter_chain.refine(metric)
|
|
87
|
+
if metric.ignored?
|
|
88
|
+
return Promise.new.reject("#{metric.class} was ignored by a filter")
|
|
89
|
+
end
|
|
74
90
|
|
|
75
|
-
|
|
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
|
|
76
97
|
end
|
|
77
98
|
end
|
|
78
99
|
|
|
79
|
-
def
|
|
80
|
-
|
|
81
|
-
|
|
100
|
+
def update_payload(metric)
|
|
101
|
+
if (total_stat = @payload[metric])
|
|
102
|
+
@payload.key(total_stat).merge(metric)
|
|
103
|
+
else
|
|
104
|
+
@payload[metric] = { total: Airbrake::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] ||= Airbrake::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?
|
|
82
121
|
|
|
83
|
-
|
|
122
|
+
if metric.timing && metric.timing == 0
|
|
123
|
+
return Promise.new.reject(':timing cannot be zero')
|
|
124
|
+
end
|
|
84
125
|
|
|
85
|
-
|
|
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|
|
|
86
133
|
url = URI.join(
|
|
87
|
-
@config.
|
|
88
|
-
"api/v5/projects/#{@config.project_id}/#{destination}"
|
|
134
|
+
@config.apm_host,
|
|
135
|
+
"api/v5/projects/#{@config.project_id}/#{destination}",
|
|
89
136
|
)
|
|
90
|
-
|
|
137
|
+
|
|
138
|
+
logger.debug do
|
|
139
|
+
"#{LOG_LABEL} #{self.class.name}##{__method__}: #{metric_hash}"
|
|
140
|
+
end
|
|
141
|
+
sender.send(metric_hash, promise, url)
|
|
91
142
|
end
|
|
92
143
|
|
|
93
144
|
promise
|
|
94
145
|
end
|
|
95
146
|
|
|
96
147
|
def with_grouped_payload(raw_payload)
|
|
97
|
-
grouped_payload = raw_payload.group_by do |
|
|
98
|
-
[
|
|
148
|
+
grouped_payload = raw_payload.group_by do |metric, _stats|
|
|
149
|
+
[metric.cargo, metric.destination]
|
|
99
150
|
end
|
|
100
151
|
|
|
101
|
-
grouped_payload.each do |(cargo, destination),
|
|
152
|
+
grouped_payload.each do |(cargo, destination), metrics|
|
|
102
153
|
payload = {}
|
|
103
|
-
payload[cargo] =
|
|
154
|
+
payload[cargo] = serialize_metrics(metrics)
|
|
104
155
|
payload['environment'] = @config.environment if @config.environment
|
|
105
156
|
|
|
106
157
|
yield(payload, destination)
|
|
107
158
|
end
|
|
108
159
|
end
|
|
109
160
|
|
|
110
|
-
def
|
|
111
|
-
|
|
112
|
-
|
|
161
|
+
def serialize_metrics(metrics)
|
|
162
|
+
metrics.map do |metric, stats|
|
|
163
|
+
metric_hash = metric.to_h.merge!(stats[:total].to_h)
|
|
113
164
|
|
|
114
|
-
if
|
|
165
|
+
if metric.groups.any?
|
|
115
166
|
group_stats = stats.reject { |name, _stat| name == :total }
|
|
116
|
-
|
|
167
|
+
metric_hash['groups'] = group_stats.merge(group_stats) do |_name, stat|
|
|
117
168
|
stat.to_h
|
|
118
169
|
end
|
|
119
170
|
end
|
|
120
171
|
|
|
121
|
-
|
|
172
|
+
metric_hash
|
|
122
173
|
end
|
|
123
174
|
end
|
|
124
175
|
end
|
|
176
|
+
# rubocop:enable Metrics/ClassLength
|
|
125
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,10 +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
|
-
|
|
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
|
|
26
36
|
end
|
|
27
37
|
|
|
28
38
|
def destination
|
|
@@ -33,21 +43,17 @@ module Airbrake
|
|
|
33
43
|
'queries'
|
|
34
44
|
end
|
|
35
45
|
|
|
36
|
-
def groups
|
|
37
|
-
{}
|
|
38
|
-
end
|
|
39
|
-
|
|
40
46
|
def to_h
|
|
41
47
|
{
|
|
42
48
|
'method' => method,
|
|
43
49
|
'route' => route,
|
|
44
50
|
'query' => query,
|
|
45
|
-
'time' =>
|
|
51
|
+
'time' => @time_utc,
|
|
46
52
|
'function' => func,
|
|
47
53
|
'file' => file,
|
|
48
|
-
'line' => line
|
|
54
|
+
'line' => line,
|
|
49
55
|
}.delete_if { |_key, val| val.nil? }
|
|
50
56
|
end
|
|
51
|
-
# rubocop:enable Metrics/ParameterLists
|
|
57
|
+
# rubocop:enable Metrics/ParameterLists
|
|
52
58
|
end
|
|
53
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
|