Đôi khi có thể thực sự khó hiểu những gì đang xảy ra với các ngoại lệ - đặc biệt là trong các ứng dụng lớn hơn. Hãy tưởng tượng rằng bạn đang làm việc trên một số mã bên trong một dự án hiện có. Bạn nêu ra một ngoại lệ, sau đó một điều gì đó kỳ lạ sẽ xảy ra. Có thể ngoại lệ bị nuốt. Có thể một biến môi trường bị thay đổi. Có thể ngoại lệ của bạn được bao bọc trong một ngoại lệ khác.
Tôi sẽ chỉ cho bạn một cách đơn giản để sử dụng TracePoints để biết thêm một chút thông tin về các ngoại lệ trong ứng dụng của bạn - ngay cả khi những ngoại lệ đó bị nuốt.
Một Ví dụ Thuận tiện
Ranh giới giữa bộ điều khiển và khung nhìn trong Rails là một điểm mà các ngoại lệ dường như thách thức logic. Thật dễ dàng để xem cho chính mình. Chỉ cần nêu ra một ngoại lệ trong một chế độ xem và cố gắng giải cứu nó trong một bộ điều khiển. Bạn sẽ thấy rằng bạn KHÔNG THỂ giải cứu lỗi mẫu từ bộ điều khiển!
# pages_controller.rb
def index
render
rescue
# this will never run
logger.debug "someone raised the roof"
end
# index.haml
- raise "the roof"
WTF!?! Tôi nghĩ rằng tôi đã giải cứu điều này!
Rõ ràng là có điều gì đó khó khăn đang xảy ra. Hãy xem liệu chúng ta có thể tìm ra nó là gì không.
Ghi nhật ký Tất cả các Ngoại lệ Với TracePoint
TracePoints là một công cụ xem xét nội quan thực sự mạnh mẽ đã có từ Ruby 2.0. Chúng cho phép bạn xác định lệnh gọi lại cho nhiều sự kiện thời gian chạy. Ví dụ, bạn có thể được thông báo bất cứ khi nào một lớp được định nghĩa, bất cứ khi nào một phương thức được gọi hoặc bất cứ khi nào một ngoại lệ được đưa ra. Xem tài liệu TracePoint để biết thêm các sự kiện khác.
Hãy bắt đầu bằng cách thêm một TracePoint được gọi bất cứ khi nào một ngoại lệ được đưa ra và ghi tóm tắt về nó vào nhật ký.
class PagesController < ApplicationController
def index
TracePoint.new(:raise) do |tp|
# tp.raised_exeption contains the actual exception object that was raised!
logger.debug "#{tp.raised_exception.object_id}: #{tp.raised_exception.class} #{tp.raised_exception.message} ".yellow + tp.raised_exception.backtrace[0].sub(Rails.root.to_s, "").blue
end.enable do
render
end
end
end
Nếu bạn tò mò về yellow
và blue
, tôi đang sử dụng đá quý màu. Nó thêm mã màu ANSI vào đầu ra.
Bây giờ khi tôi truy cập và làm mới trang của mình, nhật ký của tôi trông giống như ảnh chụp màn hình bên dưới. Một điều thú vị mà bạn có thể nhận thấy là có hai ngoại lệ riêng biệt và mỗi ngoại lệ được nâng lên hai lần. Số dài đó ở đầu mỗi dòng là id đối tượng của Exception. Đó là cách chúng tôi biết có hai đối tượng ngoại lệ, không phải bốn.
Nhật ký này hiển thị mọi hoạt động sử dụng raise
trong quá trình kết xuất
Phương pháp nào Gây ra Tăng?
Có một danh sách các sự kiện "tăng" là khá hữu ích. Nhưng sẽ tốt hơn nữa nếu chúng ta có một số ý tưởng về phương pháp nào gây ra mỗi lần tăng. Một lần nữa, TracePoint lại giải cứu.
TracePoint cho phép chúng ta thêm một trình xử lý được gọi bất cứ khi nào một phương thức trả về. Nó cũng đơn giản để sử dụng như sự kiện "tăng". Trong ví dụ dưới đây, chúng tôi ghi nhật ký mọi phương thức trả về:
TracePoint.trace(:return) do |tp|
logger.debug [tp.method_id, tp.lineno, tp.path.sub(Rails.root.to_s, "")].join(" : ").green
end
Tuy nhiên, có một vấn đề. Nếu bạn thêm mã này vào ứng dụng rails của mình, bạn sẽ thấy rằng ứng dụng của bạn ngừng phản hồi các yêu cầu. Có rất nhiều lệnh gọi phương thức trong yêu cầu Rails đơn giản nhất khiến máy chủ hết thời gian chờ trước khi có thể ghi tất cả chúng vào nhật ký.
Vì chúng tôi thực sự chỉ quan tâm đến các lệnh gọi phương thức gây ra ngoại lệ, hãy sửa đổi mã của chúng tôi để xuất ra hai sự kiện "trả về" đầu tiên xảy ra sau mỗi ngoại lệ.
class PagesController < ApplicationController
def index
counter = 0
return_trace = TracePoint.trace(:return) do |tp|
logger.debug "\t" + [tp.method_id, tp.lineno, tp.path.sub(Rails.root.to_s, "")].join(" : ").green
if (counter += 1) > 3
return_trace.disable
counter = 0
end
end
return_trace.disable # disable the tracepoint by default
TracePoint.new(:raise) do |tp|
logger.debug "#{tp.raised_exception.object_id}: #{tp.raised_exception.class} #{tp.raised_exception.message} ".yellow + tp.raised_exception.backtrace[0].sub(Rails.root.to_s, "").blue
# The "raise" enables the "return" tracepoint
return_trace.enable
end.enable do
render
end
end
end
Khi tôi làm mới trình duyệt của mình, tôi thấy các dòng sau đã được thêm vào nhật ký:
Mỗi sự kiện "tăng" được hiển thị phía trên phương thức gây ra sự kiện đó
Bởi vì chúng tôi chỉ bật TracePoint "trả về" khi một ngoại lệ được đưa ra, sự kiện "trả về" đầu tiên sẽ đến từ phương thức đã đưa ra ngoại lệ.
Chúng tôi có thể sử dụng thông tin này để giải quyết bí ẩn của chúng tôi. RuntimeError
ban đầu của chúng tôi đang được chuyển đổi thành ActionView::Template::Error
bởi handle_render_error
phương thức trên dòng 310 của template.rb.
Điều thú vị về kỹ thuật này là nó không liên quan gì đến Rails. Bạn có thể sử dụng nó bất cứ lúc nào bạn cần hiểu chi tiết hơn những trường hợp ngoại lệ nào đang được nêu ra và bị che đậy.