Come posso ottenere ruby ​​per stampare un backtrace completo anziché troncato?


170

Quando ricevo eccezioni, spesso viene dal profondo dello stack di chiamate. Quando ciò accade, il più delle volte, l'attuale riga di codice offensiva mi viene nascosta:

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

Quel troncamento "... 8 livelli ..." mi sta causando molti problemi. Non sto riscuotendo molto successo su Google: come faccio a dire a Ruby che voglio che i dump includano l'intero stack?


2
C'è un modo per farlo invece dalla riga di comando?
Andrew Grimm,

Risposte:


241

L'eccezione # backtrace contiene l'intero stack:

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

(Ispirato al blog Ruby Inside di Peter Cooper )


15
Rialzerei l'eccezione, almeno per il bene della completezza degli esempi.
reto

13
Per rilanciare devi solo dire raise. Non è necessario specificare esplicitamente l'esecuzione che si desidera sollevare.
Timo,

Bene, ho sempre pensato che dovessi superare la precedente eccezione per sollevare. Non mi rendevo conto che per impostazione predefinita è stata salvata l'ultima eccezione.
chiarisce il

Cosa succede se il tuo codice non genera un'eccezione, vuoi solo vedere la traccia dello stack di dove è andato?
Alex Levine,

170

Puoi farlo anche se desideri un semplice one-liner:

puts caller

2
Trucco fantastico. Molte grazie. Non sapevo che raisepotesse essere usato senza argomenti. Né sapevo che rescueverrà trattato correttamente come una linea. Inoltre ignoro totalmente quei variegati globali come $!.
Dmytrii Nagirniak,

11
non c'è bisogno di alzare / salvare, puoi semplicemente usare Kernel # caller, in questo modo:puts "this line was reached by #{caller.join("\n")}"
Stephen C

Ah, l'ho scoperto poco dopo aver pubblicato questa risposta e ho dimenticato di aggiornarlo. Grazie
codardo anonimo il

Uso y callerper stampare l'output come traccia stack Java.
so_mv,

caller(0,2)restituirebbe le ultime due voci nello stacktrace. Bello per produrre stacktraces abbreviati.
Magne,

100

Questo produce la descrizione dell'errore e uno stacktrace pulito e rientrato:

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

49

IRB ha un'impostazione per questa terribile "caratteristica", che puoi personalizzare.

Creare un file chiamato ~/.irbrcche include la seguente riga:

IRB.conf[:BACK_TRACE_LIMIT] = 100

Questo ti permetterà di vedere irbalmeno 100 frame di stack . Non sono stato in grado di trovare un'impostazione equivalente per il runtime non interattivo.

Informazioni dettagliate sulla personalizzazione IRB sono disponibili nel libro Pickaxe .


3
Questa dovrebbe essere la risposta accettata, perché affronta la domanda su come mostrare più backtrace anziché "... X livelli ...".
Nick

13

Una fodera per callstack:

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

Una fodera per callstack senza tutte le gemme:

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

Un liner per callstack senza tutte le gemme e relativo alla directory corrente

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 è in realtà una cosa negativa quando hai più dichiarazioni.
Nurettin,

3
@nurettin ha lo scopo di eseguire il debug rapido, quindi renderlo una riga semplifica la copia e incolla, principalmente in shell interattive
Dorian

@Dorian Mi ricordi una domanda che avevo: "Perché le shell interattive sono utili? (Escluso lo script Shell)".
Sapphire_Brick,

9

Questo imita la traccia ufficiale di Ruby, se questo è importante per te.

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

In modo divertente, non gestisce correttamente l '"eccezione non gestita", segnalandola come "RuntimeError", ma la posizione è corretta.


Mi dispiace di avere un solo voto da dare per la tua risposta. Lo aggiungo ovunque
Dbz

4

Stavo ricevendo questi errori durante il tentativo di caricare il mio ambiente di test (tramite rake test o autotest) e i suggerimenti IRB non mi hanno aiutato. Ho finito per racchiudere il mio intero test / test_helper.rb in un blocco di inizio / salvataggio e questo ha risolto il problema.

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

0

[esamina tutti i thread backtrace per trovare il colpevole]
Anche lo stack di chiamate completamente espanso può ancora nascondere la riga di codice offensiva effettiva quando usi più di un thread!

Esempio: un thread sta ripetendo Hash ruby, l'altro thread sta provando a modificarlo. BOOM! Eccezione! E il problema con la traccia dello stack che si ottiene durante il tentativo di modificare l'hash "occupato" è che mostra la catena di funzioni fino al punto in cui si sta tentando di modificare l'hash, ma NON mostra chi lo sta attualmente ripetendo in parallelo ( chi lo possiede)! Ecco il modo per capirlo stampando la traccia dello stack per TUTTI i thread attualmente in esecuzione. Ecco come si fa:

# 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

Lo snippet di codice sopra è utile anche solo a scopo educativo in quanto può mostrare (come i raggi X) quanti thread hai effettivamente (rispetto a quanti pensavi di avere - abbastanza spesso quei due sono numeri diversi;)


0

Puoi anche usare il backtrace Ruby gem (sono l'autore):

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

4
Puoi almeno spiegare perché vorremmo usare la tua gemma? Puoi mostrare qualche output di esempio?
ioquatix,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.