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
(ori
) - Information about current frame. -
backtrace
(orbt
) - Backtrace (with additional information). -
outline
(oro
,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 shownum
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
(orb
)-
break
- list all breakpoints -
break <num>
- set a breakpoint on thenum
line of the current file -
break <file:num>
- set a breakpoint on thenum
line offile
-
break <Class#method>
orbreak <Class.method>
- set a breakpoint onClass#method
orClass.method
-
break <expr>.<method>
- sets a breakpoint on<expr>
result's<method>
method.
-
-
catch <Exception>
- set a breakpoint that'll stop whenException
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
(ordel
)-
delete
- delete all breakpoints -
delete <num>
- delete the breakpoint with idnum
-
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
- whenFoo#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
- whenFoo#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 atPost#save
ifparams[: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 atPost#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.