celerbrake-ruby 0.1.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 +7 -0
- data/lib/celerbrake-ruby/async_sender.rb +57 -0
- data/lib/celerbrake-ruby/backlog.rb +123 -0
- data/lib/celerbrake-ruby/backtrace.rb +197 -0
- data/lib/celerbrake-ruby/benchmark.rb +39 -0
- data/lib/celerbrake-ruby/code_hunk.rb +51 -0
- data/lib/celerbrake-ruby/config/processor.rb +77 -0
- data/lib/celerbrake-ruby/config/validator.rb +97 -0
- data/lib/celerbrake-ruby/config.rb +291 -0
- data/lib/celerbrake-ruby/context.rb +51 -0
- data/lib/celerbrake-ruby/deploy_notifier.rb +36 -0
- data/lib/celerbrake-ruby/file_cache.rb +54 -0
- data/lib/celerbrake-ruby/filter_chain.rb +112 -0
- data/lib/celerbrake-ruby/filters/context_filter.rb +28 -0
- data/lib/celerbrake-ruby/filters/dependency_filter.rb +32 -0
- data/lib/celerbrake-ruby/filters/exception_attributes_filter.rb +46 -0
- data/lib/celerbrake-ruby/filters/gem_root_filter.rb +34 -0
- data/lib/celerbrake-ruby/filters/git_last_checkout_filter.rb +92 -0
- data/lib/celerbrake-ruby/filters/git_repository_filter.rb +73 -0
- data/lib/celerbrake-ruby/filters/git_revision_filter.rb +68 -0
- data/lib/celerbrake-ruby/filters/keys_allowlist.rb +48 -0
- data/lib/celerbrake-ruby/filters/keys_blocklist.rb +49 -0
- data/lib/celerbrake-ruby/filters/keys_filter.rb +159 -0
- data/lib/celerbrake-ruby/filters/root_directory_filter.rb +29 -0
- data/lib/celerbrake-ruby/filters/sql_filter.rb +128 -0
- data/lib/celerbrake-ruby/filters/system_exit_filter.rb +24 -0
- data/lib/celerbrake-ruby/filters/thread_filter.rb +93 -0
- data/lib/celerbrake-ruby/grouppable.rb +12 -0
- data/lib/celerbrake-ruby/hash_keyable.rb +37 -0
- data/lib/celerbrake-ruby/ignorable.rb +43 -0
- data/lib/celerbrake-ruby/inspectable.rb +39 -0
- data/lib/celerbrake-ruby/loggable.rb +34 -0
- data/lib/celerbrake-ruby/mergeable.rb +12 -0
- data/lib/celerbrake-ruby/monotonic_time.rb +48 -0
- data/lib/celerbrake-ruby/nested_exception.rb +59 -0
- data/lib/celerbrake-ruby/notice.rb +157 -0
- data/lib/celerbrake-ruby/notice_notifier.rb +142 -0
- data/lib/celerbrake-ruby/performance_breakdown.rb +52 -0
- data/lib/celerbrake-ruby/performance_notifier.rb +177 -0
- data/lib/celerbrake-ruby/promise.rb +110 -0
- data/lib/celerbrake-ruby/query.rb +59 -0
- data/lib/celerbrake-ruby/queue.rb +65 -0
- data/lib/celerbrake-ruby/remote_settings/callback.rb +44 -0
- data/lib/celerbrake-ruby/remote_settings/settings_data.rb +116 -0
- data/lib/celerbrake-ruby/remote_settings.rb +128 -0
- data/lib/celerbrake-ruby/request.rb +48 -0
- data/lib/celerbrake-ruby/response.rb +125 -0
- data/lib/celerbrake-ruby/stashable.rb +15 -0
- data/lib/celerbrake-ruby/stat.rb +66 -0
- data/lib/celerbrake-ruby/sync_sender.rb +145 -0
- data/lib/celerbrake-ruby/tdigest.rb +379 -0
- data/lib/celerbrake-ruby/thread_pool.rb +139 -0
- data/lib/celerbrake-ruby/time_truncate.rb +17 -0
- data/lib/celerbrake-ruby/timed_trace.rb +56 -0
- data/lib/celerbrake-ruby/truncator.rb +121 -0
- data/lib/celerbrake-ruby/version.rb +16 -0
- data/lib/celerbrake-ruby.rb +592 -0
- metadata +251 -0
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
module Celerbrake
|
|
2
|
+
# Represents the Celerbrake config. A config contains all the options that you
|
|
3
|
+
# can use to configure an Celerbrake instance.
|
|
4
|
+
#
|
|
5
|
+
# @api public
|
|
6
|
+
# @since v1.0.0
|
|
7
|
+
class Config
|
|
8
|
+
# @return [Integer] the project identificator. This value *must* be set.
|
|
9
|
+
# @api public
|
|
10
|
+
attr_accessor :project_id
|
|
11
|
+
|
|
12
|
+
# @return [String] the project key. This value *must* be set.
|
|
13
|
+
# @api public
|
|
14
|
+
attr_accessor :project_key
|
|
15
|
+
|
|
16
|
+
# @return [Hash] the proxy parameters such as (:host, :port, :user and
|
|
17
|
+
# :password)
|
|
18
|
+
# @api public
|
|
19
|
+
attr_accessor :proxy
|
|
20
|
+
|
|
21
|
+
# @return [Logger] the default logger used for debug output
|
|
22
|
+
# @api public
|
|
23
|
+
attr_reader :logger
|
|
24
|
+
|
|
25
|
+
# @return [String] the version of the user's application
|
|
26
|
+
# @api public
|
|
27
|
+
attr_accessor :app_version
|
|
28
|
+
|
|
29
|
+
# @return [Hash{String=>String}] arbitrary versions that your app wants to
|
|
30
|
+
# track
|
|
31
|
+
# @api public
|
|
32
|
+
# @since v2.10.0
|
|
33
|
+
attr_accessor :versions
|
|
34
|
+
|
|
35
|
+
# @return [Integer] the max number of notices that can be queued up
|
|
36
|
+
# @api public
|
|
37
|
+
attr_accessor :queue_size
|
|
38
|
+
|
|
39
|
+
# @return [Integer] the number of worker threads that process the notice
|
|
40
|
+
# queue
|
|
41
|
+
# @api public
|
|
42
|
+
attr_accessor :workers
|
|
43
|
+
|
|
44
|
+
# @return [String] the host, which provides the API endpoint to which
|
|
45
|
+
# exceptions should be sent
|
|
46
|
+
# @api public
|
|
47
|
+
# @since v5.0.0
|
|
48
|
+
attr_accessor :error_host
|
|
49
|
+
|
|
50
|
+
# @return [String] the host, which provides the API endpoint to which
|
|
51
|
+
# APM data should be sent
|
|
52
|
+
# @api public
|
|
53
|
+
# @since v5.0.0
|
|
54
|
+
attr_accessor :apm_host
|
|
55
|
+
|
|
56
|
+
# @return [String, Pathname] the working directory of your project
|
|
57
|
+
# @api public
|
|
58
|
+
attr_accessor :root_directory
|
|
59
|
+
|
|
60
|
+
# @return [String, Symbol] the environment the application is running in
|
|
61
|
+
# @api public
|
|
62
|
+
attr_accessor :environment
|
|
63
|
+
|
|
64
|
+
# @return [Array<String,Symbol,Regexp>] the array of environments that
|
|
65
|
+
# forbids sending exceptions when the application is running in them.
|
|
66
|
+
# Other possible environments not listed in the array will allow sending
|
|
67
|
+
# occurring exceptions.
|
|
68
|
+
# @api public
|
|
69
|
+
attr_accessor :ignore_environments
|
|
70
|
+
|
|
71
|
+
# @return [Integer] The HTTP timeout in seconds.
|
|
72
|
+
# @api public
|
|
73
|
+
attr_accessor :timeout
|
|
74
|
+
|
|
75
|
+
# @return [Array<String, Symbol, Regexp>] the keys, which should be
|
|
76
|
+
# filtered
|
|
77
|
+
# @api public
|
|
78
|
+
# @since v4.15.0
|
|
79
|
+
attr_accessor :allowlist_keys
|
|
80
|
+
|
|
81
|
+
# @return [Array<String, Symbol, Regexp>] the keys, which should be
|
|
82
|
+
# filtered
|
|
83
|
+
# @api public
|
|
84
|
+
# @since v4.15.0
|
|
85
|
+
attr_accessor :blocklist_keys
|
|
86
|
+
|
|
87
|
+
# @return [Boolean] true if the library should attach code hunks to each
|
|
88
|
+
# frame in a backtrace, false otherwise
|
|
89
|
+
# @api public
|
|
90
|
+
# @since v2.5.0
|
|
91
|
+
attr_accessor :code_hunks
|
|
92
|
+
|
|
93
|
+
# @return [Boolean] true if the library should send route performance stats
|
|
94
|
+
# to Celerbrake, false otherwise
|
|
95
|
+
# @api public
|
|
96
|
+
# @since v3.2.0
|
|
97
|
+
attr_accessor :performance_stats
|
|
98
|
+
|
|
99
|
+
# @return [Integer] how many seconds to wait before sending collected route
|
|
100
|
+
# stats
|
|
101
|
+
# @api private
|
|
102
|
+
# @since v3.2.0
|
|
103
|
+
attr_accessor :performance_stats_flush_period
|
|
104
|
+
|
|
105
|
+
# @return [Boolean] true if the library should send SQL stats to Celerbrake,
|
|
106
|
+
# false otherwise
|
|
107
|
+
# @api public
|
|
108
|
+
# @since v4.6.0
|
|
109
|
+
attr_accessor :query_stats
|
|
110
|
+
|
|
111
|
+
# @return [Boolean] true if the library should send job/queue/worker stats
|
|
112
|
+
# to Celerbrake, false otherwise
|
|
113
|
+
# @api public
|
|
114
|
+
# @since v4.12.0
|
|
115
|
+
attr_accessor :job_stats
|
|
116
|
+
|
|
117
|
+
# @return [Boolean] true if the library should send error reports to
|
|
118
|
+
# Celerbrake, false otherwise
|
|
119
|
+
# @api public
|
|
120
|
+
# @since v5.0.0
|
|
121
|
+
attr_accessor :error_notifications
|
|
122
|
+
|
|
123
|
+
# @return [String] the host which should be used for fetching remote
|
|
124
|
+
# configuration options
|
|
125
|
+
# @api public
|
|
126
|
+
# @since v5.0.0
|
|
127
|
+
attr_accessor :remote_config_host
|
|
128
|
+
|
|
129
|
+
# @return [String] true if notifier should periodically fetch remote
|
|
130
|
+
# configuration, false otherwise
|
|
131
|
+
# @api public
|
|
132
|
+
# @since v5.2.0
|
|
133
|
+
attr_accessor :remote_config
|
|
134
|
+
|
|
135
|
+
# @return [Boolean] true if the library should keep a backlog of failed
|
|
136
|
+
# notices or APM events and retry them after an interval, false otherwise
|
|
137
|
+
# @api public
|
|
138
|
+
# @since v6.2.0
|
|
139
|
+
attr_accessor :backlog
|
|
140
|
+
|
|
141
|
+
class << self
|
|
142
|
+
# @return [Config]
|
|
143
|
+
attr_writer :instance
|
|
144
|
+
|
|
145
|
+
# @return [Config]
|
|
146
|
+
def instance
|
|
147
|
+
@instance ||= new
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
# @param [Hash{Symbol=>Object}] user_config the hash to be used to build the
|
|
152
|
+
# config
|
|
153
|
+
# rubocop:disable Metrics/AbcSize, Metrics/MethodLength
|
|
154
|
+
def initialize(user_config = {})
|
|
155
|
+
self.proxy = {}
|
|
156
|
+
self.queue_size = 100
|
|
157
|
+
self.workers = 1
|
|
158
|
+
self.code_hunks = true
|
|
159
|
+
self.logger = ::Logger.new(File::NULL).tap { |l| l.level = Logger::WARN }
|
|
160
|
+
self.project_id = user_config[:project_id]
|
|
161
|
+
self.project_key = user_config[:project_key]
|
|
162
|
+
self.error_host = self.apm_host = 'https://api.celerbrake.com'
|
|
163
|
+
self.remote_config_host = 'https://notifier-configs.celerbrake.com'
|
|
164
|
+
|
|
165
|
+
self.ignore_environments = []
|
|
166
|
+
|
|
167
|
+
self.timeout = user_config[:timeout]
|
|
168
|
+
|
|
169
|
+
self.blocklist_keys = []
|
|
170
|
+
self.allowlist_keys = []
|
|
171
|
+
|
|
172
|
+
self.root_directory = File.realpath(
|
|
173
|
+
(defined?(Bundler) && Bundler.root) ||
|
|
174
|
+
Dir.pwd,
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
self.versions = {}
|
|
178
|
+
self.performance_stats = true
|
|
179
|
+
self.performance_stats_flush_period = 15
|
|
180
|
+
self.query_stats = true
|
|
181
|
+
self.job_stats = true
|
|
182
|
+
self.error_notifications = true
|
|
183
|
+
# Celerbrake servers do not (yet) serve a remote-config endpoint, so we
|
|
184
|
+
# default this off to avoid polling a host we don't control. Set it to
|
|
185
|
+
# +true+ explicitly if your Celerbrake instance grows one.
|
|
186
|
+
self.remote_config = false
|
|
187
|
+
self.backlog = true
|
|
188
|
+
|
|
189
|
+
merge(user_config)
|
|
190
|
+
end
|
|
191
|
+
# rubocop:enable Metrics/AbcSize, Metrics/MethodLength
|
|
192
|
+
|
|
193
|
+
# The full URL to the Celerbrake Notice API. Based on the +:error_host+ option.
|
|
194
|
+
# @return [URI] the endpoint address
|
|
195
|
+
def error_endpoint
|
|
196
|
+
@error_endpoint ||=
|
|
197
|
+
begin
|
|
198
|
+
self.error_host = ('https://' << error_host) if error_host !~ %r{\Ahttps?://}
|
|
199
|
+
api = "api/v3/projects/#{project_id}/notices"
|
|
200
|
+
URI.join(error_host, api)
|
|
201
|
+
end
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
# Sets the logger. Never allows to assign `nil` as the logger.
|
|
205
|
+
# @return [Logger] the logger
|
|
206
|
+
def logger=(logger)
|
|
207
|
+
@logger = logger || @logger
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
# Merges the given +config_hash+ with itself.
|
|
211
|
+
#
|
|
212
|
+
# @example
|
|
213
|
+
# config.merge(host: 'localhost:8080')
|
|
214
|
+
#
|
|
215
|
+
# @return [self] the merged config
|
|
216
|
+
def merge(config_hash)
|
|
217
|
+
config_hash.each_pair { |option, value| set_option(option, value) }
|
|
218
|
+
self
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
# @return [Boolean] true if the config meets the requirements, false
|
|
222
|
+
# otherwise
|
|
223
|
+
def valid?
|
|
224
|
+
validate.resolved?
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
# @return [Promise]
|
|
228
|
+
# @see Validator.validate
|
|
229
|
+
def validate
|
|
230
|
+
Validator.validate(self)
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
# @return [Promise]
|
|
234
|
+
# @see Validator.check_notify_ability
|
|
235
|
+
def check_notify_ability
|
|
236
|
+
Validator.check_notify_ability(self)
|
|
237
|
+
end
|
|
238
|
+
|
|
239
|
+
# @return [Boolean] true if the config ignores current environment, false
|
|
240
|
+
# otherwise
|
|
241
|
+
def ignored_environment?
|
|
242
|
+
check_notify_ability.rejected?
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
# @return [Promise] resolved promise if config is valid & can notify,
|
|
246
|
+
# rejected otherwise
|
|
247
|
+
def check_configuration
|
|
248
|
+
promise = validate
|
|
249
|
+
return promise if promise.rejected?
|
|
250
|
+
|
|
251
|
+
check_notify_ability
|
|
252
|
+
end
|
|
253
|
+
|
|
254
|
+
# @return [Promise] resolved promise if neither of the performance options
|
|
255
|
+
# reject it, false otherwise
|
|
256
|
+
def check_performance_options(metric)
|
|
257
|
+
promise = Celerbrake::Promise.new
|
|
258
|
+
|
|
259
|
+
if !performance_stats
|
|
260
|
+
promise.reject("The Performance Stats feature is disabled")
|
|
261
|
+
elsif metric.is_a?(Celerbrake::Query) && !query_stats
|
|
262
|
+
promise.reject("The Query Stats feature is disabled")
|
|
263
|
+
elsif metric.is_a?(Celerbrake::Queue) && !job_stats
|
|
264
|
+
promise.reject("The Job Stats feature is disabled")
|
|
265
|
+
else
|
|
266
|
+
promise
|
|
267
|
+
end
|
|
268
|
+
end
|
|
269
|
+
|
|
270
|
+
HOST_DEPRECATION_MSG = "**Celerbrake: the 'host' option is deprecated. Use " \
|
|
271
|
+
"'error_host' instead".freeze
|
|
272
|
+
|
|
273
|
+
def host
|
|
274
|
+
logger.warn(HOST_DEPRECATION_MSG)
|
|
275
|
+
@error_host
|
|
276
|
+
end
|
|
277
|
+
|
|
278
|
+
def host=(value)
|
|
279
|
+
logger.warn(HOST_DEPRECATION_MSG)
|
|
280
|
+
@error_host = value
|
|
281
|
+
end
|
|
282
|
+
|
|
283
|
+
private
|
|
284
|
+
|
|
285
|
+
def set_option(option, value)
|
|
286
|
+
__send__("#{option}=", value)
|
|
287
|
+
rescue NoMethodError
|
|
288
|
+
raise Celerbrake::Error, "unknown option '#{option}'"
|
|
289
|
+
end
|
|
290
|
+
end
|
|
291
|
+
end
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
module Celerbrake
|
|
2
|
+
# Represents a thread-safe Celerbrake context object, which carries arbitrary
|
|
3
|
+
# information added via {Celerbrake.merge_context} calls.
|
|
4
|
+
#
|
|
5
|
+
# @example
|
|
6
|
+
# Celerbrake::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[:celerbrake_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
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
module Celerbrake
|
|
2
|
+
# DeployNotifier sends deploy information to Celerbrake. The information
|
|
3
|
+
# consists of:
|
|
4
|
+
# - environment
|
|
5
|
+
# - username
|
|
6
|
+
# - repository
|
|
7
|
+
# - revision
|
|
8
|
+
# - version
|
|
9
|
+
#
|
|
10
|
+
# @api public
|
|
11
|
+
# @since v3.2.0
|
|
12
|
+
class DeployNotifier
|
|
13
|
+
include Inspectable
|
|
14
|
+
|
|
15
|
+
def initialize
|
|
16
|
+
@config = Celerbrake::Config.instance
|
|
17
|
+
@sender = SyncSender.new
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# @see Celerbrake.notify_deploy
|
|
21
|
+
def notify(deploy_info)
|
|
22
|
+
promise = @config.check_configuration
|
|
23
|
+
return promise if promise.rejected?
|
|
24
|
+
|
|
25
|
+
promise = Celerbrake::Promise.new
|
|
26
|
+
deploy_info[:environment] ||= @config.environment
|
|
27
|
+
@sender.send(
|
|
28
|
+
deploy_info,
|
|
29
|
+
promise,
|
|
30
|
+
URI.join(@config.host, "api/v4/projects/#{@config.project_id}/deploys"),
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
promise
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
module Celerbrake
|
|
2
|
+
# Extremely simple global cache.
|
|
3
|
+
#
|
|
4
|
+
# @api private
|
|
5
|
+
# @since v2.4.1
|
|
6
|
+
module FileCache
|
|
7
|
+
# @return [Integer]
|
|
8
|
+
MAX_SIZE = 50
|
|
9
|
+
|
|
10
|
+
# @return [Mutex]
|
|
11
|
+
MUTEX = Mutex.new
|
|
12
|
+
|
|
13
|
+
# Associates the value given by +value+ with the key given by +key+. Deletes
|
|
14
|
+
# entries that exceed +MAX_SIZE+.
|
|
15
|
+
#
|
|
16
|
+
# @param [Object] key
|
|
17
|
+
# @param [Object] value
|
|
18
|
+
# @return [Object] the corresponding value
|
|
19
|
+
def self.[]=(key, value)
|
|
20
|
+
MUTEX.synchronize do
|
|
21
|
+
data[key] = value
|
|
22
|
+
data.delete(data.keys.first) if data.size > MAX_SIZE
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# Retrieve an object from the cache.
|
|
27
|
+
#
|
|
28
|
+
# @param [Object] key
|
|
29
|
+
# @return [Object] the corresponding value
|
|
30
|
+
def self.[](key)
|
|
31
|
+
MUTEX.synchronize do
|
|
32
|
+
data[key]
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Checks whether the cache is empty. Needed only for the test suite.
|
|
37
|
+
#
|
|
38
|
+
# @return [Boolean]
|
|
39
|
+
def self.empty?
|
|
40
|
+
data.empty?
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# @since v4.7.0
|
|
44
|
+
# @return [void]
|
|
45
|
+
def self.reset
|
|
46
|
+
@data = {}
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def self.data
|
|
50
|
+
@data ||= {}
|
|
51
|
+
end
|
|
52
|
+
private_class_method :data
|
|
53
|
+
end
|
|
54
|
+
end
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
module Celerbrake
|
|
2
|
+
# FilterChain represents an ordered array of filters.
|
|
3
|
+
#
|
|
4
|
+
# A filter is an object that responds to <b>#call</b> (typically a Proc or a
|
|
5
|
+
# class that implements the call method). The <b>#call</b> method must accept
|
|
6
|
+
# exactly one argument: an object to be filtered.
|
|
7
|
+
#
|
|
8
|
+
# When you add a new filter to the chain, it gets inserted according to its
|
|
9
|
+
# <b>weight</b>. Smaller weight means the filter will be somewhere in the
|
|
10
|
+
# beginning of the array. Larger - in the end. If a filter doesn't implement
|
|
11
|
+
# weight, the chain assumes it's equal to 0.
|
|
12
|
+
#
|
|
13
|
+
# @example
|
|
14
|
+
# class MyFilter
|
|
15
|
+
# attr_reader :weight
|
|
16
|
+
#
|
|
17
|
+
# def initialize
|
|
18
|
+
# @weight = 1
|
|
19
|
+
# end
|
|
20
|
+
#
|
|
21
|
+
# def call(obj)
|
|
22
|
+
# puts 'Filtering...'
|
|
23
|
+
# obj[:data] = '[Filtered]'
|
|
24
|
+
# end
|
|
25
|
+
# end
|
|
26
|
+
#
|
|
27
|
+
# filter_chain = FilterChain.new
|
|
28
|
+
# filter_chain.add_filter(MyFilter)
|
|
29
|
+
#
|
|
30
|
+
# filter_chain.refine(obj)
|
|
31
|
+
# #=> Filtering...
|
|
32
|
+
#
|
|
33
|
+
# @see Celerbrake.add_filter
|
|
34
|
+
# @api private
|
|
35
|
+
# @since v1.0.0
|
|
36
|
+
class FilterChain
|
|
37
|
+
# @return [Integer]
|
|
38
|
+
DEFAULT_WEIGHT = 0
|
|
39
|
+
|
|
40
|
+
def initialize
|
|
41
|
+
@filters = []
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Adds a filter to the filter chain. Sorts filters by weight.
|
|
45
|
+
#
|
|
46
|
+
# @param [#call] filter The filter object (proc, class, module, etc)
|
|
47
|
+
# @return [void]
|
|
48
|
+
def add_filter(filter)
|
|
49
|
+
@filters = (@filters << filter).sort_by do |f|
|
|
50
|
+
f.respond_to?(:weight) ? f.weight : DEFAULT_WEIGHT
|
|
51
|
+
end.reverse!
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# Deletes a filter from the the filter chain.
|
|
55
|
+
#
|
|
56
|
+
# @param [Class] filter_class The class of the filter you want to delete
|
|
57
|
+
# @return [void]
|
|
58
|
+
# @since v3.1.0
|
|
59
|
+
def delete_filter(filter_class)
|
|
60
|
+
# rubocop:disable Style/ClassEqualityComparison
|
|
61
|
+
index = @filters.index { |f| f.class.name == filter_class.name }
|
|
62
|
+
# rubocop:enable Style/ClassEqualityComparison
|
|
63
|
+
@filters.delete_at(index) if index
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# Applies all the filters in the filter chain to the given notice. Does not
|
|
67
|
+
# filter ignored notices.
|
|
68
|
+
#
|
|
69
|
+
# @param [Celerbrake::Notice] notice The notice to be filtered
|
|
70
|
+
# @return [void]
|
|
71
|
+
# @todo Make it work with anything, not only notices
|
|
72
|
+
def refine(notice)
|
|
73
|
+
@filters.each do |filter|
|
|
74
|
+
break if notice.ignored?
|
|
75
|
+
|
|
76
|
+
filter.call(notice)
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# @return [String] customized inspect to lessen the amount of clutter
|
|
81
|
+
def inspect
|
|
82
|
+
filter_classes.to_s
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# @return [String] {#inspect} for PrettyPrint
|
|
86
|
+
def pretty_print(q)
|
|
87
|
+
q.text('[')
|
|
88
|
+
|
|
89
|
+
# Make nesting of the first element consistent on JRuby and MRI.
|
|
90
|
+
q.nest(2) { q.breakable } if @filters.any?
|
|
91
|
+
|
|
92
|
+
q.nest(2) do
|
|
93
|
+
q.seplist(@filters) { |f| q.pp(f.class) }
|
|
94
|
+
end
|
|
95
|
+
q.text(']')
|
|
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
|
|
111
|
+
end
|
|
112
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
module Celerbrake
|
|
2
|
+
module Filters
|
|
3
|
+
# Adds user context to the notice object. Clears the context after it's
|
|
4
|
+
# attached.
|
|
5
|
+
#
|
|
6
|
+
# @api private
|
|
7
|
+
# @since v2.9.0
|
|
8
|
+
class ContextFilter
|
|
9
|
+
# @return [Integer]
|
|
10
|
+
attr_reader :weight
|
|
11
|
+
|
|
12
|
+
def initialize
|
|
13
|
+
@weight = 119
|
|
14
|
+
@mutex = Mutex.new
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# @macro call_filter
|
|
18
|
+
def call(notice)
|
|
19
|
+
@mutex.synchronize do
|
|
20
|
+
return if Celerbrake::Context.current.empty?
|
|
21
|
+
|
|
22
|
+
notice[:params][:celerbrake_context] = Celerbrake::Context.current.to_h
|
|
23
|
+
Celerbrake::Context.current.clear
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
module Celerbrake
|
|
2
|
+
module Filters
|
|
3
|
+
# Attaches loaded dependencies to the notice object.
|
|
4
|
+
#
|
|
5
|
+
# @api private
|
|
6
|
+
# @since v2.10.0
|
|
7
|
+
class DependencyFilter
|
|
8
|
+
def initialize
|
|
9
|
+
@weight = 117
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
# @macro call_filter
|
|
13
|
+
def call(notice)
|
|
14
|
+
deps = {}
|
|
15
|
+
Gem.loaded_specs.map.with_object(deps) do |(name, spec), h|
|
|
16
|
+
h[name] = "#{spec.version}#{git_version(spec)}"
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
notice[:context][:versions] = {} unless notice[:context].key?(:versions)
|
|
20
|
+
notice[:context][:versions][:dependencies] = deps
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
private
|
|
24
|
+
|
|
25
|
+
def git_version(spec)
|
|
26
|
+
return unless spec.respond_to?(:git_version) || spec.git_version
|
|
27
|
+
|
|
28
|
+
spec.git_version.to_s
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
module Celerbrake
|
|
2
|
+
module Filters
|
|
3
|
+
# ExceptionAttributesFilter attempts to call `#to_celerbrake` on the stashed
|
|
4
|
+
# exception and attaches returned data to the notice object.
|
|
5
|
+
#
|
|
6
|
+
# @api private
|
|
7
|
+
# @since v2.10.0
|
|
8
|
+
class ExceptionAttributesFilter
|
|
9
|
+
include Loggable
|
|
10
|
+
|
|
11
|
+
def initialize
|
|
12
|
+
@weight = 118
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# @macro call_filter
|
|
16
|
+
def call(notice) # rubocop:disable Metrics/AbcSize
|
|
17
|
+
exception = notice.stash[:exception]
|
|
18
|
+
return unless exception.respond_to?(:to_celerbrake)
|
|
19
|
+
|
|
20
|
+
attributes = nil
|
|
21
|
+
begin
|
|
22
|
+
attributes = exception.to_celerbrake
|
|
23
|
+
rescue StandardError => ex
|
|
24
|
+
logger.error(
|
|
25
|
+
"#{LOG_LABEL} #{exception.class}#to_celerbrake failed. #{ex.class}: #{ex}",
|
|
26
|
+
)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
unless attributes.is_a?(Hash)
|
|
30
|
+
logger.error(
|
|
31
|
+
"#{LOG_LABEL} #{self.class}: wanted Hash, got #{attributes.class}",
|
|
32
|
+
)
|
|
33
|
+
return
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
attributes.each do |key, attrs|
|
|
37
|
+
if notice[key]
|
|
38
|
+
notice[key].merge!(attrs)
|
|
39
|
+
else
|
|
40
|
+
notice[key] = attrs
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
module Celerbrake
|
|
2
|
+
module Filters
|
|
3
|
+
# Replaces paths to gems with a placeholder.
|
|
4
|
+
# @api private
|
|
5
|
+
class GemRootFilter
|
|
6
|
+
# @return [String]
|
|
7
|
+
GEM_ROOT_LABEL = '/GEM_ROOT'.freeze
|
|
8
|
+
|
|
9
|
+
# @return [Integer]
|
|
10
|
+
attr_reader :weight
|
|
11
|
+
|
|
12
|
+
def initialize
|
|
13
|
+
@weight = 120
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# @macro call_filter
|
|
17
|
+
def call(notice)
|
|
18
|
+
return unless defined?(Gem)
|
|
19
|
+
|
|
20
|
+
notice[:errors].each do |error|
|
|
21
|
+
Gem.path.each do |gem_path|
|
|
22
|
+
error[:backtrace].each do |frame|
|
|
23
|
+
# If the frame is unparseable, then 'file' is nil, thus nothing to
|
|
24
|
+
# filter (all frame's data is in 'function' instead).
|
|
25
|
+
next unless (file = frame[:file])
|
|
26
|
+
|
|
27
|
+
frame[:file] = file.sub(/\A#{gem_path}/, GEM_ROOT_LABEL)
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|