Rails4.1 调试Rails应用

欧阳博超
2023-12-01

原文:http://guides.rubyonrails.org/debugging_rails_applications.html

该指导手册介绍了如何去调试Ruby onRails应用。

通过阅读此指导,你会学习到:

  • l  调试的目的
  • l  如何追踪测试中未明确标识出的问题
  • l  调试的不同方式
  • l  如何分析栈追踪信息

1          调试的一些帮助方法

这些方法的共同的目的是审查变量的内容。在Rails中,你能够使用如下这些方法:

  • l  debug
  • l  to_yaml
  • l  inspect

1.1         debug

该debug方法将返回<pre>标签,来生成YAML格式的对象并返回易读的数据格式。例如若应用中有以下代码:

         <%= debug @post %>

         <p>

                   <b>Title:</b>

                   <%= @post.title %>

         </p>

你可以查看到诸如下面这些信息:

--- !ruby/object:Post

attributes:

  updated_at: 2008-09-05 22:55:47

  body: It's a very helpful guide for debugging your Railsapp.

  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 @post.to_yaml%>

         <p>

                   <b>Title:</b>

                   <%= @post.title %>

         </p>

此例子中的to_yaml方法将对象转变成YAML格式的信息,并通过simple_format方法以命令窗口中格式输出。这就是debug方法的神奇之处。

可以查看到某些如下信息:

         ---!ruby/object:Post

attributes:

  updated_at: 2008-09-05 22:55:47

  body: It's a very helpful guide for debugging your Railsapp.

  title: Rails debugging guide

  published: t

  id: "1"

  created_at: 2008-09-05 22:55:47

attributes_cache: {}

1.3         inspect

另一个显示对象内容的方法是inspect,尤其在调试数组或哈希数据时。此方法将以字符串形式输出对象。例如:

         <%= [1,2,3,4,5].inspect %>

         <p>

                   <b>Title:</b>

                   <%= @post.title %>

         </p>

将会产生如下信息:

         [1,2,3,4,5]

         Title: Rails debugging guide

2          日志logger方法

将某些信息在运行时写入到日志文件中,有助于分析应用的错误。Rails为每种运行环境维护了单独的日志文件。

2.1         什么是Logger

Rails使用ActiveSupport::Logger类去写日志信息。你也可以使用Log4r来替代。

你可以在environment.rb或任一环境文件中如此配置:

         Rails.logger = Logger.new(STDOUT)

         Rails.logger= Log4r::Logger.new(‘ApplicationLog’)

或在Initializer部分添加:

         config.logger=Logger.new(STDOUT)

         config.logger=Log4r::Logger.new(‘ApplicationLog’)

注:默认情况下,日志文件将被保存在Rails.root/log/目录中,并以environment_name.log命名。

2.2         日志的等级

若某些消息的等级符合日志记录的等级,那么这些信息将会被写入到日志文件中。你可以通过Rails.logger.level方法来获取到当前日志的等级。

可用的日志等级有:debug,:info,:warn,:error,:fatal和:unknown,分别对应等级0到5。可如下修改日志等级:

         config.log_level = :warn # In anyenvironment initializer, or

         Rails.logger.level = 0 # at any time

此方式在开发模式或生产环境模拟中非常有用,可以方便的排除那些你不需要的信息。

注:默认情况下,在生产模式和开发、测试模式的调试中日志等级为info。

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 PostsController <ApplicationController
                   #...
                   def create
                            @post =Post.new(params[:post])
                            logger.debug “Newpost: #{@post.attributes.inspect}”
                            logger.debug “Postshould be valid: #{@post.valid}”
                            if @post.save
                                     flash[:notice]= ‘Post was successfully created.’
                                     logger.debug“The post was saved and now the user is going to be redirected…”
                                     redirect_to(@post)
                            else
                                     renderaction: “new”
                            end
                   end
                   #...
         end

执行以后,日志信息如下:

Processing PostsController#create (for 127.0.0.1 at 2008-09-0811:52:54) [POST]
  Session ID:BAh7BzoMY3NyZl9pZCIlMDY5MWU1M2I1ZDRjODBlMzkyMWI1OTg2NWQyNzViZjYiCmZsYXNoSUM6J0FjdGl
vbkNvbnRyb2xsZXI6OkZsYXNoOjpGbGFzaEhhc2h7AAY6CkB1c2VkewA=--b18cd92fba90eacf8137e5f6b3b06c4d724596a4
  Parameters: {"commit"=>"Create","post"=>{"title"=>"Debugging Rails",
 "body"=>"I'm learning how to print inlogs!!!", "published"=>"0"},
 "authenticity_token"=>"2059c1286e93402e389127b1153204e0d1e275dd","action"=>"create","controller"=>"posts"}
