v7.0.8
Veja mais em rubyonrails.org: Mais Ruby on Rails

Debug de Aplicações Rails

Esse guia introduz técnicas de debug para aplicações de Ruby on Rails

Após ler esse guia, você saberá:

1 View Helpers para Debugging

Uma tarefa comum é inspecionar o conteúdo de uma variável. O Rails fornece três diferentes formas para fazer isso:

  • debug
  • to_yaml
  • inspect

1.1 debug

O helper debug irá retornar uma tag <pre> que renderiza um objeto usando o formato YAML. Isso vai gerar um dado legível para humanos a partir de qualquer objeto. Por exemplo, se você tem esse código em uma view:

<%= debug @article %>
<p>
  <b>Title:</b>
  <%= @article.title %>
</p>

Você verá algo parecido com isso:

--- !ruby/object Article
attributes:
  updated_at: 2008-09-05 22:55:47
  body: Esse é um guia muito útil para fazer o debugging da sua app Rails.
  title: Guia de debugging do Rails
  published: t
  id: "1"
  created_at: 2008-09-05 22:55:47
attributes_cache: {}


Title: Guia de *debugging* do Rails

1.2 to_yaml

Como alternativa, chamar to_yaml em qualquer objeto o converte para YAML. Você pode passar esse objeto convertido para o método helper simple_format para formatar o output. É assim que o debug faz sua mágica.

<%= simple_format @article.to_yaml %>
<p>
  <b>Title:</b>
  <%= @article.title %>
</p>

O código acima vai renderizar algo como isso:

--- !ruby/object Article
attributes:
updated_at: 2008-09-05 22:55:47
body: Esse é um guia muito útil para fazer o debugging de sua app Rails.
title: Guia de debugging do Rails
published: t
id: "1"
created_at: 2008-09-05 22:55:47
attributes_cache: {}

Title: Guia de *debugging* do Rails

1.3 inspect

Outro método útil para mostrar valores de objeto é o inspect, especialmente quando estamos trabalhando com arrays ou hashes. Isso imprimirá o valor do objeto como uma string. Por exemplo:

<%= [1, 2, 3, 4, 5].inspect %>
<p>
  <b>Title:</b>
  <%= @article.title %>
</p>

Vai renderizar:

[1, 2, 3, 4, 5]

Title: Guia de *debugging* do Rails

2 O Logger

Pode ser útil salvar informações em um arquivo de log em tempo de execução. O Rails mantém um arquivo de log separado para cada ambiente de execução.

2.1 O que é o Logger?

O Rails utiliza a classe ActiveSupport::Logger para guardar informações de log. Outros tipos de loggers, como o Log4r, também podem ser utilizados.

Você pode especificar um logger alternativo em config/application.rb ou em qualquer outro arquivo de ambiente, por exemplo:

config.logger = Logger.new(STDOUT)
config.logger = Log4r::Logger.new("Application Log")

Ou na seção Initializer, adicione qualquer um dos seguintes:

Rails.logger = Logger.new(STDOUT)
Rails.logger = Log4r::Logger.new("Application Log")

Por padrão, cada log é criado em Rails.root/log/ e o arquivo de registro é criado com o nome do ambiente no qual a aplicação está sendo executada.

2.2 Níveis de Log

Quando algo é registrado, a informação é armazenada no local de registro correspondente se o nível de log for igual ou maior que o configurado. Se você quiser saber o nível atual do registro, você pode o método Rails.logger.level.

Os níveis de log disponiveis são: :debug, :info, :warn, :error, :fatal, e :unknown, correspondendo aos níveis de log de 0 até 5, respectivamente. Para mudar o nível de log padrão, utilize:

config.log_level = :warn # Em qualquer inicializador de ambiente, ou
Rails.logger.level = 0 # a qualquer momento

Isso é útil quando você quer criar logs em ambientes diferentes de desenvolvimento ou homologação sem sobrecarregar os registros do seu aplicativo com informação desnecessária.

O nível de log padrão do Rails é debug em todos os ambientes de desenvolvimento.

2.3 Enviando Mensagens

Para enviar uma mensagem para o log ativo, use o método logger.(debug|info|warn|error|fatal|unknown) de dentro de um controller, model ou mailer:

logger.debug "Hash com atributos de 'Person': #{@person.attributes.inspect}"
logger.info "Processando informações..."
logger.fatal "Encerrando aplicação, erro irrecuperavel!!!"

Segue um exemplo de um método instrumentado com um log extra:

class ArticlesController < ApplicationController
  # ...

  def create
    @article = Article.new(article_params)
    logger.debug "Novo Artigo: #{@article.attributes.inspect}"
    logger.debug "Artigo deve ser valido: #{@article.valid?}"

    if @article.save
      logger.debug "O Artigo foi salvo e agora o usuario será redirecionado..."
      redirect_to @article, notice: 'Artigo criado com sucesso'
    else
      render :new, status: :unprocessable_entity
    end
  end

  # ...

  private
    def article_params
      params.require(:article).permit(:title, :body, :published)
    end
end

Segue um exemplo de um log gerado quando a ação deste controller é executada:

