2017-07-09 08:06:36 -04:00
|
|
|
# frozen_string_literal: true
|
2017-07-10 09:39:13 -04:00
|
|
|
|
2008-11-22 12:06:08 -05:00
|
|
|
module ActiveSupport
|
2012-09-17 01:22:18 -04:00
|
|
|
# Backtraces often include many lines that are not relevant for the context
|
|
|
|
# under review. This makes it hard to find the signal amongst the backtrace
|
|
|
|
# noise, and adds debugging time. With a BacktraceCleaner, filters and
|
|
|
|
# silencers are used to remove the noisy lines, so that only the most relevant
|
|
|
|
# lines remain.
|
2008-11-22 12:06:08 -05:00
|
|
|
#
|
2012-09-17 01:22:18 -04:00
|
|
|
# Filters are used to modify lines of data, while silencers are used to remove
|
|
|
|
# lines entirely. The typical filter use case is to remove lengthy path
|
|
|
|
# information from the start of each line, and view file paths relevant to the
|
|
|
|
# app directory instead of the file system root. The typical silencer use case
|
|
|
|
# is to exclude the output of a noisy library from the backtrace, so that you
|
|
|
|
# can focus on the rest.
|
2008-11-22 12:06:08 -05:00
|
|
|
#
|
2017-01-22 05:19:13 -05:00
|
|
|
# bc = ActiveSupport::BacktraceCleaner.new
|
2014-06-25 13:47:10 -04:00
|
|
|
# bc.add_filter { |line| line.gsub(Rails.root.to_s, '') } # strip the Rails.root prefix
|
2019-07-29 01:23:10 -04:00
|
|
|
# bc.add_silencer { |line| /puma|rubygems/.match?(line) } # skip any lines from puma or rubygems
|
2013-06-12 06:02:45 -04:00
|
|
|
# bc.clean(exception.backtrace) # perform the cleanup
|
2008-11-22 12:06:08 -05:00
|
|
|
#
|
2012-09-17 01:22:18 -04:00
|
|
|
# To reconfigure an existing BacktraceCleaner (like the default one in Rails)
|
|
|
|
# and show as much data as possible, you can always call
|
|
|
|
# <tt>BacktraceCleaner#remove_silencers!</tt>, which will restore the
|
|
|
|
# backtrace to a pristine state. If you need to reconfigure an existing
|
|
|
|
# BacktraceCleaner so that it does not filter or modify the paths of any lines
|
2013-12-17 01:08:58 -05:00
|
|
|
# of the backtrace, you can call <tt>BacktraceCleaner#remove_filters!</tt>
|
2013-06-12 06:02:45 -04:00
|
|
|
# These two methods will give you a completely untouched backtrace.
|
2011-06-22 09:06:05 -04:00
|
|
|
#
|
2015-06-05 05:29:03 -04:00
|
|
|
# Inspired by the Quiet Backtrace gem by thoughtbot.
|
2008-11-22 12:06:08 -05:00
|
|
|
class BacktraceCleaner
|
|
|
|
def initialize
|
|
|
|
@filters, @silencers = [], []
|
2018-08-05 21:06:01 -04:00
|
|
|
add_gem_filter
|
|
|
|
add_gem_silencer
|
|
|
|
add_stdlib_silencer
|
2008-11-22 12:06:08 -05:00
|
|
|
end
|
2010-01-16 22:34:35 -05:00
|
|
|
|
2012-09-17 01:22:18 -04:00
|
|
|
# Returns the backtrace after all filters and silencers have been run
|
|
|
|
# against it. Filters run first, then silencers.
|
2010-01-16 22:34:35 -05:00
|
|
|
def clean(backtrace, kind = :silent)
|
2012-10-06 01:49:27 -04:00
|
|
|
filtered = filter_backtrace(backtrace)
|
2010-01-16 22:34:35 -05:00
|
|
|
|
|
|
|
case kind
|
|
|
|
when :silent
|
|
|
|
silence(filtered)
|
|
|
|
when :noise
|
|
|
|
noise(filtered)
|
|
|
|
else
|
|
|
|
filtered
|
|
|
|
end
|
2008-11-22 12:06:08 -05:00
|
|
|
end
|
2012-10-05 22:42:11 -04:00
|
|
|
alias :filter :clean
|
2008-11-22 12:06:08 -05:00
|
|
|
|
2012-09-17 01:22:18 -04:00
|
|
|
# Adds a filter from the block provided. Each line in the backtrace will be
|
|
|
|
# mapped against this filter.
|
2008-11-22 12:06:08 -05:00
|
|
|
#
|
|
|
|
# # Will turn "/my/rails/root/app/models/person.rb" into "/app/models/person.rb"
|
|
|
|
# backtrace_cleaner.add_filter { |line| line.gsub(Rails.root, '') }
|
|
|
|
def add_filter(&block)
|
|
|
|
@filters << block
|
|
|
|
end
|
|
|
|
|
2012-09-17 01:22:18 -04:00
|
|
|
# Adds a silencer from the block provided. If the silencer returns +true+
|
|
|
|
# for a given line, it will be excluded from the clean backtrace.
|
2008-11-22 12:06:08 -05:00
|
|
|
#
|
2016-09-06 12:59:37 -04:00
|
|
|
# # Will reject all lines that include the word "puma", like "/gems/puma/server.rb" or "/app/my_puma_server/rb"
|
2019-07-29 01:23:10 -04:00
|
|
|
# backtrace_cleaner.add_silencer { |line| /puma/.match?(line) }
|
2008-11-22 12:06:08 -05:00
|
|
|
def add_silencer(&block)
|
|
|
|
@silencers << block
|
|
|
|
end
|
|
|
|
|
2014-05-10 15:44:39 -04:00
|
|
|
# Removes all silencers, but leaves in the filters. Useful if your
|
|
|
|
# context of debugging suddenly expands as you suspect a bug in one of
|
2012-09-17 01:22:18 -04:00
|
|
|
# the libraries you use.
|
2008-11-22 12:06:08 -05:00
|
|
|
def remove_silencers!
|
|
|
|
@silencers = []
|
|
|
|
end
|
|
|
|
|
2014-05-10 15:44:39 -04:00
|
|
|
# Removes all filters, but leaves in the silencers. Useful if you suddenly
|
2013-01-02 23:17:12 -05:00
|
|
|
# need to see entire filepaths in the backtrace that you had already
|
|
|
|
# filtered out.
|
2010-03-27 12:47:39 -04:00
|
|
|
def remove_filters!
|
|
|
|
@filters = []
|
|
|
|
end
|
|
|
|
|
2008-11-22 12:06:08 -05:00
|
|
|
private
|
2018-08-05 21:06:01 -04:00
|
|
|
FORMATTED_GEMS_PATTERN = /\A[^\/]+ \([\w.]+\) /
|
|
|
|
|
|
|
|
def add_gem_filter
|
|
|
|
gems_paths = (Gem.path | [Gem.default_dir]).map { |p| Regexp.escape(p) }
|
|
|
|
return if gems_paths.empty?
|
|
|
|
|
2020-09-07 17:44:21 -04:00
|
|
|
gems_regexp = %r{\A(#{gems_paths.join('|')})/(bundler/)?gems/([^/]+)-([\w.]+)/(.*)}
|
2018-02-27 23:33:37 -05:00
|
|
|
gems_result = '\3 (\4) \5'
|
2018-08-05 21:06:01 -04:00
|
|
|
add_filter { |line| line.sub(gems_regexp, gems_result) }
|
|
|
|
end
|
|
|
|
|
|
|
|
def add_gem_silencer
|
|
|
|
add_silencer { |line| FORMATTED_GEMS_PATTERN.match?(line) }
|
|
|
|
end
|
|
|
|
|
|
|
|
def add_stdlib_silencer
|
|
|
|
add_silencer { |line| line.start_with?(RbConfig::CONFIG["rubylibdir"]) }
|
|
|
|
end
|
|
|
|
|
2020-05-15 17:59:15 -04:00
|
|
|
# Process +ary+ via +filters+ using +method+, ensuring
|
|
|
|
# _something_ gets returned.
|
|
|
|
def process_collection(ary, filters, method)
|
2020-10-02 01:40:09 -04:00
|
|
|
filters.reduce(ary) { |bt, f| bt.public_send(method) { |line| f.call(line) } }
|
2020-05-15 17:59:15 -04:00
|
|
|
end
|
2010-01-16 22:34:35 -05:00
|
|
|
|
2020-05-15 17:59:15 -04:00
|
|
|
# Use @filters to transform the backtrace via map
|
|
|
|
def filter_backtrace(backtrace)
|
|
|
|
process_collection backtrace, @filters, :map
|
2008-11-22 12:06:08 -05:00
|
|
|
end
|
2010-01-16 22:34:35 -05:00
|
|
|
|
2020-05-15 17:59:15 -04:00
|
|
|
# Use @silencers to reject parts of the backtrace. Guarantee
|
|
|
|
# something non-empty is returned.
|
2008-11-22 12:06:08 -05:00
|
|
|
def silence(backtrace)
|
2020-05-15 17:59:15 -04:00
|
|
|
result = process_collection backtrace, @silencers, :reject
|
|
|
|
result.first ? result : backtrace.dup
|
2010-01-16 22:34:35 -05:00
|
|
|
end
|
|
|
|
|
2020-05-15 17:59:15 -04:00
|
|
|
# Use @silencers to select parts of the backtrace. Guarantee
|
|
|
|
# something non-empty is returned.
|
2010-01-16 22:34:35 -05:00
|
|
|
def noise(backtrace)
|
2020-05-15 17:59:15 -04:00
|
|
|
result = backtrace.select { |line| @silencers.any? { |s| s.call(line) } }
|
|
|
|
result.first ? result : backtrace.dup
|
2008-11-22 12:06:08 -05:00
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|