Obtenez la trace actuelle de la pile dans Ruby sans lever d'exception

139

Je souhaite enregistrer le backtrace actuel (stacktrace) dans une application Rails 3 sans qu'une exception ne se produise. Une idée comment?

Pourquoi est-ce que je veux ça? J'essaie de tracer les appels qui sont effectués lorsque Rails recherche un modèle afin de pouvoir choisir une partie du processus à remplacer (car je veux changer le chemin de vue pour un contrôleur sous-classé particulier).

Je voudrais appeler à partir du fichier: gems\actionpack-3.2.3\lib\action_dispatch\middleware\templates\rescues\missing_template.erb. Je sais que ce n'est pas la meilleure pratique, mais je sais que c'est en aval de la pile d'où la recherche de modèles a lieu.

JellicleCat
la source
4
Solution sale: soulevez une exception ici, récupérez-la immédiatement et connectez-vous e.backtrace. Je l'ai vu dans l'un des projets avec lesquels je travaille. Pas la meilleure approche, mais ça marche. J'espère cependant entendre une meilleure solution de quelqu'un d'autre.
KL-7

Réponses:

185

Vous pouvez utiliser Kernel#caller:

# /tmp/caller.rb

def foo 
  puts caller # Kernel#caller returns an array of strings
end

def bar 
  foo 
end

def baz 
  bar 
end

baz

Production:

caller.rb:8:in `bar'
caller.rb:12:in `baz'
caller.rb:15:in `<main>'
KL-7
la source
N'est-ce pas Kernel.caller- avec un point? Kernel.new.callern'est pas défini ici
ecoologic
8
Non, c'est techniquement callerune méthode d'instance. Puisque le Kernelmodule est inclus dans chaque classe Ruby (sauf BasicObjectdans 1.9), il est disponible en tant que méthode d'instance sur n'importe quel objet (il est cependant privé). Vous ne pouvez pas l'appeler aussi Kernel.new.callersimplement parce que vous ne pouvez pas instancier un module (il n'a pas de newméthode).
KL-7
celui-ci prend en charge un paramètre pour sauter n'importe quel nombre d'appels; voir: stackoverflow.com/a/3829269/520567
akostadinov
7
Pour une jolie utilisation imprimée - Rails.logger.debug caller.join("\n")ou puts caller.join("\n"). Merci.
Jignesh Gohel
20

Essayez d'utiliser

Thread.current.backtrace
Rajat Bansal
la source
1
L'avantage de cette réponse est qu'elle inclut la méthode actuelle dans le backtrace, tout en omettant Kernel#callerla méthode actuelle. Par exemple, MyClass.new.returns_caller => ["(irb):42:in 'irb_binding'",...] n'est pas aussi utile que MyClass.new.returns_thread_backtrace => ["(irb):38:in 'backtrace'","(irb):38:in 'returns_thread_backtrace'","(irb):43:in 'irb_binding'",...]
stwr667
6

J'utilise ceci pour afficher une page d'erreur personnalisée lorsqu'une exception est déclenchée.

rescue_from Exception do |exception|
  logger.error exception.class
  logger.error exception.message
  logger.error exception.backtrace.join "\n"
  @exception = exception


  # ExceptionNotifier::Notifier.exception_notification env, @exception

  respond_to do |format|
    if [AbstractController::ActionNotFound, ActiveRecord::RecordNotFound, ActionController::RoutingError, ActionController::UnknownAction].include?(exception.class)
      format.html { render :template => "errors/404", :status => 404 }
      format.js   { render :nothing => true, :status => 404 }
      format.xml  { render :nothing => true, :status => 404 }
    elsif exception.class == CanCan::AccessDenied
      format.html {
        render :template => "errors/401", :status => 401 #, :layout => 'application'
      }
      # format.js   { render :json => { :errors => [exception.message] }, :status => 401 }
      # format.js   { render :js => 'alert("Hello 401")' }
      format.js   { render :template => 'errors/401.js.erb' }

    else
      ExceptionNotifier::Notifier.exception_notification(env, exception).deliver        
      format.html { render :template => "errors/500", :status => 500 } #, :layout => 'im2/application' }
      # format.js   { render :nothing => true, :status => 500 }
      format.js   { render :template => 'errors/500.js.erb' }

    end
  end
end
Stefano Lampis
la source