Merge branch 'exceptions' with the following features:

* A Railtie API for registering new exceptions and their respective status code (check Active Record railtie for an example)

* Extraction of ShowExceptions middleware logging and debugging features into a middleware called DebugExceptions

Conflicts:
	actionpack/CHANGELOG.md
This commit is contained in:
José Valim
2011-12-01 21:18:47 +01:00
16 changed files with 406 additions and 254 deletions

View File

@@ -1,5 +1,7 @@
## Rails 3.2.0 (unreleased) ##
* Allow rescue responses to be configured through a railtie as in `config.action_dispatch.rescue_responses`. Please look at ActiveRecord::Railtie for an example *José Valim*
* Allow fresh_when/stale? to take a record instead of an options hash *DHH*
* Assets should use the request protocol by default or default to relative if no request is available *Jonathan del Strother*

View File

@@ -20,7 +20,7 @@ module ActionController
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?

View File

@@ -51,6 +51,8 @@ module ActionDispatch
autoload :BestStandardsSupport
autoload :Callbacks
autoload :Cookies
autoload :DebugExceptions
autoload :ExceptionWrapper
autoload :Flash
autoload :Head
autoload :ParamsParser

View File

@@ -0,0 +1,77 @@
require 'action_dispatch/http/request'
require 'action_dispatch/middleware/exception_wrapper'
module ActionDispatch
# This middleware is responsible for logging exceptions and
# showing a debugging page in case the request is local.
class DebugExceptions
RESCUES_TEMPLATE_PATH = File.join(File.dirname(__FILE__), 'templates')
def initialize(app)
@app = app
end
def call(env)
begin
response = @app.call(env)
if response[1]['X-Cascade'] == 'pass'
raise ActionController::RoutingError, "No route matches [#{env['REQUEST_METHOD']}] #{env['PATH_INFO'].inspect}"
end
rescue Exception => exception
raise exception if env['action_dispatch.show_exceptions'] == false
end
response ? response : render_exception(env, exception)
end
private
def render_exception(env, exception)
wrapper = ExceptionWrapper.new(env, exception)
log_error(env, wrapper)
if env['action_dispatch.show_detailed_exceptions']
template = ActionView::Base.new([RESCUES_TEMPLATE_PATH],
:request => Request.new(env),
:exception => wrapper.exception,
:application_trace => wrapper.application_trace,
:framework_trace => wrapper.framework_trace,
:full_trace => wrapper.full_trace
)
file = "rescues/#{wrapper.rescue_template}"
body = template.render(:template => file, :layout => 'rescues/layout')
render(wrapper.status_code, body)
else
raise exception
end
end
def render(status, body)
[status, {'Content-Type' => "text/html; charset=#{Response.default_charset}", 'Content-Length' => body.bytesize.to_s}, [body]]
end
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 << " " << wrapper.application_trace.join("\n ")
logger.fatal("#{message}\n\n")
end
end
def logger(env)
env['action_dispatch.logger'] || stderr_logger
end
def stderr_logger
@stderr_logger ||= Logger.new($stderr)
end
end
end

View File

@@ -0,0 +1,78 @@
require 'action_controller/metal/exceptions'
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

View File

