Rails アプリケーションのデバッグ

本ガイドでは、Ruby on Rails アプリケーションのさまざまなデバッグ技法をご紹介します。

このガイドの内容:

1 デバッグに利用できるビューヘルパー

変数にどんな値が入っているかを確認する作業は何かと必要になります。Rails では以下の3つのメソッドを利用できます。

  • 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

インスタンス変数や、その他のあらゆるオブジェクトやメソッドをYAML形式で表示します。以下のような感じで使用します。

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

to_yamlメソッドは、メソッドをYAML形式に変換して読みやすくし、simple_formatヘルパーは出力結果をコンソールのように行ごとに改行します。これがdebugメソッドのマジックです。

これにより、以下のような結果がビューに表示されます。

--- !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など別のロガーに差し替えることもできます。

別のロガーの指定は、environment.rbまたは環境ごとの設定ファイルで行います。

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

あるいは、Initializerセクションに以下の いずれか を追加します。

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

ログの保存場所は、デフォルトではRails.root/log/になります。ログのファイル名は、アプリケーションが実行されるときの環境 (development/test/productionなど) が使用されます。

2.2 ログの出力レベル

ログに出力されるメッセージのログレベルが、設定済みのログレベル以上になった場合に、対応するログファイルにそのメッセージが出力されます。現在のログレベルを知りたい場合は、Rails.logger.levelメソッドを呼び出します。

指定可能なログレベルは:debug:info:warn:error:fatal:unknownの6つであり、それぞれ0から5までの数字に対応します。デフォルトのログレベルを変更するには以下のようにします。

config.log_level = :warn # 環境ごとのイニシャライザで使用可能
Rails.logger.level = 0 # いつでも使用可能

これは、development環境やstaging環境ではログを出力し、production環境では不要な情報をログに出力したくない場合などに便利です。

Railsのデフォルトログレベルは全環境でdebugです。

2.3 メッセージ送信

コントローラ、モデル、メイラーから現在のログに書き込みたい場合は、logger.(debug|info|warn|error|fatal)を使用します。

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(params[:article])
    logger.debug "新しい記事: #{@article.attributes.inspect}"
    logger.debug "記事が正しいかどうか: #{@article.valid?}"

    if @article.save
      flash[:notice] =  'Article was successfully created.'
      logger.debug "記事は正常に保存され、ユーザーをリダイレクト中..."
      redirect_to(@article)
    else
      render action: "new"
    end
  end

  # ...
end

上のコントローラのアクションを実行すると、以下のようなログが生成されます。

Processing ArticlesController#create (for 127.0.0.1 at 2008-09-08 11:52:54) [POST]
  Session ID: BAh7BzoMY3NyZl9pZCIlMDY5MWU1M2I1ZDRjODBlMzkyMWI1OTg2NWQyNzViZjYiCmZsYXNoSUM6J0FjdGl