Started POST "/articles" for 127.0.0.1 at 2018-10-18 20:09:23 -0400
Processing by ArticlesController#create as HTML
  Parameters: {"utf8"=>"✓", "authenticity_token"=>"XLveDrKzF1SwaiNRPTaMtkrsTzedtebPPkmxEFIU0ordLjICSnXsSNfrdMa4ccyBjuGwnnEiQhEoMN6H1Gtz3A==", "article"=>{"title"=>"Debugging Rails", "body"=>"I'm learning how to print in logs.", "published"=>"0"}, "commit"=>"Create Article"}
New article: {"id"=>nil, "title"=>"Debugging Rails", "body"=>"I'm learning how to print in logs.", "published"=>false, "created_at"=>nil, "updated_at"=>nil}
Article should be valid: true
   (0.0ms)  begin transaction
  ↳ app/controllers/articles_controller.rb:31
  Article Create (0.5ms)  INSERT INTO "articles" ("title", "body", "published", "created_at", "updated_at") VALUES (?, ?, ?, ?, ?)  [["title", "Debugging Rails"], ["body", "I'm learning how to print in logs."], ["published", 0], ["created_at", "2018-10-19 00:09:23.216549"], ["updated_at", "2018-10-19 00:09:23.216549"]]
  ↳ app/controllers/articles_controller.rb:31
   (2.3ms)  commit transaction
  ↳ app/controllers/articles_controller.rb:31
The article was saved and now the user is going to be redirected...
Redirected to http://localhost:3000/articles/1
Completed 302 Found in 4ms (ActiveRecord: 0.8ms)

A adição deste tipo de log facilita a busca por comportamentos não esperados ou não usuais. Se você adicionar logs extras, tenha certeza de utilizar os níveis de log de maneira adequada, para evitar encher seus logs do ambiente de produção com trivialidades inúteis.

2.4 Verbose Query Logs

Ao observar a saída de consulta de banco de dados em logs, pode não ser imediatamente claro por que várias consultas de banco de dados são acionadas quando um único método é chamado:

irb(main):001:0> Article.pamplemousse
  Article Load (0.4ms)  SELECT "articles".* FROM "articles"
  Comment Load (0.2ms)  SELECT "comments".* FROM "comments" WHERE "comments"."article_id" = ?  [["article_id", 1]]
  Comment Load (0.1ms)  SELECT "comments".* FROM "comments" WHERE "comments"."article_id" = ?  [["article_id", 2]]
  Comment Load (0.1ms)  SELECT "comments".* FROM "comments" WHERE "comments"."article_id" = ?  [["article_id", 3]]
=> #<Comment id: 2, author: "1", body: "Well, actually...", article_id: 1, created_at: "2018-10-19 00:56:10", updated_at: "2018-10-19 00:56:10">

Depois de executar ActiveRecord.verbose_query_logs = true na sessão bin/rails console para habilitar logs de consulta detalhados e executar o método novamente, torna-se óbvio qual linha única de código está gerando todas essas chamadas discretas de banco de dados:

irb(main):003:0> Article.pamplemousse
  Article Load (0.2ms)  SELECT "articles".* FROM "articles"
  ↳ app/models/article.rb:5
  Comment Load (0.1ms)  SELECT "comments".* FROM "comments" WHERE "comments"."article_id" = ?  [["article_id", 1]]
  ↳ app/models/article.rb:6
  Comment Load (0.1ms)  SELECT "comments".* FROM "comments" WHERE "comments"."article_id" = ?  [["article_id", 2]]
  ↳ app/models/article.rb:6
  Comment Load (0.1ms)  SELECT "comments".* FROM "comments" WHERE "comments"."article_id" = ?  [["article_id", 3]]
  ↳ app/models/article.rb:6
=> #<Comment id: 2, author: "1", body: "Well, actually...", article_id: 1, created_at: "2018-10-19 00:56:10", updated_at: "2018-10-19 00:56:10">

Abaixo de cada instrução do banco de dados, você pode ver setas apontando para o nome do arquivo de origem específico (e o número da linha) do método que resultou em uma chamada de banco de dados. Isso pode ajudá-lo a identificar e resolver problemas de desempenho causados por consultas N + 1: consultas únicas de banco de dados que geram várias consultas adicionais.

Logs de consulta detalhada são habilitados por padrão nos logs do ambiente de desenvolvimento após o Rails 5.2.

Não recomendamos o uso dessa configuração em ambientes de produção. Ele se baseia no métodoKernel#caller que tende a alocar muita memória para gerar o backtrace (rastreamento) de chamadas de método.

2.5 Tagged Log

Ao executar aplicativos multiusuário e multi-contas, muitas vezes é útil ser capaz de filtrar os registros usando algumas regras personalizadas. TaggedLogging no Active Support ajuda você a fazer exatamente isso, marcando linhas de registro com subdomínios, IDs de requisição e qualquer outra coisa para auxiliar no debug de tais aplicativos.

