airbrake-ruby 4.8.0 → 5.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/airbrake-ruby.rb +132 -57
- data/lib/airbrake-ruby/async_sender.rb +7 -30
- 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.rb +59 -15
- data/lib/airbrake-ruby/config/processor.rb +71 -0
- data/lib/airbrake-ruby/config/validator.rb +9 -3
- data/lib/airbrake-ruby/deploy_notifier.rb +1 -1
- data/lib/airbrake-ruby/file_cache.rb +1 -1
- 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 +7 -7
- data/lib/airbrake-ruby/filters/system_exit_filter.rb +1 -0
- data/lib/airbrake-ruby/filters/thread_filter.rb +5 -4
- 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 +1 -1
- data/lib/airbrake-ruby/mergeable.rb +12 -0
- data/lib/airbrake-ruby/monotonic_time.rb +5 -0
- data/lib/airbrake-ruby/notice.rb +7 -14
- data/lib/airbrake-ruby/notice_notifier.rb +11 -3
- data/lib/airbrake-ruby/performance_breakdown.rb +16 -10
- data/lib/airbrake-ruby/performance_notifier.rb +80 -58
- data/lib/airbrake-ruby/promise.rb +1 -0
- data/lib/airbrake-ruby/query.rb +20 -15
- data/lib/airbrake-ruby/queue.rb +65 -0
- data/lib/airbrake-ruby/remote_settings.rb +105 -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/request.rb +14 -12
- data/lib/airbrake-ruby/stat.rb +26 -33
- data/lib/airbrake-ruby/sync_sender.rb +3 -2
- data/lib/airbrake-ruby/tdigest.rb +43 -58
- data/lib/airbrake-ruby/thread_pool.rb +11 -1
- data/lib/airbrake-ruby/truncator.rb +10 -4
- data/lib/airbrake-ruby/version.rb +11 -1
- data/spec/airbrake_spec.rb +206 -71
- data/spec/async_sender_spec.rb +3 -12
- data/spec/backtrace_spec.rb +44 -44
- data/spec/code_hunk_spec.rb +11 -11
- data/spec/config/processor_spec.rb +143 -0
- data/spec/config/validator_spec.rb +23 -6
- data/spec/config_spec.rb +40 -14
- data/spec/deploy_notifier_spec.rb +2 -2
- 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 +10 -10
- 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 +58 -60
- data/spec/filters/system_exit_filter_spec.rb +1 -1
- data/spec/filters/thread_filter_spec.rb +32 -30
- data/spec/fixtures/project_root/code.rb +9 -9
- data/spec/loggable_spec.rb +17 -0
- data/spec/monotonic_time_spec.rb +11 -0
- data/spec/notice_notifier/options_spec.rb +17 -17
- data/spec/notice_notifier_spec.rb +20 -20
- data/spec/notice_spec.rb +6 -6
- data/spec/performance_breakdown_spec.rb +0 -1
- data/spec/performance_notifier_spec.rb +220 -73
- data/spec/query_spec.rb +1 -1
- data/spec/queue_spec.rb +18 -0
- data/spec/remote_settings/callback_spec.rb +143 -0
- data/spec/remote_settings/settings_data_spec.rb +348 -0
- data/spec/remote_settings_spec.rb +187 -0
- data/spec/request_spec.rb +1 -3
- data/spec/response_spec.rb +8 -8
- data/spec/spec_helper.rb +6 -6
- data/spec/stat_spec.rb +2 -12
- data/spec/sync_sender_spec.rb +14 -12
- data/spec/tdigest_spec.rb +7 -7
- data/spec/thread_pool_spec.rb +39 -10
- data/spec/timed_trace_spec.rb +1 -1
- data/spec/truncator_spec.rb +12 -12
- metadata +32 -14
@@ -30,7 +30,7 @@ module Airbrake
|
|
30
30
|
Airbrake::FileCache[file] ||= File.foreach(file)
|
31
31
|
rescue StandardError => ex
|
32
32
|
logger.error(
|
33
|
-
"#{self.class.name}: can't read code hunk for #{file}: #{ex}"
|
33
|
+
"#{self.class.name}: can't read code hunk for #{file}: #{ex}",
|
34
34
|
)
|
35
35
|
nil
|
36
36
|
end
|
data/lib/airbrake-ruby/config.rb
CHANGED
@@ -46,6 +46,17 @@ module Airbrake
|
|
46
46
|
# @api public
|
47
47
|
attr_accessor :host
|
48
48
|
|
49
|
+
# @since v5.0.0
|
50
|
+
alias error_host host
|
51
|
+
# @since v5.0.0
|
52
|
+
alias error_host= host=
|
53
|
+
|
54
|
+
# @return [String] the host, which provides the API endpoint to which
|
55
|
+
# APM data should be sent
|
56
|
+
# @api public
|
57
|
+
# @since v5.0.0
|
58
|
+
attr_accessor :apm_host
|
59
|
+
|
49
60
|
# @return [String, Pathname] the working directory of your project
|
50
61
|
# @api public
|
51
62
|
attr_accessor :root_directory
|
@@ -68,14 +79,14 @@ module Airbrake
|
|
68
79
|
# @return [Array<String, Symbol, Regexp>] the keys, which should be
|
69
80
|
# filtered
|
70
81
|
# @api public
|
71
|
-
# @since
|
72
|
-
attr_accessor :
|
82
|
+
# @since v4.15.0
|
83
|
+
attr_accessor :allowlist_keys
|
73
84
|
|
74
|
-
# @return [Array<String, Symbol, Regexp>] the keys, which
|
85
|
+
# @return [Array<String, Symbol, Regexp>] the keys, which should be
|
75
86
|
# filtered
|
76
87
|
# @api public
|
77
|
-
# @since
|
78
|
-
attr_accessor :
|
88
|
+
# @since v4.15.0
|
89
|
+
attr_accessor :blocklist_keys
|
79
90
|
|
80
91
|
# @return [Boolean] true if the library should attach code hunks to each
|
81
92
|
# frame in a backtrace, false otherwise
|
@@ -101,6 +112,30 @@ module Airbrake
|
|
101
112
|
# @since v4.6.0
|
102
113
|
attr_accessor :query_stats
|
103
114
|
|
115
|
+
# @return [Boolean] true if the library should send job/queue/worker stats
|
116
|
+
# to Airbrake, false otherwise
|
117
|
+
# @api public
|
118
|
+
# @since v4.12.0
|
119
|
+
attr_accessor :job_stats
|
120
|
+
|
121
|
+
# @return [Boolean] true if the library should send error reports to
|
122
|
+
# Airbrake, false otherwise
|
123
|
+
# @api public
|
124
|
+
# @since v5.0.0
|
125
|
+
attr_accessor :error_notifications
|
126
|
+
|
127
|
+
# @return [String] the host which should be used for fetching remote
|
128
|
+
# configuration options
|
129
|
+
# @api public
|
130
|
+
# @since v5.0.0
|
131
|
+
attr_accessor :remote_config_host
|
132
|
+
|
133
|
+
# @return [String] true if notifier should periodically fetch remote
|
134
|
+
# configuration, false otherwise
|
135
|
+
# @api public
|
136
|
+
# @since v5.2.0
|
137
|
+
attr_accessor :remote_config
|
138
|
+
|
104
139
|
class << self
|
105
140
|
# @return [Config]
|
106
141
|
attr_writer :instance
|
@@ -113,44 +148,51 @@ module Airbrake
|
|
113
148
|
|
114
149
|
# @param [Hash{Symbol=>Object}] user_config the hash to be used to build the
|
115
150
|
# config
|
151
|
+
# rubocop:disable Metrics/AbcSize, Metrics/MethodLength
|
116
152
|
def initialize(user_config = {})
|
117
153
|
self.proxy = {}
|
118
154
|
self.queue_size = 100
|
119
155
|
self.workers = 1
|
120
156
|
self.code_hunks = true
|
121
|
-
self.logger = ::Logger.new(File::NULL)
|
157
|
+
self.logger = ::Logger.new(File::NULL).tap { |l| l.level = Logger::WARN }
|
122
158
|
self.project_id = user_config[:project_id]
|
123
159
|
self.project_key = user_config[:project_key]
|
124
|
-
self.
|
160
|
+
self.error_host = 'https://api.airbrake.io'
|
161
|
+
self.apm_host = 'https://api.airbrake.io'
|
162
|
+
self.remote_config_host = 'https://notifier-configs.airbrake.io'
|
125
163
|
|
126
164
|
self.ignore_environments = []
|
127
165
|
|
128
166
|
self.timeout = user_config[:timeout]
|
129
167
|
|
130
|
-
self.
|
131
|
-
self.
|
168
|
+
self.blocklist_keys = []
|
169
|
+
self.allowlist_keys = []
|
132
170
|
|
133
171
|
self.root_directory = File.realpath(
|
134
172
|
(defined?(Bundler) && Bundler.root) ||
|
135
|
-
Dir.pwd
|
173
|
+
Dir.pwd,
|
136
174
|
)
|
137
175
|
|
138
176
|
self.versions = {}
|
139
177
|
self.performance_stats = true
|
140
178
|
self.performance_stats_flush_period = 15
|
141
179
|
self.query_stats = true
|
180
|
+
self.job_stats = true
|
181
|
+
self.error_notifications = true
|
182
|
+
self.remote_config = true
|
142
183
|
|
143
184
|
merge(user_config)
|
144
185
|
end
|
186
|
+
# rubocop:enable Metrics/AbcSize, Metrics/MethodLength
|
145
187
|
|
146
|
-
# The full URL to the Airbrake Notice API. Based on the +:
|
188
|
+
# The full URL to the Airbrake Notice API. Based on the +:error_host+ option.
|
147
189
|
# @return [URI] the endpoint address
|
148
|
-
def
|
149
|
-
@
|
190
|
+
def error_endpoint
|
191
|
+
@error_endpoint ||=
|
150
192
|
begin
|
151
|
-
self.
|
193
|
+
self.error_host = ('https://' << error_host) if error_host !~ %r{\Ahttps?://}
|
152
194
|
api = "api/v3/projects/#{project_id}/notices"
|
153
|
-
URI.join(
|
195
|
+
URI.join(error_host, api)
|
154
196
|
end
|
155
197
|
end
|
156
198
|
|
@@ -213,6 +255,8 @@ module Airbrake
|
|
213
255
|
promise.reject("The Performance Stats feature is disabled")
|
214
256
|
elsif resource.is_a?(Airbrake::Query) && !query_stats
|
215
257
|
promise.reject("The Query Stats feature is disabled")
|
258
|
+
elsif resource.is_a?(Airbrake::Queue) && !job_stats
|
259
|
+
promise.reject("The Job Stats feature is disabled")
|
216
260
|
else
|
217
261
|
promise
|
218
262
|
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
module Airbrake
|
2
|
+
class Config
|
3
|
+
# Processor is a helper class, which is responsible for setting default
|
4
|
+
# config values, default notifier filters and remote configuration changes.
|
5
|
+
#
|
6
|
+
# @since v5.0.0
|
7
|
+
# @api private
|
8
|
+
class Processor
|
9
|
+
# @param [Airbrake::Config] config
|
10
|
+
# @return [Airbrake::Config::Processor]
|
11
|
+
def self.process(config)
|
12
|
+
new(config).process
|
13
|
+
end
|
14
|
+
|
15
|
+
# @param [Airbrake::Config] config
|
16
|
+
def initialize(config)
|
17
|
+
@config = config
|
18
|
+
@blocklist_keys = @config.blocklist_keys
|
19
|
+
@allowlist_keys = @config.allowlist_keys
|
20
|
+
@project_id = @config.project_id
|
21
|
+
@poll_callback = Airbrake::RemoteSettings::Callback.new(config)
|
22
|
+
end
|
23
|
+
|
24
|
+
# @param [Airbrake::NoticeNotifier] notifier
|
25
|
+
# @return [void]
|
26
|
+
def process_blocklist(notifier)
|
27
|
+
return if @blocklist_keys.none?
|
28
|
+
|
29
|
+
blocklist = Airbrake::Filters::KeysBlocklist.new(@blocklist_keys)
|
30
|
+
notifier.add_filter(blocklist)
|
31
|
+
end
|
32
|
+
|
33
|
+
# @param [Airbrake::NoticeNotifier] notifier
|
34
|
+
# @return [void]
|
35
|
+
def process_allowlist(notifier)
|
36
|
+
return if @allowlist_keys.none?
|
37
|
+
|
38
|
+
allowlist = Airbrake::Filters::KeysAllowlist.new(@allowlist_keys)
|
39
|
+
notifier.add_filter(allowlist)
|
40
|
+
end
|
41
|
+
|
42
|
+
# @return [Airbrake::RemoteSettings]
|
43
|
+
def process_remote_configuration
|
44
|
+
return unless @config.remote_config
|
45
|
+
return unless @project_id
|
46
|
+
return if @config.environment == 'test'
|
47
|
+
|
48
|
+
RemoteSettings.poll(@project_id, @config.remote_config_host) do |data|
|
49
|
+
@poll_callback.call(data)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
# @param [Airbrake::NoticeNotifier] notifier
|
54
|
+
# @return [void]
|
55
|
+
def add_filters(notifier)
|
56
|
+
return unless @config.root_directory
|
57
|
+
|
58
|
+
[
|
59
|
+
Airbrake::Filters::RootDirectoryFilter,
|
60
|
+
Airbrake::Filters::GitRevisionFilter,
|
61
|
+
Airbrake::Filters::GitRepositoryFilter,
|
62
|
+
Airbrake::Filters::GitLastCheckoutFilter,
|
63
|
+
].each do |filter|
|
64
|
+
next if notifier.has_filter?(filter)
|
65
|
+
|
66
|
+
notifier.add_filter(filter.new(@config.root_directory))
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -29,7 +29,7 @@ module Airbrake
|
|
29
29
|
return promise.reject(
|
30
30
|
"the 'environment' option must be configured " \
|
31
31
|
"with a Symbol (or String), but '#{config.environment.class}' was " \
|
32
|
-
"provided: #{config.environment}"
|
32
|
+
"provided: #{config.environment}",
|
33
33
|
)
|
34
34
|
end
|
35
35
|
|
@@ -44,9 +44,13 @@ module Airbrake
|
|
44
44
|
def check_notify_ability(config)
|
45
45
|
promise = Airbrake::Promise.new
|
46
46
|
|
47
|
+
unless config.error_notifications
|
48
|
+
return promise.reject('error notifications are disabled')
|
49
|
+
end
|
50
|
+
|
47
51
|
if ignored_environment?(config)
|
48
52
|
return promise.reject(
|
49
|
-
"current environment '#{config.environment}' is ignored"
|
53
|
+
"current environment '#{config.environment}' is ignored",
|
50
54
|
)
|
51
55
|
end
|
52
56
|
|
@@ -57,12 +61,14 @@ module Airbrake
|
|
57
61
|
|
58
62
|
def valid_project_id?(config)
|
59
63
|
return true if config.project_id.to_i > 0
|
64
|
+
|
60
65
|
false
|
61
66
|
end
|
62
67
|
|
63
68
|
def valid_project_key?(config)
|
64
69
|
return false unless config.project_key.is_a?(String)
|
65
70
|
return false if config.project_key.empty?
|
71
|
+
|
66
72
|
true
|
67
73
|
end
|
68
74
|
|
@@ -74,7 +80,7 @@ module Airbrake
|
|
74
80
|
if config.ignore_environments.any? && config.environment.nil?
|
75
81
|
config.logger.warn(
|
76
82
|
"#{LOG_LABEL} the 'environment' option is not set, " \
|
77
|
-
"'ignore_environments' has no effect"
|
83
|
+
"'ignore_environments' has no effect",
|
78
84
|
)
|
79
85
|
end
|
80
86
|
|
@@ -70,13 +70,14 @@ module Airbrake
|
|
70
70
|
def refine(notice)
|
71
71
|
@filters.each do |filter|
|
72
72
|
break if notice.ignored?
|
73
|
+
|
73
74
|
filter.call(notice)
|
74
75
|
end
|
75
76
|
end
|
76
77
|
|
77
78
|
# @return [String] customized inspect to lessen the amount of clutter
|
78
79
|
def inspect
|
79
|
-
|
80
|
+
filter_classes.to_s
|
80
81
|
end
|
81
82
|
|
82
83
|
# @return [String] {#inspect} for PrettyPrint
|
@@ -91,5 +92,19 @@ module Airbrake
|
|
91
92
|
end
|
92
93
|
q.text(']')
|
93
94
|
end
|
95
|
+
|
96
|
+
# @param [Class] filter_class
|
97
|
+
# @return [Boolean] true if the current chain has an instance of the given
|
98
|
+
# class, false otherwise
|
99
|
+
# @since v4.14.0
|
100
|
+
def includes?(filter_class)
|
101
|
+
filter_classes.include?(filter_class)
|
102
|
+
end
|
103
|
+
|
104
|
+
private
|
105
|
+
|
106
|
+
def filter_classes
|
107
|
+
@filters.map(&:class)
|
108
|
+
end
|
94
109
|
end
|
95
110
|
end
|
@@ -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['AIRBRAKE_DEPLOY_USERNAME']
|
30
31
|
end
|
31
32
|
|
32
33
|
# @macro call_filter
|
@@ -40,32 +41,31 @@ 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
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')
|
@@ -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
|
|
@@ -39,6 +40,7 @@ module Airbrake
|
|
39
40
|
end
|
40
41
|
|
41
42
|
return unless @repository
|
43
|
+
|
42
44
|
notice[:context][:repository] = @repository
|
43
45
|
end
|
44
46
|
|
@@ -46,6 +48,7 @@ module Airbrake
|
|
46
48
|
|
47
49
|
def detect_git_version
|
48
50
|
return unless which('git')
|
51
|
+
|
49
52
|
Gem::Version.new(`git --version`.split[2])
|
50
53
|
end
|
51
54
|
|
@@ -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)
|