Skip to content

Commit

Permalink
Add an ExceptionWrapper that wraps an exception and provide convenien…
Browse files Browse the repository at this point in the history
…ce helpers.
  • Loading branch information
josevalim committed Dec 1, 2011
1 parent b4359bc commit 0b677b1
Show file tree
Hide file tree
Showing 5 changed files with 104 additions and 77 deletions.
2 changes: 1 addition & 1 deletion actionpack/lib/action_controller/log_subscriber.rb
Expand Up @@ -20,7 +20,7 @@ def process_action(event)

status = payload[:status]
if status.nil? && payload[:exception].present?
status = Rack::Utils.status_code(ActionDispatch::ShowExceptions.rescue_responses[payload[:exception].first]) rescue nil
status = Rack::Utils.status_code(ActionDispatch::ExceptionWrapper.new({}, payload[:exception]).status_code)
end
message = "Completed #{status} #{Rack::Utils::HTTP_STATUS_CODES[status]} in %.0fms" % event.duration
message << " (#{additions.join(" | ")})" unless additions.blank?
Expand Down
1 change: 1 addition & 0 deletions actionpack/lib/action_dispatch.rb
Expand Up @@ -51,6 +51,7 @@ module ActionDispatch
autoload :BestStandardsSupport
autoload :Callbacks
autoload :Cookies
autoload :ExceptionWrapper
autoload :Flash
autoload :Head
autoload :ParamsParser
Expand Down
77 changes: 77 additions & 0 deletions actionpack/lib/action_dispatch/middleware/exception_wrapper.rb
@@ -0,0 +1,77 @@
require 'active_support/core_ext/exception'

module ActionDispatch
class ExceptionWrapper
cattr_accessor :rescue_responses
@@rescue_responses = Hash.new(:internal_server_error)
@@rescue_responses.merge!(
'ActionController::RoutingError' => :not_found,
'AbstractController::ActionNotFound' => :not_found,
'ActionController::MethodNotAllowed' => :method_not_allowed,
'ActionController::NotImplemented' => :not_implemented,
'ActionController::InvalidAuthenticityToken' => :unprocessable_entity
)

cattr_accessor :rescue_templates
@@rescue_templates = Hash.new('diagnostics')
@@rescue_templates.merge!(
'ActionView::MissingTemplate' => 'missing_template',
'ActionController::RoutingError' => 'routing_error',
'AbstractController::ActionNotFound' => 'unknown_action',
'ActionView::Template::Error' => 'template_error'
)

attr_reader :env, :exception

def initialize(env, exception)
@env = env
@exception = original_exception(exception)
end

def rescue_template
@@rescue_templates[@exception.class.name]
end

def status_code
Rack::Utils.status_code(@@rescue_responses[@exception.class.name])
end

def application_trace
clean_backtrace(:silent)
end

def framework_trace
clean_backtrace(:noise)
end

def full_trace
clean_backtrace(:all)
end

private

def original_exception(exception)
if registered_original_exception?(exception)
exception.original_exception
else
exception
end
end

def registered_original_exception?(exception)
exception.respond_to?(:original_exception) && @@rescue_responses.has_key?(exception.original_exception.class.name)
end

def clean_backtrace(*args)
if backtrace_cleaner
backtrace_cleaner.clean(@exception.backtrace, *args)
else
@exception.backtrace
end
end

def backtrace_cleaner
@backtrace_cleaner ||= @env['action_dispatch.backtrace_cleaner']
end
end
end
97 changes: 23 additions & 74 deletions actionpack/lib/action_dispatch/middleware/show_exceptions.rb
@@ -1,7 +1,6 @@
require 'active_support/core_ext/exception'
require 'action_controller/metal/exceptions'
require 'active_support/notifications'
require 'action_dispatch/http/request'
require 'action_dispatch/middleware/exception_wrapper'
require 'active_support/deprecation'

module ActionDispatch
Expand All @@ -10,25 +9,6 @@ module ActionDispatch
class ShowExceptions
RESCUES_TEMPLATE_PATH = File.join(File.dirname(__FILE__), 'templates')

cattr_accessor :rescue_responses
@@rescue_responses = Hash.new(:internal_server_error)
@@rescue_responses.merge!(
'ActionController::RoutingError' => :not_found,
'AbstractController::ActionNotFound' => :not_found,
'ActionController::MethodNotAllowed' => :method_not_allowed,
'ActionController::NotImplemented' => :not_implemented,
'ActionController::InvalidAuthenticityToken' => :unprocessable_entity
)

cattr_accessor :rescue_templates
@@rescue_templates = Hash.new('diagnostics')
@@rescue_templates.merge!(
'ActionView::MissingTemplate' => 'missing_template',
'ActionController::RoutingError' => 'routing_error',
'AbstractController::ActionNotFound' => 'unknown_action',
'ActionView::Template::Error' => 'template_error'
)

