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.
Files changed (58) hide show
  1. checksums.yaml +7 -0
  2. data/lib/celerbrake-ruby/async_sender.rb +57 -0
  3. data/lib/celerbrake-ruby/backlog.rb +123 -0
  4. data/lib/celerbrake-ruby/backtrace.rb +197 -0
  5. data/lib/celerbrake-ruby/benchmark.rb +39 -0
  6. data/lib/celerbrake-ruby/code_hunk.rb +51 -0
  7. data/lib/celerbrake-ruby/config/processor.rb +77 -0
  8. data/lib/celerbrake-ruby/config/validator.rb +97 -0
  9. data/lib/celerbrake-ruby/config.rb +291 -0
  10. data/lib/celerbrake-ruby/context.rb +51 -0
  11. data/lib/celerbrake-ruby/deploy_notifier.rb +36 -0
  12. data/lib/celerbrake-ruby/file_cache.rb +54 -0
  13. data/lib/celerbrake-ruby/filter_chain.rb +112 -0
  14. data/lib/celerbrake-ruby/filters/context_filter.rb +28 -0
  15. data/lib/celerbrake-ruby/filters/dependency_filter.rb +32 -0
  16. data/lib/celerbrake-ruby/filters/exception_attributes_filter.rb +46 -0
  17. data/lib/celerbrake-ruby/filters/gem_root_filter.rb +34 -0
  18. data/lib/celerbrake-ruby/filters/git_last_checkout_filter.rb +92 -0
  19. data/lib/celerbrake-ruby/filters/git_repository_filter.rb +73 -0
  20. data/lib/celerbrake-ruby/filters/git_revision_filter.rb +68 -0
  21. data/lib/celerbrake-ruby/filters/keys_allowlist.rb +48 -0
  22. data/lib/celerbrake-ruby/filters/keys_blocklist.rb +49 -0
  23. data/lib/celerbrake-ruby/filters/keys_filter.rb +159 -0
  24. data/lib/celerbrake-ruby/filters/root_directory_filter.rb +29 -0
  25. data/lib/celerbrake-ruby/filters/sql_filter.rb +128 -0
  26. data/lib/celerbrake-ruby/filters/system_exit_filter.rb +24 -0
  27. data/lib/celerbrake-ruby/filters/thread_filter.rb +93 -0
  28. data/lib/celerbrake-ruby/grouppable.rb +12 -0
  29. data/lib/celerbrake-ruby/hash_keyable.rb +37 -0
  30. data/lib/celerbrake-ruby/ignorable.rb +43 -0
  31. data/lib/celerbrake-ruby/inspectable.rb +39 -0
  32. data/lib/celerbrake-ruby/loggable.rb +34 -0
  33. data/lib/celerbrake-ruby/mergeable.rb +12 -0
  34. data/lib/celerbrake-ruby/monotonic_time.rb +48 -0
  35. data/lib/celerbrake-ruby/nested_exception.rb +59 -0
  36. data/lib/celerbrake-ruby/notice.rb +157 -0
  37. data/lib/celerbrake-ruby/notice_notifier.rb +142 -0
  38. data/lib/celerbrake-ruby/performance_breakdown.rb +52 -0
  39. data/lib/celerbrake-ruby/performance_notifier.rb +177 -0
  40. data/lib/celerbrake-ruby/promise.rb +110 -0
  41. data/lib/celerbrake-ruby/query.rb +59 -0
  42. data/lib/celerbrake-ruby/queue.rb +65 -0
  43. data/lib/celerbrake-ruby/remote_settings/callback.rb +44 -0
  44. data/lib/celerbrake-ruby/remote_settings/settings_data.rb +116 -0
  45. data/lib/celerbrake-ruby/remote_settings.rb +128 -0
  46. data/lib/celerbrake-ruby/request.rb +48 -0
  47. data/lib/celerbrake-ruby/response.rb +125 -0
  48. data/lib/celerbrake-ruby/stashable.rb +15 -0
  49. data/lib/celerbrake-ruby/stat.rb +66 -0
  50. data/lib/celerbrake-ruby/sync_sender.rb +145 -0
  51. data/lib/celerbrake-ruby/tdigest.rb +379 -0
  52. data/lib/celerbrake-ruby/thread_pool.rb +139 -0
  53. data/lib/celerbrake-ruby/time_truncate.rb +17 -0
  54. data/lib/celerbrake-ruby/timed_trace.rb +56 -0
  55. data/lib/celerbrake-ruby/truncator.rb +121 -0
  56. data/lib/celerbrake-ruby/version.rb +16 -0
  57. data/lib/celerbrake-ruby.rb +592 -0
  58. 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