Một trong những điều thú vị khi làm việc với rails là khi có vấn đề gì xảy ra trong quá trình phát triển, bạn sẽ nhận được một trang chi tiết lỗi rất hay. Bạn nhận được một backtrace đẹp, với các phần liên quan đến ứng dụng của bạn được đánh dấu. Bạn có thể xem các thông số đã được đăng, cũng như kiểm tra các biến môi trường và phiên.
Hôm nay chúng ta sẽ xem xét cách hoạt động của các trang lỗi lạ mắt này.
Bẻ khóa Actionpack đang mở
Tệp mà chúng ta sẽ quan tâm nhất hôm nay là actionpack / lib / action_dispatch / middleware / debug_exceptions.rb. Nó thực hiện hầu hết các công việc nặng nhọc khi hiển thị các trang lỗi chế độ phát triển đó. Nếu bạn tò mò về nguồn gốc của màn hình lỗi chế độ sản xuất, hãy xem public_exceptions.rb.
Phần mềm Trung gian Rack
Nếu bạn không quen thuộc với phần mềm trung gian rack, khái niệm này rất đơn giản. Nó cho phép bạn chặn các yêu cầu HTTP trước khi chúng truy cập vào ứng dụng của bạn và chặn đầu ra của ứng dụng trước khi nó quay trở lại người dùng.
Đây là một phần mềm trung gian đơn giản không làm bất cứ điều gì thú vị.
class MyMiddleware
def initialize(app)
@app = app
end
def call(env)
@app.call(env)
end
end
Giải cứu tất cả các ngoại lệ thông qua phần mềm trung gian của rack
Bất kỳ ngoại lệ nào xảy ra trong ứng dụng của bạn đều xảy ra do gọi @ app.call (). Vì vậy, việc giải cứu tất cả các trường hợp ngoại lệ trong ứng dụng rack cũng đơn giản như việc thêm điều khoản cứu hộ vào phần mềm trung gian.
def call(env)
@app.call(env)
rescue StandardError => exception
# this is a method we have to provide to generate the exception page
render_exception(env, exception)
end
Mọi thứ trả về từ phương thức gọi sẽ được xử lý như thể nó là một trang web bình thường. Vì vậy, nội dung được trả về bởi render_exception sẽ thay thế phản hồi ban đầu.
Hiển thị ngoại lệ
Tôi đã trích dẫn phương thức render_exception từ ActionDispatch ::DebugExceptions. Như bạn có thể thấy, nó chỉ cần lấy dữ liệu có liên quan từ ngoại lệ và đưa dữ liệu đó vào một mẫu ERB.
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
Các mục đích sử dụng khác
Bạn có thể sử dụng thủ thuật phần mềm trung gian rack này để làm rất nhiều điều thú vị với các trường hợp ngoại lệ. Tại Honeybadger, chúng tôi sử dụng nó để chặn lỗi và ghi lại chúng vào API của chúng tôi. Đây là mã chúng tôi sử dụng để làm điều đó:
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