v7.1.3.2
更多資訊請至 rubyonrails.org: 更多 Ruby on Rails

除錯 Rails 應用程式

本指南介紹除錯 Ruby on Rails 應用程式的技術。

閱讀本指南後,您將知道

1 除錯的檢視輔助程式

一個常見的任務是檢查變數的內容。Rails 提供了三種不同的方法來執行此操作

  • debug
  • to_yaml
  • inspect

1.1 debug

debug 輔助函式會傳回一個 <pre> 標籤,使用 YAML 格式來呈現物件。這會從任何物件產生人類可讀的資料。例如,如果在檢視中具有此程式碼

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

你會看到類似這樣

--- !ruby/object Article
attributes:
  updated_at: 2008-09-05 22:55:47
  body: It's a very helpful guide for debugging your Rails app.
  title: Rails debugging guide
  published: t
  id: "1"
  created_at: 2008-09-05 22:55:47
attributes_cache: {}


Title: Rails debugging guide

1.2 to_yaml

或者,對任何物件呼叫 to_yaml 會將其轉換為 YAML。你可以將此轉換的物件傳遞到 simple_format 輔助函式方法,以格式化輸出。這就是 debug 發揮其作用的方式。

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

上述程式碼會呈現類似這樣

--- !ruby/object Article
attributes:
updated_at: 2008-09-05 22:55:47
body: It's a very helpful guide for debugging your Rails app.
title: Rails debugging guide
published: t
id: "1"
created_at: 2008-09-05 22:55:47
attributes_cache: {}

Title: Rails debugging guide

1.3 inspect

另一個用於顯示物件值的有用方法是 inspect,特別是在使用陣列或雜湊時。這會將物件值列印為字串。例如

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

會呈現

[1, 2, 3, 4, 5]

Title: Rails debugging guide

2 記錄器

在執行階段將資訊儲存到記錄檔中也可能很有用。Rails 為每個執行階段環境維護一個獨立的記錄檔。

2.1 記錄器是什麼?

Rails 使用 ActiveSupport::Logger 類別來撰寫記錄資訊。其他記錄器,例如 Log4r,也可以替換。

你可以指定 config/application.rb 或任何其他環境檔案中的替代記錄器,例如

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

或在 Initializer 區段中,新增下列任何項目

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

預設情況下,每個記錄都會在 Rails.root/log/ 下建立,而記錄檔會以應用程式執行的環境命名。

2.2 記錄層級

當記錄某些內容時,如果訊息的記錄層級等於或高於已設定的記錄層級,則會將其列印到對應的記錄中。如果你想了解目前的記錄層級,你可以呼叫 Rails.logger.level 方法。

可用的記錄層級為::debug:info:warn:error:fatal:unknown,分別對應於 0 至 5 的記錄層級數字。如要變更預設記錄層級,請使用

config.log_level = :warn # In any environment initializer, or
Rails.logger.level = 0 # at any time

這在您想要在開發或暫存環境中記錄,而不會因為不必要的資訊而淹沒您的生產記錄時很有用。

預設 Rails 記錄層級為 :debug。不過,在預設產生的 config/environments/production.rb 中,production 環境已設定為 :info

2.3 傳送訊息

若要寫入目前的記錄,請在控制器、模型或郵件程式中使用 logger.(debug|info|warn|error|fatal|unknown) 方法

logger.debug "Person attributes hash: #{@person.attributes.inspect}"
logger.info "Processing the request..."
logger.fatal "Terminating application, raised unrecoverable error!!!"

以下是搭配額外記錄的範例方法

class ArticlesController < ApplicationController
  # ...

  def create
    @article = Article.new(article_params)
    logger.debug "New article: #{@article.attributes.inspect}"
    logger.debug "Article should be valid: #{@article.valid?}"

    if @article.save
      logger.debug "The article was saved and now the user is going to be redirected..."
      redirect_to @article, notice: 'Article was successfully created.'
    else
      render :new, status: :unprocessable_entity
    end
  end

  # ...

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

以下是執行此控制器動作時產生的記錄範例

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 https://127.0.0.1:3000/articles/1
Completed 302 Found in 4ms (ActiveRecord: 0.8ms)

加入額外的記錄,例如這樣,可以輕鬆搜尋記錄中意外或不尋常的行為。如果您加入額外的記錄,請務必明智地使用記錄層級,以避免在生產記錄中填入無用的瑣事。

2.4 詳細查詢記錄

在記錄中查看資料庫查詢輸出時,可能無法立即清楚為什麼在呼叫單一方法時會觸發多個資料庫查詢

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">

bin/rails console 會話中執行 ActiveRecord.verbose_query_logs = true 以啟用詳細查詢記錄,並再次執行方法後,就會很明顯哪一行程式碼產生了所有這些個別的資料庫呼叫

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">