New post: {"updated_at"=>nil,"title"=>"Debugging Rails","body"=>"I'm learning how to print in logs!!!",
 "published"=>false,"created_at"=>nil}
Post should be valid: true
  Post Create (0.000443)   INSERT INTO"posts" ("updated_at", "title", "body","published",
 "created_at") VALUES('2008-09-08 14:52:54', 'DebuggingRails',
 'I''m learning how to print in logs!!!', 'f', '2008-09-0814:52:54')
The post was saved and now the user is going to be redirected...
Redirected to #<Post:0x20af760>
Completed in 0.01224 (81 reqs/sec) | DB: 0.00044 (3%) | 302 Found [http://localhost/posts]

             使用以上方式来添加日志信息,可方便的获得调试信息,便于查找错误信息。但你需要确保使用正确的日志记录等级,避免生成过多的无用信息。

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的情况下也一样。原因在于Ruby需要去包含这些String实例化对象,将这些变量插入到字符串中,而这会花费时间。因此,建议使用快来传入参数给logger方法,因为它们只会在等级被允许时,才会输出即懒惰执行。同样功能的代码,可以这样写:

                     logger.debug{“Personattribute hash: #{@person.attributes.inspect}”}

                块内的内容,当日志等级为deug等级时,才执行。此方法会在应用有许多日志记录时,提高性能,但是是一个最佳实践方案。

3          使用debugger gem包进行调试

当应用出现问题时,你可以通过控制台或日志信息来判断追踪错误。不幸的是,很难一次性找到问题的根源。当你可能需要逐步对代码进行排查的时候,debugger gem包就是最好的选择。

在你无法确认从哪里开始查找问题的时候,debuggergem包能够帮助你方便的进行追踪。通过调试应用对请求的响应,通过该指导手册可以学会如何逐步调试Rails的代码。

3.1         安装

你可以通过debugger gem包设置断点和逐步查看Rails代码。使用以下命令安装:

         $ gem install debugger

Rails从2.0版本开始有内置的调试支持。在任何Rails应用中,你都可以通过调用debugger来开始调试。

例如:

         class PeopleController <ApplicationController

                   def new

                            debugger

                            @person = Person.new

                   end

end

 你可以在控制台看到:

           *****Debugger requested, but was not available: Start server with --debugger toenable *****

请确保你使用—debugger选项开启了服务器:

          $rails server --debugger

=> Booting WEBrick

=> Rails 4.1.1 application starting on http://0.0.0.0:3000

=> Debugger enabled

...

注:在开发模式下,你可以不必以—debugger选项开启服务器,就可以动态的包含debugger进行调试。

3.2         Shell命令

一旦你的应用运行中调到了debugger方法,调试命令就会被触发,在终端窗口中会显示调试提示命令(rdb:n)。rdb:n,其中的n代表这线程数。提示信息将显示运行的下一行代码是哪个。

若你调试浏览器发出的请求,那么浏览器将呈挂起状态直到调试完成本次请求操作。

例如:

         @posts = Post.all

         (rdb:7)

现在,是时候开始深入调试应用的代码了。可以通过debugger的help命令开始:

         (rdb:7) help

         ruby-debug help v0.10.2

         Type ‘help <command-name>’ forhelp on a specific command

         Avaliable commands:

         backtrace delete   enable  help    next quit     show    trace

break      disable eval    info    p    reload   source  undisplay

catch      display exit    irb     pp   restart  step    up

condition  down     finish list    ps    save    thread  var

continue   edit     frame  method  putl  set      tmate  where

注:在命令窗口中,可以使用help<command-name>来获取调试信息。例如:help var。

下一个需要学习的命令为list。你能够简写任一的调试命令,只要有足够的字母来辨认出该命令,因此你也能够使用l来代替list命令。

通过list命令显示了你当前所在行代码以及该行周围10行代码。例如:显示当前行在第六行,并以=>标记出。

     (rdb:7) list

[1, 10] in /PathTo/project/app/controllers/posts_controller.rb

   1  class PostsController <ApplicationController

   2    # GET /posts

   3    # GET /posts.json

   4    def index

   5      debugger

=> 6      @posts = Post.all

   7

   8      respond_to do|format|

   9       format.html # index.html.erb

   10       format.json { render json: @posts }

                   如你重复使用list命令,可直接使用l,接着下10行代码又将被打印。

(rdb:7) l

[11, 20] in /PathTo/project/app/controllers/posts_controller.rb

   11      end

   12    end

   13

   14    # GET /posts/1

   15    # GET /posts/1.json

   16    def show

   17      @post =Post.find(params[:id])

   18

   19      respond_to do|format|

   20       format.html # show.html.erb

                   可以直到当前文件的结束。当已到文件结尾,list命令将重新从文件头开始显示代码,以一个循环的缓存来看待文件。

                   另一方面,可通过list-或l-来查看前10行代码:

              (rdb:7) l-

[1, 10] in /PathTo/project/app/controllers/posts_controller.rb

   1  class PostsController <ApplicationController

   2    # GET /posts

   3    # GET /posts.json

   4    def index

   5      debugger

   6      @posts = Post.all

   7

   8      respond_to do|format|

   9       format.html # index.html.erb

   10       format.json { render json: @posts }

                   通过此种方法,你可以查看到代码文件中的内容。最后,你可以通过list=命令查看你当前所在的位置。

                       (rdb:7)list=

[1, 10] in /PathTo/project/app/controllers/posts_controller.rb

   1  class PostsController <ApplicationController

   2    # GET /posts

   3    # GET /posts.json

   4    def index

   5      debugger

=> 6      @posts = Post.all

   7

   8      respond_to do|format|

   9       format.html # index.html.erb

   10       format.json { render json: @posts }

3.3         上下文

当你开始调试应用的时候,通过不同部分的栈,你会被放置到不同的上下文中。当结束点或事件被触发时,debugger会创建上下文。上下文中包含了当前调试可查看的栈框架信息,从被调试程序的透视图中获取变量,并包含调试程序被停止处的代码信息。

在任何时候,你都可以通过backtrace命令(或where简写命令)打印出应用的追踪信息。此命令对于获取你当前所在位置信息非常有用。如果你曾困惑于如何跳转的代码,那么backtrace将提供答案:

     (rdb:5) where

    #0 PostsController.index

       at line/PathTo/project/app/controllers/posts_controller.rb:6

    #1 Kernel.send

       at line/PathTo/project/vendor/rails/actionpack/lib/action_controller/base.rb:1175

    #2ActionController::Base.perform_action_without_filters

       at line/PathTo/project/vendor/rails/actionpack/lib/action_controller/base.rb:1175

    #3ActionController::Filters::InstanceMethods.call_filters(chain#ActionController::Fil...,...)

       at line/PathTo/project/vendor/rails/actionpack/lib/action_controller/filters.rb:617

...

           你可以在此追踪信息的任何地方(即改变上下文),通过frame _n_command命令,来跳转,其中n代表着具体的frame编号。

(rdb:5) frame 2

#2 ActionController::Base.perform_action_without_filters

       at line/PathTo/project/vendor/rails/actionpack/lib/action_controller/base.rb:1175

           可查看的变量同你逐步运行代码一致。这就是调试。

          上下跳转frame:你可通过up [n](简写命令u)和down [n](简写命令d)来改变上下文。n默认为1。Up代表向编号越大的frame运行,而Down代表像编号越小的frame运行。

3.4         线程

通过debugger可以list,stop,resume和thread(命令简写为th,作用是在运行的不同线程之间跳转)。此命令有如下一些参数:

    • l  thread显示了当前的线程
    • l  thread list用于显示所有线程以及他们的状态。+标记以及编号,暗示当前执行的线程。
    • l  thread stop _n_用于停止线程n。
    • l  thread resume _n_用于恢复线程n。
    • l  thread switch _n_用于选择当前线程上下文到n。

此命令非常有用,例如在你需要验证代码中是否有未执行到的条件时,可以使用该命令调试当前线程。

3.5         审查变量

任意表达式能够在当前的上下文中执行。执行的方式很简单,在命令行输入即可。

此例子中展示了如何打印出当前上下文中所有的实例变量:

         @posts=Post.all

         (rdb:11) instance_variables

["@_response","@action_name", "@url", "@_session","@_cookies", "@performed_render", "@_flash","@template", "@_params", "@before_filter_chain_aborted","@request_origin", "@_headers","@performed_redirect", "@_request"]

             在上例中,你能够注意到当前控制器中你能够使用的所有实例变量都列出来了。这个列表随着一步步执行代码会自动进行更新。例如,运行下一行使用next:

                       (rdb:11)next

Processing PostsController#index (for 127.0.0.1 at 2008-09-0419:51:34) [GET]

  Session ID:BAh7BiIKZmxhc2hJQzonQWN0aW9uQ29udHJvbGxlcjo6Rmxhc2g6OkZsYXNoSGFzaHsABjoKQHVzZWR7AA==--b16e91b992453a8cc201694d660147bba8b0fd0e

  Parameters: {"action"=>"index","controller"=>"posts"}

/PathToProject/posts_controller.rb:8

respond_to do |format|

              再次访问实例变量:

(rdb:11) instance_variables.include? "@posts"

true

             现在@posts被包含进了实例变量中,因为此行代码被执行了。

            注:你也可以输入irb进行irb模式。这种方式将使得当前上下问进入到irb会话模式。警告:这是一种实验特性。

                   var方法可以方便的显示变量以及其值。

                       var

(rdb:1) v[ar] const<object>           show constants of object

(rdb:1) v[ar] g[lobal]                 show global variables

(rdb:1) v[ar] i[nstance]<object>       show instance variables ofobject

(rdb:1) v[ar]l[ocal]                  show local variables

                   通过var方法,是查看当前上下文中变量值的最佳方式。例如:

(rdb:9) var local

  __dbg_verbose_save => false

                   你也能够以如下方式查看对象的方法:

                         (rdb:9) var instance Post.new

@attributes = {"updated_at"=>nil,"body"=>nil, "title"=>nil,"published"=>nil, "created_at"...

@attributes_cache = {}

@new_record = true

                   注:命令p(print)和pp(prettyprint)可用来执行Ruby表达式和显示变量的值到控制台。

                  你能够使用display来开始监视变量。这种方式可在代码执行过程中,方便的追踪到变量值的变化。

(rdb:1) display @recent_comments

1: @recent_comments =

                   当你在栈中移动时,在显示列表中的变量的值将打印到控制台。停止监视某个变量,请使用undisplay _n_,其中n为变量的编号(例如上例中的1)。

3.6         逐步检查

现在你已经知道如何追踪和如何查看变量。那么,我们继续随着应用执行。

使用step(简写为s)命令,继续执行到下一个逻辑拐点并返回到控制台。

注:你能够使用step+ n和step- n来继续前进或后退n步。

你也可以使用next来一行一行的执行。同step命令一样,你可以使用+号来一次性跳过n行。

使用next与step的不同之处是step会在下一逻辑执行行停止,而next将在下一行停止不跳入方法中。

例如,对以下块做debugger:

class Author < ActiveRecord::Base

  has_one :editorial

  has_many :comments

  def find_recent_comments(limit = 10)

    debugger

    @recent_comments ||=comments.where("created_at > ?", 1.week.ago).limit(limit)

  end

end

          注:你能够在使用railsconsole中使用debugger。请注意,确保在使用debugger方法之前进行require “debugger”。

                       $ rails console

Loading development environment (Rails 4.1.1)

>> require "debugger"

=> []

>> author = Author.first

=> #<Author id: 1, first_name: "Bob", last_name:"Smith", created_at: "2008-07-31 12:46:10", updated_at:"2008-07-31 12:46:10">

>> author.find_recent_comments

/PathTo/project/app/models/author.rb:11

)

            在代码停止处,查看上下文:

                       (rdb:1)list

[2, 9] in /PathTo/project/app/models/author.rb

   2    has_one :editorial

   3    has_many :comments

   4

   5    def find_recent_comments(limit= 10)

   6      debugger

=> 7      @recent_comments ||=comments.where("created_at > ?", 1.week.ago).limit(limit)

   8    end

   9  end

            当前例子中,你所在的行为方法中的最后一行,该行是否执行?可以通过查看实例变量知晓:

                       (rdb:1)var instance

@attributes = {"updated_at"=>"2008-07-3112:46:10", "id"=>"1","first_name"=>"Bob", "las...

@attributes_cache = {}

             可见@recent_comments未被定义,因此当前行未被执行。

             使用next命令执行下一行:

                       (rdb:1)next

/PathTo/project/app/models/author.rb:12

@recent_comments

(rdb:1) var instance

@attributes = {"updated_at"=>"2008-07-3112:46:10", "id"=>"1","first_name"=>"Bob", "las...

@attributes_cache = {}

@comments = []

@recent_comments = []

           现在你可以查看到@comment变量被加载并且@recent_comments变量被定义。因为该行被执行。

           若你将深入到追踪栈,你可以通过逐步的step调试,查看代码中调用的每一行。这是Ruby on Rails中发现应用中bug的最佳方式。

3.7         断点

设置断点可以让你的应用在程序中指定处停止。将在指定行处调用debugger。

你可以使用break(简称为b)来添加断点。有三种方式来添加断点:

  • l  break line:在当前源文件中,设置行断点。
  • l  break file:line [if expression]:设置行断点,当条件为true的时候,触发断点调试。
  • l  break class(.|\#)method [if expression]:设置类中方法处的断点(.和#分别为类方法和实例方法)。其中表达式的意义同上。

(rdb:5) break 10

Breakpoint 1 file/PathTo/project/vendor/rails/actionpack/lib/action_controller/filters.rb, line10

        可使用breakpoints_n_或info break _n_显示断点列表。若你提供了编号,那么此命令将显示具体的断点。否则,将显示全部断点:

                       (rdb:5)info breakpoints

Num Enb What

  1 y   at filters.rb:10

               通过delete _n_来删除序号为n的断点。若没有提供编号,将删除当前激活的所有断点。

                       (rdb:5)delete 1

(rdb:5) info breakpoints

No breakpoints.

              你可以通过以下方式开启或关闭断点功能:

  • enable breakpoints:允许使用断点来中断应用。当你创建断点时,默认的状态。
  • disable breakpoints:禁止所有断点。

3.8         捕获异常

使用catchexception-name(或cat exception-name)能够捕获指定的异常。              

通过catch命令可罗列出所有当前激活的异常捕获行。

3.9         继续执行

在debugger过程中有两种方式可用于继续执行应用:

  • continue [line-specification](或c):从上一次调试的停止处,继续往下执行代码;跳过设置的断点继续执行。可以通过设置line-specification参数来设置具体的行编号来设置一个一次性的断点(该断点将在被运行后自动删除)。
  • finish [frame-number](或fin):继续执行直到选择的栈模块跳转。若没有栈模块序列,应用将运行直到当前所选择的栈模块跳转。当前所选择的栈模块开始于最近的栈模块,若没有栈模块的具体编号(例如使用up,down,frame命令),将从0序列开始。若给予了栈模块编号,那么该模块将运行直到返回为止。

3.10      编辑

可以通过以下两种方式,在debugger下打开编辑器编辑代码:

  • edit [file:line]:调用系统中EDITOR环境变量指定的编辑器进行编辑文件,可以指定具体的行号。
  • tmate _n_(简写为tm):使用TextMate打开当前文件。若n被指定,将调用第n个frame。

3.11      退出

退出调试环境,使用quit(简写为q)命令或别称exit。

退出将终止当前所有的线程。因此服务器必须重新启动。

3.12      设置

使用debugger gem包后,将自动显示你通过编辑器修改后的代码并重新加载它。这里是一些可用的参数选项:

    • set reload:当修改代码后,重新加载代码。
    • set autolist:在每个断点处执行list命令。
    • set listsize _n_:设置list默认显示的代码行数。
    • set forcestep:确保next和step命令总是停止在新的行。

可以通过help set查看到以上这些选项参数。可通过set _subcommand_来查看当前特殊的命令。

注:你可以将这些配置写到应用主目录下的.rdebugrc文件中,debugger将在运行是自动加载这些配置信息。

如以下这些配置信息:

             set  autolist

set  forcestep

set  listsize  25

4          调试内存泄露

对于任何的Ruby应用(可能基于Rails),可能造成内存泄露—不管是在Ruyb代码层次还是C代码层次。

在此,你将学习到如何使用诸如Valgrind之类的工具,来查找和修复此类内存。

4.1         Valgrind

此工具是基于Linux系统的应用,用于检测C代码中的内存泄露问题。

有许多诸如Valgrind能够检测内存管理以及线程的bug并且详细的显示程序轮廓的工具。例如,若在C代码文件(以.c结尾)中调用了malloc()函数,但是没有调用相应的free()函数,将造成内存不可用直到应用终断。

对于Valgrind工具如何与Ruby应用结果,详情请查看EvanWeaver写的Valgrindand Ruby

5          调试的插件

在Rails中,有许多插件可帮助你发现错误并调试应用。如:

  •  Footnotes:在应用中的每个页面添加调试信息的页脚,并可通过TextMate打开链接信息。
  • Query Trace:添加查找追踪到你的日志中。
  • Query Reviewer:对于开发模式下应用中每次对数据库的查询添加运行”EXPLAIN”,并在每个页面添加DIV来显示分析结果。
  • Exception Notifier:提供了邮件对象和默认的邮件模板,当有错误出现时进行邮件提示。
  • Better Errors:替代Rails默认的错误信息页面,添加更多上下文信息,如源代码以及变量信息。
  • RailsPanel:Chrome浏览器的扩展工具,可以查看到开发日志部分信息。对于应用中的请求参数可以在开发工具面板中查看到。提供了db/rendering/总次数,参数列表,渲染视图等等。

6          引用

 类似资料: