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
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
module Airbrake
|
|
2
|
+
# Represents a thread-safe Airbrake context object, which carries arbitrary
|
|
3
|
+
# information added via {Airbrake.merge_context} calls.
|
|
4
|
+
#
|
|
5
|
+
# @example
|
|
6
|
+
# Airbrake::Context.current.merge!(foo: 'bar')
|
|
7
|
+
#
|
|
8
|
+
# @api private
|
|
9
|
+
# @since v5.2.1
|
|
10
|
+
class Context
|
|
11
|
+
# Returns current, thread-local, context.
|
|
12
|
+
# @return [self]
|
|
13
|
+
def self.current
|
|
14
|
+
Thread.current[:airbrake_context] ||= new
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def initialize
|
|
18
|
+
@mutex = Mutex.new
|
|
19
|
+
@context = {}
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# Merges the given context with the current one.
|
|
23
|
+
#
|
|
24
|
+
# @param [Hash{Object=>Object}] other
|
|
25
|
+
# @return [void]
|
|
26
|
+
def merge!(other)
|
|
27
|
+
@mutex.synchronize do
|
|
28
|
+
@context.merge!(other)
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# @return [Hash] duplicated Hash context
|
|
33
|
+
def to_h
|
|
34
|
+
@mutex.synchronize do
|
|
35
|
+
@context.dup
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# @return [Hash] clears (resets) the current context
|
|
40
|
+
def clear
|
|
41
|
+
@mutex.synchronize do
|
|
42
|
+
@context.clear
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# @return [Boolean] checks whether the context has any data
|
|
47
|
+
def empty?
|
|
48
|
+
@context.empty?
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
@@ -57,7 +57,9 @@ module Airbrake
|
|
|
57
57
|
# @return [void]
|
|
58
58
|
# @since v3.1.0
|
|
59
59
|
def delete_filter(filter_class)
|
|
60
|
+
# rubocop:disable Style/ClassEqualityComparison
|
|
60
61
|
index = @filters.index { |f| f.class.name == filter_class.name }
|
|
62
|
+
# rubocop:enable Style/ClassEqualityComparison
|
|
61
63
|
@filters.delete_at(index) if index
|
|
62
64
|
end
|
|
63
65
|
|
|
@@ -70,13 +72,14 @@ module Airbrake
|
|
|
70
72
|
def refine(notice)
|
|
71
73
|
@filters.each do |filter|
|
|
72
74
|
break if notice.ignored?
|
|
75
|
+
|
|
73
76
|
filter.call(notice)
|
|
74
77
|
end
|
|
75
78
|
end
|
|
76
79
|
|
|
77
80
|
# @return [String] customized inspect to lessen the amount of clutter
|
|
78
81
|
def inspect
|
|
79
|
-
|
|
82
|
+
filter_classes.to_s
|
|
80
83
|
end
|
|
81
84
|
|
|
82
85
|
# @return [String] {#inspect} for PrettyPrint
|
|
@@ -91,5 +94,19 @@ module Airbrake
|
|
|
91
94
|
end
|
|
92
95
|
q.text(']')
|
|
93
96
|
end
|
|
97
|
+
|
|
98
|
+
# @param [Class] filter_class
|
|
99
|
+
# @return [Boolean] true if the current chain has an instance of the given
|
|
100
|
+
# class, false otherwise
|
|
101
|
+
# @since v4.14.0
|
|
102
|
+
def includes?(filter_class)
|
|
103
|
+
filter_classes.include?(filter_class)
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
private
|
|
107
|
+
|
|
108
|
+
def filter_classes
|
|
109
|
+
@filters.map(&:class)
|
|
110
|
+
end
|
|
94
111
|
end
|
|
95
112
|
end
|
|
@@ -9,8 +9,7 @@ module Airbrake
|
|
|
9
9
|
# @return [Integer]
|
|
10
10
|
attr_reader :weight
|
|
11
11
|
|
|
12
|
-
def initialize
|
|
13
|
-
@context = context
|
|
12
|
+
def initialize
|
|
14
13
|
@weight = 119
|
|
15
14
|
@mutex = Mutex.new
|
|
16
15
|
end
|
|
@@ -18,10 +17,10 @@ module Airbrake
|
|
|
18
17
|
# @macro call_filter
|
|
19
18
|
def call(notice)
|
|
20
19
|
@mutex.synchronize do
|
|
21
|
-
return if
|
|
20
|
+
return if Airbrake::Context.current.empty?
|
|
22
21
|
|
|
23
|
-
notice[:params][:airbrake_context] =
|
|
24
|
-
|
|
22
|
+
notice[:params][:airbrake_context] = Airbrake::Context.current.to_h
|
|
23
|
+
Airbrake::Context.current.clear
|
|
25
24
|
end
|
|
26
25
|
end
|
|
27
26
|
end
|
|
@@ -13,7 +13,7 @@ module Airbrake
|
|
|
13
13
|
end
|
|
14
14
|
|
|
15
15
|
# @macro call_filter
|
|
16
|
-
def call(notice)
|
|
16
|
+
def call(notice) # rubocop:disable Metrics/AbcSize
|
|
17
17
|
exception = notice.stash[:exception]
|
|
18
18
|
return unless exception.respond_to?(:to_airbrake)
|
|
19
19
|
|
|
@@ -22,13 +22,13 @@ module Airbrake
|
|
|
22
22
|
attributes = exception.to_airbrake
|
|
23
23
|
rescue StandardError => ex
|
|
24
24
|
logger.error(
|
|
25
|
-
"#{LOG_LABEL} #{exception.class}#to_airbrake failed. #{ex.class}: #{ex}"
|
|
25
|
+
"#{LOG_LABEL} #{exception.class}#to_airbrake failed. #{ex.class}: #{ex}",
|
|
26
26
|
)
|
|
27
27
|
end
|
|
28
28
|
|
|
29
29
|
unless attributes.is_a?(Hash)
|
|
30
30
|
logger.error(
|
|
31
|
-
"#{LOG_LABEL} #{self.class}: wanted Hash, got #{attributes.class}"
|
|
31
|
+
"#{LOG_LABEL} #{self.class}: wanted Hash, got #{attributes.class}",
|
|
32
32
|
)
|
|
33
33
|
return
|
|
34
34
|
end
|
|
@@ -27,6 +27,7 @@ module Airbrake
|
|
|
27
27
|
@git_path = File.join(root_directory, '.git')
|
|
28
28
|
@weight = 116
|
|
29
29
|
@last_checkout = nil
|
|
30
|
+
@deploy_username = ENV.fetch('AIRBRAKE_DEPLOY_USERNAME', nil)
|
|
30
31
|
end
|
|
31
32
|
|
|
32
33
|
# @macro call_filter
|
|
@@ -40,39 +41,38 @@ module Airbrake
|
|
|
40
41
|
|
|
41
42
|
return unless File.exist?(@git_path)
|
|
42
43
|
return unless (checkout = last_checkout)
|
|
44
|
+
|
|
43
45
|
notice[:context][:lastCheckout] = checkout
|
|
44
46
|
end
|
|
45
47
|
|
|
46
48
|
private
|
|
47
49
|
|
|
48
|
-
# rubocop:disable Metrics/AbcSize
|
|
49
50
|
def last_checkout
|
|
50
51
|
return unless (line = last_checkout_line)
|
|
51
52
|
|
|
52
|
-
parts = line.chomp.split("\t").first.split
|
|
53
|
+
parts = line.chomp.split("\t").first.split
|
|
53
54
|
if parts.size < MIN_HEAD_COLS
|
|
54
55
|
logger.error(
|
|
55
|
-
"#{LOG_LABEL} Airbrake::#{self.class.name}: can't parse line: #{line}"
|
|
56
|
+
"#{LOG_LABEL} Airbrake::#{self.class.name}: can't parse line: #{line}",
|
|
56
57
|
)
|
|
57
58
|
return
|
|
58
59
|
end
|
|
59
60
|
|
|
60
61
|
author = parts[2..-4]
|
|
61
62
|
@last_checkout = {
|
|
62
|
-
username: author[0..1].join(' '),
|
|
63
|
+
username: @deploy_username || author[0..1].join(' '),
|
|
63
64
|
email: parts[-3][1..-2],
|
|
64
65
|
revision: parts[1],
|
|
65
|
-
time: timestamp(parts[-2].to_i)
|
|
66
|
+
time: timestamp(parts[-2].to_i),
|
|
66
67
|
}
|
|
67
68
|
end
|
|
68
|
-
# rubocop:enable Metrics/AbcSize
|
|
69
69
|
|
|
70
70
|
def last_checkout_line
|
|
71
71
|
head_path = File.join(@git_path, 'logs', 'HEAD')
|
|
72
72
|
return unless File.exist?(head_path)
|
|
73
73
|
|
|
74
74
|
last_line = nil
|
|
75
|
-
|
|
75
|
+
File.foreach(head_path) do |line|
|
|
76
76
|
last_line = line if checkout_line?(line)
|
|
77
77
|
end
|
|
78
78
|
last_line
|
|
@@ -18,6 +18,7 @@ module Airbrake
|
|
|
18
18
|
# @macro call_filter
|
|
19
19
|
def call(notice)
|
|
20
20
|
return if notice[:context].key?(:repository)
|
|
21
|
+
|
|
21
22
|
attach_repository(notice)
|
|
22
23
|
end
|
|
23
24
|
|
|
@@ -35,10 +36,11 @@ module Airbrake
|
|
|
35
36
|
`cd #{@git_path} && git config --get remote.origin.url`.chomp
|
|
36
37
|
else
|
|
37
38
|
"`git remote get-url` is unsupported in git #{@git_version}. " \
|
|
38
|
-
|
|
39
|
+
'Consider an upgrade to 2.7+'
|
|
39
40
|
end
|
|
40
41
|
|
|
41
42
|
return unless @repository
|
|
43
|
+
|
|
42
44
|
notice[:context][:repository] = @repository
|
|
43
45
|
end
|
|
44
46
|
|
|
@@ -46,7 +48,14 @@ module Airbrake
|
|
|
46
48
|
|
|
47
49
|
def detect_git_version
|
|
48
50
|
return unless which('git')
|
|
49
|
-
|
|
51
|
+
|
|
52
|
+
begin
|
|
53
|
+
Gem::Version.new(`git --version`.split[2])
|
|
54
|
+
rescue Errno::EAGAIN
|
|
55
|
+
# Bugfix for the case when the system cannot allocate memory for
|
|
56
|
+
# a fork() call: https://github.com/airbrake/airbrake-ruby/issues/680
|
|
57
|
+
nil
|
|
58
|
+
end
|
|
50
59
|
end
|
|
51
60
|
|
|
52
61
|
# Cross-platform way to tell if an executable is accessible.
|
|
@@ -30,6 +30,7 @@ module Airbrake
|
|
|
30
30
|
|
|
31
31
|
@revision = find_revision
|
|
32
32
|
return unless @revision
|
|
33
|
+
|
|
33
34
|
notice[:context][:revision] = @revision
|
|
34
35
|
end
|
|
35
36
|
|
|
@@ -41,6 +42,7 @@ module Airbrake
|
|
|
41
42
|
|
|
42
43
|
head = File.read(head_path)
|
|
43
44
|
return head unless head.start_with?(PREFIX)
|
|
45
|
+
|
|
44
46
|
head = head.chomp[PREFIX.size..-1]
|
|
45
47
|
|
|
46
48
|
ref_path = File.join(@git_path, head)
|
|
@@ -55,7 +57,7 @@ module Airbrake
|
|
|
55
57
|
|
|
56
58
|
File.readlines(packed_refs_path).each do |line|
|
|
57
59
|
next if %w[# ^].include?(line[0])
|
|
58
|
-
next unless (parts = line.split
|
|
60
|
+
next unless (parts = line.split).size == 2
|
|
59
61
|
return parts.first if parts.last == head
|
|
60
62
|
end
|
|
61
63
|
|
|
@@ -4,7 +4,7 @@ module Airbrake
|
|
|
4
4
|
# notice, but specified keys.
|
|
5
5
|
#
|
|
6
6
|
# @example
|
|
7
|
-
# filter = Airbrake::Filters::
|
|
7
|
+
# filter = Airbrake::Filters::KeysAllowlist.new(
|
|
8
8
|
# [:email, /credit/i, 'password']
|
|
9
9
|
# )
|
|
10
10
|
# airbrake.add_filter(filter)
|
|
@@ -22,9 +22,9 @@ module Airbrake
|
|
|
22
22
|
# # email: 'john@example.com',
|
|
23
23
|
# # account_id: 42 }
|
|
24
24
|
#
|
|
25
|
-
# @see
|
|
25
|
+
# @see KeysBlocklist
|
|
26
26
|
# @see KeysFilter
|
|
27
|
-
class
|
|
27
|
+
class KeysAllowlist
|
|
28
28
|
include KeysFilter
|
|
29
29
|
|
|
30
30
|
def initialize(*)
|
|
@@ -4,7 +4,7 @@ module Airbrake
|
|
|
4
4
|
# list of parameters in the payload of a notice.
|
|
5
5
|
#
|
|
6
6
|
# @example
|
|
7
|
-
# filter = Airbrake::Filters::
|
|
7
|
+
# filter = Airbrake::Filters::KeysBlocklist.new(
|
|
8
8
|
# [:email, /credit/i, 'password']
|
|
9
9
|
# )
|
|
10
10
|
# airbrake.add_filter(filter)
|
|
@@ -22,10 +22,10 @@ module Airbrake
|
|
|
22
22
|
# # email: '[Filtered]',
|
|
23
23
|
# # credit_card: '[Filtered]' }
|
|
24
24
|
#
|
|
25
|
-
# @see
|
|
25
|
+
# @see KeysAllowlist
|
|
26
26
|
# @see KeysFilter
|
|
27
27
|
# @api private
|
|
28
|
-
class
|
|
28
|
+
class KeysBlocklist
|
|
29
29
|
include KeysFilter
|
|
30
30
|
|
|
31
31
|
def initialize(*)
|
|
@@ -7,8 +7,8 @@ module Airbrake
|
|
|
7
7
|
# class that includes this module must implement.
|
|
8
8
|
#
|
|
9
9
|
# @see Notice
|
|
10
|
-
# @see
|
|
11
|
-
# @see
|
|
10
|
+
# @see KeysAllowlist
|
|
11
|
+
# @see KeysBlocklist
|
|
12
12
|
# @api private
|
|
13
13
|
module KeysFilter
|
|
14
14
|
# @return [String] The label to replace real values of filtered payload
|
|
@@ -19,19 +19,30 @@ module Airbrake
|
|
|
19
19
|
VALID_PATTERN_CLASSES = [String, Symbol, Regexp].freeze
|
|
20
20
|
|
|
21
21
|
# @return [Array<Symbol>] parts of a Notice's payload that can be modified
|
|
22
|
-
# by
|
|
22
|
+
# by blocklist/allowlist filters
|
|
23
23
|
FILTERABLE_KEYS = %i[environment session params].freeze
|
|
24
24
|
|
|
25
25
|
# @return [Array<Symbol>] parts of a Notice's *context* payload that can
|
|
26
|
-
# be modified by
|
|
27
|
-
FILTERABLE_CONTEXT_KEYS = %i[
|
|
26
|
+
# be modified by blocklist/allowlist filters
|
|
27
|
+
FILTERABLE_CONTEXT_KEYS = %i[
|
|
28
|
+
user
|
|
29
|
+
|
|
30
|
+
# Provided by Airbrake::Rack::HttpHeadersFilter
|
|
31
|
+
headers
|
|
32
|
+
referer
|
|
33
|
+
httpMethod
|
|
34
|
+
|
|
35
|
+
# Provided by Airbrake::Rack::ContextFilter
|
|
36
|
+
userAddr
|
|
37
|
+
userAgent
|
|
38
|
+
].freeze
|
|
28
39
|
|
|
29
40
|
include Loggable
|
|
30
41
|
|
|
31
42
|
# @return [Integer]
|
|
32
43
|
attr_reader :weight
|
|
33
44
|
|
|
34
|
-
# Creates a new
|
|
45
|
+
# Creates a new KeysBlocklist or KeysAllowlist filter that uses the given
|
|
35
46
|
# +patterns+ for filtering a notice's payload.
|
|
36
47
|
#
|
|
37
48
|
# @param [Array<String,Regexp,Symbol>] patterns
|
|
@@ -53,10 +64,14 @@ module Airbrake
|
|
|
53
64
|
validate_patterns
|
|
54
65
|
end
|
|
55
66
|
|
|
56
|
-
FILTERABLE_KEYS.each
|
|
67
|
+
FILTERABLE_KEYS.each do |key|
|
|
68
|
+
notice[key] = filter_hash(notice[key])
|
|
69
|
+
end
|
|
70
|
+
|
|
57
71
|
FILTERABLE_CONTEXT_KEYS.each { |key| filter_context_key(notice, key) }
|
|
58
72
|
|
|
59
73
|
return unless notice[:context][:url]
|
|
74
|
+
|
|
60
75
|
filter_url(notice)
|
|
61
76
|
end
|
|
62
77
|
|
|
@@ -67,29 +82,29 @@ module Airbrake
|
|
|
67
82
|
|
|
68
83
|
private
|
|
69
84
|
|
|
70
|
-
def filter_hash(hash)
|
|
85
|
+
def filter_hash(hash) # rubocop:disable Metrics/AbcSize
|
|
71
86
|
return hash unless hash.is_a?(Hash)
|
|
72
87
|
|
|
88
|
+
hash_copy = hash.dup
|
|
89
|
+
|
|
73
90
|
hash.each_key do |key|
|
|
74
91
|
if should_filter?(key.to_s)
|
|
75
|
-
|
|
76
|
-
elsif
|
|
77
|
-
filter_hash(
|
|
92
|
+
hash_copy[key] = FILTERED
|
|
93
|
+
elsif hash_copy[key].is_a?(Hash)
|
|
94
|
+
hash_copy[key] = filter_hash(hash_copy[key])
|
|
78
95
|
elsif hash[key].is_a?(Array)
|
|
79
|
-
|
|
96
|
+
hash_copy[key].each_with_index do |h, i|
|
|
97
|
+
hash_copy[key][i] = filter_hash(h)
|
|
98
|
+
end
|
|
80
99
|
end
|
|
81
100
|
end
|
|
101
|
+
|
|
102
|
+
hash_copy
|
|
82
103
|
end
|
|
83
104
|
|
|
84
105
|
def filter_url_params(url)
|
|
85
|
-
url.query =
|
|
86
|
-
|
|
87
|
-
# invalid characters, so be sure to escape individual components.
|
|
88
|
-
if should_filter?(key)
|
|
89
|
-
"#{URI.encode_www_form_component(key)}=[Filtered]"
|
|
90
|
-
else
|
|
91
|
-
"#{URI.encode_www_form_component(key)}=#{URI.encode_www_form_component(val)}"
|
|
92
|
-
end
|
|
106
|
+
url.query = URI.decode_www_form(url.query).to_h.map do |key, val|
|
|
107
|
+
should_filter?(key) ? "#{key}=[Filtered]" : "#{key}=#{val}"
|
|
93
108
|
end.join('&')
|
|
94
109
|
|
|
95
110
|
url.to_s
|
|
@@ -103,6 +118,7 @@ module Airbrake
|
|
|
103
118
|
end
|
|
104
119
|
|
|
105
120
|
return unless url.query
|
|
121
|
+
|
|
106
122
|
notice[:context][:url] = filter_url_params(url)
|
|
107
123
|
end
|
|
108
124
|
|
|
@@ -111,6 +127,7 @@ module Airbrake
|
|
|
111
127
|
|
|
112
128
|
@patterns = @patterns.flat_map do |pattern|
|
|
113
129
|
next(pattern) unless pattern.respond_to?(:call)
|
|
130
|
+
|
|
114
131
|
pattern.call
|
|
115
132
|
end
|
|
116
133
|
end
|
|
@@ -124,14 +141,16 @@ module Airbrake
|
|
|
124
141
|
|
|
125
142
|
logger.error(
|
|
126
143
|
"#{LOG_LABEL} one of the patterns in #{self.class} is invalid. " \
|
|
127
|
-
"Known patterns: #{@patterns}"
|
|
144
|
+
"Known patterns: #{@patterns}",
|
|
128
145
|
)
|
|
129
146
|
end
|
|
130
147
|
|
|
131
148
|
def filter_context_key(notice, key)
|
|
132
149
|
return unless notice[:context][key]
|
|
133
150
|
return if notice[:context][key] == FILTERED
|
|
134
|
-
|
|
151
|
+
unless should_filter?(key)
|
|
152
|
+
return notice[:context][key] = filter_hash(notice[:context][key])
|
|
153
|
+
end
|
|
135
154
|
|
|
136
155
|
notice[:context][key] = FILTERED
|
|
137
156
|
end
|
|
@@ -23,20 +23,24 @@ module Airbrake
|
|
|
23
23
|
|
|
24
24
|
# @return [Hash{Symbol=>Regexp}] matchers for certain features of SQL
|
|
25
25
|
ALL_FEATURES = {
|
|
26
|
-
# rubocop:disable
|
|
26
|
+
# rubocop:disable Layout/LineLength
|
|
27
27
|
single_quotes: /'(?:[^']|'')*?(?:\\'.*|'(?!'))/,
|
|
28
28
|
double_quotes: /"(?:[^"]|"")*?(?:\\".*|"(?!"))/,
|
|
29
29
|
dollar_quotes: /(\$(?!\d)[^$]*?\$).*?(?:\1|$)/,
|
|
30
|
-
uuids: /\{?(?:[0-9a-fA-F]
|
|
30
|
+
uuids: /\{?(?:[0-9a-fA-F]-*){32}\}?/,
|
|
31
31
|
numeric_literals: /\b-?(?:[0-9]+\.)?[0-9]+([eE][+-]?[0-9]+)?\b/,
|
|
32
32
|
boolean_literals: /\b(?:true|false|null)\b/i,
|
|
33
33
|
hexadecimal_literals: /0x[0-9a-fA-F]+/,
|
|
34
34
|
comments: /(?:#|--).*?(?=\r|\n|$)/i,
|
|
35
35
|
multi_line_comments: %r{/\*(?:[^/]|/[^*])*?(?:\*/|/\*.*)},
|
|
36
|
-
oracle_quoted_strings: /q'\[.*?(?:\]'|$)|q'\{.*?(?:\}'|$)|q'
|
|
37
|
-
# rubocop:enable
|
|
36
|
+
oracle_quoted_strings: /q'\[.*?(?:\]'|$)|q'\{.*?(?:\}'|$)|q'<.*?(?:>'|$)|q'\(.*?(?:\)'|$)/,
|
|
37
|
+
# rubocop:enable Layout/LineLength
|
|
38
38
|
}.freeze
|
|
39
39
|
|
|
40
|
+
# @return [Regexp] the regexp that is applied after the feature regexps
|
|
41
|
+
# were used
|
|
42
|
+
POST_FILTER = /(?<=[values|in ]\().+(?=\))/i.freeze
|
|
43
|
+
|
|
40
44
|
# @return [Hash{Symbol=>Array<Symbol>}] a set of features that corresponds
|
|
41
45
|
# to a certain dialect
|
|
42
46
|
DIALECT_FEATURES = {
|
|
@@ -60,7 +64,7 @@ module Airbrake
|
|
|
60
64
|
cassandra: %i[
|
|
61
65
|
single_quotes uuids numeric_literals boolean_literals
|
|
62
66
|
hexadecimal_literals comments multi_line_comments
|
|
63
|
-
].freeze
|
|
67
|
+
].freeze,
|
|
64
68
|
}.freeze
|
|
65
69
|
|
|
66
70
|
# @return [Hash{Symbol=>Regexp}] a set of regexps to check for unmatches
|
|
@@ -72,9 +76,22 @@ module Airbrake
|
|
|
72
76
|
sqlite: %r{'|/\*|\*/},
|
|
73
77
|
cassandra: %r{'|/\*|\*/},
|
|
74
78
|
oracle: %r{'|/\*|\*/},
|
|
75
|
-
oracle_enhanced: %r{'|/\*|\*/}
|
|
79
|
+
oracle_enhanced: %r{'|/\*|\*/},
|
|
76
80
|
}.freeze
|
|
77
81
|
|
|
82
|
+
# @return [Array<Regexp>] the list of queries to be ignored
|
|
83
|
+
IGNORED_QUERIES = [
|
|
84
|
+
/\ACOMMIT/i,
|
|
85
|
+
/\ABEGIN/i,
|
|
86
|
+
/\ASET/i,
|
|
87
|
+
/\ASHOW/i,
|
|
88
|
+
/\AWITH/i,
|
|
89
|
+
/FROM pg_attribute/i,
|
|
90
|
+
/FROM pg_index/i,
|
|
91
|
+
/FROM pg_class/i,
|
|
92
|
+
/FROM pg_type/i,
|
|
93
|
+
].freeze
|
|
94
|
+
|
|
78
95
|
def initialize(dialect)
|
|
79
96
|
@dialect =
|
|
80
97
|
case dialect
|
|
@@ -91,13 +108,20 @@ module Airbrake
|
|
|
91
108
|
@regexp = Regexp.union(features)
|
|
92
109
|
end
|
|
93
110
|
|
|
94
|
-
# @param [Airbrake::Query]
|
|
95
|
-
def call(
|
|
96
|
-
return unless
|
|
111
|
+
# @param [Airbrake::Query] metric
|
|
112
|
+
def call(metric)
|
|
113
|
+
return unless metric.respond_to?(:query)
|
|
114
|
+
|
|
115
|
+
query = metric.query
|
|
116
|
+
if IGNORED_QUERIES.any? { |q| q =~ query }
|
|
117
|
+
metric.ignore!
|
|
118
|
+
return
|
|
119
|
+
end
|
|
97
120
|
|
|
98
|
-
q =
|
|
121
|
+
q = query.gsub(@regexp, FILTERED)
|
|
122
|
+
q.gsub!(POST_FILTER, FILTERED) if q =~ POST_FILTER
|
|
99
123
|
q = ERROR_MSG if UNMATCHED_PAIR[@dialect] =~ q
|
|
100
|
-
|
|
124
|
+
metric.query = q
|
|
101
125
|
end
|
|
102
126
|
end
|
|
103
127
|
end
|
|
@@ -16,7 +16,7 @@ module Airbrake
|
|
|
16
16
|
String,
|
|
17
17
|
Symbol,
|
|
18
18
|
Regexp,
|
|
19
|
-
Numeric
|
|
19
|
+
Numeric,
|
|
20
20
|
].freeze
|
|
21
21
|
|
|
22
22
|
# Variables starting with this prefix are not attached to a notice.
|
|
@@ -41,8 +41,7 @@ module Airbrake
|
|
|
41
41
|
thread_info[:fiber_variables] = vars
|
|
42
42
|
end
|
|
43
43
|
|
|
44
|
-
|
|
45
|
-
if th.respond_to?(:name) && (name = th.name)
|
|
44
|
+
if (name = th.name)
|
|
46
45
|
thread_info[:name] = name
|
|
47
46
|
end
|
|
48
47
|
|
|
@@ -56,6 +55,7 @@ module Airbrake
|
|
|
56
55
|
def thread_variables(th)
|
|
57
56
|
th.thread_variables.map.with_object({}) do |var, h|
|
|
58
57
|
next if var.to_s.start_with?(IGNORE_PREFIX)
|
|
58
|
+
|
|
59
59
|
h[var] = sanitize_value(th.thread_variable_get(var))
|
|
60
60
|
end
|
|
61
61
|
end
|
|
@@ -63,6 +63,7 @@ module Airbrake
|
|
|
63
63
|
def fiber_variables(th)
|
|
64
64
|
th.keys.map.with_object({}) do |key, h|
|
|
65
65
|
next if key.to_s.start_with?(IGNORE_PREFIX)
|
|
66
|
+
|
|
66
67
|
h[key] = sanitize_value(th[key])
|
|
67
68
|
end
|
|
68
69
|
end
|
|
@@ -72,7 +73,7 @@ module Airbrake
|
|
|
72
73
|
thread_info[:group] = th.group.list.map(&:inspect)
|
|
73
74
|
thread_info[:priority] = th.priority
|
|
74
75
|
|
|
75
|
-
thread_info[:safe_level] = th.safe_level
|
|
76
|
+
thread_info[:safe_level] = th.safe_level if Airbrake::HAS_SAFE_LEVEL
|
|
76
77
|
end
|
|
77
78
|
|
|
78
79
|
def sanitize_value(value)
|
|
@@ -82,7 +83,7 @@ module Airbrake
|
|
|
82
83
|
when Array
|
|
83
84
|
value = value.map { |elem| sanitize_value(elem) }
|
|
84
85
|
when Hash
|
|
85
|
-
|
|
86
|
+
value.transform_values { |v| sanitize_value(v) }
|
|
86
87
|
else
|
|
87
88
|
value.to_s
|
|
88
89
|
end
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
module Airbrake
|
|
2
|
+
# Grouppable adds the `#groups` method, so that we don't need to define it in
|
|
3
|
+
# all of performance models every time we add a model without groups.
|
|
4
|
+
#
|
|
5
|
+
# @since v4.9.0
|
|
6
|
+
# @api private
|
|
7
|
+
module Grouppable
|
|
8
|
+
def groups
|
|
9
|
+
{}
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
end
|
|
@@ -18,11 +18,9 @@ module Airbrake
|
|
|
18
18
|
# Checks whether the instance was ignored.
|
|
19
19
|
# @return [Boolean]
|
|
20
20
|
# @see #ignore!
|
|
21
|
-
# rubocop:disable Style/DoubleNegation
|
|
22
21
|
def ignored?
|
|
23
22
|
!!ignored
|
|
24
23
|
end
|
|
25
|
-
# rubocop:enable Style/DoubleNegation
|
|
26
24
|
|
|
27
25
|
# Ignores an instance. Ignored instances must never reach the Airbrake
|
|
28
26
|
# dashboard.
|
|
@@ -38,6 +36,7 @@ module Airbrake
|
|
|
38
36
|
# @raise [Airbrake::Error] when instance is ignored
|
|
39
37
|
def raise_if_ignored
|
|
40
38
|
return unless ignored?
|
|
39
|
+
|
|
41
40
|
raise Airbrake::Error, "cannot access ignored #{self.class}"
|
|
42
41
|
end
|
|
43
42
|
end
|