logger = ActiveSupport::TaggedLogging.new(Logger.new(STDOUT))
logger.tagged("BCX") { logger.info "Stuff" }                            # Logs "[BCX] Stuff"
logger.tagged("BCX", "Jason") { logger.info "Stuff" }                   # Logs "[BCX] [Jason] Stuff"
logger.tagged("BCX") { logger.tagged("Jason") { logger.info "Stuff" } } # Logs "[BCX] [Jason] Stuff"

2.6 Impacto dos logs no desempenho

O log sempre terá um pequeno impacto no desempenho do seu aplicativo Rails, particularmente ao fazer o registro no disco. Além disso, existem algumas sutilezas:

Ao utilizar o nível :debug terá uma penalidade de desempenho maior do que :fatal, já que um número muito maior de strings está sendo avaliado e gravado na saída do log (e.g. disco).

Outra armadilha potencial são muitas chamadas para Logger em seu código:

logger.debug "Person attributes hash: #{@person.attributes.inspect}"

No exemplo acima, haverá um impacto no desempenho, mesmo se o nível de saída permitido não incluir debug. A razão é que o Ruby tem que avaliar essas strings, que inclui instanciar o objeto String um tanto pesado e interpolar as variáveis.

Portanto, é recomendado passar blocos para os métodos logger, pois estes são apenas avaliado se o nível de saída for o mesmo - ou incluído - o nível permitido (i.e. carregamento lento). O mesmo código reescrito seria:

logger.debug {"Person attributes hash: #{@person.attributes.inspect}"}

O conteúdo do bloco e, portanto, a interpolação de string, são apenas avaliada se o debug está habilitado. Essa economia de desempenho é apenas realmente perceptível com grandes quantidades de registro, mas é uma boa prática empregar.

Esta seção foi escrita por Jon Cairns em uma resposta no Stack Overflow e está licenciada sob cc by-sa 4.0.

3 Debugging with the debug Gem

When your code is behaving in unexpected ways, you can try printing to logs or the console to diagnose the problem. Unfortunately, there are times when this sort of error tracking is not effective in finding the root cause of a problem. When you actually need to journey into your running source code, the debugger is your best companion.

The debugger can also help you if you want to learn about the Rails source code but don't know where to start. Just debug any request to your application and use this guide to learn how to move from the code you have written into the underlying Rails code.

Rails 7 includes the debug gem in the Gemfile of new applications generated by CRuby. By default, it is ready in the development and test environments. Please check its documentation for usage.

3.1 Entering a Debugging Session

By default, a debugging session will start after the debug library is required, which happens when your app boots. But don't worry, the session won't interfere your program.

To enter the debugging session, you can use binding.break and its aliases: binding.b and debugger. The following examples will use debugger:

class PostsController < ApplicationController
  before_action :set_post, only: %i[ show edit update destroy ]

  # GET /posts or /posts.json
  def index
    @posts = Post.all
    debugger
  end
  # ...
end

Once your app evaluates the debugging statement, it'll enter the debugging session:

Processing by PostsController#index as HTML
[2, 11] in ~/projects/rails-guide-example/app/controllers/posts_controller.rb
     2|   before_action :set_post, only: %i[ show edit update destroy ]
     3|
     4|   # GET /posts or /posts.json
     5|   def index
     6|     @posts = Post.all
=>   7|     debugger
     8|   end
     9|
    10|   # GET /posts/1 or /posts/1.json
    11|   def show
=>#0    PostsController#index at ~/projects/rails-guide-example/app/controllers/posts_controller.rb:7
  #1    ActionController::BasicImplicitRender#send_action(method="index", args=[]) at ~/.rbenv/versions/3.0.1/lib/ruby/gems/3.0.0/gems/actionpack-7.0.0.alpha2/lib/action_controller/metal/basic_implicit_render.rb:6
  # and 72 frames (use `bt' command for all frames)
(rdbg)

You can exit the debugging session at any time and continue your application execution with the continue (or c) command. Or, to exit both the debugging session and your application, use the quit (or q) command.

3.2 The Context

After entering the debugging session, you can type in Ruby code as you're in a Rails console or IRB.

(rdbg) @posts    # ruby
[]
(rdbg) self
#<PostsController:0x0000000000aeb0>
(rdbg)

You can also use p or pp command to evaluate Ruby expressions (e.g. when a variable name conflicts with a debugger command).

(rdbg) p headers    # command
=> {"X-Frame-Options"=>"SAMEORIGIN", "X-XSS-Protection"=>"1; mode=block", "X-Content-Type-Options"=>"nosniff", "X-Download-Options"=>"noopen", "X-Permitted-Cross-Domain-Policies"=>"none", "Referrer-Policy"=>"strict-origin-when-cross-origin"}
(rdbg) pp headers    # command
{"X-Frame-Options"=>"SAMEORIGIN",
 "X-XSS-Protection"=>"1; mode=block",
 "X-Content-Type-Options"=>"nosniff",
 "X-Download-Options"=>"noopen",
 "X-Permitted-Cross-Domain-Policies"=>"none",
 "Referrer-Policy"=>"strict-origin-when-cross-origin"}
(rdbg)

Besides direct evaluation, debugger also helps you collect rich amount of information through different commands. Just to name a few here:

  • info (or i) - Information about current frame.
  • backtrace (or bt) - Backtrace (with additional information).
  • outline (or o, ls) - Available methods, constants, local variables, and instance variables in the current scope.