FAILSAFE_RESPONSE = [500, {'Content-Type' => 'text/html'},
["<html><body><h1>500 Internal Server Error</h1>" <<
"If you are the administrator of this website, then please read this web " <<
Expand Down Expand Up @@ -59,13 +39,13 @@ def call(env)

private
def render_exception(env, exception)
log_error(env, exception)
exception = original_exception(exception)
wrapper = ExceptionWrapper.new(env, exception)
log_error(env, wrapper)

if env['action_dispatch.show_detailed_exceptions'] == true
rescue_action_diagnostics(env, exception)
rescue_action_diagnostics(wrapper)
else
rescue_action_error_page(exception)
rescue_action_error_page(wrapper)
end
rescue Exception => failsafe_error
$stderr.puts "Error during failsafe response: #{failsafe_error}\n #{failsafe_error.backtrace * "\n "}"
Expand All @@ -74,17 +54,17 @@ def render_exception(env, exception)

# Render detailed diagnostics for unhandled exceptions rescued from
# a controller action.
def rescue_action_diagnostics(env, exception)
def rescue_action_diagnostics(wrapper)
template = ActionView::Base.new([RESCUES_TEMPLATE_PATH],
:request => Request.new(env),
:exception => exception,
:application_trace => application_trace(env, exception),
:framework_trace => framework_trace(env, exception),
:full_trace => full_trace(env, exception)
:request => Request.new(wrapper.env),
:exception => wrapper.exception,
:application_trace => wrapper.application_trace,
:framework_trace => wrapper.framework_trace,
:full_trace => wrapper.full_trace
)
file = "rescues/#{@@rescue_templates[exception.class.name]}"
file = "rescues/#{wrapper.rescue_template}"
body = template.render(:template => file, :layout => 'rescues/layout')
render(status_code(exception), body)
render(wrapper.status_code, body)
end

# Attempts to render a static error page based on the
Expand All @@ -94,8 +74,8 @@ def rescue_action_diagnostics(env, exception)
# it will first attempt to render the file at <tt>public/500.da.html</tt>
# then attempt to render <tt>public/500.html</tt>. If none of them exist,
# the body of the response will be left empty.
def rescue_action_error_page(exception)
status = status_code(exception)
def rescue_action_error_page(wrapper)
status = wrapper.status_code
locale_path = "#{public_path}/#{status}.#{I18n.locale}.html" if I18n.locale
path = "#{public_path}/#{status}.html"

Expand All @@ -108,10 +88,6 @@ def rescue_action_error_page(exception)
end
end

def status_code(exception)
Rack::Utils.status_code(@@rescue_responses[exception.class.name])
end

def render(status, body)
[status, {'Content-Type' => "text/html; charset=#{Response.default_charset}", 'Content-Length' => body.bytesize.to_s}, [body]]
end
Expand All @@ -120,53 +96,26 @@ def public_path
defined?(Rails.public_path) ? Rails.public_path : 'public_path'
end

def log_error(env, exception)
return unless logger(env)
def log_error(env, wrapper)
logger = logger(env)
return unless logger

exception = wrapper.exception

ActiveSupport::Deprecation.silence do
message = "\n#{exception.class} (#{exception.message}):\n"
message << exception.annoted_source_code.to_s if exception.respond_to?(:annoted_source_code)
message << " " << application_trace(env, exception).join("\n ")
logger(env).fatal("#{message}\n\n")
message << " " << wrapper.application_trace.join("\n ")
logger.fatal("#{message}\n\n")
end
end

def application_trace(env, exception)
clean_backtrace(env, exception, :silent)
end

def framework_trace(env, exception)
clean_backtrace(env, exception, :noise)
end

def full_trace(env, exception)
clean_backtrace(env, exception, :all)
end

def clean_backtrace(env, exception, *args)
env['action_dispatch.backtrace_cleaner'] ?
env['action_dispatch.backtrace_cleaner'].clean(exception.backtrace, *args) :
exception.backtrace
end

def logger(env)
env['action_dispatch.logger'] || stderr_logger
end

def stderr_logger
Logger.new($stderr)
end

def original_exception(exception)
if registered_original_exception?(exception)
exception.original_exception
else
exception
@stderr_logger ||= Logger.new($stderr)
end
end

def registered_original_exception?(exception)
exception.respond_to?(:original_exception) && @@rescue_responses.has_key?(exception.original_exception.class.name)
end
end
end
4 changes: 2 additions & 2 deletions actionpack/lib/action_dispatch/railtie.rb
Expand Up @@ -22,8 +22,8 @@ class Railtie < Rails::Railtie
ActionDispatch::Http::URL.tld_length = app.config.action_dispatch.tld_length
ActionDispatch::Request.ignore_accept_header = app.config.action_dispatch.ignore_accept_header

ActionDispatch::ShowExceptions.rescue_responses.merge!(config.action_dispatch.rescue_responses)
ActionDispatch::ShowExceptions.rescue_templates.merge!(config.action_dispatch.rescue_templates)
ActionDispatch::ExceptionWrapper.rescue_responses.merge!(config.action_dispatch.rescue_responses)
ActionDispatch::ExceptionWrapper.rescue_templates.merge!(config.action_dispatch.rescue_templates)

config.action_dispatch.always_write_cookie = Rails.env.development? if config.action_dispatch.always_write_cookie.nil?
ActionDispatch::Cookies::CookieJar.always_write_cookie = config.action_dispatch.always_write_cookie
Expand Down

0 comments on commit 0b677b1

Please sign in to comment.