Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Added ActiveSupport::BacktraceCleaner and Rails::BacktraceCleaner for…

… cutting down on backtrace noise (inspired by the Thoughtbot Quiet Backtrace plugin) [DHH]
  • Loading branch information...
commit f42c77f927eb49b00e84d355e07de48723d03fcb 1 parent a026b4c
David Heinemeier Hansson dhh authored
14 actionpack/lib/action_controller/rescue.rb
View
@@ -68,9 +68,8 @@ def log_error(exception) #:doc:
logger.fatal(exception.to_s)
else
logger.fatal(
- "\n\n#{exception.class} (#{exception.message}):\n " +
- clean_backtrace(exception).join("\n ") +
- "\n\n"
+ "\n#{exception.class} (#{exception.message}):\n " +
+ clean_backtrace(exception).join("\n ") + "\n\n"
)
end
end
@@ -151,13 +150,8 @@ def response_code_for_rescue(exception)
end
def clean_backtrace(exception)
- if backtrace = exception.backtrace
- if defined?(RAILS_ROOT)
- backtrace.map { |line| line.sub RAILS_ROOT, '' }
- else
- backtrace
- end
- end
+ defined?(Rails) && Rails.respond_to?(:backtrace_cleaner) ?
+ Rails.backtrace_cleaner.clean(exception.backtrace) : exception.backtrace
end
end
end
17 actionpack/lib/action_view/template_error.rb
View
@@ -20,7 +20,11 @@ def message
end
def clean_backtrace
- original_exception.clean_backtrace
+ if defined?(Rails) && Rails.respond_to?(:backtrace_cleaner)
+ Rails.backtrace_cleaner.clean(original_exception.backtrace)
+ else
+ original_exception.backtrace
+ end
end
def sub_template_message
@@ -66,8 +70,8 @@ def line_number
end
def to_s
- "\n\n#{self.class} (#{message}) #{source_location}:\n" +
- "#{source_extract}\n #{clean_backtrace.join("\n ")}\n\n"
+ "\n#{self.class} (#{message}) #{source_location}:\n" +
+ "#{source_extract}\n #{clean_backtrace.join("\n ")}\n\n"
end
# don't do anything nontrivial here. Any raised exception from here becomes fatal
@@ -92,9 +96,4 @@ def source_location
end + file_name
end
end
-end
-
-if defined?(Exception::TraceSubstitutions)
- Exception::TraceSubstitutions << [/:in\s+`_run_.*'\s*$/, '']
- Exception::TraceSubstitutions << [%r{^\s*#{Regexp.escape RAILS_ROOT}/}, ''] if defined?(RAILS_ROOT)
-end
+end
18 actionpack/test/controller/rescue_test.rb
View
@@ -291,24 +291,6 @@ def test_rescue_templates
assert_equal 'template_error', templates[ActionView::TemplateError.name]
end
- def test_clean_backtrace
- with_rails_root nil do
- # No action if RAILS_ROOT isn't set.
- cleaned = @controller.send(:clean_backtrace, @exception)
- assert_equal @exception.backtrace, cleaned
- end
-
- with_rails_root Dir.pwd do
- # RAILS_ROOT is removed from backtrace.
- cleaned = @controller.send(:clean_backtrace, @exception)
- expected = @exception.backtrace.map { |line| line.sub(RAILS_ROOT, '') }
- assert_equal expected, cleaned
-
- # No action if backtrace is nil.
- assert_nil @controller.send(:clean_backtrace, Exception.new)
- end
- end
-
def test_not_implemented
with_all_requests_local false do
with_rails_public_path(".") do
2  activesupport/CHANGELOG
View
@@ -1,5 +1,7 @@
*2.3.0 [Edge]*
+* Added ActiveSupport::BacktraceCleaner to cut down on backtrace noise according to filters and silencers [DHH]
+
* Added Object#try. ( Taken from http://ozmm.org/posts/try.html ) [Chris Wanstrath]
* Added Enumerable#none? to check that none of the elements match the block #1408 [Damian Janowski]
1  activesupport/lib/active_support.rb
View
@@ -29,6 +29,7 @@
require 'active_support/core_ext'
require 'active_support/buffered_logger'
+require 'active_support/backtrace_cleaner'
require 'active_support/gzip'
require 'active_support/cache'
72 activesupport/lib/active_support/backtrace_cleaner.rb
View
@@ -0,0 +1,72 @@
+module ActiveSupport
+ # Many backtraces include too much information that's not relevant for the context. This makes it hard to find the signal
+ # in the backtrace and adds debugging time. With a BacktraceCleaner, you can setup filters and silencers for your particular
+ # context, so only the relevant lines are included.
+ #
+ # If you need to reconfigure an existing BacktraceCleaner, like the one in Rails, to show as much as possible, you can always
+ # call BacktraceCleaner#remove_silencers!
+ #
+ # Example:
+ #
+ # bc = BacktraceCleaner.new
+ # bc.add_filter { |line| line.gsub(Rails.root, '') }
+ # bc.add_silencer { |line| line =~ /mongrel|rubygems/ }
+ # bc.clean(exception.backtrace) # will strip the Rails.root prefix and skip any lines from mongrel or rubygems
+ #
+ # Inspired by the Quiet Backtrace gem by Thoughtbot.
+ class BacktraceCleaner
+ def initialize
+ @filters, @silencers = [], []
+ end
+
+ # Returns the backtrace after all filters and silencers has been run against it. Filters run first, then silencers.
+ def clean(backtrace)
+ silence(filter(backtrace))
+ end
+
+ # Adds a filter from the block provided. Each line in the backtrace will be mapped against this filter.
+ #
+ # Example:
+ #
+ # # 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
+
+ # Adds a silencer from the block provided. If the silencer returns true for a given line, it'll be excluded from the
+ # clean backtrace.
+ #
+ # Example:
+ #
+ # # Will reject all lines that include the word "mongrel", like "/gems/mongrel/server.rb" or "/app/my_mongrel_server/rb"
+ # backtrace_cleaner.add_silencer { |line| line =~ /mongrel/ }
+ def add_silencer(&block)
+ @silencers << block
+ end
+
+ # Will remove all silencers, but leave in the filters. This is useful if your context of debugging suddenly expands as
+ # you suspect a bug in the libraries you use.
+ def remove_silencers!
+ @silencers = []
+ end
+
+
+ private
+ def filter(backtrace)
+ @filters.each do |f|
+ backtrace = backtrace.map { |line| f.call(line) }
+ end
+
+ backtrace
+ end
+
+ def silence(backtrace)
+ @silencers.each do |s|
+ backtrace = backtrace.reject { |line| s.call(line) }
+ end
+
+ backtrace
+ end
+ end
+end
1  activesupport/lib/active_support/core_ext/exception.rb
View
@@ -6,6 +6,7 @@ module ActiveSupport
end
end
+# TODO: Turn all this into using the BacktraceCleaner.
class Exception # :nodoc:
def clean_message
Pathname.clean_within message
10 activesupport/lib/active_support/test_case.rb
View
@@ -21,15 +21,21 @@ class TestCase < ::Test::Unit::TestCase
Assertion = MiniTest::Assertion
end
+ # TODO: Figure out how to get the Rails::BacktraceFilter into minitest/unit
# Test::Unit compatibility.
rescue LoadError
require 'test/unit/testcase'
require 'active_support/testing/default'
+ if defined?(Rails)
+ require 'rails/backtrace_cleaner'
+ Test::Unit::Util::BacktraceFilter.module_eval { include Rails::BacktraceFilterForTestUnit }
+ end
+
class TestCase < ::Test::Unit::TestCase
Assertion = Test::Unit::AssertionFailedError
include ActiveSupport::Testing::Default
- end
+ end
end
class TestCase
@@ -37,4 +43,4 @@ class TestCase
include ActiveSupport::Testing::Assertions
extend ActiveSupport::Testing::Declarative
end
-end
+end
1  activesupport/test/abstract_unit.rb
View
@@ -3,6 +3,7 @@
gem 'mocha', '>= 0.9.0'
require 'mocha'
+$:.unshift "#{File.dirname(__FILE__)}/../lib"
require 'active_support'
require 'active_support/test_case'
47 activesupport/test/clean_backtrace_test.rb
View
@@ -0,0 +1,47 @@
+require 'abstract_unit'
+
+class BacktraceCleanerFilterTest < ActiveSupport::TestCase
+ def setup
+ @bc = ActiveSupport::BacktraceCleaner.new
+ @bc.add_filter { |line| line.gsub("/my/prefix", '') }
+ end
+
+ test "backtrace should not contain prefix when it has been filtered out" do
+ assert_equal "/my/class.rb", @bc.clean([ "/my/prefix/my/class.rb" ]).first
+ end
+
+ test "backtrace should contain unaltered lines if they dont match a filter" do
+ assert_equal "/my/other_prefix/my/class.rb", @bc.clean([ "/my/other_prefix/my/class.rb" ]).first
+ end
+
+ test "backtrace should filter all lines in a backtrace" do
+ assert_equal \
+ ["/my/class.rb", "/my/module.rb"],
+ @bc.clean([ "/my/prefix/my/class.rb", "/my/prefix/my/module.rb" ])
+ end
+end
+
+class BacktraceCleanerSilencerTest < ActiveSupport::TestCase
+ def setup
+ @bc = ActiveSupport::BacktraceCleaner.new
+ @bc.add_silencer { |line| line =~ /mongrel/ }
+ end
+
+ test "backtrace should not contain lines that match the silencer" do
+ assert_equal \
+ [ "/other/class.rb" ],
+ @bc.clean([ "/mongrel/class.rb", "/other/class.rb", "/mongrel/stuff.rb" ])
+ end
+end
+
+class BacktraceCleanerFilterAndSilencerTest < ActiveSupport::TestCase
+ def setup
+ @bc = ActiveSupport::BacktraceCleaner.new
+ @bc.add_filter { |line| line.gsub("/mongrel", "") }
+ @bc.add_silencer { |line| line =~ /mongrel/ }
+ end
+
+ test "backtrace should not silence lines that has first had their silence hook filtered out" do
+ assert_equal [ "/class.rb" ], @bc.clean([ "/mongrel/class.rb" ])
+ end
+end
2  activesupport/test/core_ext/array_ext_test.rb
View
@@ -21,7 +21,7 @@ def test_second_through_tenth
assert_equal array[2], array.third
assert_equal array[3], array.fourth
assert_equal array[4], array.fifth
- assert_equal array[41], array.fourty_two
+ assert_equal array[41], array.forty_two
end
end
2  railties/CHANGELOG
View
@@ -1,5 +1,7 @@
*2.3.0 [Edge]*
+* Added Rails.backtrace_cleaner as an accessor for the Rails::BacktraceCleaner instance used by the framework to cut down on backtrace noise and config/initializers/backtrace_silencers.rb to add your own (or turn them all off) [DHH]
+
* Switch from Test::Unit::TestCase to ActiveSupport::TestCase. [Jeremy Kemper]
* Added config.i18n settings gatherer to config/environment, auto-loading of all locales in config/locales/*.rb,yml, and config/locales/en.yml as a sample locale [DHH]
6 railties/Rakefile
View
@@ -197,8 +197,10 @@ task :copy_configs do
cp "configs/routes.rb", "#{PKG_DESTINATION}/config/routes.rb"
- cp "configs/initializers/inflections.rb", "#{PKG_DESTINATION}/config/initializers/inflections.rb"
- cp "configs/initializers/mime_types.rb", "#{PKG_DESTINATION}/config/initializers/mime_types.rb"
+ cp "configs/initializers/backtrace_silencers.rb", "#{PKG_DESTINATION}/config/initializers/backtrace_silencers.rb"
+ cp "configs/initializers/inflections.rb", "#{PKG_DESTINATION}/config/initializers/inflections.rb"
+ cp "configs/initializers/mime_types.rb", "#{PKG_DESTINATION}/config/initializers/mime_types.rb"
+ cp "configs/initializers/new_rails_defaults.rb", "#{PKG_DESTINATION}/config/initializers/new_rails_defaults.rb"
cp "configs/locales/en.yml", "#{PKG_DESTINATION}/config/locales/en.yml"
7 railties/configs/initializers/backtrace_silencers.rb
View
@@ -0,0 +1,7 @@
+# Be sure to restart your server when you modify this file.
+
+# You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces.
+# Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ }
+
+# You can also remove all the silencers if you're trying do debug a problem that might steem from framework code.
+# Rails.backtrace_cleaner.remove_silencers!
8 railties/lib/initializer.rb
View
@@ -39,6 +39,14 @@ def logger
nil
end
end
+
+ def backtrace_cleaner
+ @@backtrace_cleaner ||= begin
+ # Relies on ActiveSupport, so we have to lazy load to postpone definition until AS has been loaded
+ require 'rails/backtrace_cleaner'
+ Rails::BacktraceCleaner.new
+ end
+ end
def root
if defined?(RAILS_ROOT)
34 railties/lib/rails/backtrace_cleaner.rb
View
@@ -0,0 +1,34 @@
+module Rails
+ class BacktraceCleaner < ActiveSupport::BacktraceCleaner
+ ERB_METHOD_SIG = /:in `_run_erb_.*/
+
+ VENDOR_DIRS = %w( vendor/plugins vendor/gems vendor/rails )
+ MONGREL_DIRS = %w( lib/mongrel bin/mongrel )
+ RAILS_NOISE = %w( script/server )
+ RUBY_NOISE = %w( rubygems/custom_require benchmark.rb )
+
+ ALL_NOISE = VENDOR_DIRS + MONGREL_DIRS + RAILS_NOISE + RUBY_NOISE
+
+
+ def initialize
+ super
+ add_filter { |line| line.sub(RAILS_ROOT, '') }
+ add_filter { |line| line.sub(ERB_METHOD_SIG, '') }
+ add_silencer { |line| ALL_NOISE.any? { |dir| line.include?(dir) } }
+ end
+ end
+
+
+ # For installing the BacktraceCleaner in the test/unit
+ module BacktraceFilterForTestUnit #:nodoc:
+ def self.included(klass)
+ klass.send :alias_method_chain, :filter_backtrace, :cleaning
+ end
+
+ def filter_backtrace_with_cleaning(backtrace)
+ backtrace = filter_backtrace_without_cleaning(backtrace)
+ backtrace = backtrace.first.split("\n") if backtrace.size == 1
+ Rails.backtrace_cleaner.clean(backtrace)
+ end
+ end
+end
7 railties/lib/rails_generator/generators/applications/app/app_generator.rb
View
@@ -62,9 +62,10 @@ def manifest
m.template "configs/routes.rb", "config/routes.rb"
# Initializers
- m.template "configs/initializers/inflections.rb", "config/initializers/inflections.rb"
- m.template "configs/initializers/mime_types.rb", "config/initializers/mime_types.rb"
- m.template "configs/initializers/new_rails_defaults.rb", "config/initializers/new_rails_defaults.rb"
+ m.template "configs/initializers/backtrace_silencers.rb", "config/initializers/backtrace_silencers.rb"
+ m.template "configs/initializers/inflections.rb", "config/initializers/inflections.rb"
+ m.template "configs/initializers/mime_types.rb", "config/initializers/mime_types.rb"
+ m.template "configs/initializers/new_rails_defaults.rb", "config/initializers/new_rails_defaults.rb"
# Locale
m.template "configs/locales/en.yml", "config/locales/en.yml"

2 comments on commit f42c77f

Dan Croak

Very pleased to see this, especially the backtrace_silencers.rb initializer. Nice work!

Please sign in to comment.
Something went wrong with that request. Please try again.