Wie kann ich Ruby dazu bringen, eine vollständige Rückverfolgung anstelle einer abgeschnittenen zu drucken?

170

Wenn ich Ausnahmen bekomme, kommt es oft aus der Tiefe des Aufrufstapels. Wenn dies passiert, ist mir die eigentliche fehlerhafte Codezeile meistens verborgen:

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

Diese Kürzung von "... 8 Stufen ..." bereitet mir große Probleme. Ich habe nicht viel Erfolg beim Googeln: Wie kann ich Ruby sagen, dass Dumps den gesamten Stapel enthalten sollen?

Sniggerfardimungus
quelle
2
Gibt es eine Möglichkeit, dies stattdessen über die Befehlszeile zu tun?
Andrew Grimm

Antworten:

241

Ausnahme # backtrace enthält den gesamten Stapel:

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

(Inspiriert von Peter Coopers Ruby Inside Blog)

Gareth
quelle
15
Ich würde die Ausnahme zumindest im Interesse der Vollständigkeit der Beispiele wiederholen.
Reto
13
Um zu erhöhen, müssen Sie nur sagen raise. Sie müssen die Ausführung, die Sie auslösen möchten, nicht explizit angeben.
Timo
Schön, ich dachte immer, du musst die vorherige Ausnahme bestehen, um zu erhöhen. Ich habe nicht bemerkt, dass standardmäßig die letzte gerettete Ausnahme verwendet wird.
Unflores
170

Sie können dies auch tun, wenn Sie einen einfachen Einzeiler wünschen:

puts caller
anonymer Feigling
quelle
2
Genialer Trick. Vielen Dank. Ich wusste nicht, dass raisedas ohne Argumente verwendet werden kann. Ich wusste auch nicht, dass rescuedies korrekt als Einzeiler behandelt wird. Ich ignoriere auch diese globalen Vars wie total $!.
Dmytrii Nagirniak
11
Keine Notwendigkeit zu erhöhen / retten, Sie können einfach Kernel # Anrufer verwenden, wie folgt:puts "this line was reached by #{caller.join("\n")}"
Stephen C
Ah, ich habe kurz nach dem Posten dieser Antwort davon erfahren und vergessen, sie zu aktualisieren. Danke
anonymer Feigling
Ich benutze y caller, um die Ausgabe wie Java-Stack-Trace zu drucken.
so_mv
caller(0,2)würde die zwei neuesten Einträge in der Stapelverfolgung zurückgeben. Nizza für die Ausgabe von abgekürzten Stacktraces.
Magne
100

Dies erzeugt die Fehlerbeschreibung und eine schöne saubere, eingerückte Stapelverfolgung:

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

IRB hat eine Einstellung für diese schreckliche "Funktion", die Sie anpassen können.

Erstellen Sie eine Datei mit dem Namen ~/.irbrc, die die folgende Zeile enthält:

IRB.conf[:BACK_TRACE_LIMIT] = 100

Auf diese Weise können Sie irbmindestens 100 Stapelrahmen anzeigen . Ich konnte keine entsprechende Einstellung für die nicht interaktive Laufzeit finden.

Detaillierte Informationen zur IRB-Anpassung finden Sie im Pickaxe-Buch .

Robinluckey
quelle
3
Dies sollte die akzeptierte Antwort sein, da hier die Frage behandelt wird, wie mehr Backtrace anstelle von "... X-Ebenen ..." angezeigt werden kann.
Nickh
13

Ein Liner für Callstack:

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

Ein Liner für Callstack ohne alle Edelsteine:

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

Ein Liner für Callstack ohne alle Edelsteine ​​und relativ zum aktuellen Verzeichnis

begin; Whatever.you.want; rescue => e; puts e.message; puts; puts e.backtrace.grep_v(/\/gems\//).map { |l| l.gsub(`pwd`.strip + '/', '') }; end
Dorian
quelle
2
Einzeiler ist eigentlich eine schlechte Sache, wenn Sie mehrere Aussagen haben.
Nurettin
3
@nurettin Dies ist für schnelle Debugging-Zwecke, so dass es einfach ist, es in eine Zeile zu kopieren und einzufügen, meistens in interaktiven Shells
Dorian
@Dorian Du erinnerst mich an eine Frage, die ich hatte: "Warum sind interaktive Shells nützlich? (Ohne Shell-Skript)".
Sapphire_Brick
9

Dies ahmt die offizielle Ruby-Spur nach, wenn Ihnen das wichtig ist.

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

Amüsanterweise wird "nicht behandelte Ausnahme" nicht richtig behandelt und als "RuntimeError" gemeldet, aber der Speicherort ist korrekt.

android.weasel
quelle
Ich bedauere, dass ich nur eine Gegenstimme für Ihre Antwort geben kann. Ich füge dies überall hinzu
Dbz
4

Ich habe diese Fehler beim Laden meiner Testumgebung (über Rake-Test oder Autotest) erhalten, und die IRB-Vorschläge haben nicht geholfen. Am Ende habe ich meine gesamte Datei test / test_helper.rb in einen Start- / Rettungsblock eingewickelt, und das hat die Probleme behoben.

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

[Untersuchen Sie alle Thread-Backtraces, um den Schuldigen zu finden]
Selbst ein vollständig erweiterter Aufrufstapel kann die tatsächlich fehlerhafte Codezeile vor Ihnen verbergen, wenn Sie mehr als einen Thread verwenden!

Beispiel: Ein Thread iteriert Ruby Hash, ein anderer Thread versucht, ihn zu ändern. BOOM! Ausnahme! Das Problem mit der Stapelverfolgung, die Sie erhalten, wenn Sie versuchen, den "beschäftigten" Hash zu ändern, besteht darin, dass die Funktionskette bis zu der Stelle angezeigt wird, an der Sie den Hash ändern möchten. Es wird jedoch NICHT angezeigt, wer ihn derzeit parallel iteriert ( Wem gehört es)! Hier erfahren Sie, wie Sie dies herausfinden können, indem Sie die Stapelverfolgung für ALLE aktuell ausgeführten Threads drucken. So geht's:

# 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

Das obige Code-Snippet ist auch nur für Bildungszwecke nützlich, da es Ihnen (wie Röntgen) zeigen kann, wie viele Threads Sie tatsächlich haben (im Vergleich zu der Anzahl, von der Sie dachten, dass Sie sie haben - ziemlich oft sind diese beiden unterschiedliche Zahlen;)

Dmitry Shevkoplyas
quelle
0

Sie können auch Ruby Gem zurückverfolgen (ich bin der Autor):

require 'backtrace'
begin
  # do something dangerous
rescue StandardError => e
  puts Backtrace.new(e)
end
yegor256
quelle
4
Können Sie zumindest erklären, warum wir Ihren Edelstein verwenden möchten? Können Sie eine Beispielausgabe zeigen?
ioquatix