airbrake-ruby 4.8.0 → 5.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +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)
|