Obter rastreamento de pilha atual no Ruby sem gerar uma exceção

138

Desejo registrar o backtrace atual (stacktrace) em um aplicativo Rails 3 sem que ocorra uma exceção. Alguma idéia de como?

Por que eu quero isso? Estou tentando rastrear as chamadas feitas quando o Rails procura um modelo para poder escolher uma parte do processo a ser substituída (porque quero alterar o caminho de exibição de um controlador subclasse específico meu).

Eu gostaria de chamá-lo a partir do arquivo: gems\actionpack-3.2.3\lib\action_dispatch\middleware\templates\rescues\missing_template.erb. Sei que não é uma prática recomendada, mas sei que é a jusante da pilha de onde ocorre a pesquisa de modelos.

JellicleCat
fonte
4
Solução suja: crie uma exceção, salve-a imediatamente e faça o log e.backtrace. Eu já vi isso em um dos projetos com os quais estou trabalhando. Não é a melhor abordagem, mas funciona. Espero ouvir uma solução melhor de outra pessoa, no entanto.
KL-7

Respostas:

185

Você pode usar 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

Resultado:

caller.rb:8:in `bar'
caller.rb:12:in `baz'
caller.rb:15:in `<main>'
KL-7
fonte
Não é Kernel.caller- com um ponto? Kernel.new.callernão está definido aqui
ecoologic
8
Não, tecnicamente calleré um método de instância. Como o Kernelmódulo está incluído em todas as classes Ruby (exceto BasicObjectno 1.9), ele está disponível como método de instância em qualquer objeto (embora seja privado). Você não pode chamá-lo Kernel.new.callersimplesmente porque não pode instanciar um módulo (ele não possui newmétodo).
KL-7
este suporta um parâmetro para pular qualquer número de chamadores; Veja: stackoverflow.com/a/3829269/520567
akostadinov
7
Para impressão bonita, use - Rails.logger.debug caller.join("\n")ou puts caller.join("\n"). Obrigado.
Jignesh Gohel 9/03/2015
20

Tente usar

Thread.current.backtrace
Rajat Bansal
fonte
1
A vantagem desta resposta é que ela inclui o método atual no backtrace, enquanto Kernel#callerdeixa de fora o método atual. Por exemplo, MyClass.new.returns_caller => ["(irb):42:in 'irb_binding'",...] não é tão útil quanto MyClass.new.returns_thread_backtrace => ["(irb):38:in 'backtrace'","(irb):38:in 'returns_thread_backtrace'","(irb):43:in 'irb_binding'",...]
stwr667 12/07
6

Eu uso isso para mostrar uma página de erro personalizada quando exceções são geradas.

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
fonte