Điều hay của Rails: khi lỗi ở development, nhận error detail page rất đẹp. Backtrace đẹp, phần liên quan app highlight. Xem params posted, inspect env và session variable.
Hôm nay xem cách fancy error page này hoạt động.

Mở Actionpack
File chủ yếu quan tâm: `actionpack/lib/action_dispatch/middleware/debug_exceptions.rb`. Nó làm heavy lifting cho development mode error page. Tò mò production error screen, xem `public_exceptions.rb`.
Rack Middleware
Nếu không quen Rack middleware, concept đơn giản: cho intercept HTTP request trước khi đến app, và intercept output app trước khi trả user.
Middleware đơn giản không làm gì thú vị:
class MyMiddleware
def initialize(app)
@app = app
end
def call(env)
@app.call(env)
end
end
Rescue tất cả exception qua Rack Middleware
Exception trong app xảy ra do gọi `@app.call()`. Vì vậy rescue all exception trong Rack app đơn giản: thêm rescue clause vào middleware.
def call(env)
@app.call(env)
rescue StandardError => exception
# method này generate exception page
render_exception(env, exception)
end
Bất kỳ gì return từ call method được treat như normal web page. Nội dung từ `render_exception` replace response gốc.
Render exception
Trích method `render_exception` từ `ActionDispatch::DebugExceptions`. Nhìn thấy nó pull data từ exception feed vào ERB template.
def render_exception(env, exception)
wrapper = ExceptionWrapper.new(env, exception)
log_error(env, wrapper) if env['action_dispatch.show_detailed_exceptions']
request = Request.new(env)
template = ActionView::Base.new([RESCUES_TEMPLATE_PATH],
request: request,
exception: wrapper.exception,
application_trace: wrapper.application_trace,
framework_trace: wrapper.framework_trace,
full_trace: wrapper.full_trace,
routes_inspector: routes_inspector(exception),
source_extract: wrapper.source_extract,
line_number: wrapper.line_number,
file: wrapper.file
)
file = "rescues/#{wrapper.rescue_template}"
if request.xhr?
body = template.render(template: file, layout: false, formats: [:text])
format = "text/plain"
else
body = template.render(template: file, layout: 'rescues/layout')
format = "text/html"
end
render(wrapper.status_code, body, format)
else
raise exception
end
end
def render(status, body, format)
[status, {'Content-Type' => "#{format}; charset=#{Response.default_charset}",
'Content-Length' => body.bytesize.to_s}, [body]]
end
Ứng dụng khác
Dùng Rack middleware trick này làm nhiều điều thú vị với exception. Tại Honeybadger, dùng intercept error record lên API. Code dùng:
def call(env)
config.with_request(::Rack::Request.new(env)) do
begin
env['honeybadger.config'] = config
response = @app.call(env)
rescue Exception => raised
env['honeybadger.error_id'] = notify_honeybadger(raised, env)
raise
end
framework_exception = framework_exception(env)
if framework_exception
env['honeybadger.error_id'] = notify_honeybadger(framework_exception, env)
end
response
end
ensure
Honeybadger.context.clear!
end