12/08/2018, 13:06

Debugging Rails Applications

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 ...

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ẻ <pre> 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

0