Jak sprawić, by ruby ​​wydrukował pełny ślad zamiast obciętego?


170

Kiedy dostaję wyjątki, często dzieje się to z głębi stosu wywołań. Kiedy tak się dzieje, częściej niż nie, faktyczny wiersz kodu jest dla mnie ukryty:

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

To obcięcie "... 8 poziomów ..." sprawia mi wiele kłopotów. Nie mam większego sukcesu w wyszukiwaniu tego w Google: Jak powiedzieć ruby, że chcę, aby zrzuty zawierały pełny stos?


2
Czy zamiast tego można to zrobić z wiersza poleceń?
Andrew Grimm

Odpowiedzi:


241

W wyjątku # backtrace znajduje się cały stos:

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

(Zainspirowany blogiem Ruby Inside Petera Coopera )


15
Podkreśliłbym wyjątek, przynajmniej ze względu na kompletność przykładów.
reto

13
Aby przebić, wystarczy powiedzieć raise. Nie ma potrzeby jawnego określania wykonania, które chcesz podnieść.
Timo

Fajnie, zawsze myślałem, że musisz przejść przez poprzedni wyjątek, aby podbić. Nie zdawałem sobie sprawy, że domyślnie jest to ostatni uratowany wyjątek.
unflores

Co się stanie, jeśli Twój kod nie zgłosi wyjątku, a chcesz tylko zobaczyć ślad stosu, gdzie poszedł?
Alex Levine

170

Możesz to również zrobić, jeśli potrzebujesz prostego, jednowierszowego:

puts caller

2
Niesamowita sztuczka. Wielkie dzięki. Nie wiedziałem, że raisemożna tego użyć bez argumentów. Nie wiedziałem też, że rescuebędzie poprawnie traktowany jako jednolinijkowy. Całkowicie ignoruję też takie globalne vars $!.
Dmytrii Nagirniak

11
nie trzeba podnosić / ratować, możesz po prostu użyć dzwoniącego Kernel #, na przykład:puts "this line was reached by #{caller.join("\n")}"
Stephen C

Ach, dowiedziałem się o tym wkrótce po opublikowaniu tej odpowiedzi i zapomniałem zaktualizować. Dzięki
anonimowy tchórz

Używam y callerdo drukowania danych wyjściowych, takich jak ślad stosu Java.
so_mv

caller(0,2)zwróci dwa ostatnie wpisy w stosie śledzenia. Przyjemne do drukowania skróconych śladów stosu.
Magne

100

To daje opis błędu i ładny, czysty, wcięty ślad stosu:

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

49

IRB ma ustawienie tej okropnej „funkcji”, które możesz dostosować.

Utwórz plik o nazwie, ~/.irbrcktóry zawiera następujący wiersz:

IRB.conf[:BACK_TRACE_LIMIT] = 100

Pozwoli to zobaczyć irbco najmniej 100 klatek stosu . Nie udało mi się znaleźć równoważnego ustawienia dla nieinteraktywnego środowiska wykonawczego.

Szczegółowe informacje na temat dostosowywania IRB można znaleźć w książce Kilof .


3
Powinna to być akceptowana odpowiedź, ponieważ dotyczy pytania, jak pokazać więcej śladu wstecznego zamiast „... X poziomów ...”.
nickh

13

Jedna wkładka do stosu wywołań:

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

Jedna linijka dla stosu wywołań bez wszystkich klejnotów:

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

Jedna linijka dla stosu wywołań bez wszystkich klejnotów i względem bieżącego katalogu

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

2
jednowierszowy to właściwie zła rzecz, gdy masz wiele instrukcji.
nurettin

3
@nurettin służy do szybkiego debugowania, więc utworzenie jednej linii ułatwia kopiowanie i wklejanie, głównie w interaktywnych powłokach
Dorian

@Dorian Przypominasz mi o pytaniu, które zadałem: „Dlaczego powłoki interaktywne są przydatne? (Z wyjątkiem skryptu powłoki)”.
Sapphire_Brick,

9

To naśladuje oficjalny ślad Rubiego, jeśli jest to dla Ciebie ważne.

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

Co zabawne, nie obsługuje poprawnie „nieobsługiwanego wyjątku”, zgłaszając go jako „RuntimeError”, ale lokalizacja jest poprawna.


Żałuję, że mam tylko jeden głos za twoją odpowiedzią. Dodam to wszędzie
Dbz

4

Otrzymywałem te błędy podczas próby załadowania mojego środowiska testowego (przez test rake lub autotest), a sugestie IRB nie pomogły. Skończyło się na tym, że opakowałem cały test / test_helper.rb w blok start / ratunek i to naprawiło problem.

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

0

[zbadaj ślady wsteczne wszystkich wątków, aby znaleźć winowajcę]
Nawet w pełni rozwinięty stos wywołań może nadal ukrywać przed tobą właściwą linię kodu, gdy używasz więcej niż jednego wątku!

Przykład: jeden wątek iteruje ruby ​​Hash, a inny próbuje go zmodyfikować. BUM! Wyjątek! Problem ze śladem stosu, który otrzymujesz, próbując zmodyfikować hash `` zajęty '', polega na tym, że pokazuje ci łańcuch funkcji aż do miejsca, w którym próbujesz zmodyfikować hash, ale NIE pokazuje, kto aktualnie iteruje go równolegle ( kto jest właścicielem)! Oto sposób, aby to sprawdzić, drukując ślad stosu dla WSZYSTKICH aktualnie uruchomionych wątków. Oto jak to robisz:

# 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

Powyższy fragment kodu jest przydatny nawet do celów edukacyjnych, ponieważ może pokazać (jak prześwietlenie), ile faktycznie masz wątków (w porównaniu z tym, ile myślisz, że masz - dość często te dwa to różne liczby;)


0

Możesz także użyć backtrace Ruby gem (jestem autorem):

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

4
Czy możesz przynajmniej wyjaśnić, dlaczego chcieliśmy użyć twojego klejnotu? Czy możesz pokazać przykładowe dane wyjściowe?
ioquatix
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.