3.2.1 The info Command

It'll give you an overview of the values of local and instance variables that are visible from the current frame.

(rdbg) info    # command
%self = #<PostsController:0x0000000000af78>
@_action_has_layout = true
@_action_name = "index"
@_config = {}
@_lookup_context = #<ActionView::LookupContext:0x00007fd91a037e38 @details_key=nil, @digest_cache=...
@_request = #<ActionDispatch::Request GET "http://localhost:3000/posts" for 127.0.0.1>
@_response = #<ActionDispatch::Response:0x00007fd91a03ea08 @mon_data=#<Monitor:0x00007fd91a03e8c8>...
@_response_body = nil
@_routes = nil
@marked_for_same_origin_verification = true
@posts = []
@rendered_format = nil
3.2.2 The backtrace Command

When used without any options, it lists all the frames on the stack:

=>#0    PostsController#index at ~/projects/rails-guide-example/app/controllers/posts_controller.rb:7
  #1    ActionController::BasicImplicitRender#send_action(method="index", args=[]) at ~/.rbenv/versions/3.0.1/lib/ruby/gems/3.0.0/gems/actionpack-7.0.0.alpha2/lib/action_controller/metal/basic_implicit_render.rb:6
  #2    AbstractController::Base#process_action(method_name="index", args=[]) at ~/.rbenv/versions/3.0.1/lib/ruby/gems/3.0.0/gems/actionpack-7.0.0.alpha2/lib/abstract_controller/base.rb:214
  #3    ActionController::Rendering#process_action(#arg_rest=nil) at ~/.rbenv/versions/3.0.1/lib/ruby/gems/3.0.0/gems/actionpack-7.0.0.alpha2/lib/action_controller/metal/rendering.rb:53
  #4    block in process_action at ~/.rbenv/versions/3.0.1/lib/ruby/gems/3.0.0/gems/actionpack-7.0.0.alpha2/lib/abstract_controller/callbacks.rb:221
  #5    block in run_callbacks at ~/.rbenv/versions/3.0.1/lib/ruby/gems/3.0.0/gems/activesupport-7.0.0.alpha2/lib/active_support/callbacks.rb:118
  #6    ActionText::Rendering::ClassMethods#with_renderer(renderer=#<PostsController:0x0000000000af78>) at ~/.rbenv/versions/3.0.1/lib/ruby/gems/3.0.0/gems/actiontext-7.0.0.alpha2/lib/action_text/rendering.rb:20
  #7    block {|controller=#<PostsController:0x0000000000af78>, action=#<Proc:0x00007fd91985f1c0 /Users/st0012/...|} in <class:Engine> (4 levels) at ~/.rbenv/versions/3.0.1/lib/ruby/gems/3.0.0/gems/actiontext-7.0.0.alpha2/lib/action_text/engine.rb:69
  #8    [C] BasicObject#instance_exec at ~/.rbenv/versions/3.0.1/lib/ruby/gems/3.0.0/gems/activesupport-7.0.0.alpha2/lib/active_support/callbacks.rb:127
  ..... and more

Every frame comes with:

  • Frame identifier
  • Call location
  • Additional information (e.g. block or method arguments)

This will give you a great sense about what's happening in your app. However, you probably will notice that:

  • There are too many frames (usually 50+ in a Rails app).
  • Most of the frames are from Rails or other libraries you use.

Don't worry, the backtrace command provides 2 options to help you filter frames:

  • backtrace [num] - only show num numbers of frames, e.g. backtrace 10 .
  • backtrace /pattern/ - only show frames with identifier or location that matches the pattern, e.g. backtrace /MyModel/.

It's also possible to use these options together: backtrace [num] /pattern/.

3.2.3 The outline Command

This command is similar to pry and irb's ls command. It will show you what's accessible from the current scope, including:

  • Local variables
  • Instance variables
  • Class variables
  • Methods & their sources
ActiveSupport::Configurable#methods: config
AbstractController::Base#methods:
  action_methods  action_name  action_name=  available_action?  controller_path  inspect
  response_body
ActionController::Metal#methods:
  content_type       content_type=  controller_name  dispatch          headers
  location           location=      media_type       middleware_stack  middleware_stack=
  middleware_stack?  performed?     request          request=          reset_session
  response           response=      response_body=   response_code     session
  set_request!       set_response!  status           status=           to_a
ActionView::ViewPaths#methods:
  _prefixes  any_templates?  append_view_path   details_for_lookup  formats     formats=  locale
  locale=    lookup_context  prepend_view_path  template_exists?    view_paths
AbstractController::Rendering#methods: view_assigns

# .....

PostsController#methods: create  destroy  edit  index  new  show  update
instance variables:
  @_action_has_layout  @_action_name    @_config  @_lookup_context                      @_request
  @_response           @_response_body  @_routes  @marked_for_same_origin_verification  @posts
  @rendered_format
class variables: @@raise_on_missing_translations  @@raise_on_open_redirects

3.3 Breakpoints

