Bạn đã bao giờ có một lỗi mà bạn không thể dễ dàng tái tạo? Điều này dường như chỉ xảy ra khi mọi người đã sử dụng ứng dụng của bạn một thời gian. Và thông báo lỗi và backtrace đáng ngạc nhiên là vô ích.
Những lúc như thế này sẽ thực sự hữu ích nếu bạn có thể chụp nhanh trạng thái của ứng dụng ngay trước khi ngoại lệ xảy ra. Ví dụ:nếu bạn có thể có một danh sách tất cả các biến cục bộ và giá trị của chúng. Chà, bạn có thể - và nó thậm chí không khó như vậy!
Trong bài đăng này, tôi sẽ chỉ cho bạn cách chụp người dân địa phương tại thời điểm ngoại lệ. Nhưng trước tiên, tôi cần phải cảnh báo bạn. Không nên sử dụng kỹ thuật nào trong số này trong sản xuất. Bạn có thể sử dụng chúng trong dàn dựng, chuẩn bị, phát triển, v.v. Chỉ cần không sản xuất. Các viên ngọc mà chúng tôi sẽ sử dụng dựa trên một số phép thuật xem xét nội tâm khá nặng, mà tốt nhất là sẽ làm chậm ứng dụng của bạn. Tệ nhất là ... ai biết được?
Giới thiệu bind_of_caller
Đá quý binding_of_caller cho phép bạn truy cập các ràng buộc cho bất kỳ cấp nào của ngăn xếp hiện tại. Sooo ..... chính xác thì điều đó có nghĩa là gì?
"Ngăn xếp" chỉ đơn giản là một danh sách các phương pháp hiện đang được "xử lý". Bạn có thể sử dụng caller
phương pháp để kiểm tra ngăn xếp hiện tại. Đây là một ví dụ đơn giản:
def a
puts caller.inspect # ["caller.rb:20:in `<main>'"]
b()
end
def b
puts caller.inspect # ["caller.rb:4:in `a'", "caller.rb:20:in `<main>'"]
c()
end
def c
puts caller.inspect # ["caller.rb:11:in `b'", "caller.rb:4:in `a'", "caller.rb:20:in `<main>'"]
end
a()
Một ràng buộc là một ảnh chụp nhanh của ngữ cảnh thực thi hiện tại. Trong ví dụ dưới đây, tôi nắm bắt liên kết của một phương thức, sau đó sử dụng nó để truy cập các biến cục bộ của phương thức.
def get_binding
a = "marco"
b = "polo"
return binding
end
my_binding = get_binding
puts my_binding.local_variable_get(:a) # "marco"
puts my_binding.local_variable_get(:b) # "polo"
Đá quý bind_of_caller cung cấp cho bạn quyền truy cập vào ràng buộc cho bất kỳ cấp nào của ngăn xếp thực thi hiện tại. Ví dụ:tôi có thể sử dụng nó để cho phép c
phương thức truy cập vào a
các biến cục bộ của phương thức.
require "rubygems"
require "binding_of_caller"
def a
fruit = "orange"
b()
end
def b
fruit = "apple"
c()
end
def c
fruit = "pear"
# Get the binding "two levels up" and ask it for its local variable "fruit"
puts binding.of_caller(2).local_variable_get(:fruit)
end
a() # prints "orange"
Tại thời điểm này, có lẽ bạn đang cảm thấy hai cảm xúc trái ngược nhau. Sự phấn khích, bởi vì điều này thực sự là mát mẻ. Và sự phản đối, bởi vì điều này có thể biến thành một mớ hỗn độn xấu xí của những phụ thuộc nhanh hơn bạn có thể nói DHH.
Ghi nhật ký người dân địa phương tại thời điểm ngoại lệ
Bây giờ chúng ta đã làm chủ được ràng buộc_of_caller, việc ghi nhật ký tất cả các biến cục bộ tại thời điểm ngoại lệ là một phần nhỏ. Trong ví dụ dưới đây, tôi ghi đè phương pháp tăng. Phương thức tăng mới của tôi tìm nạp ràng buộc của bất kỳ phương thức nào được gọi là nó. Sau đó, nó lặp lại qua tất cả người dân địa phương và in chúng ra.
require "rubygems"
require "binding_of_caller"
module LogLocalsOnRaise
def raise(*args)
b = binding.of_caller(1)
b.eval("local_variables").each do |k|
puts "Local variable #{ k }: #{ b.local_variable_get(k) }"
end
super
end
end
class Object
include LogLocalsOnRaise
end
def buggy
s = "hello world"
raise RuntimeError
end
buggy()
Đây là những gì nó trông giống như trong hoạt động:
Bài tập:Các biến phiên bản nhật ký
Tôi sẽ để nó như một bài tập cho bạn để ghi lại các biến cá thể cùng với các local. Đây là một gợi ý:bạn có thể sử dụng my_binding.eval("instance_variables")
và my_binding.instance_variable_get
theo đúng cách mà bạn sẽ sử dụng my_binding.eval("local_variables")
và my_binding.instance_variable_get
.
Cách dễ dàng
Đây là một thủ thuật khá hay. Nhưng việc thu thập các tệp nhật ký không phải là cách thuận tiện nhất để sửa lỗi, đặc biệt nếu ứng dụng của bạn đang ở trạng thái chạy thử và bạn có nhiều người sử dụng nó. Ngoài ra, nó chỉ là nhiều mã hơn mà bạn phải duy trì.
Nếu bạn tình cờ sử dụng Honeybadger để theo dõi lỗi ứng dụng của mình, chúng tôi có thể tự động nắm bắt người dân địa phương. Tất cả những gì bạn phải làm là thêm gem bind_of_caller vào Gemfile của bạn:
# Gemfile
group :development, :staging do
# Including this gem enables local variable capture via Honeybadger
gem "binding_of_caller"
...
end
Giờ đây, bất cứ khi nào có ngoại lệ xảy ra, bạn sẽ nhận được báo cáo về tất cả người dân địa phương cùng với backtrace, params, v.v.