v7.0.0
更多資訊請前往 rubyonrails.org: 更多在 Ruby on Rails

除錯 Rails 應用程式

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

閱讀本指南後,您將瞭解:

1 View Helpers 用於除錯

一項常見任務是檢查變數的內容。 Rails 提供了三種不同的方法來做到這一點:

  • debug
  • to_yaml
  • inspect

1.1 debug

debug helper 將返回一個 <pre> 標籤,該標籤使用 YAML 格式呈現物件。這將從任何物件產生人類可讀的資料。例如,如果您在 view 中有此程式碼:

<%= 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 helper 方法以格式化輸出。這就是 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

顯示物件 values 的另一種有用方法是 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部分,新增以下any

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

2.3 傳送訊息

要寫入當前日誌,請使用 controller、model 或郵件程式中的 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

以下是執行此 controller action 時產生的日誌示例:

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)

像這樣新增額外的日誌記錄可以很容易地在日誌中搜索意外或異常的行為。如果您新增額外的日誌記錄,請務必合理使用日誌級別,以避免用無用的瑣事填充您的生產日誌。

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 session 中執行 ActiveRecord::Base.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 標記日誌

在執行多使用者、多帳戶應用程式時,它通常很有用 能夠使用一些自定義規則過濾日誌。 TaggedLogging 在 Active Support 中,透過使用子域、請求 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"

2.6 日誌對效能的影響

日誌記錄總是會對 Rails 應用程式的效能產生很小的影響, 特別是在登入到磁碟時。此外,還有一些微妙之處:

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

另一個潛在的陷阱是在您的程式碼中對 Logger 的呼叫過多:

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

在上面的例子中,即使允許,也會有效能影響 輸出級別不包括除錯。原因是Ruby要求值 這些字串,包括實例化有點重的 String 物件 並插入變數。

因此,建議將塊傳遞給記錄器方法,因為它們是 僅在輸出級別與允許級別相同或包含在允許級別時進行評估 (即延遲載入)。重寫的相同程式碼將是:

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

塊的內容,因此字串插值,只是 評估是否啟用除錯。這種效能節省只是真的 在大量日誌記錄中很明顯,但這是一個很好的實踐。

資訊:本節由 Jon Cairns 在 StackOverflow 上的回答 撰寫 它在 cc by-sa 4.0 下獲得許可。

3 使用 debug gem 進行除錯

當您的程式碼以意想不到的方式執行時,您可以嘗試列印到日誌或 控制檯來診斷問題。不幸的是,有時這 某種錯誤 tracking 不能有效地找到問題的根本原因。 當您真正需要進入正在執行的原始碼時,偵錯程式 是你最好的伴侶。

如果您想了解 Rails 原始碼,偵錯程式也可以幫助您 但不知道從哪裡開始。只需除錯對您的應用程式的任何請求,然後 使用本指南瞭解如何從您編寫的程式碼中移到 底層 Rails 程式碼。

Rails 7 在產生的新應用程式的 Gemfile 中包含 debug gem 透過 CRuby。預設情況下,它在 developmenttest 環境中準備就緒。 請檢查其 文件 以瞭解用法。

4 使用 web-console gem 進行除錯

Web Console 有點像 debug,但它執行在瀏覽器中。在任何頁面你 正在開發中,您可以在 view 或 controller。控制檯將呈現在您的 HTML 內容旁邊。

4.1 控制檯

在任何 controller action 或 view 中,您可以透過以下方式呼叫控制檯 呼叫 console 方法。

例如,在 controller 中:

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

或者在 view 中:

<% console %>

<h2>New Post</h2>

這將在您的 view 中呈現一個控制檯。你不需要關心 console 呼叫的位置;它不會被當場渲染 呼叫但在您的 HTML 內容旁邊。

控制檯執行純Ruby程式碼:可以定義和實例化 自定義類,建立新的 models,並檢查變數。

每個請求只能渲染一個控制檯。否則 web-console 將在第二次 console 呼叫時引發錯誤。

4.2 檢查變數

您可以呼叫 instance_variables 列出所有實例變數 在您的上下文中可用。如果你想列出所有的區域性變數,你可以 用 local_variables 做到這一點。

4.3 設定

  • config.web_console.allowed_ips:IPv4 或 IPv6 的授權列表 地址和網路(預設值:127.0.0.1/8, ::1)。
  • config.web_console.whiny_requests:控制檯渲染時記錄一條訊息 被阻止(預設值:true)。

由於 web-console 在伺服器上遠端評估普通的 Ruby 程式碼,所以不要嘗試 在生產中使用它。

5 除錯記憶體洩漏

Ruby 應用程式(在 Rails 上或不在 Rails 上)可能會洩漏記憶體——無論是在 Ruby 程式碼中 或在 C 程式碼級別。

在本節中,您將學習如何使用工具查詢和修復此類洩漏 比如瓦爾格林。

5.1 Valgrind

Valgrind 是一個檢測根據C的記憶體的應用程式 洩漏和比賽條件。

有Valgrind工具可以自動檢測很多記憶體管理 和執行緒錯誤,並詳細分析您的程式。例如,如果一個 C 直譯器中的擴充套件呼叫 malloc() 但沒有正確呼叫 free(),在應用程式終止之前,此記憶體將不可用。

有關如何安裝 Valgrind 並與 Ruby 一起使用的更多資訊,請參閱 Valgrind 和 Ruby 透過埃文韋弗。

5.2 查詢記憶體洩漏

Derailed 上有一篇關於檢測和修復記憶體洩漏的優秀文章,您可以在這裡閱讀

6 除錯外掛

有一些 Rails 外掛可以幫助您查詢錯誤和除錯您的 應用。以下是用於除錯的有用外掛列表:

  • Query Trace 新增查詢 源頭跟蹤到您的日誌。
  • 異常通知 提供郵件程式物件和用於傳送電子郵件的預設模板集 Rails 應用程式中發生錯誤時的通知。
  • 更好的錯誤 替換了 標準的 Rails 錯誤頁面,帶有一個包含更多上下文資訊的新頁面, 比如原始碼和變數檢查。
  • RailsPanel Rails 的 Chrome 擴充套件 development 將結束您對 development.log 的拖尾。擁有所有資訊 關於您在瀏覽器中的 Rails 應用請求——在開發者工具面板中。 提供對資料庫/渲染/總時間、引數列表、渲染的 views 和 更多的。
  • Pry IRB 替代和執行時開發者控制檯。

7 參考

回饋

我們鼓勵您幫助提高本指南的品質。

如果您發現任何拼寫錯誤或資訊錯誤,請提供回饋。 要開始回饋,您可以閱讀我們的 回饋 部分。

您還可能會發現不完整的內容或不是最新的內容。 請務必為 main 新增任何遺漏的文件。假設是 非穩定版指南(edge guides) 請先驗證問題是否已經在主分支上解決。 請前往 Ruby on Rails 指南寫作準則 查看寫作風格和慣例。

如果由於某種原因您發現要修復的內容但無法自行修補,請您 提出 issue

關於 Ruby on Rails 的任何類型的討論歡迎提供任何文件至 rubyonrails-docs 討論區