คุณเคยมีข้อผิดพลาดที่คุณไม่สามารถทำซ้ำได้อย่างง่ายดายหรือไม่? ดูเหมือนว่าจะเกิดขึ้นเมื่อมีคนใช้แอปของคุณมาระยะหนึ่งแล้วเท่านั้น และข้อความแสดงข้อผิดพลาดและการติดตามย้อนกลับก็ไม่ช่วยอะไรอย่างน่าประหลาดใจ
ช่วงเวลาเช่นนี้จะมีประโยชน์มากหากคุณถ่ายภาพสแนปชอตของสถานะของแอปก่อนเกิดข้อยกเว้น ตัวอย่างเช่น หากคุณมีรายการตัวแปรท้องถิ่นทั้งหมดและค่าของตัวแปรเหล่านั้น คุณสามารถทำได้ - และมันก็ไม่ยากเลย!
ในโพสต์นี้ ฉันจะแสดงวิธีจับภาพคนในท้องถิ่นในเวลาที่มีข้อยกเว้น แต่ก่อนอื่นฉันต้องเตือนคุณ ไม่ควรใช้เทคนิคเหล่านี้ในการผลิต คุณสามารถใช้พวกมันในการแสดงละคร พรีผลิตภัณฑ์ การพัฒนา ฯลฯ ไม่ใช่แค่การผลิต อัญมณีที่เราจะใช้นั้นขึ้นอยู่กับเวทย์มนตร์วิปัสสนาที่ค่อนข้างหนักซึ่งอย่างดีที่สุดจะทำให้แอปของคุณช้าลง แย่ที่สุด...ใครจะรู้?
แนะนำการผูก_of_caller
Binding_of_caller gem ให้คุณเข้าถึงการผูกสำหรับระดับใดๆ ของสแต็คปัจจุบัน ซู่.....หมายความว่าไงเนี่ย
"สแต็ก" เป็นเพียงรายการเมธอดที่ "อยู่ในระหว่างดำเนินการ" ในปัจจุบัน คุณสามารถใช้ caller
วิธีการตรวจสอบสแต็กปัจจุบัน นี่เป็นตัวอย่างง่ายๆ:
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()
การเชื่อมโยงเป็นสแนปชอตของบริบทการดำเนินการปัจจุบัน ในตัวอย่างด้านล่าง ฉันจับการเชื่อมโยงของเมธอด จากนั้นใช้เพื่อเข้าถึงตัวแปรในเครื่องของเมธอด
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"
Binding_of_caller gem ให้คุณเข้าถึงการผูกสำหรับระดับใด ๆ ของสแต็คการดำเนินการปัจจุบัน ตัวอย่างเช่น ฉันสามารถใช้เพื่ออนุญาต c
วิธีการเข้าถึง a
ตัวแปรท้องถิ่นของเมธอด
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"
ณ จุดนี้ คุณอาจรู้สึกสองอารมณ์ที่ขัดแย้งกัน ตื่นเต้นเพราะมันเจ๋งจริงๆ และความรังเกียจ เพราะสิ่งนี้อาจทำให้การพึ่งพาอาศัยกันที่ยุ่งเหยิงแย่ลงได้เร็วกว่าที่คุณพูด DHH
การบันทึกในพื้นที่ในเวลาที่มีการยกเว้น
ตอนนี้เราได้เข้าใจถึงการผูกมัด_of_caller แล้ว การบันทึกตัวแปรโลคัลทั้งหมดในเวลาที่มีข้อยกเว้นนั้นไม่ใช่เรื่องยาก ในตัวอย่างด้านล่าง ฉันกำลังแทนที่วิธีการเพิ่ม วิธีการเพิ่มใหม่ของฉันดึงการเชื่อมโยงของวิธีการใดก็ตามที่เรียกว่ามัน จากนั้นจะทำซ้ำผ่านคนในท้องถิ่นทั้งหมดและพิมพ์ออกมา
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()
นี่คือลักษณะการใช้งานจริง:
การออกกำลังกาย:บันทึกตัวแปรอินสแตนซ์
ฉันจะปล่อยให้มันเป็นแบบฝึกหัดสำหรับคุณในการบันทึกตัวแปรอินสแตนซ์ควบคู่ไปกับคนในพื้นที่ คำแนะนำ:คุณสามารถใช้ my_binding.eval("instance_variables")
และ my_binding.instance_variable_get
ในลักษณะเดียวกับที่คุณใช้ my_binding.eval("local_variables")
และ my_binding.instance_variable_get
.
วิธีที่ง่าย
นี่เป็นเคล็ดลับที่เจ๋งมาก แต่การวนรอบไฟล์บันทึกไม่ใช่วิธีที่สะดวกที่สุดในการแก้ไขจุดบกพร่อง โดยเฉพาะอย่างยิ่งหากแอปของคุณอยู่ในขั้นตอนการทำงานและคุณมีผู้ใช้หลายคน นอกจากนี้ยังเป็นเพียงแค่โค้ดเพิ่มเติมที่คุณต้องรักษาไว้
หากคุณบังเอิญใช้ Honeybadger เพื่อตรวจสอบแอปของคุณเพื่อหาข้อผิดพลาด เราสามารถจับภาพคนในพื้นที่ได้โดยอัตโนมัติ สิ่งที่คุณต้องทำคือเพิ่ม binding_of_caller gem ลงใน Gemfile ของคุณ:
# Gemfile
group :development, :staging do
# Including this gem enables local variable capture via Honeybadger
gem "binding_of_caller"
...
end
ตอนนี้ เมื่อใดก็ตามที่เกิดข้อยกเว้น คุณจะได้รับรายงานของคนในพื้นที่ทั้งหมดพร้อมกับ backtrace, params ฯลฯ