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.