vbkNvbnRyb2xsZXI6OkZsYXNoOjpGbGFzaEhhc2h7AAY6CkB1c2VkewA=--b18cd92fba90eacf8137e5f6b3b06c4d724596a4
  Parameters: {"commit"=>"Create", "article"=>{"title"=>"Debugging Rails",
"body"=>"I'm learning how to print in logs!!!", "published"=>"0"},
"authenticity_token"=>"2059c1286e93402e389127b1153204e0d1e275dd", "action"=>"create", "controller"=>"articles"}
新しい記事: {"updated_at"=>nil, "title"=>"Debugging Rails", "body"=>"I'm learning how to print in logs!!!",
"published"=>false, "created_at"=>nil}
記事が正しいかどうか: true
  Article Create (0.000443)   INSERT INTO "articles" ("updated_at", "title", "body", "published",
"created_at") VALUES('2008-09-08 14:52:54', 'Debugging Rails',
'I''m learning how to print in logs!!!', 'f', '2008-09-08 14:52:54')
記事は正常に保存され、ユーザーをリダイレクト中...
Redirected to # Article:0x20af760>
Completed in 0.01224 (81 reqs/sec) | DB: 0.00044 (3%) | 302 Found [http://localhost/articles]

このようにログに独自の情報を追加すると、予想外の異常な動作をログで見つけやすくなります。ログに独自の情報を追加する場合は、productionログが意味のない大量のメッセージでうずまることのないよう、適切なログレベルを使用するようにしてください。

2.4 タグ付きログの出力

ユーザーとアカウントを多数使用するアプリケーションを実行するときに、何らかのカスタムルールを設定してログをフィルタできると便利です。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"

2.5 ログがパフォーマンスに与える影響

ログ出力がRailsアプリのパフォーマンスに与える影響は常にわずかです。ログをディスクに保存する場合は特にそうです。ただし、場合によってはそうとは言い切れないことがあります。

ログレベル:debugは、:fatalと比べてはるかに多くの文字列が評価および(ディスクなどに)出力されるため、パフォーマンスに与える影響がずっと大きくなります。

他にも、以下のようにLoggerの呼び出しを多数実行した場合には落とし穴に注意する必要があります。

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

上の例では、たとえログ出力レベルをdebugにしなかった場合でもパフォーマンスが低下します。その理由は、上のコードでは文字列を評価する必要があり、その際に比較的動作が重いStringオブジェクトのインスタンス化と、実行に時間のかかる変数の式展開 (interpolation) が行われているからです。 したがって、ロガーメソッドに渡すものはブロックの形にしておくことをお勧めします。ブロックとして渡しておけば、ブロックの評価は出力レベルが設定レベル以上になった場合にしか行われない (遅延読み込みなど) ためです。これに従って上のコードを書き直すと以下のようになります。

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

渡したブロックの内容 (ここでは文字列の式展開) は、debug が有効になっている場合にしか評価されません。この方法によるパフォーマンスの改善は、大量のログを出力しているときでないとそれほど実感できないかもしれませんが、それでも採用する価値があります。

3 byebug gemを使用してデバッグする

コードが期待どおりに動作しない場合は、ログやコンソールに出力して問題を診断することができます。ただし、この方法ではエラー追跡を何度も繰り返さねばならず、根本的な原因を突き止めるには能率がよいとは言えません。 実行中のコードに探りを入れる必要があるのであれば、最も頼りになるのはやはりデバッガーです。

デバッガーは、Railsのソースコードを追うときに、そのコードをどこで開始するのかがを知りたいときにも有用です。アプリケーションへのリクエストをすべてデバッグし、自分が書いたコードからRailsのもっと深いところへダイブする方法を本ガイドから学びましょう。

3.1 セットアップ

byebug gemを使用すると、Railsコードにブレークポイントを設定してステップ実行できます。次を実行するだけでインストールできます。

$ gem install byebug

後はRailsアプリケーション内でbyebugメソッドを呼び出せばいつでもデバッガーを起動できます。

以下に例を示します。

class PeopleController < ApplicationController
  def new
    byebug
    @person = Person.new
  end
end

3.2 シェル

アプリケーションでbyebugを呼び出すと、アプリケーションサーバーを実行しているターミナルウィンドウ内のデバッガーシェルで即座にデバッガーが起動し、(byebug)というプロンプトが表示されます。 実行しようとしている行の前後のコードがプロンプトの前に表示され、'=>'で現在の行が示されます。以下に例を示します。

[1, 10] in /PathTo/project/app/controllers/articles_controller.rb
    3:
    4:   # GET /articles
    5:   # GET /articles.json
    6:   def index
    7:     byebug
=>  8:     @articles = Article.find_recent
    9:
   10:     respond_to do |format|
   11:       format.html # index.html.erb
   12:       format.json { render json: @articles }

(byebug)

ブラウザからのリクエストによってデバッグ行に到達した場合、リクエストしたブラウザのタブ上の処理は、デバッガが終了してリクエストの処理が完全に終了するまで中断します。

以下に例を示します。

=> Booting WEBrick
=> Rails 5.0.0 application starting in development on http://0.0.0.0:3000
=> Run `rails server -h` for more startup options
=> Notice: server is listening on all interfaces (0.0.0.0). Consider using 127.0.0.1 (--binding option)
=> Ctrl-C to shutdown server
[2014-04-11 13:11:47] INFO  WEBrick 1.3.1
[2014-04-11 13:11:47] INFO  ruby 2.1.1 (2014-02-24) [i686-linux]
[2014-04-11 13:11:47] INFO  WEBrick::HTTPServer#start: pid=6370 port=3000


Started GET "/" for 127.0.0.1 at 2014-04-11 13:11:48 +0200
  ActiveRecord::SchemaMigration Load (0.2ms)  SELECT "schema_migrations".* FROM "schema_migrations"
Processing by ArticlesController#index as HTML

[3, 12] in /PathTo/project/app/controllers/articles_controller.rb
    3:
    4:   # GET /articles
    5:   # GET /articles.json
    6:   def index
    7:     byebug
=>  8:     @articles = Article.find_recent
    9:
   10:     respond_to do |format|
   11:       format.html # index.html.erb
   12:       format.json { render json: @articles }

(byebug)

それではアプリケーションの奥深くにダイブしてみましょう。まずはデバッガーのヘルプを表示してみるのがよいでしょう。helpと入力します。

(byebug) help

byebug 2.7.0

Type 'help <command-name>' for help on a specific command

Available commands:
backtrace  delete   enable  help       list    pry next  restart  source     up
break      disable  eval    info       method  ps        save     step       var
catch      display  exit    interrupt  next    putl      set      thread
condition  down     finish  irb        p       quit      show     trace
continue   edit     frame   kill       pp      reload    skip     undisplay

個別のコマンドのヘルプを表示するには、デバッガーのプロンプトでhelp <コマンド名>と入力します。(例: help list)デバッグ用コマンドは、他のコマンドと区別できる程度に短縮できます。たとえばlistコマンドの代わりにlと入力することもできます。

前の10行を表示するには、list- (または l-) と入力します。

(byebug) l-

[1, 10] in /PathTo/project/app/controllers/articles_controller.rb
   1  class ArticlesController < ApplicationController
   2    before_action :set_article, only: [:show, :edit, :update, :destroy]
   3
   4    # GET /articles
   5    # GET /articles.json
   6    def index
   7      byebug
   8      @articles = Article.find_recent
   9
   10      respond_to do |format|

上に示したように、該当のファイルに移動して、byebug呼び出しを追加した行の前を表示できます。最後に、list=と入力して現在の位置を再び表示してみましょう。

(byebug) list=

[3, 12] in /PathTo/project/app/controllers/articles_controller.rb
    3:
    4:   # GET /articles
    5:   # GET /articles.json
    6:   def index
    7:     byebug
=>  8:     @articles = Article.find_recent
    9:
   10:     respond_to do |format|
   11:       format.html # index.html.erb
   12:       format.json { render json: @articles }

(byebug)

3.3 コンテキスト

アプリケーションのデバッグ中は、通常と異なる「コンテキスト」に置かれます。具体的には、スタックの別の部分を通って進むコンテキストです。

デバッガーは、停止位置やイベントに到達するときに「コンテキスト」を作成します。作成されたコンテキストには、中断しているプログラムに関する情報が含まれており、デバッガーはこの情報を使用して、フレームスタックの検査やデバッグ中のプログラムにおける変数の評価を行います。また、デバッグ中のプログラムが停止している位置の情報もコンテキストに含まれます。

backtraceコマンド (またはそのエイリアスであるwhereコマンド) を使用すれば、いつでもアプリケーションのバックトレースを出力できます。これは、コードのその位置に至るまでの経過を知るうえで非常に便利です。コードのある行にたどりついたとき、その経緯を知りたければbacktraceでわかります。

(byebug) where
--> #0  ArticlesController.index
      at /PathTo/project/test_app/app/controllers/articles_controller.rb:8
    #1  ActionController::ImplicitRender.send_action(method#String, *args#Array)
      at /PathToGems/actionpack-5.0.0/lib/action_controller/metal/implicit_render.rb:4
    #2  AbstractController::Base.process_action(action#NilClass, *args#Array)
      at /PathToGems/actionpack-5.0.0/lib/abstract_controller/base.rb:189
    #3  ActionController::Rendering.process_action(action#NilClass, *args#NilClass)
      at /PathToGems/actionpack-5.0.0/lib/action_controller/metal/rendering.rb:10
...

現在のフレームは-->で示されます。framenコマンド (nはフレーム番号) を使用すれば、トレース内のどのコンテキストにも自由に移動できます。このコマンドを実行すると、byebugは新しいコンテキストを表示します。

(byebug) frame 2

[184, 193] in /PathToGems/actionpack-5.0.0/lib/abstract_controller/base.rb
   184:       # is the intended way to override action dispatching.
   185:       #
   186:       # Notice that the first argument is the method to be dispatched
   187:       # which is *not* necessarily the same as the action name.
   188:       def process_action(method_name, *args)
=> 189:         send_action(method_name, *args)
   190:       end
   191:
   192:       # Actually call the method associated with the action. Override
   193:       # this method if you wish to change how action methods are called,

(byebug)

コードを1行ずつ実行していた場合、利用できる変数は同一です。つまり、これこそがデバッグという作業です。

up [n] (短縮形のuも可) コマンドやdown [n]コマンドを使用して、スタックを n フレーム上または下に移動し、コンテキストを切り替えることもできます。upはスタックフレーム番号の大きい方に進み、downは小さい方に進みます。

3.4 スレッド

デバッガーでthread(短縮形はth) コマンドを使用すると、スレッド実行中にスレッドのリスト表示/停止/再開/切り替えを行えます。このコマンドには以下のささやかなオプションがあります。

  • threadは現在のスレッドを表示します。
  • thread listはすべてのスレッドのリストをステータス付きで表示します。現在実行中のスレッドは「+」記号と数字で示されます。
  • thread stopn はスレッド n を停止します。
  • thread resumen はスレッド n を再開します。
  • thread switchn は現在のスレッドコンテキストを n に切り替えます。

このコマンドは他の場合にも非常に便利です。同時実行スレッドのデバッグ中に、競合状態が発生していないかどうかを確認する必要がある場合にも使えます。

3.5 変数の検査

すべての式は、現在のコンテキストで評価されます。式を評価するには、単にその式を入力します。

次の例では、現在のコンテキスト内で定義されたインスタンス変数を出力する方法を示しています。

[3, 12] in /PathTo/project/app/controllers/articles_controller.rb
    3:
    4:   # GET /articles
    5:   # GET /articles.json
    6:   def index
    7:     byebug
=>  8:     @articles = Article.find_recent
    9:
   10:     respond_to do |format|
   11:       format.html # index.html.erb
   12:       format.json { render json: @articles }

(byebug) instance_variables
[:@_action_has_layout, :@_routes, :@_headers, :@_status, :@_request, :@_response, :@_env, :@_prefixes, :@_lookup_context, :@_action_name, :@_response_body, :@marked_for_same_origin_verification, :@_config]

見ての通り、コントローラからアクセスできるすべての変数が表示されています。表示される変数リストは、コードの実行に伴って動的に更新されます。 たとえば、nextコマンドで次の行に進んだとします (このコマンドの詳細については後述します)。

(byebug) next
[5, 14] in /PathTo/project/app/controllers/articles_controller.rb
   5     # GET /articles.json
   6     def index
   7       byebug
   8       @articles = Article.find_recent
   9
=> 10       respond_to do |format|
   11         format.html # index.html.erb
   12        format.json { render json: @articles }
   13      end
   14    end
   15
(byebug)

それではinstance_variablesをもう一度調べてみましょう。

(byebug) instance_variables.include? "@articles"
true

定義行が実行されたことによって、今度は@articlesもインスタンス変数に表示されます。

irbコマンドを使用することで、irbモードで実行できます。 これにより、呼び出し中のコンテキスト内でirbセッションが開始されます。ただし、この機能はまだ実験中の段階です。

変数と値のリストを表示するのに便利なのは何と言ってもvarメソッドでしょう。 byebugでこのメソッドを使ってみましょう。

(byebug) help var
v[ar] cl[ass]                   show class variables of self
v[ar] const <object>            show constants of object
v[ar] g[lobal]                  show global variables
v[ar] i[nstance] <object>       show instance variables of object
v[ar] l[ocal]                   show local variables

このメソッドは、現在のコンテキストでの変数の値を検査するのにうってつけの方法です。たとえば、現時点でローカル変数が何も定義されていないことを確認してみましょう。

(byebug) var local
(byebug)

以下の方法でオブジェクトのメソッドを検査することもできます。

(byebug) var instance Article.new
@_start_transaction_state = {}
@aggregation_cache = {}
@association_cache = {}
@attributes = {"id"=>nil, "created_at"=>nil, "updated_at"=>nil}
@attributes_cache = {}
@changed_attributes = nil
...

p (print) コマンドとpp (pretty print) コマンドを使用して Rubyの式を評価し、変数の値をコンソールに出力することができます。

displayコマンドを使用して変数をウォッチすることもできます。これは、デバッガーで実行を進めながら変数の値の移り変わりを追跡するのに大変便利です。

(byebug) display @articles
1: @articles = nil

スタック内で移動するたびに、そのときの変数と値のリストが出力されます。変数の表示を止めるには、undisplayn (n は変数番号) を実行します。上の例では変数番号は 1 になっています。

3.6 ステップ実行

これで、トレース実行中に現在の実行位置を確認し、利用可能な変数をいつでも確認できるようになりました。アプリケーションの実行について引き続き学んでみましょう。

stepコマンド (短縮形はs) を使用すると、プログラムの実行を継続し、次の論理的な停止行まで進んだらデバッガーに制御を返します。

stepとよく似たnextを使用することももちろんできますが、nextはそのコードの行に関数やメソッドがあっても止まらずにそれらの関数やメソッドを実行してしまう点が異なります。

step nnext nと入力することで、nステップずつ進めることもできます。

nextstepの違いは次のとおりです。stepは次のコード行を実行したらそこで止まるので、常に一度に1行だけを実行します。nextはメソッドがあってもその中に潜らずに次の行に進みます。

たとえば、次のような状況を考えてみましょう

Started GET "/" for 127.0.0.1 at 2014-04-11 13:39:23 +0200
Processing by ArticlesController#index as HTML

[1, 8] in /home/davidr/Proyectos/test_app/app/models/article.rb
   1: class Article < ActiveRecord::Base
   2:
   3:   def self.find_recent(limit = 10)
   4:     byebug
=> 5:     where('created_at > ?', 1.week.ago).limit(limit)
   6:   end
   7:
   8: end

(byebug)

nextを使用していて、メソッド呼び出しに潜ってみたいとしましょう。しかしbyebugは、潜る代わりに単に同じコンテキストの次の行に進みます。この例の場合、次の行とはそのメソッドの最終行になります。従って、byebugは前のフレームにある次の次の行にジャンプします。

(byebug) next
前のフレームの実行が完了するので、Nextによって1つ上のフレームに移動する

[4, 13] in /PathTo/project/test_app/app/controllers/articles_controller.rb
    4:   # GET /articles
    5:   # GET /articles.json
    6:   def index
    7:     @articles = Article.find_recent
    8:
=>  9:     respond_to do |format|
   10:       format.html # index.html.erb
   11:       format.json { render json: @articles }
   12:     end
   13:   end

(byebug)

同じ状況でstepを使用すると、文字通り「Rubyコードの、実行すべき次の行」に進みます。ここではactivesupportのweekメソッドに潜って進むことになります。

(byebug) step

[50, 59] in /PathToGems/activesupport-5.0.0/lib/active_support/core_ext/numeric/time.rb
   50:     ActiveSupport::Duration.new(self * 24.hours, [[:days, self]])
   51:   end
   52:   alias :day :days
   53:
   54:   def weeks
=> 55:     ActiveSupport::Duration.new(self * 7.days, [[:days, self * 7]])
   56:   end
   57:   alias :week :weeks
   58:
   59:   def fortnights

(byebug)

これは自分のコードの、ひいてはRuby on Railsのバグを見つけ出す方法として非常に優れています。

3.7 ブレークポイント

ブレークポイントとは、アプリケーションの実行がプログラムの特定の場所に達した時に停止する位置を指します。そしてその場所でデバッガーシェルが起動します。

break (またはb) コマンドを使用してブレークポイントを動的に追加できます。 手動でブレークポイントを追加する方法は次の 3 とおりです。

  • break line: 現在のソースファイルの line で示した行にブレークポイントを設定します。
  • break file:line [if expression]: fileline行目にブレークポイントを設定します。expression が与えられた場合、デバッガを起動するにはこの式が true と評価される必要があります。
  • break class(.|\#)method [if expression]: class に定義されている method にブレークポイントを設定します (「.」と「#」はそれぞれクラスとインスタンスメソッドを指す)。expressionの動作はfile:lineの場合と同じです。

さっきと同じ状況を例に説明します。

[4, 13] in /PathTo/project/app/controllers/articles_controller.rb
    4:   # GET /articles
    5:   # GET /articles.json
    6:   def index
    7:     @articles = Article.find_recent
    8:
=>  9:     respond_to do |format|
   10:       format.html # index.html.erb
   11:       format.json { render json: @articles }
   12:     end
   13:   end

(byebug) break 11
Created breakpoint 1 at /PathTo/project/app/controllers/articles_controller.rb:11

ブレークポイントをリスト表示するには、info breakpointsninfo breaknを使用します。番号を指定すると、その番号のブレークポイントをリスト表示します。番号を指定しない場合は、すべてのブレークポイントをリスト表示します。

(byebug) info breakpoints
Num Enb What
1   y   at /PathTo/project/app/controllers/articles_controller.rb:11

deletenコマンドを使用するとn番のブレークポイントを削除できます。番号を指定しない場合、現在有効なブレークポイントをすべて削除します。

(byebug) delete 1
(byebug) info breakpoints
No breakpoints.

ブレークポイントを有効にしたり、無効にしたりすることもできます。

  • enable breakpoints: breakpointsで指定したブレークポイントのリスト (無指定の場合はすべてのブレークポイント) でのプログラムの停止を有効にします。ブレークポイントを作成するとデフォルトでこの状態になります。
  • disable breakpoints: breakpointsで指定したブレークポイントのリストで停止しなくなります。

3.8 例外のキャッチ

catch exception-name (省略形は cat exception-name) を使用すると、例外を受けるハンドラが他にないと考えられる場合に、exception-nameで例外の種類を指定してインターセプトできます。

例外のキャッチポイントをすべてリスト表示するには単にcatchと入力します。

3.9 実行再開

デバッガーで停止したアプリケーションの再開方法は2種類あります。

  • continue [line-specification] (またはc): スクリプトが直前に停止していたアドレスからプログラムの実行を再開します。このとき、それまで設定されていたブレークポイントはすべて無視されます。オプションとして、特定の行番号をワンタイムブレークポイントとして[line-specification]で指定できます。このワンタイムブレークポイントに達するとブレークポイントは削除されます。
  • finish [frame-number] (or fin): 指定のスタックフレームが返るまで実行を続けます。frame-numberが指定されていない場合は、現在選択しているフレームが返るまで実行を続けます。フレーム位置が指定されていない (upやdownやフレーム番号指定が行われていない) 場合は、現在の位置から最も近いフレームまたは0フレームから開始します。フレーム番号を指定すると、そのフレームが返るまで実行を続けます。

3.10 編集

デバッガー上のコードをエディタで開くためのコマンドは2種類あります。

  • edit [file:line]: fileをエディタで開きます。エディタはEDITOR環境変数で指定します。lineで行数を指定することもできます。

3.11 終了

デバッガーを終了するには、quitコマンド (短縮形は q) または別名のexitを使用します。

quitを実行すると、事実上すべてのスレッドを終了しようとします。これによりサーバーが停止するので、サーバーを再起動する必要があります。

3.12 設定

byebugの振る舞いを変更するためのオプションがいくつかあります。

  • set autoreload: ソースコードが変更されると再読み込みします (デフォルト: true)。
  • set autolist: すべてのブレークポイントでlistコマンドを実行します (デフォルト: true)。
  • set listsize _n_: リスト表示の行数をデフォルトからn に変更します (デフォルト: 10)。
  • set forcestep: nextstepコマンドを実行すると常に新しい行に移動するようにします。

すべてのオプションを表示するにはhelp setを実行します。特定のsetコマンドを調べるにはhelp setsubcommand を実行します。

これらの設定は、ホームディレクトリの.byebugrcファイルに保存しておくことができます。 デバッガーが起動すると、この設定がグローバルに適用されます。以下に例を示します。

set forcestep
set listsize 25

4 web-console gemを使用するデバッグ

Web Consoleはbyebugと似ていますが、ブラウザ上で動作する点が異なります。開発中のどのページでも、ビューやコントローラのコンテキストでコンソールをリクエストできます。コンソールは、HTMLコンテンツの隣に表示されます。

4.1 Console

consoleメソッドを呼び出すことで、任意のコントローラのアクションやビューでいつでもコンソールを呼び出せます。

たとえば、コントローラで以下のように呼び出せます。

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

ビューでも以下のように呼び出せます。

<% console %>

<h2>New Post</h2>

上のコードは、ビューの内部でコンソールを出力します。consoleを呼び出す位置を気にする必要はありません。コンソールは、呼び出し位置にかかわらず、HTMLコンテンツの隣りに出力されます。

コンソールでは純粋なRubyコードを実行できます。ここでカスタムクラスの定義やインスタンス化を行ったり、新しいモデルを作成したり、変数を検査したりすることができます。

一回のリクエストで出力できるコンソールは1つだけです。console呼び出しを2回以上行うとweb-consoleでエラーが発生します。

4.2 変数の検査

instance_variablesを呼び出すと、コンテキストで利用可能なインスタンス変数をすべてリスト表示できます。すべてのローカル変数をリスト表示したい場合は、local_variablesを使用します。

4.3 設定

  • config.web_console.whitelisted_ips: 認証済みの IPv4/IPv6アドレスとネットワークのリストです (デフォルト値: 127.0.0.1/8、::1).
  • config.web_console.whiny_requests: コンソール出力が抑制されている場合にメッセージをログ出力します (デフォルト値: true).

web-consoleはサーバー上の純粋なRubyコードをリモート評価できるので、production環境では絶対に使用しないください。

5 メモリーリークのデバッグ

Railsに限らず、Rubyアプリケーションではメモリーリークが発生することがあります。リークはRubyコードレベルのこともあれば、Cコードレベルであることもあります。

このセクションでは、Valgrindなどのツールを使用してこうしたメモリーリークの検出と修正を行う方法をご紹介します。

5.1 Valgrind

ValgrindはLinux専用のアプリケーションであり、Cコードベースのメモリーリークや競合状態の検出を行います。

Valgrindには、さまざまなメモリー管理上のバグやスレッドバグなどを自動検出し、プログラムの詳細なプロファイリングを行うための各種ツールがあります。たとえば、インタプリタ内にあるC拡張機能がmalloc()を呼び出した後free()を正しく呼び出さなかった場合、このメモリーはアプリケーションが終了するまで利用できなくなります。

Valgrindのインストール方法とRuby内での使用方法については、ValgrindとRuby(Evan Weaver著、英語) を参照してください。

6 デバッグ用プラグイン

アプリケーションのエラーを検出し、デバッグするためのRailsプラグインがあります。デバッグ用に便利なプラグインのリストを以下にご紹介します。

  • Footnotes: すべてのRailsページに脚注を追加し、リクエスト情報を表示したり、TextMateでソースを開くためのリンクを表示したりします。
  • Query Trace: ログにクエリ元のトレースを追加します。
  • Query Reviewer: このRailsプラグインは、開発中のselectクエリの前に"EXPLAIN"を実行します。また、ページごとにDIVセクションを追加して、分析対象のクエリごとの警告の概要をそこに表示します。
  • Exception Notifier: Railsアプリケーションでのエラー発生時用の、メイラーオブジェクトとメール通知送信テンプレートのデフォルトセットを提供します。
  • Better Errors: Rails標準のエラーページを新しい表示に置き換えて、ソースコードや変数検査などのコンテキスト情報を見やすくしてくれます。
  • RailsPanel: Rails開発用のChrome機能拡張です。これがあればdevelopment.logでtailコマンドを実行する必要がなくなります。Railsアプリケーションのリクエストに関するすべての情報をブラウザ上 (Developer Toolsパネル) に表示できます。 db時間、レンダリング時間、トータル時間、パラメータリスト、出力したビューなども表示されます。

7 参考資料