88
Debugging on Rails Mykhaylo Sorochan kiev.rb #2 kiev.rb #2

Debugging on rails

Embed Size (px)

DESCRIPTION

debugger commands, call controller from console, curl requests, useful browser tools. Presented at kiev.rb#2 conference, Kiev, Ukraine 30 Nov 2013

Citation preview

Page 1: Debugging on rails

Debugging on RailsMykhaylo Sorochan

kiev.rb #2kiev.rb #2

Page 2: Debugging on rails

Contents

Page 3: Debugging on rails

Sample app for the Ruby on Rails Tutorial (Rails 4)https://github.com/railstutorial/sample_app_rails_4

Debugged application

Page 4: Debugging on rails

debugger

gem 'debugger'debugger - call debuggerconfig - .rdebugrc

Page 5: Debugging on rails

Debug locationUserController#show

def show

@user = User.find(params[:id])

debugger

@microposts = @user.microposts.paginate(page: params[:page])

end

Page 6: Debugging on rails

Debugger commands(rdb:9) help

ruby-debug help v1.6.2

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

Available commands:

backtrace delete enable help list pry restart source undisplay

break disable eval info method ps save start up

catch display exit irb next putl set step var

condition down finish jump p quit show thread where

continue edit frame kill pp reload skip trace

Page 7: Debugging on rails

Program info(rdb:4) help info

Generic command for showing things about the program being debugged.

--

List of info subcommands:

--

info args -- Argument variables of current stack frame

info breakpoints -- Status of user-settable breakpoints

info catch -- Exceptions that can be caught in the current stack frame

info display -- Expressions to display when program stops

info file -- Info about a particular file read in

info files -- File names and timestamps of files read in

info global_variables -- Global variables

info instance_variables -- Instance variables of the current stack frame

info line -- Line number and file name of current position in source file

info locals -- Local variables of the current stack frame

info program -- Execution status of the program

info stack -- Backtrace of the stack

info thread -- List info about thread NUM

info threads -- information of currently-known threads

info variables -- Local and instance variables of the current stack frame

Page 8: Debugging on rails

Listing(rdb:1) help list

l[ist] list forward

l[ist] - list backward

l[ist] = list current line

l[ist] nn-mm list given lines

NOTE - to turn on autolist, use 'set autolist' -> .rdebugrc

Page 9: Debugging on rails

Listing: in debug 12 @user = User.find(params[:id])

13 debugger

=> 14 @microposts = @user.microposts.paginate(page: params[:page])

15 end

16

Page 10: Debugging on rails

Listing: l 23,25l 23,25

[23, 25] in */sample_app_rails_4/app/controllers/users_controller.rb

23 if @user.save

24 sign_in @user

25 flash[:success] = "Welcome to the Sample App!"

Page 11: Debugging on rails

Breakpoints(rdb:4) help break

b[reak] file:line [if expr]

