Debugging Rails Applications
Bài đăng này đã không được cập nhật trong 8 năm
Việc debug là việc không thể thiếu trong quá trình phát triển ứng dụng. Sau đây là một số các kỹ thuật để dubug cho ứng dụng ruby on rails.
1. View Helpers for Debugging
Nếu bạn muốn kiểm tra nội dung của một biến thì trong rails bạn có thể làm việc này bằng 3 cách:
- debug
- to_yaml
- inspect
1.1 debug
Debug sẽ trả về một thẻ
trả về một object sử dụng định dạnh YAML. Bạn có thể đọc dược dữ liệu từ bất kỳ đối tượng nào. Ví dụ: nếu bạn có 1 đoạn code như sau:
<%= debug @user %>
<p>
<b>Name:</b>
<%= @user.name %>
</p>
Bạn sẽ có được thông tin của đối tượng đó:
#<User id: 1, age: 10, name: "Rails_debugging_guide", delete_flag: false, created_at: "2015-12-02 15:57:58", updated_at: "2015-12-22 11:11:35">
Name: Rails_debugging_guide
1.2 to_yaml
Cho phép hiển thị một biến instance, một object bất kỳ hay một method. Ví dụ:
<%= simple_format @user.to_yaml %>
<p>
<b>Name:</b>
<%= @user.name %>
</p>
Method to_yaml chuyển đổi phương thức thành định dạng YAML để nó dễ đọc hơn, sau đó helper simple_format được sử dụng để trả về mỗi dòng như sau:
--- !ruby/object User
attributes:
id: 1
age: 10
name: "Rails_debugging_guide"
delete_flag: false
created_at: "2015-12-02 15:57:58"
updated_at: "2015-12-22 11:11:35"
Name: Rails_debugging_guide
1.3 inspect
Một phương pháp hữu ích khác để hiển thị các giá trị của object là inspect, đặc biệt là khi làm việc với arrays hoặc hashes. Ví dụ:
# a = [1, 2, 3, 4]
<%= a.inspect %>
<p>
<b>Title: Rails debugging guide</b>
</p>
Ta sẽ có thông tin sau:
[1, 2, 3, 4]
Title: Rails debugging guide
2. The Logger
Nó rất hữu ích cho việc lưu thông tin vào file log tại thời gian chạy. Rails dành một file log riêng biệt cho từng môi trường.
2.1 What is the Logger?
Rails dùng class ActiveSupport::Logger
để ghi lại thông tin log. Bạn có thể thay thế logeger khác như Log4r nếu bạn muốn.
Bạn có thể xác định logger thay thế trong environment.rb
của bạn hoặc file môi trường bất kỳ.
Rails.logger = Logger.new(STDOUT)
Rails.logger = Log4r::Logger.new("Application Log")
Hoặc trong Initializer, thêm vào như sau:
config.logger = Logger.new(STDOUT)
config.logger = Log4r::Logger.new("Application Log")
Chú ý: Mặc định mỗi log được tạo ra trong Rails.root/log/
và file log được đặt tên theo môi trường mà ứng dụng đang chạy.
2.2 Log Levels
Nếu bạn muốn biết level log hiện tại của bạn, bạn có thể gọi phương thức Rails.logger.level
mức độ log có thể là :debug
, :info
, :warn
, :error
, :fatal
, và :unknown
, tương ứng với các số từ 0 đến 5. Để thay đổi mức độ mặc định bạn có thể làm như sau:
config.log_level = :warn # In any environment initializer, or
Rails.logger.level = 0 # at any time
Điều này rất có ích khi bạn muốn log dưới development hoặc staging mà không muốn log vào production các thông tin không cần thiết.
Chú ý: mặc định mức độ log của rails là debug trong tất cả các môi trường.
2.3 Sending Messages
Để viết trong log hiện tại sử dụng logger. phương thức (debug|info|warn|error|fatal) bên trong một controller, model hoặc mailer:
logger.debug "Person attributes hash: #{@person.attributes.inspect}"
logger.info "Processing the request..."
logger.fatal "Terminating application, raised unrecoverable error!!!"
Sau đây là 1 ví dụ sử dụng logger:
class ArticlesController < ApplicationController
# ...
def create
@article = Article.new(params[:article])
logger.debug "New article: #{@article.attributes.inspect}"
logger.debug "Article should be valid: #{@article.valid?}"
if @article.save
flash[:notice] = 'Article was successfully created.'
logger.debug "The article was saved and now the user is going to be redirected..."
redirect_to(@article)
else
render :new
end
end
# ...
end
Khi bạn thực thi hành động create của controller trên log sẽ được ghi lại:
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"}
New article: {"updated_at"=>nil, "title"=>"Debugging Rails", "body"=>"I'm learning how to print in logs!!!",
"published"=>false, "created_at"=>nil}
Article should be valid: 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')
The article was saved and now the user is going to be redirected...
Redirected to # Article:0x20af760>
Completed in 0.01224 (81 reqs/sec) | DB: 0.00044 (3%) | 302 Found [http://localhost/articles]
Như vậy là bạn đã có thể dễ dàng ghi lại những thông tin cần thiết mà mình muốn.
2.4 Tagged Logging
Khi chạy các ứng dụng đa người dùng, đa tài khoản, nó thường hữu ích để có thể lọc các bản ghi bằng cách sử dụng một số quy tắc tùy chỉnh. TaggedLogging trong Active Support giúp làm việc chính xác bằng cách đánh dấu các dòng log với subdomains, yêu cầu các id, và bất cứ điều gì khác để hỗ trợ debug các ứng dụng.
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. Debugging with the byebug gem
Khi code của bạn thực hiện một cách khác thường, không như mong muốn, bạn có thể thử mở các log hoặc console ra để có thể xem xét và phán đoán lỗi. Nhưng mà có những lúc nó không giúp cho bạn tìm hiểu đc tận gốc vấn đề. Khi đó bạn sẽ cần phải can thiệp vào tiến trình mã nguồn của bạn đang chạy để có thể sửa lỗi một cách tốt nhất.
Debugger cũng có thể giúp đỡ cho bạn nếu bạn muốn tìm hiểu về mã nguồn của nó nhưng mà bạn sẽ ko biết bắt đầu từ đâu. Vì vậy bạn có thể sử dụng gem để hỗ trợ việc này.
3.1 Setup
Bạn có thể sử dụng gem byebug để đặt các điểm dừng trong quá trình chạy. Để cài đặt chỉ cần chạy lạnh:
$ gem install byebug
hoặc thêm đoạn sau vào Gemfile của bạn sau đó chạy bundle install
gem 'byebug', '~> 8.2', '>= 8.2.1'
Trong trang bất kỳ của ứng dụng bạn có thể gọi debugger bằng cách gọi phương thức byebug. Ví dụ:
class PeopleController < ApplicationController
def new
byebug
@person = Person.new
end
end
3.2 The Shell
Sau khi ứng dụng của bạn gọi phương thức byebug, debugger sẽ bắt đầu debug trong cửa sổ terminal mà bạn chạy server ứng dụng của bạn, và tại vị trí nơi mà bạn đặt lệnh gọi phương thức byebug. Các đoạn code xung quanh byebug cũng được hiển thị và dòng hiện thời sẽ được đánh dấu '=>'. Ví dụ:
[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)
Nếu bạn gửi request trên trình duyệt thực hiện có chứa byebug thì cửa sổ sẽ bị treo cho đến khi quá trình debugger kết thúc và tiến trình xử lý xong tất cả các request. Ví dụ:
=> Booting WEBrick
=> Rails 4.2.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)
Bây giờ bạn có thể đi sâu và tìm hiểu ứng dụng của bạn. Nếu bạn cần sự trợ giúp hãy nhập vào 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
Để xem mười dòng trước đó, bạn có thể gõ list- (hoặc 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|
Bằng cách này mà bạn có thể di chuyển bên trong các file, có thể thấy đoạn code xung quanh nơi mà bạn gọi byebug. Bạn có thể xem bạn đang ở đoạn mã nào bằng cách gõ 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 Inspecting Variables
Biểu thức bất kỳ có thể được đánh giá trong lúc này. Để đánh giá nó bạn có thể nhập nó vào.
Đây là ví dụ làm thế nào để bạn có thể in các biến instance được định nghĩa tại thời điểm đó:
[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]
Như bạn thấy, tất cả các biến mà bạn có thể truy cập từ controller được hiển thị. Danh sách này được tự động cập nhật khi bạn thực thi code. Ví dụ chạy dòng tiếp theo sử dụng 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)
Và sau đó bạn gọi lại instance_variables:
(byebug) instance_variables.include? "@articles"
true
Bây giờ @article đã được include vào các biến instance, bởi vì dòng định nghĩa biến @article đã được thực hiện.
Phương thức var là cách tốt nhất để show các biến và giá trị của chúng.
(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
Đây là một cách tuyệt vời để kiểm tra các giá trị của các biến hiện thời. Ví dụ kiểm tra xem có các biến local đã được định nghĩa không:
(byebug) var local
(byebug)
Bạn cũng có thể kiểm tra cho một phương thức object như sau:
(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
...
Bạn cũng có thể sử dụng display để bắt đầu xem các biến. Đây là cách tốt nhất để theo dõi giá trị của 1 biến trong khi thực hiện.
(byebug) display @articles
1: @articles = nil
3.4 Step by Step
Sử dụng step (viết tắt là s) để tiếp tục chạy chương trình của bạn. Bạn cũng có thể sử dụng next nó tương tự như step, nhưng lời gọi hàm và phương thức mà xuất hiện trong dòng code được thực hiện không cần dừng lại.
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)
Nếu chúng ta sử dụng next, chúng ta muốn đi sâu vào bên trong phuong thức gọi. Thay vào đó, byebug sẽ đi đến dòng tiếp theo. Trong trường hợp đây là dòng cuối của phương thức đó, byebug sẽ nhảy đến dòng tiếp theo của frame trước đó.
(byebug) next
Next went up a frame because previous frame finished
[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)
Nếu Chúng ta sử dụng step trong tình huống trên thì chúng ta sẽ đi đến thư viện cấu trúc ruby tiếp theo được thực hiện. Trong trường hợp này sẽ là phương thức week của activesupport
[50, 59] in /PathToGems/activesupport-4.2.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)
Đây là một trong những cách tốt nhất để tìm các lỗi trong code của bạn trong ruby on rails.
3.5 Editing
hai lệnh cho phép bạn mở code từ debugger để sửa:
edit [file:line]: sửa file bằng cách sử dụng trình soạn thảo quy định bởi các biến môi trường EDITOR.
3.6 Quitting
Đêr thoát khỏi debugger, sử dụng lệnh quit (viết tắt q).
Kết Luận
Trên đây là một số phương pháp tìm kiếm phát hiện lỗi cho một ứng dụng ruby on rails. Bài viết còn nhiều thiếu sót rất mong nhận được sự góp ý của các bạn. Cảm ơn đã theo dõi bài viết này.
Tham khảo: http://guides.rubyonrails.org/debugging_rails_applications.html
All rights reserved