在每個資料庫陳述式下方,您會看到指向導致資料庫呼叫的方法的特定來源檔案名稱(和行號)的箭頭。這可以協助您找出並解決由 N+1 查詢(產生多個額外查詢的單一資料庫查詢)所造成的效能問題。

在 Rails 5.2 之後的開發環境記錄中,預設會啟用詳細查詢記錄。

我們建議不要在生產環境中使用此設定。它依賴於 Ruby 的 Kernel#caller 方法,該方法傾向於分配大量記憶體來產生方法呼叫的堆疊追蹤。請改用查詢記錄標籤(見下文)。

2.5 詳細排程記錄

與上述「詳細查詢記錄」類似,允許列印排程背景工作的來源位置。

在開發環境中預設啟用。若要在其他環境中啟用,請在 application.rb 或任何環境初始化程式中加入

config.active_job.verbose_enqueue_logs = true

如同詳細查詢記錄,建議不要在生產環境中使用。

3 SQL 查詢註解

SQL 陳述式可以用包含執行時間資訊的標籤註解,例如控制器或工作的名稱,以追蹤問題查詢回到產生這些陳述式的應用程式區域。當您記錄慢速查詢(例如 MySQLPostgreSQL)時,檢視目前執行的查詢,或用於端對端追蹤工具時,這很有用。

若要啟用,請在 application.rb 或任何環境初始化程式中加入

config.active_record.query_log_tags_enabled = true

預設會記錄應用程式名稱、控制器名稱和動作,或工作名稱。預設格式為 SQLCommenter。例如

Article Load (0.2ms)  SELECT "articles".* FROM "articles" /*application='Blog',controller='articles',action='index'*/

Article Update (0.3ms)  UPDATE "articles" SET "title" = ?, "updated_at" = ? WHERE "posts"."id" = ? /*application='Blog',job='ImproveTitleJob'*/  [["title", "Improved Rails debugging guide"], ["updated_at", "2022-10-16 20:25:40.091371"], ["id", 1]]

ActiveRecord::QueryLogs 的行為可以修改為包含任何有助於連接 SQL 查詢的點,例如應用程式記錄的請求和工作 ID、帳戶和租戶識別碼等。

3.1 標籤記錄

在執行多使用者、多帳戶應用程式時,通常需要使用一些自訂規則來過濾記錄。Active Support 中的 TaggedLogging 可協助您執行此操作,方法是使用子網域、請求 ID 和任何其他有助於偵錯此類應用程式的項目來標記記錄列。

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"

3.2 記錄對效能的影響

記錄始終會對 Rails 應用程式的效能產生輕微影響,特別是記錄到磁碟時。此外,還有一些細微差別

使用 :debug 層級會比 :fatal 有更大的效能損失,因為會評估並寫入更多字串至記錄檔輸出(例如磁碟)。

另一個潛在的陷阱是在程式碼中呼叫 Logger 的次數太多

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

在上述範例中,即使允許的輸出層級不包含偵錯,效能仍會受到影響。原因是 Ruby 必須評估這些字串,其中包括建立有點吃重的 String 物件並內插變數。

因此,建議將區塊傳遞給記錄器方法,因為這些方法只有在輸出層級與允許的層級相同(例如延遲載入)或包含在允許的層級中時才會評估。重新編寫的程式碼如下

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

只有在啟用偵錯時,才會評估區塊的內容,因此才會內插字串。這種效能節省只有在大量記錄時才會真正明顯,但這是一個值得採用的好習慣。

此部分由 Jon Cairns 在 Stack Overflow 回答 中撰寫,並根據 cc by-sa 4.0 授權。

4 使用 debug Gem 進行偵錯

當你的程式碼行為異常時,你可以嘗試列印至記錄檔或主控台以診斷問題。不幸的是,有時這種錯誤追蹤無法有效找出問題的根本原因。當你實際需要進入正在執行的原始碼時,偵錯器是你最好的夥伴。

如果你想了解 Rails 原始碼但不知道從何開始,偵錯器也可以提供協助。只要偵錯應用程式的任何要求,然後使用本指南瞭解如何從你編寫的程式碼轉移到基礎 Rails 程式碼。

Rails 7 在由 CRuby 生成的應用程式的 Gemfile 中包含 debug 程式庫。預設情況下,它在 developmenttest 環境中準備就緒。請查看其 文件 以了解用法。

4.1 進入除錯階段

預設情況下,在需要 debug 程式庫後,將開始除錯階段,這會在您的應用程式啟動時發生。但別擔心,這個階段不會干擾您的應用程式。

若要進入除錯階段,您可以使用 binding.break 及其別名:binding.bdebugger。下列範例將使用 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

