¿Cómo consigo que Ruby imprima una traza inversa completa en lugar de una truncada?


170

Cuando recibo excepciones, a menudo es desde lo profundo de la pila de llamadas. Cuando esto sucede, la mayoría de las veces, la línea de código ofensiva real se me 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

Ese truncamiento de "... 8 niveles ..." me está causando muchos problemas. No estoy teniendo mucho éxito buscando en Google: ¿Cómo le digo a Ruby que quiero que los vertederos incluyan la pila completa?


2
¿Hay alguna manera de hacer esto desde la línea de comando?
Andrew Grimm

Respuestas:


241

La excepción # backtrace tiene toda la pila:

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

(Inspirado en el blog Ruby Inside de Peter Cooper )


15
Volvería a plantear la excepción, al menos por el bien de la integridad de los ejemplos.
reto

13
Para volver a subir solo tienes que decir raise. No es necesario especificar explícitamente la ejecución que desea generar.
Timo

Agradable, siempre pensé que tenías que pasar la excepción anterior para subir. No me di cuenta de que por defecto es la última excepción rescatada.
despliega el

¿Qué pasa si su código no arroja una excepción, solo desea ver el rastro de la pila de dónde fue?
Alex Levine

170

También puede hacer esto si desea una línea simple:

puts caller

2
Truco impresionante. Muchas gracias. No sabía que raisese puede usar sin argumentos. Tampoco sabía que rescuese tratará correctamente como un trazo. También ignoro totalmente esos vars globales como $!.
Dmytrii Nagirniak

11
no es necesario subir / rescatar, solo puede usar Kernel # llamador, así:puts "this line was reached by #{caller.join("\n")}"
Stephen C

Ah, descubrí eso poco después de publicar esta respuesta y olvidé actualizarla. Gracias
cobarde anónimo

Yo uso y callerpara imprimir la salida como el seguimiento de la pila de Java.
so_mv

caller(0,2)devolvería las dos últimas entradas en el stacktrace. Agradable para la salida de stacktraces abreviados.
Magne

100

Esto produce la descripción del error y una buena traza limpia e indentada:

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

49

IRB tiene una configuración para esta horrible "característica", que puede personalizar.

Cree un archivo llamado ~/.irbrcque incluya la siguiente línea:

IRB.conf[:BACK_TRACE_LIMIT] = 100

Esto le permitirá ver 100 cuadros de pila irbal menos. No he podido encontrar una configuración equivalente para el tiempo de ejecución no interactivo.

Puede encontrar información detallada sobre la personalización de IRB en el libro Pickaxe .


3
Esta debería ser la respuesta aceptada, ya que aborda la cuestión de cómo mostrar más de la traza en lugar de "... X niveles ...".
nickh

13

Un revestimiento para callstack:

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

Un revestimiento para callstack sin todas las gemas:

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

Un revestimiento para callstack sin todas las gemas y relativo al directorio actual

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 es realmente algo malo cuando tienes varias declaraciones.
nurettin

3
@nurettin esto es para propósitos de depuración rápida, por lo que hacer que sea una línea facilita copiar y pegar, principalmente en shells interactivos
Dorian

@ Dorian Me recuerdas una pregunta que tenía: "¿Por qué son útiles los shells interactivos? (Excluyendo Shell-script)".
Sapphire_Brick

9

Esto imita el rastro oficial de Ruby, si eso es importante para ti.

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, no maneja correctamente la 'excepción no controlada', informando que es 'RuntimeError', pero la ubicación es correcta.


Lamento no haber recibido un solo voto por su respuesta. Agrego esto en todas partes
Dbz

4

Recibía estos errores al intentar cargar mi entorno de prueba (a través de la prueba de rastrillo o la prueba automática) y las sugerencias de IRB no me ayudaron. Terminé envolviendo todo mi test / test_helper.rb en un bloque de inicio / rescate y eso solucionó las cosas.

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

0

[Examine todos los hilos retrocesos para encontrar al culpable]
Incluso la pila de llamadas completamente expandida puede ocultarle la línea de código ofensiva real cuando usa más de un subproceso!

Ejemplo: un hilo está iterando ruby ​​Hash, otro hilo está tratando de modificarlo. ¡AUGE! ¡Excepción! Y el problema con el seguimiento de la pila que se obtiene al intentar modificar el hash 'ocupado' es que muestra la cadena de funciones hasta el lugar donde está tratando de modificar el hash, pero NO muestra quién lo está iterando actualmente en paralelo ( quien lo posee)! Esta es la forma de resolverlo imprimiendo el seguimiento de la pila para TODOS los subprocesos actualmente en ejecución. Así es como haces esto:

# 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

El fragmento de código anterior es útil incluso solo con fines educativos, ya que puede mostrarle (como rayos X) cuántos hilos tiene realmente (en comparación con cuántos creía que tenía, a menudo esos dos son números diferentes;)


0

También puede utilizar traza gema de rubíes (yo soy el autor):

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

44
¿Al menos puedes explicar por qué queremos usar tu gema? ¿Puedes mostrar alguna salida de muestra?
ioquatix
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.