Como faço para o ruby ​​imprimir um backtrace completo em vez de um truncado?

170

Quando recebo exceções, geralmente é do fundo da pilha de chamadas. Quando isso acontece, na maioria das vezes, a linha de código incorreta real está oculta:

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

Esse truncamento "... 8 níveis ..." está me causando muitos problemas. Não estou tendo muito sucesso pesquisando no Google: como posso dizer ao ruby ​​que quero que os dumps incluam a pilha completa?

Sniggerfardimungus
fonte
2
Existe uma maneira de fazer isso na linha de comando?
6119 Andrew Grimm

Respostas:

241

A exceção # backtrace possui toda a pilha:

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

(Inspirado no blog Ruby Inside de Peter Cooper )

Gareth
fonte
15
Eu releria a exceção, pelo menos em prol da completude dos exemplos.
reto
13
Para reraise você só precisa dizer raise. Não há necessidade de especificar explicitamente a execução que você deseja aumentar.
Timo
Bom, eu sempre pensei que você tinha que passar na exceção anterior para aumentar. Não percebi que o padrão é a última exceção resgatada.
unflores 22/07
E se o seu código não der uma exceção, você só quer ver o rastreamento da pilha de onde foi?
Alex Levine
170

Você também pode fazer isso se desejar uma linha única:

puts caller
covarde anônimo
fonte
2
Truque incrível. Muito obrigado. Eu não sabia que isso raisepode ser usado sem argumentos. Nem eu sabia que rescueisso seria tratado corretamente como uma linha. Eu também ignoro totalmente esses vars globais como $!.
Dmytrii Nagirniak
11
não há necessidade de aumentar / resgatar, você pode simplesmente usar o chamador do Kernel #, assim:puts "this line was reached by #{caller.join("\n")}"
Stephen C
Ah, descobri isso logo após postar esta resposta e esqueci de atualizá-la. Obrigado
covarde anônimo
Eu uso y callerpara imprimir a saída como rastreamento de pilha Java.
26612 so_mv
caller(0,2)retornaria as duas entradas mais recentes no rastreamento de pilha. Bom para a saída de traços de pilha abreviados.
Magne
100

Isso produz a descrição do erro e o bom e limpo stacktrace:

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

O IRB possui uma configuração para esse "recurso" terrível, que você pode personalizar.

Crie um arquivo chamado ~/.irbrcque inclua a seguinte linha:

IRB.conf[:BACK_TRACE_LIMIT] = 100

Isso permitirá que você veja 100 quadros de pilha em irb , pelo menos. Não consegui encontrar uma configuração equivalente para o tempo de execução não interativo.

Informações detalhadas sobre a personalização do IRB podem ser encontradas no livro Pickaxe .

robinluckey
fonte
3
Essa deve ser a resposta aceita, porque aborda a questão de como mostrar mais do backtrace em vez de "... níveis X ...".
N
13

Um revestimento para pilha de chamada:

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

Um forro para pilha de chamadas sem todas as gemas:

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

Um forro para pilha de chamadas sem todas as gemas e em relação ao diretório atual

begin; Whatever.you.want; rescue => e; puts e.message; puts; puts e.backtrace.grep_v(/\/gems\//).map { |l| l.gsub(`pwd`.strip + '/', '') }; end
Dorian
fonte
2
uma linha é realmente uma coisa ruim quando você tem várias instruções.
precisa
3
@nurettin este é para fins de depuração rápida para torná-lo uma linha faz com que seja fácil de copiar colá-lo, principalmente em conchas interativos
Dorian
@Dorian Você me lembra uma pergunta que eu tinha: "Por que os shells interativos são úteis? (Exceto o shell-script)".
Sapphire_Brick
9

Isso imita o rastreio oficial do Ruby, se isso é importante para você.

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

Divertidamente, ele não lida com 'exceção não tratada' corretamente, relatando-o como 'RuntimeError', mas o local está correto.

android.weasel
fonte
Lamento ter apenas um voto positivo para dar sua resposta. Eu adiciono isso em qualquer lugar #
Dbz
4

Eu estava recebendo esses erros ao tentar carregar meu ambiente de teste (via rake test ou autoteste) e as sugestões do IRB não ajudaram. Acabei envolvendo meu test / test_helper.rb inteiro em um bloco de início / resgate e isso corrigiu as coisas.

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

[examine todos os backtraces de threads para encontrar o culpado]
Mesmo a pilha de chamadas totalmente expandida ainda pode ocultar a linha de código incorreta real quando você usa mais de um thread!

Exemplo: um segmento está iterando o ruby ​​Hash, outro está tentando modificá-lo. ESTRONDO! Exceção! E o problema com o rastreamento de pilha que você obtém ao tentar modificar o hash 'ocupado' é que ele mostra a cadeia de funções até o local em que você está tentando modificar o hash, mas NÃO mostra quem está iterando no momento ( quem é o dono)! Aqui está a maneira de descobrir isso imprimindo o rastreamento de pilha para TODOS os threads em execução no momento. Aqui está como você faz isso:

# 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

O snippet de código acima é útil mesmo para fins educacionais, pois pode mostrar (como raio-x) quantos threads você realmente tem (versus quantos você pensou ter - com bastante frequência esses dois são números diferentes)

Dmitry Shevkoplyas
fonte
0

Você também pode usar a gema Ruby de backtrace (eu sou o autor):

require 'backtrace'
begin
  # do something dangerous
rescue StandardError => e
  puts Backtrace.new(e)
end
yegor256
fonte
4
Você pode pelo menos explicar por que queremos usar sua gema? Você pode mostrar alguma saída de amostra?
ioquatix 19/04