原文:http://guides.rubyonrails.org/debugging_rails_applications.html
该指导手册介绍了如何去调试Ruby onRails应用。
通过阅读此指导,你会学习到:
1 调试的一些帮助方法
这些方法的共同的目的是审查变量的内容。在Rails中,你能够使用如下这些方法:
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,作用是在运行的不同线程之间跳转)。此命令有如下一些参数:
此命令非常有用,例如在你需要验证代码中是否有未执行到的条件时,可以使用该命令调试当前线程。
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包后,将自动显示你通过编辑器修改后的代码并重新加载它。这里是一些可用的参数选项:
可以通过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 引用