Computer >> Hướng Dẫn Máy Tính >  >> Lập Trình >> Ruby

Ghi log biến cục bộ và biến instance khi ngoại lệ Ruby để debug nhanh hơn

Bạn từng gặp bug không dễ tái hiện không? Nó chỉ xảy ra khi mọi người dùng app một thời gian. Và thông báo lỗi cùng backtrace đâu vào đâu có ích.

Lúc này, thật tiện nếu bạn có thể chụp ảnh trạng thái app ngay trước khi ngoại lệ xảy ra. Ví dụ, danh sách tất cả biến cục bộ và giá trị của chúng. Vâng, bạn có thể - và nó không hề khó!

Trong bài này tôi sẽ chỉ cách capture locals tại thời điểm ngoại lệ. Nhưng trước tiên, tôi cần cảnh báo. Không có kỹ thuật nào ở đây nên dùng trong production. Bạn có thể dùng ở staging, preprod, development, v.v. Chỉ không dùng production. Các gem chúng ta sẽ dùng dựa vào siêu khả năng introspection nặng nề, tốt nhất chỉ làm chậm app. Tồi nhất... ai biết?

Giới thiệu binding_of_caller

Gem binding_of_caller cho phép truy cập binding cho bất kỳ cấp độ nào của stack hiện tại. Vậy... điều đó có nghĩa là gì?

"Stack" đơn giản là danh sách các phương thức hiện đang "đang thực thi". Bạn có thể dùng phương thức caller để kiểm tra stack hiện tại. 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 binding là ảnh chụp nhanh ngữ cảnh thực thi hiện tại. Trong ví dụ dưới, tôi capture binding của một phương thức, rồi dùng nó để truy cập 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" 

Gem binding_of_caller cho phép truy cập binding cho bất kỳ cấp độ nào của stack thực thi hiện tại. Ví dụ, tôi có thể dùng nó để cho phép phương thức c truy cập biến cục bộ của phương thức a.

require "rubygems" require "binding_of_caller" def a fruit = "orange" b() end def b fruit = "apple" c() end def c fruit = "pear" # Lấy binding "hai cấp lên" và hỏi nó về biến cục bộ "fruit" puts binding.of_caller(2).local_variable_get(:fruit) end a() # in ra "orange" 

Lúc này, bạn có thể cảm thấy hai cảm xúc xung đột. Hào hứng, vì đây RẤT COOL. Và chán nản, vì điều này có thể biến thành hỗn loạn phụ thuộc xấu xí nhanh hơn bạn nói được DHH.

Ghi log locals tại thời điểm ngoại lệ

Bây giờ chúng ta đã làm chủ binding_of_caller, ghi log tất cả biến cục bộ tại thời điểm ngoại lệ là chuyện nhỏ. Trong ví dụ dưới, tôi ghi đè phương thức raise. Phương thức raise mới của tôi fetch binding của phương thức gọi nó. Sau đó lặp qua tất cả locals và in 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à giao diện khi chạy:

Ghi log biến cục bộ và biến instance khi ngoại lệ Ruby để debug nhanh hơn

Bài tập: Ghi log instance variables

Tôi để lại bài tập cho bạn ghi log instance variables cùng với locals. Gợi ý: bạn có thể dùng my_binding.eval("instance_variables") và my_binding.instance_variable_get chính xác như cách bạn dùng my_binding.eval("local_variables") và my_binding.instance_variable_get.

Cách dễ dàng

Đây là thủ thuật khá cool. Nhưng grep trong file log không phải cách tiện nhất để fix bug, nhất là nếu app ở staging và có nhiều người dùng. Ngoài ra, chỉ là thêm code bạn phải maintain.

Nếu bạn dùng Honeybadger để monitor lỗi app, chúng tôi có thể capture locals tự động. Chỉ cần thêm gem binding_of_caller vào Gemfile:

# Gemfile group :development, :staging do # Including this gem enables local variable capture via Honeybadger gem "binding_of_caller" ... end 

Bây giờ, mỗi khi ngoại lệ xảy ra, bạn sẽ nhận báo cáo tất cả locals cùng backtrace, params, v.v.

Ghi log biến cục bộ và biến instance khi ngoại lệ Ruby để debug nhanh hơn