Skip to content

Commit

Permalink
Silence server backtrace in rescue templates and log files. Also remo…
Browse files Browse the repository at this point in the history
…ve some noise from missing template errors.
  • Loading branch information
josh committed Dec 5, 2008
1 parent 9c9da6c commit 731dcd8
Show file tree
Hide file tree
Showing 8 changed files with 84 additions and 53 deletions.
66 changes: 42 additions & 24 deletions actionpack/lib/action_controller/rescue.rb
@@ -1,13 +1,19 @@
module ActionController #:nodoc:
# Actions that fail to perform as expected throw exceptions. These exceptions can either be rescued for the public view
# (with a nice user-friendly explanation) or for the developers view (with tons of debugging information). The developers view
# is already implemented by the Action Controller, but the public view should be tailored to your specific application.
#
# The default behavior for public exceptions is to render a static html file with the name of the error code thrown. If no such
# file exists, an empty response is sent with the correct status code.
# Actions that fail to perform as expected throw exceptions. These
# exceptions can either be rescued for the public view (with a nice
# user-friendly explanation) or for the developers view (with tons of
# debugging information). The developers view is already implemented by
# the Action Controller, but the public view should be tailored to your
# specific application.
#
# You can override what constitutes a local request by overriding the <tt>local_request?</tt> method in your own controller.
# Custom rescue behavior is achieved by overriding the <tt>rescue_action_in_public</tt> and <tt>rescue_action_locally</tt> methods.
# The default behavior for public exceptions is to render a static html
# file with the name of the error code thrown. If no such file exists, an
# empty response is sent with the correct status code.
#
# You can override what constitutes a local request by overriding the
# <tt>local_request?</tt> method in your own controller. Custom rescue
# behavior is achieved by overriding the <tt>rescue_action_in_public</tt>
# and <tt>rescue_action_locally</tt> methods.
module Rescue
LOCALHOST = '127.0.0.1'.freeze

Expand All @@ -32,6 +38,9 @@ module Rescue
'ActionView::TemplateError' => 'template_error'
}

RESCUES_TEMPLATE_PATH = ActionView::PathSet::Path.new(
"#{File.dirname(__FILE__)}/templates", true)

def self.included(base) #:nodoc:
base.cattr_accessor :rescue_responses
base.rescue_responses = Hash.new(DEFAULT_RESCUE_RESPONSE)
Expand All @@ -56,12 +65,15 @@ def process_with_exception(request, response, exception) #:nodoc:
end

protected
# Exception handler called when the performance of an action raises an exception.
# Exception handler called when the performance of an action raises
# an exception.
def rescue_action(exception)
rescue_with_handler(exception) || rescue_action_without_handler(exception)
rescue_with_handler(exception) ||
rescue_action_without_handler(exception)
end

# Overwrite to implement custom logging of errors. By default logs as fatal.
# Overwrite to implement custom logging of errors. By default
# logs as fatal.
def log_error(exception) #:doc:
ActiveSupport::Deprecation.silence do
if ActionView::TemplateError === exception
Expand All @@ -75,16 +87,19 @@ def log_error(exception) #:doc:
end
end

# Overwrite to implement public exception handling (for requests answering false to <tt>local_request?</tt>). By
# default will call render_optional_error_file. Override this method to provide more user friendly error messages.
# Overwrite to implement public exception handling (for requests
# answering false to <tt>local_request?</tt>). By default will call
# render_optional_error_file. Override this method to provide more
# user friendly error messages.
def rescue_action_in_public(exception) #:doc:
render_optional_error_file response_code_for_rescue(exception)
end

# Attempts to render a static error page based on the <tt>status_code</tt> thrown,
# or just return headers if no such file exists. For example, if a 500 error is
# being handled Rails will first attempt to render the file at <tt>public/500.html</tt>.
# If the file doesn't exist, the body of the response will be left empty.

# Attempts to render a static error page based on the
# <tt>status_code</tt> thrown, or just return headers if no such file
# exists. For example, if a 500 error is being handled Rails will first
# attempt to render the file at <tt>public/500.html</tt>. If the file
# doesn't exist, the body of the response will be left empty.
def render_optional_error_file(status_code)
status = interpret_status(status_code)
path = "#{Rails.public_path}/#{status[0,3]}.html"
Expand All @@ -106,11 +121,13 @@ def local_request? #:doc:
# a controller action.
def rescue_action_locally(exception)
@template.instance_variable_set("@exception", exception)
@template.instance_variable_set("@rescues_path", File.dirname(rescues_path("stub")))
@template.instance_variable_set("@contents", @template.render(:file => template_path_for_local_rescue(exception)))
@template.instance_variable_set("@rescues_path", RESCUES_TEMPLATE_PATH)
@template.instance_variable_set("@contents",
@template.render(:file => template_path_for_local_rescue(exception)))

response.content_type = Mime::HTML
render_for_file(rescues_path("layout"), response_code_for_rescue(exception))
render_for_file(rescues_path("layout"),
response_code_for_rescue(exception))
end