There are many ways to insert and trigger a breakpoint in the debugger. In addition to adding debugging statements (e.g. debugger) directly in your code, you can also insert breakpoints with commands:

  • break (or b)
    • break - list all breakpoints
    • break <num> - set a breakpoint on the num line of the current file
    • break <file:num> - set a breakpoint on the num line of file
    • break <Class#method> or break <Class.method> - set a breakpoint on Class#method or Class.method
    • break <expr>.<method> - sets a breakpoint on <expr> result's <method> method.
  • catch <Exception> - set a breakpoint that'll stop when Exception is raised
  • watch <@ivar> - set a breakpoint that'll stop when the result of current object's @ivar is changed (this is slow)

And to remove them, you can use:

  • delete (or del)
    • delete - delete all breakpoints
    • delete <num> - delete the breakpoint with id num
3.3.1 The break Command

Set a breakpoint on a specified line number - e.g. b 28

[20, 29] in ~/projects/rails-guide-example/app/controllers/posts_controller.rb
    20|   end
    21|
    22|   # POST /posts or /posts.json
    23|   def create
    24|     @post = Post.new(post_params)
=>  25|     debugger
    26|
    27|     respond_to do |format|
    28|       if @post.save
    29|         format.html { redirect_to @post, notice: "Post was successfully created." }
=>#0    PostsController#create at ~/projects/rails-guide-example/app/controllers/posts_controller.rb:25
  #1    ActionController::BasicImplicitRender#send_action(method="create", args=[]) at ~/.rbenv/versions/3.0.1/lib/ruby/gems/3.0.0/gems/actionpack-7.0.0.alpha2/lib/action_controller/metal/basic_implicit_render.rb:6
  # and 72 frames (use `bt' command for all frames)
(rdbg) b 28    # break command
#0  BP - Line  /Users/st0012/projects/rails-guide-example/app/controllers/posts_controller.rb:28 (line)
(rdbg) c    # continue command
[23, 32] in ~/projects/rails-guide-example/app/controllers/posts_controller.rb
    23|   def create
    24|     @post = Post.new(post_params)
    25|     debugger
    26|
    27|     respond_to do |format|
=>  28|       if @post.save
    29|         format.html { redirect_to @post, notice: "Post was successfully created." }
    30|         format.json { render :show, status: :created, location: @post }
    31|       else
    32|         format.html { render :new, status: :unprocessable_entity }
=>#0    block {|format=#<ActionController::MimeResponds::Collec...|} in create at ~/projects/rails-guide-example/app/controllers/posts_controller.rb:28
  #1    ActionController::MimeResponds#respond_to(mimes=[]) at ~/.rbenv/versions/3.0.1/lib/ruby/gems/3.0.0/gems/actionpack-7.0.0.alpha2/lib/action_controller/metal/mime_responds.rb:205
  # and 74 frames (use `bt' command for all frames)

Stop by #0  BP - Line  /Users/st0012/projects/rails-guide-example/app/controllers/posts_controller.rb:28 (line)

Set a breakpoint on a given method call - e.g. b @post.save

[20, 29] in ~/projects/rails-guide-example/app/controllers/posts_controller.rb
    20|   end
    21|
    22|   # POST /posts or /posts.json
    23|   def create
    24|     @post = Post.new(post_params)
=>  25|     debugger
    26|
    27|     respond_to do |format|
    28|       if @post.save
    29|         format.html { redirect_to @post, notice: "Post was successfully created." }
=>#0    PostsController#create at ~/projects/rails-guide-example/app/controllers/posts_controller.rb:25
  #1    ActionController::BasicImplicitRender#send_action(method="create", args=[]) at ~/.rbenv/versions/3.0.1/lib/ruby/gems/3.0.0/gems/actionpack-7.0.0.alpha2/lib/action_controller/metal/basic_implicit_render.rb:6
  # and 72 frames (use `bt' command for all frames)
(rdbg) b @post.save    # break command
#0  BP - Method  @post.save at /Users/st0012/.rbenv/versions/3.0.1/lib/ruby/gems/3.0.0/gems/activerecord-7.0.0.alpha2/lib/active_record/suppressor.rb:43

(rdbg) c    # continue command
[39, 48] in ~/.rbenv/versions/3.0.1/lib/ruby/gems/3.0.0/gems/activerecord-7.0.0.alpha2/lib/active_record/suppressor.rb
    39|         SuppressorRegistry.suppressed[name] = previous_state
    40|       end
    41|     end
    42|
    43|     def save(**) # :nodoc:
=>  44|       SuppressorRegistry.suppressed[self.class.name] ? true : super
    45|     end
    46|
    47|     def save!(**) # :nodoc:
    48|       SuppressorRegistry.suppressed[self.class.name] ? true : super
=>#0    ActiveRecord::Suppressor#save(#arg_rest=nil) at ~/.rbenv/versions/3.0.1/lib/ruby/gems/3.0.0/gems/activerecord-7.0.0.alpha2/lib/active_record/suppressor.rb:44
  #1    block {|format=#<ActionController::MimeResponds::Collec...|} in create at ~/projects/rails-guide-example/app/controllers/posts_controller.rb:28
  # and 75 frames (use `bt' command for all frames)