@@ -1,44 +1,33 @@
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
# This middleware rescues any exception returned by the application and renders
# nice exception pages if it's being rescued locally.
# This middleware rescues any exception returned by the application
# and wraps them in a format for the end user.
class ShowExceptions
RESCUES_TEMPLATE_PATH = File.join(File.dirname(__FILE__), 'templates')
cattr_accessor :rescue_responses
@@rescue_responses = Hash.new(:internal_server_error)
@@rescue_responses.update({
'ActionController::RoutingError' => :not_found,
'AbstractController::ActionNotFound' => :not_found,
'ActiveRecord::RecordNotFound' => :not_found,
'ActiveRecord::StaleObjectError' => :conflict,
'ActiveRecord::RecordInvalid' => :unprocessable_entity,
'ActiveRecord::RecordNotSaved' => :unprocessable_entity,
'ActionController::MethodNotAllowed' => :method_not_allowed,
'ActionController::NotImplemented' => :not_implemented,
'ActionController::InvalidAuthenticityToken' => :unprocessable_entity
})
cattr_accessor :rescue_templates
@@rescue_templates = Hash.new('diagnostics')
@@rescue_templates.update({
'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 " <<
"application's log file and/or the web server's log file to find out what " <<
"went wrong.</body></html>"]]
class << self
def rescue_responses
ActiveSupport::Deprecation.warn "ActionDispatch::ShowExceptions.rescue_responses is deprecated. " \
"Please configure your exceptions using a railtie or in your application config instead."
ExceptionWrapper.rescue_responses
end
def rescue_templates
ActiveSupport::Deprecation.warn "ActionDispatch::ShowExceptions.rescue_templates is deprecated. " \
"Please configure your exceptions using a railtie or in your application config instead."
ExceptionWrapper.rescue_templates
end
end
def initialize(app, consider_all_requests_local = nil)
ActiveSupport::Deprecation.warn "Passing consider_all_requests_local option to ActionDispatch::ShowExceptions middleware no longer works" unless consider_all_requests_local.nil?
@app = app
@@ -46,131 +35,47 @@ module ActionDispatch
def call(env)
begin
status, headers, body = @app.call(env)
exception = nil
# Only this middleware cares about RoutingError. So, let's just raise
# it here.
if headers['X-Cascade'] == 'pass'
raise ActionController::RoutingError, "No route matches [#{env['REQUEST_METHOD']}] #{env['PATH_INFO'].inspect}"
end
response = @app.call(env)
rescue Exception => exception
raise exception if env['action_dispatch.show_exceptions'] == false
end
exception ? render_exception(env, exception) : [status, headers, body]
response ? response : render_exception_with_failsafe(env, exception)
end
private
def render_exception(env, exception)
log_error(env, exception)
exception = original_exception(exception)
if env['action_dispatch.show_detailed_exceptions'] == true
rescue_action_diagnostics(env, exception)
else
rescue_action_error_page(exception)
end
rescue Exception => failsafe_error
$stderr.puts "Error during failsafe response: #{failsafe_error}\n #{failsafe_error.backtrace * "\n "}"
FAILSAFE_RESPONSE
end
def render_exception_with_failsafe(env, exception)
render_exception(env, exception)
rescue Exception => failsafe_error
$stderr.puts "Error during failsafe response: #{failsafe_error}\n #{failsafe_error.backtrace * "\n "}"
FAILSAFE_RESPONSE
end
# Render detailed diagnostics for unhandled exceptions rescued from
# a controller action.
def rescue_action_diagnostics(env, exception)
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)
)
file = "rescues/#{@@rescue_templates[exception.class.name]}"
body = template.render(:template => file, :layout => 'rescues/layout')
render(status_code(exception), body)
end
def render_exception(env, exception)
wrapper = ExceptionWrapper.new(env, exception)
# Attempts to render a static error page based on the
# <tt>status_code</tt> thrown, or just return headers if no such file
# exists. At first, it will try to render a localized static page.
# For example, if a 500 error is being handled Rails and locale is :da,
# 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)
locale_path = "#{public_path}/#{status}.#{I18n.locale}.html" if I18n.locale
path = "#{public_path}/#{status}.html"
status = wrapper.status_code
locale_path = "#{public_path}/#{status}.#{I18n.locale}.html" if I18n.locale
path = "#{public_path}/#{status}.html"
if locale_path && File.exist?(locale_path)
render(status, File.read(locale_path))
elsif File.exist?(path)
render(status, File.read(path))
else
render(status, '')
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
def public_path
defined?(Rails.public_path) ? Rails.public_path : 'public_path'
end
def log_error(env, exception)
return unless logger(env)
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")
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
if locale_path && File.exist?(locale_path)
render(status, File.read(locale_path))
elsif File.exist?(path)
render(status, File.read(path))
else
exception
render(status, '')
end
end
def registered_original_exception?(exception)
exception.respond_to?(:original_exception) && @@rescue_responses.has_key?(exception.original_exception.class.name)
def render(status, body)
[status, {'Content-Type' => "text/html; charset=#{Response.default_charset}", 'Content-Length' => body.bytesize.to_s}, [body]]
end
# TODO: Make this a middleware initialization parameter once
# we removed the second option (which is deprecated)
def public_path
defined?(Rails.public_path) ? Rails.public_path : 'public_path'
end
end
end