b[reak] class(.|#)method [if expr]

set breakpoint to some position, (optionally) if expr == true

(rdb:4) help delete

del[ete][ nnn...] delete some or all breakpoints

Page 12: Debugging on rails

Set breakpoint(rdb:4) b ApplicationHelper#full_title

Breakpoint 1 at ApplicationHelper::full_title

(rdb:4) b 18

Breakpoint 2 file */sample_app_rails_4/app/controllers/users_controller.rb, line 18

(rdb:4) info b

Num Enb What

1 y at ApplicationHelper:full_title

2 y at */sample_app_rails_4/app/controllers/users_controller.rb:18

Page 13: Debugging on rails

Navigate(rdb:4) help step

s[tep][+-]?[ nnn] step (into methods) once or nnn times

'+' forces to move to another line.

'-' is the opposite of '+' and disables the force_stepping setting.

(rdb:4) help next

n[ext][+-]?[ nnn] step over once or nnn times,

'+' forces to move to another line.

'-' is the opposite of '+' and disables the force_stepping setting.

(rdb:4) help up

up[count] move to higher frame

Page 14: Debugging on rails

Stop at breakpoint(rdb:4) c

Breakpoint 1 at ApplicationHelper:full_title

[-1, 8] in */sample_app_rails_4/app/helpers/application_helper.rb

1 module ApplicationHelper

2

3 # Returns the full title on a per-page basis.

=> 4 def full_title(page_title)

5 base_title = "Ruby on Rails Tutorial Sample App"

Page 15: Debugging on rails

Display(rdb:4) help display

disp[lay] <expression> add expression into display expression list

disp[lay] display expression list

(rdb:4) help undisplay

undisp[lay][ nnn]

Cancel some expressions to be displayed when program stops.

Page 16: Debugging on rails

Display configured 4 def full_title(page_title)

=> 5 base_title = "Ruby on Rails Tutorial Sample App"

6 if page_title.empty?

(rdb:4) disp

2: controller_name = users

3: base_title =

Page 17: Debugging on rails

Display changed(rdb:4) n

*/sample_app_rails_4/app/helpers/application_helper.rb:6

if page_title.empty?

2: controller_name = users

3: base_title = Ruby on Rails Tutorial Sample App

[1, 10] in */sample_app_rails_4/app/helpers/application_helper.rb

5 base_title = "Ruby on Rails Tutorial Sample App"

=> 6 if page_title.empty?

7 base_title

Page 18: Debugging on rails

Conditional breakpoint(rdb:8) b ApplicationHelper#full_title if page_title=="Alexander"

Breakpoint 4 at ApplicationHelper::full_title

(rdb:8) c

Breakpoint 1 at ApplicationHelper:full_title

Page 19: Debugging on rails

Variables(rdb:8) help var

v[ar] cl[ass] show class variables of self

v[ar] co[nst] <object> show constants of object

v[ar] g[lobal] show global variables

v[ar] i[nstance] <object> show instance variables of object. You may pass object id's hex as well.

v[ar] l[ocal] show local variables

Page 20: Debugging on rails

Show variables 12 @user = User.find(params[:id])

13 debugger

=> 14 @microposts = @user.microposts.paginate(page: params[:page])

15 end

16

17 def new

(rdb:12) v i

@_action_has_layout = true

@_action_name = "show"

@_config = {}

@_env = {"GATEWAY_INTERFACE"=>"CGI/1.1", "PATH_INFO"=>"/users/1", "QUERY_STRING"=>"",...

@_headers = {"Content-Type"=>"text/html"}

@_lookup_context = #<ActionView::LookupContext:0x00000008e4b710 @details_key=nil, @details={:loc...

@_params = {"action"=>"show", "controller"=>"users", "id"=>"1"}

@_prefixes = ["users", "application"]

@_request = #<ActionDispatch::Request:0x00000008e4bad0 @env={"GATEWAY_INTERFACE"=>"CGI/1....

@_response = #<ActionDispatch::Response:0x00000008e4baa8 @mon_owner=nil, @mon_count=0, @mo...

@_response_body = nil

@_routes = nil

@_status = 200

@user = #<User id: 1, name: "Alexander", email: "[email protected]", created_at: "2013-11...

Page 21: Debugging on rails

Remote debugger

Client# rdebug --client -h 127.0.0.1

Connected.

*/sample_app_rails_4/app/controllers/users_controller.rb:14

@microposts = @user.microposts.paginate(page: params[:page])

[9, 18] in */sample_app_rails_4/app/controllers/users_controller.rb

9 end

10

11 def show

12 @user = User.find(params[:id])

13 debugger

=> 14 @microposts = @user.microposts.paginate(page: params[:page])

ServerDebugger.wait_connection = true

Debugger.start_remote

Page 22: Debugging on rails

pry

REPL

Page 23: Debugging on rails

debugger-pry

Call pry from debugger

Page 24: Debugging on rails

pry-debugger

Debugger commands inside pry:step, next, continue, breakpoints

Page 25: Debugging on rails

jazz_hands

jazz_hands is an opinionated set of console-related gems and a bit of glue:pry, awesome_print, hirb, pry-rails, pry-doc, pry-git, pry-remote, pry-debugger, pry-stack_explorer, coolline, coderay

Page 26: Debugging on rails

pry – jazz_hands 1

Page 27: Debugging on rails

pry – jazz_hands 2

Page 28: Debugging on rails

pry – jazz_hands 3

Page 29: Debugging on rails

pry – jazz_hands 4

Page 30: Debugging on rails

Logging

ActiveSupport::Logger is used for logging

Rails.root/log/[environment_name].log

Page 31: Debugging on rails

Log levelsRails.logger.level

| name | level |

|----------+-------|

| :debug | 0 |

| :info | 1 |

| :warn | 2 |

| :error | 3 |

| :fatal | 4 |

| :unknown | 5 |

development, testing: log_level = 0 (:debug)production: log_level = 1

Messages with equal or higher level are sent to log

Page 32: Debugging on rails

Write to loglogger.debug User.inspect

logger.info user.id

logger.fatal "Help!"

Page 33: Debugging on rails

Tagging log messageslogger = ActiveSupport::TaggedLogging.new(Logger.new(STDOUT))

logger.tagged("USER") { logger.info "hello" }

=> [USER] hello

Page 34: Debugging on rails

Tagged logging

def show

@user = User.find(params[:id])

logger.tagged("USER") { logger.info @user.email }

# tail -f log/development.log|grep \\\[USER\]

[USER] [email protected]

Page 35: Debugging on rails

Global variables for tracing__FILE__ - file name

__LINE__ - line number

Page 36: Debugging on rails

Some useful CLI commandstail -n 100

tail -f

grep, less

Page 37: Debugging on rails

Querying logs# cat log/development.log|grep GET|tail -n 2

Started GET "/assets/users.js?body=1" for 127.0.0.1 at 2013-11-21 23:32:01 +0200

Started GET "/assets/application.js?body=1" for 127.0.0.1 at 2013-11-21 23:32:01 +0200

# cat log/development.log|grep GET|wc -l

574

Page 38: Debugging on rails

config.log_formatterDefines the formatter of the Rails logger. Defaults to ActiveSupport::Logger::SimpleFormatter for all modes, production defaults to Logger::Formatter.

module ActiveSupport

class Logger < ::Logger

# Simple formatter which only displays the message.

class SimpleFormatter < ::Logger::Formatter

# This method is invoked when a log event occurs

def call(severity, timestamp, progname, msg)

"#{String === msg ? msg : msg.inspect}\n"

end

end

Page 39: Debugging on rails

config.log_levelDefaults to :debug for all modes, production defaults to :info.

Page 40: Debugging on rails

config.log_tagsSee ActionDispatch::Request methods. config.log_tags = [ :fullpath ]

[/users/1] Started GET "/users/1" for 127.0.0.1 at 2013-11-...

[/users/1] ActiveRecord::SchemaMigration Load (0.1ms) SELECT ...

[/assets/application.css?body=1] Started GET "/assets/application.css?body=1" for 127...

Page 41: Debugging on rails

config.loggerAccepts a logger conforming to the interface of Log4r or the default Ruby Logger class. Defaults to ActiveSupport::Logger, with auto flushing off in production mode.

Page 42: Debugging on rails

Rails consolerails c

rails c --sandbox

irb

pry

Page 43: Debugging on rails

rake routes> all_routes = Rails.application.routes.routes

> require 'action_dispatch/routing/inspector'

> inspector = ActionDispatch::Routing::RoutesInspector.new(all_routes)

Page 44: Debugging on rails

All routes> puts inspector.format(ActionDispatch::Routing::ConsoleFormatter.new)

Prefix Verb URI Pattern Controller#Action

following_user GET /users/:id/following(.:format) users#following

followers_user GET /users/:id/followers(.:format) users#followers

users GET /users(.:format) users#index

POST /users(.:format) users#create

new_user GET /users/new(.:format) users#new

edit_user GET /users/:id/edit(.:format) users#edit

user GET /users/:id(.:format) users#show

PATCH /users/:id(.:format) users#update

PUT /users/:id(.:format) users#update

DELETE /users/:id(.:format) users#destroy

sessions POST /sessions(.:format) sessions#create

new_session GET /sessions/new(.:format) sessions#new

session DELETE /sessions/:id(.:format) sessions#destroy

microposts POST /microposts(.:format) microposts#create

micropost DELETE /microposts/:id(.:format) microposts#destroy

relationships POST /relationships(.:format) relationships#create

relationship DELETE /relationships/:id(.:format) relationships#destroy

root GET / static_pages#home

signup GET /signup(.:format) users#new

Page 45: Debugging on rails

Routes for controller> puts inspector.format(ActionDispatch::Routing::ConsoleFormatter.new, 'microposts')

Prefix Verb URI Pattern Controller#Action

microposts POST /microposts(.:format) microposts#create

micropost DELETE /microposts/:id(.:format) microposts#destroy

Page 46: Debugging on rails

Filtering routes: GET> puts inspector.format(ActionDispatch::Routing::ConsoleFormatter.new, 'users').lines.grep(/GET/).join

following_user GET /users/:id/following(.:format) users#following

followers_user GET /users/:id/followers(.:format) users#followers

users GET /users(.:format) users#index

new_user GET /users/new(.:format) users#new

edit_user GET /users/:id/edit(.:format) users#edit

user GET /users/:id(.:format) users#show

signup GET /signup(.:format) users#new

Page 47: Debugging on rails

Filtering routes: /relation/> puts inspector.format(ActionDispatch::Routing::ConsoleFormatter.new).lines.grep(/relation/).join

relationships POST /relationships(.:format) relationships#create

relationship DELETE /relationships/:id(.:format) relationships#destroy

Page 48: Debugging on rails

Matching routes> r = Rails.application.routes

> r.recognize_path "/users/47"

=> {:action=>"show", :controller=>"users", :id=>"47"}

> r.recognize_path "/users/87", :method => "PUT"

=> {:action=>"update", :controller=>"users", :id=>"87"}

> r.recognize_path "/users/47.json"

=> {:action=>"show", :controller=>"users", :id=>"47", :format=>"json"}

Page 49: Debugging on rails

Named routes> app.users_path

=> "/users"

> app.users_path(:json)

=> "/users.json"

> app.user_path(1)

=> "/users/1"

> app.user_path(1, :xml)

=> "/users/1.xml"

> app.user_path(1, :count => 4)

=> "/users/1?count=4"

Page 50: Debugging on rails

Making requests> app

=> #<ActionDispatch::Integration::Session:...>

> app.reset!

Page 51: Debugging on rails

Get requests> app.get '/users/1/edit'

Started GET "/users/1/edit" for 127.0.0.1 at 2013-11-26 23:24:18 +0200

Processing by UsersController#edit as HTML

Parameters: {"id"=>"1"}

Redirected to http://localhost:3000/signin

Filter chain halted as :signed_in_user rendered or redirected

Completed 302 Found in 3ms (ActiveRecord: 0.4ms)

> app.response.body

=> "<html><body>You are being <a href=\"http://localhost:3000/signin\">redirected</a>.</body></html>"

> app.get_via_redirect '/users/1/edit'

Started GET "/users/1/edit" for 127.0.0.1 at 2013-11-26 23:26:44 +0200

Redirected to http://localhost:3000/signin

...

Started GET "/signin" for 127.0.0.1 at 2013-11-26 23:26:44 +0200

Page 52: Debugging on rails

Session cookies> app.cookies

=> #<Rack::Test::CookieJar...

> app.cookies.to_hash

=> {"_sample_app_session"=>"RC9j...

app.cookies - received/sent cookies

Page 53: Debugging on rails

Post requests: signin> app.response.body.lines.grep /csrf-token/

=> ["<meta content=\"n+9uCcG2JJmgwhnNcp4s9jTwOU55RAPOdtAHWstcpKQ=\" name=\"csrf-token\" />\n"]

> app.post '/sessions', :authenticity_token => 'n+9uCcG2JJmgwhnNcp4s9jTwOU55RAPOdtAHWstcpKQ=',

'session[email]' => '[email protected]', 'session[password]' => '123456'

Started POST "/sessions" for 127.0.0.1 at 2013-11-26 23:33:01 +0200

Processing by SessionsController#create as HTML

Parameters: {"authenticity_token"=>"n+9uCcG2JJmgwhnNcp4s9jTwOU55RAPOdtAHWstcpKQ=", "session"=>{"email"=>"[email protected]", "password"=>"[FILTERED]"}}

Redirected to http://localhost:3000/users/1/edit

Completed 302 Found in 281ms (ActiveRecord: 7.2ms)

app.post_via_redirect

Page 54: Debugging on rails

Access to restricted resource> app.get '/users/1/edit'

Started GET "/users/1/edit" for 127.0.0.1 at 2013-11-26 23:38:47 +0200

Processing by UsersController#edit as HTML

Completed 200 OK in 41ms (Views: 35.7ms | ActiveRecord: 0.8ms)

Page 55: Debugging on rails

Call helper> helper

=> #<ActionView::Base:... >

# ApplicationHelper#full_title

> helper.full_title "Sign Up"

=> "Ruby on Rails Tutorial Sample App | Sign Up"

Page 56: Debugging on rails

Call helper with cookie> def cookies; @cookies ||= HashWithIndifferentAccess.new(app.cookies.to_hash); end

> helper.current_user

=> #<User id: 1, name: ....

Page 57: Debugging on rails

ActiveRecord logging> ActiveRecord::Base.logger = Logger.new(STDOUT)

> ActiveRecord::Base.clear_active_connections!

> # reload!

> User.find 1

D, [2013-12-30T21:55:17.775769 #24810] DEBUG -- : User Load (0.2ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT 1 [["id", 1]]

=> #<User id: 1, name: "hello",....

Page 58: Debugging on rails

ActiveRecord hide logging> ActiveRecord::Base.logger = Logger.new(nil)

> ActiveRecord::Base.clear_active_connections!

> # reload!

> User.find 1

=> #<User id: 1, name: "hello",....

Page 59: Debugging on rails

Raw SQL requests> ActiveRecord::Base.connection.select_all("select * from users")

=> #<ActiveRecord::Result:...

> puts _

{"id"=>1, "name"=>"hello", "email"=>"[email protected]", "created_at"=>"2013-....

Page 60: Debugging on rails

.irbrc

Save your helper methods● routes● sql logging● etc

Page 61: Debugging on rails

CLI tools

Making HTTP requests with curl● -s silent● -v verbose● -c save cookie● -b use cookie● -data POST data● -data-urlencode URL-encode POST data

Page 62: Debugging on rails

Access restricted areacurl -s -v http://localhost:3000/users/1/edit > /dev/null

> GET /users/1/edit HTTP/1.1

< HTTP/1.1 302 Found

< Location: http://localhost:3000/signin

Page 63: Debugging on rails

Visit /signin, get tokencurl -s -c hello_cookies http://localhost:3000/signin > /dev/null |grep csrf-token

<meta content="/t/IoUQxKVEL+KR2/HsnxTKmnALUA99jIr/LvjlgPKs=" name="csrf-token" />

Page 64: Debugging on rails

Sign incurl -s -v --data "session[email][email protected];session[password]=123456" \

--data-urlencode "authenticity_token=/t/IoUQxKVEL+KR2/HsnxTKmnALUA99jIr/LvjlgPKs=" \

-b hello_cookies -c cookies \

http://localhost:3000/sessions > /dev/null

> POST /sessions HTTP/1.1

< HTTP/1.1 302 Found

< Location: http://localhost:3000/users/1

Page 65: Debugging on rails

Successful access to restricted areacurl -s -v http://localhost:3000/users/1/edit -b cookies > /dev/null

> GET /users/1/edit HTTP/1.1

< HTTP/1.1 200 OK

Page 66: Debugging on rails

Browser tools

● console● render helpers● debug info● etc

Page 67: Debugging on rails

rack-webconsole>> User.find 1

=> #<User id: 1, name: "Alexander", email: "[email protected]", created_at: "2013-11-17 16:19:07", updated_at: "2013-11-27 21:52:06", password_digest: "$2a$10$MEICr2zekeBhh9HYCMLmXut3ckOsiL0TkksFWVX4asFf...", remember_token: "cda4da34a5ee4238ddb78f20d4ec7e52b59fab4e", admin: nil>

>> helper

=> Error: undefined local variable or method `helper' for #<Rack::Webconsole::Sandbox:0x000000089cf600>

>> app

=> Error: undefined local variable or method `app' for #<Rack::Webconsole::Sandbox:0x000000089cf600>

>> Rails.root

=> #<Pathname:*/sample_app_rails_4>

>> self

=> #<Rack::Webconsole::Sandbox:0x000000089cf600 @locals={:u=>#<User id: 1, name: "Alexander"

Page 68: Debugging on rails

rack-webconsole

` - activate

Page 69: Debugging on rails

Rails Panel

Page 70: Debugging on rails

rails-footnotes: Assigns

Page 71: Debugging on rails

rails-footnotes: DB

Page 72: Debugging on rails

rails-footnotes: disable?footnotes=false

Page 73: Debugging on rails

xray-railsCtrl+Shift+X

Page 74: Debugging on rails

better_errors 1

Page 75: Debugging on rails

better_errors 2

Page 76: Debugging on rails

exception_notifier

Provides a set of notifiers for sending notifications when errors occur in a Rack/Rails application

Page 77: Debugging on rails

letter_opener

Preview email in the browser instead of sending it

Page 78: Debugging on rails

exception_notifier + letter_opener

Page 79: Debugging on rails

Chrome: Form Editor

Page 80: Debugging on rails

Chrome: JunkFill

Page 81: Debugging on rails

Chrome: Sight

Page 82: Debugging on rails

API calls, sight

Page 83: Debugging on rails

hurl.it: params

Page 84: Debugging on rails

hurl.it: response

Page 85: Debugging on rails

POSTMAN

Page 86: Debugging on rails

Chrome: REST console

Page 87: Debugging on rails

Chrome: Advanced Rest Client

Page 88: Debugging on rails

ВСЁ