Stop by #0  BP - Method  @post.save at /Users/st0012/.rbenv/versions/3.0.1/lib/ruby/gems/3.0.0/gems/activerecord-7.0.0.alpha2/lib/active_record/suppressor.rb:43
3.3.2 The catch Command

Stop when an exception is raised - e.g. catch ActiveRecord::RecordInvalid

[20, 29] in ~/projects/rails-guide-example/app/controllers/posts_controller.rb
    20|   end
    21|
    22|   # POST /posts or /posts.json
    23|   def create
    24|     @post = Post.new(post_params)
=>  25|     debugger
    26|
    27|     respond_to do |format|
    28|       if @post.save!
    29|         format.html { redirect_to @post, notice: "Post was successfully created." }
=>#0    PostsController#create at ~/projects/rails-guide-example/app/controllers/posts_controller.rb:25
  #1    ActionController::BasicImplicitRender#send_action(method="create", args=[]) at ~/.rbenv/versions/3.0.1/lib/ruby/gems/3.0.0/gems/actionpack-7.0.0.alpha2/lib/action_controller/metal/basic_implicit_render.rb:6
  # and 72 frames (use `bt' command for all frames)
(rdbg) catch ActiveRecord::RecordInvalid    # command
#1  BP - Catch  "ActiveRecord::RecordInvalid"
(rdbg) c    # continue command
[75, 84] in ~/.rbenv/versions/3.0.1/lib/ruby/gems/3.0.0/gems/activerecord-7.0.0.alpha2/lib/active_record/validations.rb
    75|     def default_validation_context
    76|       new_record? ? :create : :update
    77|     end
    78|
    79|     def raise_validation_error
=>  80|       raise(RecordInvalid.new(self))
    81|     end
    82|
    83|     def perform_validations(options = {})
    84|       options[:validate] == false || valid?(options[:context])
=>#0    ActiveRecord::Validations#raise_validation_error at ~/.rbenv/versions/3.0.1/lib/ruby/gems/3.0.0/gems/activerecord-7.0.0.alpha2/lib/active_record/validations.rb:80
  #1    ActiveRecord::Validations#save!(options={}) at ~/.rbenv/versions/3.0.1/lib/ruby/gems/3.0.0/gems/activerecord-7.0.0.alpha2/lib/active_record/validations.rb:53
  # and 88 frames (use `bt' command for all frames)

Stop by #1  BP - Catch  "ActiveRecord::RecordInvalid"
3.3.3 The watch Command

Stop when the instance variable is changed - e.g. watch @_response_body

[20, 29] in ~/projects/rails-guide-example/app/controllers/posts_controller.rb
    20|   end
    21|
    22|   # POST /posts or /posts.json
    23|   def create
    24|     @post = Post.new(post_params)
=>  25|     debugger
    26|
    27|     respond_to do |format|
    28|       if @post.save!
    29|         format.html { redirect_to @post, notice: "Post was successfully created." }
=>#0    PostsController#create at ~/projects/rails-guide-example/app/controllers/posts_controller.rb:25
  #1    ActionController::BasicImplicitRender#send_action(method="create", args=[]) at ~/.rbenv/versions/3.0.1/lib/ruby/gems/3.0.0/gems/actionpack-7.0.0.alpha2/lib/action_controller/metal/basic_implicit_render.rb:6
  # and 72 frames (use `bt' command for all frames)
(rdbg) watch @_response_body    # command
#0  BP - Watch  #<PostsController:0x00007fce69ca5320> @_response_body =
(rdbg) c    # continue command
[173, 182] in ~/.rbenv/versions/3.0.1/lib/ruby/gems/3.0.0/gems/actionpack-7.0.0.alpha2/lib/action_controller/metal.rb
   173|       body = [body] unless body.nil? || body.respond_to?(:each)
   174|       response.reset_body!
   175|       return unless body
   176|       response.body = body
   177|       super
=> 178|     end
   179|
   180|     # Tests if render or redirect has already happened.
   181|     def performed?
   182|       response_body || response.committed?
=>#0    ActionController::Metal#response_body=(body=["<html><body>You are being <a href=\"ht...) at ~/.rbenv/versions/3.0.1/lib/ruby/gems/3.0.0/gems/actionpack-7.0.0.alpha2/lib/action_controller/metal.rb:178 #=> ["<html><body>You are being <a href=\"ht...
  #1    ActionController::Redirecting#redirect_to(options=#<Post id: 13, title: "qweqwe", content:..., response_options={:allow_other_host=>false}) at ~/.rbenv/versions/3.0.1/lib/ruby/gems/3.0.0/gems/actionpack-7.0.0.alpha2/lib/action_controller/metal/redirecting.rb:74
  # and 82 frames (use `bt' command for all frames)

Stop by #0  BP - Watch  #<PostsController:0x00007fce69ca5320> @_response_body =  -> ["<html><body>You are being <a href=\"http://localhost:3000/posts/13\">redirected</a>.</body></html>"]
(rdbg)
3.3.4 Breakpoint Options