View File

@@ -9,11 +9,22 @@ module ActionDispatch
config.action_dispatch.best_standards_support = true
config.action_dispatch.tld_length = 1
config.action_dispatch.ignore_accept_header = false
config.action_dispatch.rack_cache = {:metastore => "rails:/", :entitystore => "rails:/", :verbose => true}
config.action_dispatch.rescue_templates = { }
config.action_dispatch.rescue_responses = { }
config.action_dispatch.rack_cache = {
:metastore => "rails:/",
:entitystore => "rails:/",
:verbose => true
}
initializer "action_dispatch.configure" do |app|
ActionDispatch::Http::URL.tld_length = app.config.action_dispatch.tld_length
ActionDispatch::Request.ignore_accept_header = app.config.action_dispatch.ignore_accept_header
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
end

View File

@@ -175,6 +175,7 @@ class ActionDispatch::IntegrationTest < ActiveSupport::TestCase
def self.build_app(routes = nil)
RoutedRackApp.new(routes || ActionDispatch::Routing::RouteSet.new) do |middleware|
middleware.use "ActionDispatch::ShowExceptions"
middleware.use "ActionDispatch::DebugExceptions"
middleware.use "ActionDispatch::Callbacks"
middleware.use "ActionDispatch::ParamsParser"
middleware.use "ActionDispatch::Cookies"
@@ -338,16 +339,19 @@ end
module ActionDispatch
class ShowExceptions
private
remove_method :public_path
def public_path
"#{FIXTURE_LOAD_PATH}/public"
end
remove_method :public_path
def public_path
"#{FIXTURE_LOAD_PATH}/public"
end
end
remove_method :stderr_logger
# Silence logger
def stderr_logger
nil
end
class DebugExceptions
private
remove_method :stderr_logger
# Silence logger
def stderr_logger
nil
end
end
end

View File

@@ -3,6 +3,7 @@ require 'abstract_unit'
module ShowExceptions
class ShowExceptionsController < ActionController::Base
use ActionDispatch::ShowExceptions
use ActionDispatch::DebugExceptions
def boom
raise 'boom!'

View File