def rescue_action_without_handler(exception)
Expand Down Expand Up @@ -138,7 +155,7 @@ def perform_action_with_rescue #:nodoc:
end

def rescues_path(template_name)
"#{File.dirname(__FILE__)}/templates/rescues/#{template_name}.erb"
RESCUES_TEMPLATE_PATH["rescues/#{template_name}.erb"]
end

def template_path_for_local_rescue(exception)
Expand All @@ -150,8 +167,9 @@ def response_code_for_rescue(exception)
end

def clean_backtrace(exception)
defined?(Rails) && Rails.respond_to?(:backtrace_cleaner) ?
Rails.backtrace_cleaner.clean(exception.backtrace) : exception.backtrace
defined?(Rails) && Rails.respond_to?(:backtrace_cleaner) ?
Rails.backtrace_cleaner.clean(exception.backtrace) :
exception.backtrace
end
end
end
Expand Up @@ -6,6 +6,6 @@
</h1>
<pre><%=h @exception.clean_message %></pre>

<%= render(:file => @rescues_path + "/_trace.erb") %>
<%= render :file => @rescues_path["rescues/_trace.erb"] %>
<%= render(:file => @rescues_path + "/_request_and_response.erb") %>
<%= render :file => @rescues_path["rescues/_request_and_response.erb"] %>
Expand Up @@ -15,7 +15,7 @@

<% @real_exception = @exception
@exception = @exception.original_exception || @exception %>
<%= render(:file => @rescues_path + "/_trace.erb") %>
<%= render :file => @rescues_path["rescues/_trace.erb"] %>
<% @exception = @real_exception %>
<%= render(:file => @rescues_path + "/_request_and_response.erb") %>
<%= render :file => @rescues_path["rescues/_request_and_response.erb"] %>
2 changes: 1 addition & 1 deletion actionpack/lib/action_view/base.rb
Expand Up @@ -5,7 +5,7 @@ class ActionViewError < StandardError #:nodoc:
class MissingTemplate < ActionViewError #:nodoc:
def initialize(paths, path, template_format = nil)
full_template_path = path.include?('.') ? path : "#{path}.erb"
display_paths = paths.join(':')
display_paths = paths.compact.join(":")
template_type = (path =~ /layouts/i) ? 'layout' : 'template'
super("Missing #{template_type} #{full_template_path} in view path #{display_paths}")
end
Expand Down
10 changes: 9 additions & 1 deletion actionpack/lib/action_view/paths.rb
Expand Up @@ -41,14 +41,22 @@ def unshift(*objs)

class Path #:nodoc:
attr_reader :path, :paths
delegate :to_s, :to_str, :hash, :inspect, :to => :path
delegate :hash, :inspect, :to => :path

def initialize(path, load = false)
raise ArgumentError, "path already is a Path class" if path.is_a?(Path)
@path = path.freeze
reload! if load
end

