Bagaimana cara mendapatkan ruby ​​untuk mencetak backtrace penuh dan bukannya terpotong?


170

Ketika saya mendapatkan pengecualian, seringkali dari dalam tumpukan panggilan. Ketika ini terjadi, lebih sering daripada tidak, baris kode menyinggung yang sebenarnya disembunyikan dari saya:

tmp.rb:7:in `t': undefined method `bar' for nil:NilClass (NoMethodError)
        from tmp.rb:10:in `s'
        from tmp.rb:13:in `r'
        from tmp.rb:16:in `q'
        from tmp.rb:19:in `p'
        from tmp.rb:22:in `o'
        from tmp.rb:25:in `n'
        from tmp.rb:28:in `m'
        from tmp.rb:31:in `l'
         ... 8 levels...
        from tmp.rb:58:in `c'
        from tmp.rb:61:in `b'
        from tmp.rb:64:in `a'
        from tmp.rb:67

Pemotongan "... 8 tingkat ..." itu menyebabkan saya banyak masalah. Saya tidak memiliki banyak kesuksesan googling untuk yang satu ini: Bagaimana saya memberi tahu ruby ​​bahwa saya ingin dump menyertakan tumpukan penuh?


2
Apakah ada cara untuk melakukan ini dari baris perintah?
Andrew Grimm

Jawaban:


241

Pengecualian # backtrace memiliki seluruh tumpukan di dalamnya:

def do_division_by_zero; 5 / 0; end
begin
  do_division_by_zero
rescue => exception
  puts exception.backtrace
  raise # always reraise
end

(Terinspirasi oleh blog Ruby Inside Peter Cooper )


15
Saya akan mengembalikan pengecualian, setidaknya demi kelengkapan contoh.
reto

13
Untuk mengembalikan Anda hanya perlu mengatakan raise. Tidak perlu secara eksplisit menentukan eksekusi yang ingin Anda tingkatkan.
Timo

Bagus, saya selalu berpikir Anda harus melewati pengecualian sebelumnya untuk menaikkan. Saya tidak menyadari bahwa default untuk pengecualian terakhir diselamatkan.
unflores

Bagaimana jika kode Anda tidak melempar pengecualian, Anda hanya ingin melihat jejak tumpukan di mana ia pergi?
Alex Levine

170

Anda juga bisa melakukan ini jika Anda ingin satu kalimat:

puts caller

2
Trik yang luar biasa. Terima kasih banyak. Saya tidak tahu itu raisebisa digunakan tanpa argumen. Saya tidak tahu bahwa itu rescueakan diperlakukan dengan benar sebagai satu-liner. Saya juga benar-benar mengabaikan vars global seperti itu $!.
Dmytrii Nagirniak

11
tidak perlu menaikkan / menyelamatkan, Anda bisa menggunakan Kernel # caller, seperti:puts "this line was reached by #{caller.join("\n")}"
Stephen C

Ah, saya mengetahui hal itu segera setelah memposting jawaban ini dan lupa memperbaruinya. Terima kasih
pengecut anonim

Saya gunakan y calleruntuk mencetak output seperti jejak tumpukan Java.
so_mv

caller(0,2)akan mengembalikan dua entri terbaru di stacktrace. Bagus untuk menghasilkan stacktraces yang disingkat.
Magne

100

Ini menghasilkan deskripsi kesalahan dan bersih, stacktrace indentasi bagus:

begin               
 # Some exception throwing code
rescue => e
  puts "Error during processing: #{$!}"
  puts "Backtrace:\n\t#{e.backtrace.join("\n\t")}"
end

49

IRB memiliki pengaturan untuk "fitur" yang mengerikan ini, yang dapat Anda sesuaikan.

Buat file bernama ~/.irbrcyang termasuk baris berikut:

IRB.conf[:BACK_TRACE_LIMIT] = 100

Ini memungkinkan Anda untuk melihat irbsetidaknya 100 frame stack . Saya belum dapat menemukan pengaturan yang setara untuk runtime non-interaktif.

Informasi terperinci tentang kustomisasi IRB dapat ditemukan di buku Beliung .


3
Ini harus menjadi jawaban yang diterima, karena ini menjawab pertanyaan tentang bagaimana menunjukkan lebih banyak backtrace daripada "... level X ...".
Nickh

13

Satu liner untuk callstack:

begin; Whatever.you.want; rescue => e; puts e.message; puts; puts e.backtrace; end

Satu liner untuk callstack tanpa semua permata:

begin; Whatever.you.want; rescue => e; puts e.message; puts; puts e.backtrace.grep_v(/\/gems\//); end

Satu liner untuk callstack tanpa semua permata dan relatif terhadap direktori saat ini

begin; Whatever.you.want; rescue => e; puts e.message; puts; puts e.backtrace.grep_v(/\/gems\//).map { |l| l.gsub(`pwd`.strip + '/', '') }; end

2
one-liner sebenarnya adalah hal yang buruk ketika Anda memiliki banyak pernyataan.
nurettin

3
@nurettin ini untuk tujuan debugging cepat sehingga menjadikannya satu baris memudahkan untuk menyalinnya, sebagian besar dalam shell interaktif
Dorian

@Dorian Anda mengingatkan saya tentang pertanyaan yang saya miliki: "Mengapa shell interaktif berguna? (Tidak termasuk script Shell)".
Sapphire_Brick

9

Ini meniru jejak Ruby resmi, jika itu penting bagi Anda.

begin
  0/0  # or some other nonsense
rescue => e
  puts e.backtrace.join("\n\t")
       .sub("\n\t", ": #{e}#{e.class ? " (#{e.class})" : ''}\n\t")
end

Yang mengherankan, itu tidak menangani 'unhandled exception' dengan benar, melaporkannya sebagai 'RuntimeError', tetapi lokasinya benar.


Saya menyesal hanya memiliki satu suara untuk memberikan jawaban Anda. Saya menambahkan ini di mana
Dbz

4

Saya mendapatkan kesalahan ini ketika mencoba memuat lingkungan pengujian saya (melalui tes rake atau autotest) dan saran IRB tidak membantu. Saya akhirnya membungkus seluruh test / test_helper.rb saya di blok begin / rescue dan semuanya sudah beres.

begin
  class ActiveSupport::TestCase
    #awesome stuff
  end
rescue => e
  puts e.backtrace
end

0

[periksa semua utas jejak untuk menemukan penyebabnya]
Bahkan tumpukan panggilan yang diperluas sepenuhnya masih dapat menyembunyikan baris kode yang menyinggung dari Anda saat Anda menggunakan lebih dari satu utas!

Contoh: Satu utas adalah iterating ruby ​​Hash, utas lainnya sedang mencoba memodifikasinya. LEDAKAN! Pengecualian! Dan masalah dengan jejak tumpukan yang Anda dapatkan ketika mencoba untuk memodifikasi hash 'sibuk' adalah bahwa ia menunjukkan Anda rantai fungsi ke tempat di mana Anda mencoba untuk memodifikasi hash, tetapi itu TIDAK menunjukkan siapa yang saat ini mengulanginya secara paralel ( siapa yang memilikinya)! Inilah cara untuk mengetahuinya dengan mencetak jejak tumpukan untuk SEMUA utas yang saat ini berjalan. Begini cara Anda melakukan ini:

# This solution was found in comment by @thedarkone on https://github.com/rails/rails/issues/24627
rescue Object => boom

    thread_count = 0
    Thread.list.each do |t|
      thread_count += 1
      err_msg += "--- thread #{thread_count} of total #{Thread.list.size} #{t.object_id} backtrace begin \n"
      # Lets see if we are able to pin down the culprit
      # by collecting backtrace for all existing threads:
      err_msg += t.backtrace.join("\n")
      err_msg += "\n---thread #{thread_count} of total #{Thread.list.size} #{t.object_id} backtrace end \n"
    end

    # and just print it somewhere you like:
    $stderr.puts(err_msg)

    raise # always reraise
end

Cuplikan kode di atas berguna bahkan hanya untuk tujuan pendidikan karena dapat menunjukkan kepada Anda (seperti x-ray) berapa banyak utas yang Anda miliki (versus berapa banyak yang Anda pikir Anda miliki - cukup sering keduanya adalah angka yang berbeda;)


0

Anda juga dapat menggunakan backtrace Ruby gem (saya penulis):

require 'backtrace'
begin
  # do something dangerous
rescue StandardError => e
  puts Backtrace.new(e)
end

4
Setidaknya bisakah Anda menjelaskan mengapa kami ingin menggunakan permata Anda? Bisakah Anda menunjukkan beberapa contoh output?
ioquatix
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.