一旦您的應用程式評估除錯陳述,它將進入除錯階段

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.1.0.alpha/lib/action_controller/metal/basic_implicit_render.rb:6
  # and 72 frames (use `bt' command for all frames)
(rdbg)

您可以隨時退出除錯階段,並使用 continue(或 c)命令繼續執行您的應用程式。或者,若要同時退出除錯階段和您的應用程式,請使用 quit(或 q)命令。

4.2 內容

在進入除錯階段後,您可以輸入 Ruby 程式碼,就像您在 Rails 主控台或 IRB 中一樣。

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

您也可以使用 ppp 命令來評估 Ruby 運算式,這在變數名稱與除錯器命令衝突時很有用。

(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)

除了直接評估外,除錯器還可協助您透過不同的命令收集大量資訊,例如

  • info(或 i) - 目前框架的資訊。
  • backtrace(或 bt) - 呼叫堆疊(含額外資訊)。
  • outline(或 ols) - 目前範圍內可用的方法、常數、區域變數和執行個體變數。

4.2.1 info 命令

info 提供從目前框架可見的區域和執行個體變數值的概觀。

(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 "https://127.0.0.1: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

4.2.2 backtrace 指令

在沒有任何選項的情況下使用時,backtrace 會列出堆疊上的所有框架

=>#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.1.0.alpha/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.1.0.alpha/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.1.0.alpha/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.1.0.alpha/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.1.0.alpha/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.1.0.alpha/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.1.0.alpha/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.1.0.alpha/lib/active_support/callbacks.rb:127
  ..... and more

每個框架都包含

  • 框架識別碼
  • 呼叫位置
  • 其他資訊(例如區塊或方法引數)

這將讓您很好地了解應用程式中正在發生的事情。但是,您可能會注意到

  • 框架太多(在 Rails 應用程式中通常超過 50 個)。
  • 大多數框架來自 Rails 或您使用的其他函式庫。

backtrace 指令提供 2 個選項來協助您篩選框架

  • backtrace [num] - 僅顯示 num 個框架,例如 backtrace 10
  • backtrace /pattern/ - 僅顯示識別碼或位置符合模式的框架,例如 backtrace /MyModel/

也可以一起使用這些選項:backtrace [num] /pattern/

4.2.3 outline 指令

outline 類似於 pryirbls 指令。它將顯示您從目前範圍可以存取的內容,包括

  • 局部變數
  • 執行個體變數
  • 類別變數
  • 方法及其來源
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_open_redirects

4.3 中斷點

有許多方法可以在偵錯器中插入和觸發中斷點。除了直接在程式碼中新增偵錯陳述式(例如 debugger)之外,您也可以使用指令插入中斷點

  • break(或 b
    • break - 列出所有中斷點
    • break <num> - 在目前檔案的 num 行設定中斷點
    • break <file:num> - 在 filenum 行設定中斷點
    • break <Class#method>break <Class.method> - 在 Class#methodClass.method 設定中斷點
    • break <expr>.<method> - 在 <expr> 結果的 <method> 方法上設定中斷點。
  • catch <Exception> - 設定一個中斷點,當 Exception 發生時將會停止
  • watch <@ivar> - 設定一個中斷點,當目前物件的 @ivar 結果改變時將會停止(這很慢)

要移除它們,可以使用

  • delete(或 del
    • delete - 刪除所有中斷點
    • delete <num> - 刪除 id 為 num 的中斷點

4.3.1 break 指令

在指定的行號上設定中斷點 - 例如 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)

在給定的方法呼叫上設定中斷點 - 例如 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

4.3.2 catch 指令

當發生例外時停止 - 例如 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"

4.3.3 watch 指令

當實例變數改變時停止 - 例如 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=\"https://127.0.0.1:3000/posts/13\">redirected</a>.</body></html>"]
(rdbg)

4.3.4 中斷點選項

除了不同類型的中斷點外,你也可以指定選項來達成更進階的除錯工作流程。目前,除錯器支援 4 個選項

  • do: <cmd or expr> - 當中斷點觸發時,執行給定的指令/表達式並繼續程式
    • break Foo#bar do: bt - 當 Foo#bar 被呼叫時,列印堆疊框架
  • pre: <cmd or expr> - 當中斷點觸發時,在停止之前執行給定的指令/表達式
    • break Foo#bar pre: info - 當 Foo#bar 被呼叫時,在停止之前列印其周圍的變數。
  • if: <expr> - 只有當 <expr> 的結果為真時,中斷點才會停止
    • break Post#save if: params[:debug] - 如果 params[:debug] 也為 true,則在 Post#save 中停止
  • path: <path_regexp> - 只有當觸發中斷點的事件(例如方法呼叫)從給定的路徑發生時,中斷點才會停止
    • break Post#save if: app/services/a_service - 如果方法呼叫發生在與 Ruby 正規表示法 /app\/services\/a_service/ 相符的方法中,則在 Post#save 中停止。

請注意,前 3 個選項:do:pre:if: 也可用於我們前面提到的偵錯陳述式。例如

[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 "https://127.0.0.1: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

4.3.5 編寫偵錯工作流程

使用這些選項,您可以像這樣在一行中編寫偵錯工作流程

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

然後偵錯器將執行腳本命令並插入 catch 中斷點

(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)

一旦觸發 catch 中斷點,它將列印堆疊框架

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

此技術可以讓您免於重複手動輸入,並使偵錯體驗更順暢。

您可以在其 文件 中找到更多命令和配置選項。

5 使用 web-console Gem 進行偵錯

Web Console 有點像 debug,但它在瀏覽器中執行。您可以在任何頁面上的檢視或控制器中請求控制台。控制台將會呈現在您的 HTML 內容旁邊。

5.1 控制台

在任何控制器動作或檢視中,您都可以透過呼叫 console 方法來呼叫控制台。

例如,在控制器中

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

或在檢視中

<% console %>

<h2>New Post</h2>

這將在您的檢視中呈示一個控制台。您不必在意 console 呼叫的位置;它不會在呼叫點上呈示,而會呈現在您的 HTML 內容旁邊。

控制台執行純 Ruby 程式碼:您可以定義和實例化自訂類別、建立新的模型,以及檢查變數。

每個請求只能呈示一個控制台。否則,web-console 會在第二次 console 呼叫時引發錯誤。

5.2 檢查變數

你可以呼叫 instance_variables 來列出在你的內容中可用的所有實例變數。如果你想列出所有區域變數,你可以使用 local_variables 來達成。

5.3 設定

  • config.web_console.allowed_ips:已授權的 IPv4 或 IPv6 位址和網路清單(預設:127.0.0.1/8, ::1)。
  • config.web_console.whiny_requests:當主控台呈現受到阻擋時,記錄一則訊息(預設:true)。

由於 web-console 在伺服器上遠端評估純 Ruby 程式碼,請不要嘗試在實際環境中使用它。

6 除錯記憶體外洩

一個 Ruby 應用程式(基於 Rails 或非 Rails),可能會外洩記憶體 — 無論是在 Ruby 程式碼或 C 程式碼層級。

在本節中,你將學習如何使用 Valgrind 等工具來尋找和修復此類外洩。

6.1 Valgrind

Valgrind 是一個用於偵測基於 C 的記憶體外洩和競爭條件的應用程式。

有一些 Valgrind 工具可以自動偵測許多記憶體管理和執行緒錯誤,並詳細分析你的程式。例如,如果直譯器中的 C 擴充套件呼叫 malloc() 但沒有正確呼叫 free(),則此記憶體將無法使用,直到應用程式終止為止。

有關如何安裝 Valgrind 並與 Ruby 搭配使用的進一步資訊,請參閱 Evan Weaver 的 Valgrind 和 Ruby

6.2 找出記憶體外洩

在 Derailed 中有一篇關於偵測和修復記憶體外洩的出色文章,你可以在此處閱讀

7 除錯外掛程式

有一些 Rails 外掛程式可以幫助你找出錯誤並除錯你的應用程式。以下是幾個有用的除錯外掛程式清單

  • Query Trace 將查詢來源追蹤新增到你的記錄中。
  • Exception Notifier 提供一個郵件物件和一組預設範本,用於在 Rails 應用程式中發生錯誤時傳送電子郵件通知。
  • Better Errors 以一個包含更多背景資訊的新錯誤頁面取代標準的 Rails 錯誤頁面,例如原始程式碼和變數檢查。
  • RailsPanel Rails 開發的 Chrome 擴充功能,可以讓您終止追蹤 development.log。在瀏覽器中取得 Rails 應用程式要求的所有資訊,位於開發人員工具面板中。提供資料庫/呈現/總時間、參數清單、呈現的檢視等深入資訊。
  • Pry 一個 IRB 替代方案和執行時期開發人員主控台。

8 個參考

回饋

我們鼓勵您協助提升本指南的品質。

如果您發現任何錯字或事實錯誤,請協助我們修正。首先,您可以閱讀我們的 文件貢獻 區段。

您也可能會發現不完整或過時的內容。請務必新增任何遺漏的 main 文件。請務必先查看 Edge Guides,以驗證問題是否已在主分支中修正。查看 Ruby on Rails 指南準則,了解風格和慣例。

如果您發現需要修正的地方,但無法自行修補,請 開啟問題

最後但並非最不重要的一點,我們非常歡迎在 官方 Ruby on Rails 論壇 上針對 Ruby on Rails 文件進行任何討論。