เมื่อ error เกิดใน application Ruby raise exception และพิมพ์ stack trace ไป log บทความนี้เราจะดูวิธีอ่าน stack trace และใช้ locate source ของ exception ใน application
The Call Stack
ทุกครั้งที่คุณเรียก method Ruby วาง stack frame บน call stack (หรือ "runtime stack" แต่บ่อยครั้งเรียกว่า "stack") Stack frame เป็น memory allocation ที่เก็บ arguments ของ method พื้นที่สำหรับ internal variables และ return address ของ caller
# divide.rb
def divide(a, b)
"Dividing #{a} by #{b} gives #{a / b}."
end
puts divide(8, 4)
เมื่อ method หนึ่ง (divide) เรียก method อื่น (Fixnum#/ หรือ / สำหรับสั้น) ตัวหลังจะวางบน top of stack เพราะต้อง execute ก่อนตัวแรกจะเสร็จ เมื่อเรียก divide(8, 4) ในตัวอย่างนี้ Ruby จะ execute actions ต่อไปตามลำดับ:
- Call
8./(4) - Collect division's result (
2) ใส่ใน string - Collect string (
"Dividing 8 by 4 gives 2") พิมพ์ไป console ด้วยputs
Stack Traces
Stack trace (มักชื่อ "backtrace" ใน Ruby แต่เรียกได้ว่า "stack backtrace" และ "stack traceback") เป็น human-readable representation ของ stack ณ moment เฉพาะขณะรันโปรแกรม Stack trace สำหรับ call ไป / method จะเป็นแบบนี้ แสดงว่ามันถูกเรียกใน divide method line 2 ซึ่งถูกเรียกใน <main> method line 5
divide.rb:2:in `/'
divide.rb:2:in `divide'
divide.rb:5:in `'
ในตัวอย่างข้างต้น เรียก divide method ด้วย 0 เป็น argument หนึ่ง จะได้ ZeroDivisionError exception
# divide_by_zero.rb
def divide(a, b)
"Dividing #{a} by #{b} gives #{a / b}."
end
puts divide(8, 0)
เมื่อเกิด Ruby จะพิมพ์ exception พร้อม stack trace ไป console Stack trace แสดง stack ใน human-readable format พร้อม location ของแต่ละ method ในโค้ด ช่วย pinpoint ว่า exception มาจากไหน
$ ruby divide_by_zero.rb
divide_by_zero.rb:2:in `/': divided by 0 (ZeroDivisionError)
from divide_by_zero.rb:2:in `divide'
from divide_by_zero.rb:5:in `'
- บรรทัดแรกใน stack trace เราเห็น
ZeroDivisionErrorraised พร้อม "divided by 0" เป็น message นอกจาก exception เอง เราเห็นเกิดที่divide_by_zero.rb:2:in `/'หมายถึง error raised จากบรรทัดที่สองของ example file จาก method ชื่อ/(คือFixnum#/เพราะ argument แรกคือ Fixnum8) - บรรทัดที่สองของ stack trace แสดงว่า
/method ถูกเรียกจากไหน ในกรณีนี้จากdividemethod บรรทัดที่ 2 - บรรทัดสุดท้ายแสดงว่า
divideถูกเรียกจาก<main>ซึ่งหมายถึง initial context ของ Ruby application โดยทั่วไปหมายถึงเรียกจากนอก "real" method ใดๆ
NOTE: ใน Ruby 2.5 logger พิมพ์ stack trace ใน reverse เพื่อให้ fit ใน terminal window บรรทัดสุดท้ายแสดง exception ก่อนหน้าบรรทัดที่ exception เกิด บรรทัดเหนือเป็น route ขึ้น stack
Understanding Stack Traces
Stack trace ให้ dump ของ current state ของ call stack ทุกครั้งที่ exception raised และช่วยหาว่าผิดที่ไหน
แม้บรรทัดแรกของ stacktrace แสดงบรรทัดที่ exception occurred แต่ไม่ได้แสดง source ของ error เสมอไป ในตัวอย่างข้างต้น โปรแกรม execute ถูกต้อง แต่ handle data ที่ส่งให้ divide method ไม่ได้ Walking up stack trace นำไปที่ที่มันถูกเรียก ซึ่ง คือ source ของปัญหา
เหมือนเดิม เรายินดีรู้ความคิดเห็นว่าชอบบทความนี้หรือไม่ มีคำถามอะไร และอยากอ่านเรื่องไหนต่อไป บอกเราที่ @AppSignal ได้เลย