@@ -0,0 +1,116 @@
require 'abstract_unit'
class DebugExceptionsTest < ActionDispatch::IntegrationTest
class Boomer
def initialize(detailed = false)
@detailed = detailed
end
def call(env)
env['action_dispatch.show_detailed_exceptions'] = @detailed
req = ActionDispatch::Request.new(env)
case req.path
when "/not_found"
raise ActionController::UnknownAction
when "/runtime_error"
raise RuntimeError
when "/method_not_allowed"
raise ActionController::MethodNotAllowed
when "/not_implemented"
raise ActionController::NotImplemented
when "/unprocessable_entity"
raise ActionController::InvalidAuthenticityToken
when "/not_found_original_exception"
raise ActionView::Template::Error.new('template', {}, AbstractController::ActionNotFound.new)
else
raise "puke!"
end
end
end
ProductionApp = ActionDispatch::DebugExceptions.new((Boomer.new(false)))
DevelopmentApp = ActionDispatch::DebugExceptions.new((Boomer.new(true)))
test 'skip diagnosis if not showing detailed exceptions' do
@app = ProductionApp
assert_raise RuntimeError do
get "/", {}, {'action_dispatch.show_exceptions' => true}
end
end
test 'skip diagnosis if not showing exceptions' do
@app = DevelopmentApp
assert_raise RuntimeError do
get "/", {}, {'action_dispatch.show_exceptions' => false}
end
end
test "rescue with diagnostics message" do
@app = DevelopmentApp
get "/", {}, {'action_dispatch.show_exceptions' => true}
assert_response 500
assert_match(/puke/, body)
get "/not_found", {}, {'action_dispatch.show_exceptions' => true}
assert_response 404
assert_match(/#{ActionController::UnknownAction.name}/, body)
get "/method_not_allowed", {}, {'action_dispatch.show_exceptions' => true}
assert_response 405
assert_match(/ActionController::MethodNotAllowed/, body)
end
test "does not show filtered parameters" do
@app = DevelopmentApp
get "/", {"foo"=>"bar"}, {'action_dispatch.show_exceptions' => true,
'action_dispatch.parameter_filter' => [:foo]}
assert_response 500
assert_match("&quot;foo&quot;=&gt;&quot;[FILTERED]&quot;", body)
end
test "show registered original exception for wrapped exceptions" do
@app = DevelopmentApp
get "/not_found_original_exception", {}, {'action_dispatch.show_exceptions' => true}
assert_response 404
assert_match(/AbstractController::ActionNotFound/, body)
end
test "show the controller name in the diagnostics template when controller name is present" do
@app = DevelopmentApp
get("/runtime_error", {}, {
'action_dispatch.show_exceptions' => true,
'action_dispatch.request.parameters' => {
'action' => 'show',
'id' => 'unknown',
'controller' => 'featured_tile'
}
})
assert_response 500
assert_match(/RuntimeError\n in FeaturedTileController/, body)
end
test "sets the HTTP charset parameter" do
@app = DevelopmentApp
get "/", {}, {'action_dispatch.show_exceptions' => true}
assert_equal "text/html; charset=utf-8", response.headers["Content-Type"]
end
test 'uses logger from env' do
@app = DevelopmentApp
output = StringIO.new
get "/", {}, {'action_dispatch.show_exceptions' => true, 'action_dispatch.logger' => Logger.new(output)}
assert_match(/puke/, output.rewind && output.read)
end
test 'uses backtrace cleaner from env' do
@app = DevelopmentApp
cleaner = stub(:clean => ['passed backtrace cleaner'])
get "/", {}, {'action_dispatch.show_exceptions' => true, 'action_dispatch.backtrace_cleaner' => cleaner}
assert_match(/passed backtrace cleaner/, body)
end
end

View File

@@ -3,24 +3,13 @@ require 'abstract_unit'
class ShowExceptionsTest < ActionDispatch::IntegrationTest
class Boomer
def initialize(detailed = false)
@detailed = detailed
end
def call(env)
env['action_dispatch.show_detailed_exceptions'] = @detailed
req = ActionDispatch::Request.new(env)
case req.path
when "/not_found"
raise ActionController::UnknownAction
when "/runtime_error"
raise RuntimeError
when "/method_not_allowed"
raise ActionController::MethodNotAllowed
when "/not_implemented"
raise ActionController::NotImplemented
when "/unprocessable_entity"
raise ActionController::InvalidAuthenticityToken
when "/not_found_original_exception"
raise ActionView::Template::Error.new('template', {}, AbstractController::ActionNotFound.new)
else
@@ -29,10 +18,16 @@ class ShowExceptionsTest < ActionDispatch::IntegrationTest
end
end
ProductionApp = ActionDispatch::ShowExceptions.new(Boomer.new(false))
DevelopmentApp = ActionDispatch::ShowExceptions.new(Boomer.new(true))
ProductionApp = ActionDispatch::ShowExceptions.new(Boomer.new)
test "rescue with error page when show_exceptions is false" do
test 'skip diagnosis if not showing exceptions' do
@app = ProductionApp
assert_raise RuntimeError do
get "/", {}, {'action_dispatch.show_exceptions' => false}
end
end
test "rescue with error page" do
@app = ProductionApp
get "/", {}, {'action_dispatch.show_exceptions' => true}
@@ -48,24 +43,7 @@ class ShowExceptionsTest < ActionDispatch::IntegrationTest
assert_equal "", body
end
test "rescue with diagnostics message when show_exceptions is true" do
@app = DevelopmentApp
get "/", {}, {'action_dispatch.show_exceptions' => true}
assert_response 500
assert_match(/puke/, body)
get "/not_found", {}, {'action_dispatch.show_exceptions' => true}
assert_response 404
assert_match(/#{ActionController::UnknownAction.name}/, body)
get "/method_not_allowed", {}, {'action_dispatch.show_exceptions' => true}
assert_response 405
assert_match(/ActionController::MethodNotAllowed/, body)
end
test "localize rescue error page" do
# Change locale
old_locale, I18n.locale = I18n.locale, :da
begin
@@ -83,63 +61,18 @@ class ShowExceptionsTest < ActionDispatch::IntegrationTest
end
end
test "does not show filtered parameters" do
@app = DevelopmentApp
test "sets the HTTP charset parameter" do
@app = ProductionApp
get "/", {"foo"=>"bar"}, {'action_dispatch.show_exceptions' => true,
'action_dispatch.parameter_filter' => [:foo]}
assert_response 500
assert_match("&quot;foo&quot;=&gt;&quot;[FILTERED]&quot;", body)
get "/", {}, {'action_dispatch.show_exceptions' => true}
assert_equal "text/html; charset=utf-8", response.headers["Content-Type"]
end
test "show registered original exception for wrapped exceptions when show_exceptions is false" do
test "show registered original exception for wrapped exceptions" do
@app = ProductionApp
get "/not_found_original_exception", {}, {'action_dispatch.show_exceptions' => true}
assert_response 404
assert_match(/404 error/, body)
end
test "show registered original exception for wrapped exceptions when show_exceptions is true" do
@app = DevelopmentApp
get "/not_found_original_exception", {}, {'action_dispatch.show_exceptions' => true}
assert_response 404
assert_match(/AbstractController::ActionNotFound/, body)
end
test "show the controller name in the diagnostics template when controller name is present" do
@app = DevelopmentApp
get("/runtime_error", {}, {
'action_dispatch.show_exceptions' => true,
'action_dispatch.request.parameters' => {
'action' => 'show',
'id' => 'unknown',
'controller' => 'featured_tile'
}
})
assert_response 500
assert_match(/RuntimeError\n in FeaturedTileController/, body)
end
test "sets the HTTP charset parameter" do
@app = DevelopmentApp
get "/", {}, {'action_dispatch.show_exceptions' => true}
assert_equal "text/html; charset=utf-8", response.headers["Content-Type"]
end
test 'uses logger from env' do
@app = ProductionApp
output = StringIO.new
get "/", {}, {'action_dispatch.show_exceptions' => true, 'action_dispatch.logger' => Logger.new(output)}
assert_match(/puke/, output.rewind && output.read)
end
test 'uses backtrace cleaner from env' do
@app = DevelopmentApp
cleaner = stub(:clean => ['passed backtrace cleaner'])
get "/", {}, {'action_dispatch.show_exceptions' => true, 'action_dispatch.backtrace_cleaner' => cleaner}
assert_match(/passed backtrace cleaner/, body)
end
end

View File

@@ -22,6 +22,13 @@ module ActiveRecord
config.app_middleware.insert_after "::ActionDispatch::Callbacks",
"ActiveRecord::ConnectionAdapters::ConnectionManagement"
config.action_dispatch.rescue_responses.merge!(
'ActiveRecord::RecordNotFound' => :not_found,
'ActiveRecord::StaleObjectError' => :conflict,
'ActiveRecord::RecordInvalid' => :unprocessable_entity,
'ActiveRecord::RecordNotSaved' => :unprocessable_entity
)
rake_tasks do
load "active_record/railties/databases.rake"
end

View File

@@ -1,13 +1,15 @@
## Rails 3.2.0 (unreleased) ##
* Display mounted engine's routes in `rake routes`. *Piotr Sarnacki*
* Add DebugExceptions middleware which contains features extracted from ShowExceptions middleware *José Valim*
* Display mounted engine's routes in `rake routes` *Piotr Sarnacki*
* Allow to change the loading order of railties with `config.railties_order=` *Piotr Sarnacki*
Example:
config.railties_order = [Blog::Engine, :main_app, :all]
* Scaffold returns 204 No Content for API requests without content. This makes scaffold work with jQuery out of the box. *José Valim*
* Scaffold returns 204 No Content for API requests without content. This makes scaffold work with jQuery out of the box *José Valim*
* Update Rails::Rack::Logger middleware to apply any tags set in config.log_tags to the newly ActiveSupport::TaggedLogging Rails.logger. This makes it easy to tag log lines with debug information like subdomain and request id -- both very helpful in debugging multi-user production applications *DHH*

View File

@@ -195,10 +195,13 @@ module Rails
middleware.use ::ActionDispatch::RequestId
middleware.use ::Rails::Rack::Logger, config.log_tags # must come after Rack::MethodOverride to properly log overridden methods
middleware.use ::ActionDispatch::ShowExceptions
middleware.use ::ActionDispatch::DebugExceptions
middleware.use ::ActionDispatch::RemoteIp, config.action_dispatch.ip_spoofing_check, config.action_dispatch.trusted_proxies
if config.action_dispatch.x_sendfile_header.present?
middleware.use ::Rack::Sendfile, config.action_dispatch.x_sendfile_header
end
middleware.use ::ActionDispatch::Reloader unless config.cache_classes
middleware.use ::ActionDispatch::Callbacks
middleware.use ::ActionDispatch::Cookies

View File

@@ -3,7 +3,7 @@ require 'isolation/abstract_unit'
require 'rack/test'
module ApplicationTests
class ShowExceptionsTest < Test::Unit::TestCase
class MiddlewareExceptionsTest < Test::Unit::TestCase
include ActiveSupport::Testing::Isolation
include Rack::Test::Methods
@@ -16,6 +16,35 @@ module ApplicationTests
teardown_app
end
test "show exceptions middleware filter backtrace before logging" do
my_middleware = Struct.new(:app) do
def call(env)
raise "Failure"
end
end
app.config.middleware.use my_middleware
stringio = StringIO.new
Rails.logger = Logger.new(stringio)
get "/"
assert_no_match(/action_dispatch/, stringio.string)
end
test "renders active record exceptions as 404" do
my_middleware = Struct.new(:app) do
def call(env)
raise ActiveRecord::RecordNotFound
end
end
app.config.middleware.use my_middleware
get "/"
assert_equal 404, last_response.status
end
test "unspecified route when set action_dispatch.show_exceptions to false" do
app.config.action_dispatch.show_exceptions = false

View File

@@ -33,6 +33,7 @@ module ApplicationTests
"ActionDispatch::RequestId",
"Rails::Rack::Logger", # must come after Rack::MethodOverride to properly log overridden methods
"ActionDispatch::ShowExceptions",
"ActionDispatch::DebugExceptions",
"ActionDispatch::RemoteIp",
"Rack::Sendfile",
"ActionDispatch::Reloader",
@@ -104,10 +105,11 @@ module ApplicationTests
assert !middleware.include?("ActionDispatch::Static")
end
test "includes show exceptions even action_dispatch.show_exceptions is disabled" do
test "includes exceptions middlewares even if action_dispatch.show_exceptions is disabled" do
add_to_config "config.action_dispatch.show_exceptions = false"
boot!
assert middleware.include?("ActionDispatch::ShowExceptions")
assert middleware.include?("ActionDispatch::DebugExceptions")
end
test "removes ActionDispatch::Reloader if cache_classes is true" do
@@ -191,26 +193,6 @@ module ApplicationTests
assert_equal nil, last_response.headers["Etag"]
end
# Show exceptions middleware
test "show exceptions middleware filter backtrace before logging" do
my_middleware = Struct.new(:app) do
def call(env)
raise "Failure"
end
end
make_basic_app do |app|
app.config.middleware.use my_middleware
end
stringio = StringIO.new
Rails.logger = Logger.new(stringio)
env = Rack::MockRequest.env_for("/")
Rails.application.call(env)
assert_no_match(/action_dispatch/, stringio.string)
end
private
def boot!