In addition to different types of breakpoints, you can also specify options to achieve more advanced debugging workflow. Currently, the debugger supports 4 options:

  • do: <cmd or expr> - when the breakpoint is triggered, execute the given command/expression and continue the program:
    • break Foo#bar do: bt - when Foo#bar is called, print the stack frames
  • pre: <cmd or expr> - when the breakpoint is triggered, execute the given command/expression before stopping:
    • break Foo#bar pre: info - when Foo#bar is called, print its surrounding variables before stopping.
  • if: <expr> - the breakpoint only stops if the result of <expr> is true:
    • break Post#save if: params[:debug] - stops at Post#save if params[:debug] is also true
  • path: <path_regexp> - the breakpoint only stops if the event that triggers it (e.g. a method call) happens from the given path:
    • break Post#save if: app/services/a_service - stops at Post#save if the method call happens at a method matches Ruby regexp /app\/services\/a_service/.

Please also note that the first 3 options: do:, pre: and if: are also available for the debug statements we mentioned earlier. For example:

[2, 11] in ~/projects/rails-guide-example/app/controllers/posts_controller.rb
     2|   before_action :set_post, only: %i[ show edit update destroy ]
     3|
     4|   # GET /posts or /posts.json
     5|   def index
     6|     @posts = Post.all
=>   7|     debugger(do: "info")
     8|   end
     9|
    10|   # GET /posts/1 or /posts/1.json
    11|   def show
=>#0    PostsController#index at ~/projects/rails-guide-example/app/controllers/posts_controller.rb:7
  #1    ActionController::BasicImplicitRender#send_action(method="index", args=[]) at ~/.rbenv/versions/3.0.1/lib/ruby/gems/3.0.0/gems/actionpack-7.0.0.alpha2/lib/action_controller/metal/basic_implicit_render.rb:6
  # and 72 frames (use `bt' command for all frames)
(rdbg:binding.break) info
%self = #<PostsController:0x00000000017480>
@_action_has_layout = true
@_action_name = "index"
@_config = {}
@_lookup_context = #<ActionView::LookupContext:0x00007fce3ad336b8 @details_key=nil, @digest_cache=...
@_request = #<ActionDispatch::Request GET "http://localhost:3000/posts" for 127.0.0.1>
@_response = #<ActionDispatch::Response:0x00007fce3ad397e8 @mon_data=#<Monitor:0x00007fce3ad396a8>...
@_response_body = nil
@_routes = nil
@marked_for_same_origin_verification = true
@posts = #<ActiveRecord::Relation [#<Post id: 2, title: "qweqwe", content: "qweqwe", created_at: "...
@rendered_format = nil
3.3.5 Program Your Debugging Workflow

With those options, you can script your debugging workflow in one line like:

def create
  debugger(do: "catch ActiveRecord::RecordInvalid do: bt 10")
  # ...
end

And then the debugger will run the scripted command and insert the catch breakpoint

(rdbg:binding.break) catch ActiveRecord::RecordInvalid do: bt 10
#0  BP - Catch  "ActiveRecord::RecordInvalid"
[75, 84] in ~/.rbenv/versions/3.0.1/lib/ruby/gems/3.0.0/gems/activerecord-7.0.0.alpha2/lib/active_record/validations.rb
    75|     def default_validation_context
    76|       new_record? ? :create : :update
    77|     end
    78|
    79|     def raise_validation_error
=>  80|       raise(RecordInvalid.new(self))
    81|     end
    82|
    83|     def perform_validations(options = {})
    84|       options[:validate] == false || valid?(options[:context])
=>#0    ActiveRecord::Validations#raise_validation_error at ~/.rbenv/versions/3.0.1/lib/ruby/gems/3.0.0/gems/activerecord-7.0.0.alpha2/lib/active_record/validations.rb:80
  #1    ActiveRecord::Validations#save!(options={}) at ~/.rbenv/versions/3.0.1/lib/ruby/gems/3.0.0/gems/activerecord-7.0.0.alpha2/lib/active_record/validations.rb:53
  # and 88 frames (use `bt' command for all frames)

Once the catch breakpoint is triggered, it'll print the stack frames

Stop by #0  BP - Catch  "ActiveRecord::RecordInvalid"

(rdbg:catch) bt 10
=>#0    ActiveRecord::Validations#raise_validation_error at ~/.rbenv/versions/3.0.1/lib/ruby/gems/3.0.0/gems/activerecord-7.0.0.alpha2/lib/active_record/validations.rb:80
  #1    ActiveRecord::Validations#save!(options={}) at ~/.rbenv/versions/3.0.1/lib/ruby/gems/3.0.0/gems/activerecord-7.0.0.alpha2/lib/active_record/validations.rb:53
  #2    block in save! at ~/.rbenv/versions/3.0.1/lib/ruby/gems/3.0.0/gems/activerecord-7.0.0.alpha2/lib/active_record/transactions.rb:302

This technique can save you from repeated manual input and make the debugging experience smoother.

You can find more commands and configuration options from its documentation.

3.3.6 Autoloading Caveat

Debugging with debug works fine most of the time, but there's an edge case: If you evaluate an expression in the console that autoloads a namespace defined in a file, constants in that namespace won't be found.