def to_s
if defined?(RAILS_ROOT)
path.to_s.sub(/^#{Regexp.escape(File.expand_path(RAILS_ROOT))}\//, '')
else
path.to_s
end
end

def ==(path)
to_str == path.to_str
end
Expand Down
26 changes: 13 additions & 13 deletions actionpack/test/controller/view_paths_test.rb
Expand Up @@ -43,40 +43,40 @@ def teardown
end

def test_template_load_path_was_set_correctly
assert_equal [FIXTURE_LOAD_PATH], @controller.view_paths
assert_equal [FIXTURE_LOAD_PATH], @controller.view_paths.map(&:to_s)
end

def test_controller_appends_view_path_correctly
@controller.append_view_path 'foo'
assert_equal [FIXTURE_LOAD_PATH, 'foo'], @controller.view_paths
assert_equal [FIXTURE_LOAD_PATH, 'foo'], @controller.view_paths.map(&:to_s)

@controller.append_view_path(%w(bar baz))
assert_equal [FIXTURE_LOAD_PATH, 'foo', 'bar', 'baz'], @controller.view_paths
assert_equal [FIXTURE_LOAD_PATH, 'foo', 'bar', 'baz'], @controller.view_paths.map(&:to_s)

@controller.append_view_path(FIXTURE_LOAD_PATH)
assert_equal [FIXTURE_LOAD_PATH, 'foo', 'bar', 'baz', FIXTURE_LOAD_PATH], @controller.view_paths
assert_equal [FIXTURE_LOAD_PATH, 'foo', 'bar', 'baz', FIXTURE_LOAD_PATH], @controller.view_paths.map(&:to_s)
end

def test_controller_prepends_view_path_correctly
@controller.prepend_view_path 'baz'
assert_equal ['baz', FIXTURE_LOAD_PATH], @controller.view_paths
assert_equal ['baz', FIXTURE_LOAD_PATH], @controller.view_paths.map(&:to_s)

@controller.prepend_view_path(%w(foo bar))
assert_equal ['foo', 'bar', 'baz', FIXTURE_LOAD_PATH], @controller.view_paths
assert_equal ['foo', 'bar', 'baz', FIXTURE_LOAD_PATH], @controller.view_paths.map(&:to_s)

@controller.prepend_view_path(FIXTURE_LOAD_PATH)
assert_equal [FIXTURE_LOAD_PATH, 'foo', 'bar', 'baz', FIXTURE_LOAD_PATH], @controller.view_paths
assert_equal [FIXTURE_LOAD_PATH, 'foo', 'bar', 'baz', FIXTURE_LOAD_PATH], @controller.view_paths.map(&:to_s)
end

def test_template_appends_view_path_correctly
@controller.instance_variable_set :@template, ActionView::Base.new(TestController.view_paths, {}, @controller)
class_view_paths = TestController.view_paths

@controller.append_view_path 'foo'
assert_equal [FIXTURE_LOAD_PATH, 'foo'], @controller.view_paths
assert_equal [FIXTURE_LOAD_PATH, 'foo'], @controller.view_paths.map(&:to_s)

@controller.append_view_path(%w(bar baz))
assert_equal [FIXTURE_LOAD_PATH, 'foo', 'bar', 'baz'], @controller.view_paths
assert_equal [FIXTURE_LOAD_PATH, 'foo', 'bar', 'baz'], @controller.view_paths.map(&:to_s)
assert_equal class_view_paths, TestController.view_paths
end

Expand All @@ -85,10 +85,10 @@ def test_template_prepends_view_path_correctly
class_view_paths = TestController.view_paths

@controller.prepend_view_path 'baz'
assert_equal ['baz', FIXTURE_LOAD_PATH], @controller.view_paths
assert_equal ['baz', FIXTURE_LOAD_PATH], @controller.view_paths.map(&:to_s)

@controller.prepend_view_path(%w(foo bar))
assert_equal ['foo', 'bar', 'baz', FIXTURE_LOAD_PATH], @controller.view_paths
assert_equal ['foo', 'bar', 'baz', FIXTURE_LOAD_PATH], @controller.view_paths.map(&:to_s)
assert_equal class_view_paths, TestController.view_paths
end

Expand Down Expand Up @@ -130,12 +130,12 @@ class C < ActionController::Base; end

A.view_paths = ['a/path']

assert_equal ['a/path'], A.view_paths
assert_equal ['a/path'], A.view_paths.map(&:to_s)
assert_equal A.view_paths, B.view_paths
assert_equal original_load_paths, C.view_paths

C.view_paths = []
assert_nothing_raised { C.view_paths << 'c/path' }
assert_equal ['c/path'], C.view_paths
assert_equal ['c/path'], C.view_paths.map(&:to_s)
end
end
19 changes: 11 additions & 8 deletions activesupport/lib/active_support/core_ext/exception.rb
Expand Up @@ -11,31 +11,34 @@ class Exception # :nodoc:
def clean_message
Pathname.clean_within message
end

TraceSubstitutions = []
FrameworkRegexp = /generated|vendor|dispatch|ruby|script\/\w+/

FrameworkStart = /action_controller\/dispatcher\.rb/.freeze
FrameworkRegexp = /generated|vendor|dispatch|ruby|script\/\w+/.freeze

def clean_backtrace
backtrace.collect do |line|
Pathname.clean_within(TraceSubstitutions.inject(line) do |result, (regexp, sub)|
result.gsub regexp, sub
end)
end
end

def application_backtrace
before_framework_frame = nil
before_application_frame = true

trace = clean_backtrace.reject do |line|
before_framework_frame ||= (line =~ FrameworkStart)
non_app_frame = (line =~ FrameworkRegexp)
before_application_frame = false unless non_app_frame
non_app_frame && ! before_application_frame
before_framework_frame || (non_app_frame && !before_application_frame)
end

# If we didn't find any application frames, return an empty app trace.
before_application_frame ? [] : trace
end

def framework_backtrace
clean_backtrace.grep FrameworkRegexp
end
Expand Down
6 changes: 4 additions & 2 deletions railties/lib/rails/backtrace_cleaner.rb
Expand Up @@ -3,7 +3,9 @@ class BacktraceCleaner < ActiveSupport::BacktraceCleaner
ERB_METHOD_SIG = /:in `_run_erb_.*/

VENDOR_DIRS = %w( vendor/plugins vendor/gems vendor/rails )
SERVER_DIRS = %w( lib/mongrel bin/mongrel lib/rack )
SERVER_DIRS = %w( lib/mongrel bin/mongrel
lib/passenger bin/passenger-spawn-server
lib/rack )
RAILS_NOISE = %w( script/server )
RUBY_NOISE = %w( rubygems/custom_require benchmark.rb )

Expand All @@ -30,4 +32,4 @@ def filter_backtrace_with_cleaning(backtrace, prefix=nil)
Rails.backtrace_cleaner.clean(backtrace)
end
end
end
end

0 comments on commit 731dcd8

Please sign in to comment.