Comment faire pour que Ruby imprime une trace arrière complète au lieu d'une trace tronquée?


170

Quand j'obtiens des exceptions, cela vient souvent du plus profond de la pile d'appels. Lorsque cela se produit, le plus souvent, la ligne de code incriminée m'est cachée:

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

Cette troncature "... 8 niveaux ..." me cause beaucoup de problèmes. Je n'ai pas beaucoup de succès avec Google pour celui-ci: comment dire à Ruby que je veux que les dumps incluent la pile complète?


2
Existe-t-il un moyen de le faire à partir de la ligne de commande à la place?
Andrew Grimm

Réponses:


241

L'exception # backtrace contient la pile entière:

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

(Inspiré du blog Ruby Inside de Peter Cooper )


15
Je soulèverais l'exception, au moins pour le bien de l'exhaustivité des exemples.
Reto

13
Pour relancer, il suffit de dire raise. Inutile de spécifier explicitement l’exécution que vous souhaitez déclencher.
Timo

Bien, j'ai toujours pensé qu'il fallait passer l'exception précédente pour augmenter. Je ne savais pas qu'il était par défaut la dernière exception sauvée.
unflores

Que faire si votre code ne lève pas d'exception, vous voulez simplement voir la trace de la pile de l'endroit où il est allé?
Alex Levine

170

Vous pouvez également le faire si vous souhaitez un simple one-liner:

puts caller

2
Super truc. Merci beaucoup. Je ne savais pas que cela raisepouvait être utilisé sans argument. Je ne savais pas non plus que rescuecela serait traité correctement comme une seule ligne. J'ignore aussi totalement ces variables globales comme $!.
Dmytrii Nagirniak

11
pas besoin de lever / sauver, vous pouvez simplement utiliser l'appelant Kernel #, comme ceci:puts "this line was reached by #{caller.join("\n")}"
Stephen C

Ah, j'ai découvert cela peu de temps après avoir publié cette réponse et j'ai oublié de la mettre à jour. Merci
lâche anonyme

J'utilise y callerpour imprimer la sortie comme trace de pile Java.
so_mv

caller(0,2)renverrait les deux dernières entrées dans le stacktrace. Bien pour la sortie de traces de pile abrégées.
Magne

100

Cela produit la description de l'erreur et une belle trace de pile propre et indentée:

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

49

IRB a un paramètre pour cette horrible "fonctionnalité", que vous pouvez personnaliser.

Créez un fichier appelé ~/.irbrcqui comprend la ligne suivante:

IRB.conf[:BACK_TRACE_LIMIT] = 100

Cela vous permettra de voir irbau moins 100 cadres de pile . Je n'ai pas été en mesure de trouver un paramètre équivalent pour le runtime non interactif.

Vous trouverez des informations détaillées sur la personnalisation de l'IRB dans le livre Pickaxe .


3
Cela devrait être la réponse acceptée, car elle aborde la question de savoir comment afficher davantage de traces au lieu de "... X niveaux ...".
nickh

13

Une doublure pour la pile d'appels:

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

Une doublure pour la pile d'appels sans toutes les gemmes:

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

Une doublure pour la pile d'appels sans toutes les gemmes et relative au répertoire actuel

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

2
une seule ligne est en fait une mauvaise chose lorsque vous avez plusieurs déclarations.
nurettin

3
@nurettin c'est à des fins de débogage rapide, donc en faire une seule ligne, il est facile de copier-coller, principalement dans des shells interactifs
Dorian

@Dorian Vous me rappelez une question que j'avais: "Pourquoi les shells interactifs sont-ils utiles? (Hors Shell-script)".
Sapphire_Brick

9

Cela imite la trace officielle de Ruby, si cela est important pour vous.

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

De manière amusante, il ne gère pas correctement les «exceptions non gérées», les signalant comme «RuntimeError», mais l'emplacement est correct.


Je regrette de n'avoir qu'une seule voix positive à donner pour votre réponse. J'ajoute ça partout
Dbz

4

J'obtenais ces erreurs en essayant de charger mon environnement de test (via rake test ou autotest) et les suggestions IRB n'ont pas aidé. J'ai fini par emballer tout mon test / test_helper.rb dans un bloc begin / rescue et cela a corrigé les choses.

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

0

[examinez toutes les traces de threads pour trouver le coupable]
Même la pile d'appels entièrement développée peut toujours vous cacher la ligne de code incriminée lorsque vous utilisez plus d'un thread!

Exemple: un thread itère ruby ​​Hash, un autre thread tente de le modifier. BOOM! Exception! Et le problème avec la trace de pile que vous obtenez en essayant de modifier le hachage `` occupé '' est qu'il vous montre la chaîne de fonctions jusqu'à l'endroit où vous essayez de modifier le hachage, mais il ne montre PAS qui l'itère actuellement en parallèle ( qui le possède)! Voici le moyen de comprendre cela en imprimant la trace de pile pour TOUS les threads en cours d'exécution. Voici comment procéder:

# 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

L'extrait de code ci-dessus est utile même à des fins éducatives car il peut vous montrer (comme la radiographie) le nombre de threads que vous avez réellement (par rapport au nombre que vous pensiez en avoir - bien souvent, ces deux nombres sont différents;)


0

Vous pouvez également utiliser la gemme Ruby backtrace (j'en suis l'auteur):

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

4
Pouvez-vous au moins expliquer pourquoi nous aimerions utiliser votre bijou? Pouvez-vous montrer un exemple de sortie?
ioquatix le
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.