For example, if the application has these two files:

# hotel.rb
class Hotel
end

# hotel/pricing.rb
module Hotel::Pricing
end

and Hotel is not yet loaded, then

(rdbg) p Hotel::Pricing

will raise a NameError. In some cases, Ruby will be able to resolve an unintended constant in a different scope.

If you hit this, please restart your debugging session with eager loading enabled (config.eager_load = true).

Stepping commands line next, continue, etc., do not present this issue. Namespaces defined implicitly only by subdirectories are not subject to this issue either.

See ruby/debug#408 for details.

4 Debug com a Gem web-console

A gem Web Console é um pouco semelhante à gem debug, porém é executada no navegador. Em qualquer página você pode solicitar um console no contexto da view ou do controller. O console deve ser renderizado próximo ao conteúdo HTML.

4.1 Console

Dentro de qualquer controller action ou view, você pode utilizar o console através da chamada do método console.

Por exemplo, em um controller:

class PostsController < ApplicationController
  def new
    console
    @post = Post.new
  end
end

Ou em uma view:

<% console %>

<h2>New Post</h2>

Isso vai renderizar um console dentro da view. Você não precisa se preocupar com a localização da chamada do método console; não será renderizado próximo ao elemento em que foi invocado, mas sim próximo ao seu conteúdo HTML.

O console executa código Ruby nativo: Você pode definir e instanciar classes personalizadas, criar novos models, e inspecionar variáveis.

Somente um console pode ser renderizado por request. De outra maneira web-console lança um erro na segunda invocação do método console.

4.2 Inspecionando Variáveis

Você pode chamar instance_variables para listar todas as variáveis de instância disponíveis em seu contexto. Se você deseja listar todas as variáveis locais, você pode fazer isso usando local_variables.

4.3 Configurações

  • config.web_console.allowed_ips: Lista autorizada de endereços e redes IPv4 ou IPv6 (padrões: 127.0.0.1/8, ::1).
  • config.web_console.whiny_requests: Registra uma mensagem quando a renderização do console é impedida (padrões: true).

Uma vez que web-console avalia código Ruby simples remotamente no servidor, não tente usar em produção.

5 Debug de Vazamentos de Memórias

Um aplicativo Ruby (on Rails ou não) pode vazar memória (memory leak) - tanto no código Ruby quanto no nível de código C.

Nesta seção, você aprenderá como encontrar e corrigir esses vazamentos usando ferramentas tais como o Valgrind.

5.1 Valgrind

Valgrind é um aplicativo para detectar vazamentos de memória baseados em C e condições de corrida.

Existem ferramentas Valgrind que podem detectar automaticamente muitos bugs de threading e gerenciamento de memória, e criar perfis de seus programas em detalhes. Por exemplo, se uma extensão C no intérprete chama malloc() mas não chama corretamente free(), essa memória não estará disponível até que o aplicativo seja encerrado.

Para obter mais informações sobre como instalar o Valgrind e usar com Ruby, consulte Valgrind and Ruby por Evan Weaver.

5.2 Encontrando um vazamento de memória

Há um excelente artigo (em inglês) sobre detecção e correção de vazamentos de memória no Derailed, que você pode ler aqui.

6 Plugins para Debug

Existem alguns plugins Rails para te ajudar a procurar erros e debugar a aplicação. Aqui está uma lista de plugins úteis para debug:

  • Query Trace Adiciona rastreamento de origem de consulta aos seus logs.
  • Exception Notifier Fornece um objeto mailer e um conjunto padrão de templates para enviar notificações por email quando ocorrerem erros em um aplicativo Rails.
  • Better Errors Substitui a página de erro padrão do Rails por uma nova contendo mais informações contextuais, como código-fonte e inspeção de variáveis.
  • RailsPanel extensão do Chrome para desenvolvimento Rails que encerrerá seu acompanhamento de development.log. Tenha todas as informações sobre as solicitações do seu aplicativo Rails no navegador - no painel Ferramentas do desenvolvedor. Fornece informações sobre db / renderização / tempos totais, lista de parâmetros, visualizações renderizadas e muito mais.
  • Pry Uma alternativa IRB e console de desenvolvedor em tempo de execução.

7 Referencias

Feedback

Você é incentivado a ajudar a melhorar a qualidade deste guia.

Por favor, contribua caso veja quaisquer erros, inclusive erros de digitação. Para começar, você pode ler nossa sessão de contribuindo com a documentação.

Você também pode encontrar conteúdo incompleto ou coisas que não estão atualizadas. Por favor, adicione qualquer documentação em falta na main do Rails. Certifique-se de checar o Edge Guides (en-US) primeiro para verificar se o problema já foi resolvido ou não no branch main. Verifique as Diretrizes do Guia Ruby on Rails para estilo e convenções.

Se, por qualquer motivo, você encontrar algo para consertar, mas não conseguir consertá-lo, por favor abra uma issue no nosso Guia.

E por último, mas não menos importante, qualquer tipo de discussão sobre a documentação do Ruby on Rails é muito bem vinda na forum oficial do Ruby on Rails e nas issues do Guia em português.


dark theme icon