mirror of https://github.com/rails/rails
Add breakpoint section
Add breakpoint examples Add breakpoint options Some corrections Introduce chained debugging commands
This commit is contained in:
parent
87d65e9b12
commit
03d76fb40a
|
@ -468,6 +468,287 @@ instance variables:
|
|||
class variables: @@raise_on_missing_translations @@raise_on_open_redirects
|
||||
```
|
||||
|
||||
### Breakpoints
|
||||
|
||||
There are many ways to insert and trigger a breakpoint in the debugger. In additional to adding debugging statements (e.g. `debugger`) directly in your code, you can also insert breakpoints with commands:
|
||||
|
||||
- `break` (or `b`)
|
||||
- `break` - list all breakpoints
|
||||
- `break <num>` - set a breakpoint on the `num` line of the current file
|
||||
- `break <file:num>` - set a breakpoint on the `num` line of `file`
|
||||
- `break <Class#method>` or `break <Class.method>` - set a breakpoint on `Class#method` or `Class.method`
|
||||
- `break <expr>.<method>` - sets a breakpoint on `<expr>` result's `<method>` method.
|
||||
- `catch <Exception>` - set a breakpoint that'll stop when `Exception` is raised
|
||||
- `watch <@ivar>` - set a breakpoint that'll stop when the result of current object's `@ivar` is changed (this is slow)
|
||||
|
||||
And to remove them, you can use:
|
||||
|
||||
- `delete` (or `del`)
|
||||
- `delete` - delete all breakpoints
|
||||
- `delete <num>` - delete the breakpoint with id `num`
|
||||
|
||||
#### The break command
|
||||
|
||||
**Set a breakpoint with specified line number - e.g. `b 28`**
|
||||
|
||||
```rb
|
||||
[20, 29] in ~/projects/rails-guide-example/app/controllers/posts_controller.rb
|
||||
20| end
|
||||
21|
|
||||
22| # POST /posts or /posts.json
|
||||
23| def create
|
||||
24| @post = Post.new(post_params)
|
||||
=> 25| debugger
|
||||
26|
|
||||
27| respond_to do |format|
|
||||
28| if @post.save
|
||||
29| format.html { redirect_to @post, notice: "Post was successfully created." }
|
||||
=>#0 PostsController#create at ~/projects/rails-guide-example/app/controllers/posts_controller.rb:25
|
||||
#1 ActionController::BasicImplicitRender#send_action(method="create", args=[]) at ~/.rbenv/versions/3.0.1/lib/ruby/gems/3.0.0/gems/actionpack-7.0.0.alpha2/lib/action_controller/metal/basic_implicit_render.rb:6
|
||||
# and 72 frames (use `bt' command for all frames)
|
||||
(rdbg) b 28 # break command
|
||||
#0 BP - Line /Users/st0012/projects/rails-guide-example/app/controllers/posts_controller.rb:28 (line)
|
||||
```
|
||||
|
||||
```rb
|
||||
(rdbg) c # continue command
|
||||
[23, 32] in ~/projects/rails-guide-example/app/controllers/posts_controller.rb
|
||||
23| def create
|
||||
24| @post = Post.new(post_params)
|
||||
25| debugger
|
||||
26|
|
||||
27| respond_to do |format|
|
||||
=> 28| if @post.save
|
||||
29| format.html { redirect_to @post, notice: "Post was successfully created." }
|
||||
30| format.json { render :show, status: :created, location: @post }
|
||||
31| else
|
||||
32| format.html { render :new, status: :unprocessable_entity }
|
||||
=>#0 block {|format=#<ActionController::MimeResponds::Collec...|} in create at ~/projects/rails-guide-example/app/controllers/posts_controller.rb:28
|
||||
#1 ActionController::MimeResponds#respond_to(mimes=[]) at ~/.rbenv/versions/3.0.1/lib/ruby/gems/3.0.0/gems/actionpack-7.0.0.alpha2/lib/action_controller/metal/mime_responds.rb:205
|
||||
# and 74 frames (use `bt' command for all frames)
|
||||
|
||||
Stop by #0 BP - Line /Users/st0012/projects/rails-guide-example/app/controllers/posts_controller.rb:28 (line)
|
||||
```
|
||||
|
||||
**Set a breakpoint on a given method call - e.g. `b @post.save`**
|
||||
|
||||
```rb
|
||||
[20, 29] in ~/projects/rails-guide-example/app/controllers/posts_controller.rb
|
||||
20| end
|
||||
21|
|
||||
22| # POST /posts or /posts.json
|
||||
23| def create
|
||||
24| @post = Post.new(post_params)
|
||||
=> 25| debugger
|
||||
26|
|
||||
27| respond_to do |format|
|
||||
28| if @post.save
|
||||
29| format.html { redirect_to @post, notice: "Post was successfully created." }
|
||||
=>#0 PostsController#create at ~/projects/rails-guide-example/app/controllers/posts_controller.rb:25
|
||||
#1 ActionController::BasicImplicitRender#send_action(method="create", args=[]) at ~/.rbenv/versions/3.0.1/lib/ruby/gems/3.0.0/gems/actionpack-7.0.0.alpha2/lib/action_controller/metal/basic_implicit_render.rb:6
|
||||
# and 72 frames (use `bt' command for all frames)
|
||||
(rdbg) b @post.save # break command
|
||||
#0 BP - Method @post.save at /Users/st0012/.rbenv/versions/3.0.1/lib/ruby/gems/3.0.0/gems/activerecord-7.0.0.alpha2/lib/active_record/suppressor.rb:43
|
||||
|
||||
```
|
||||
|
||||
```rb
|
||||
(rdbg) c # continue command
|
||||
[39, 48] in ~/.rbenv/versions/3.0.1/lib/ruby/gems/3.0.0/gems/activerecord-7.0.0.alpha2/lib/active_record/suppressor.rb
|
||||
39| SuppressorRegistry.suppressed[name] = previous_state
|
||||
40| end
|
||||
41| end
|
||||
42|
|
||||
43| def save(**) # :nodoc:
|
||||
=> 44| SuppressorRegistry.suppressed[self.class.name] ? true : super
|
||||
45| end
|
||||
46|
|
||||
47| def save!(**) # :nodoc:
|
||||
48| SuppressorRegistry.suppressed[self.class.name] ? true : super
|
||||
=>#0 ActiveRecord::Suppressor#save(#arg_rest=nil) at ~/.rbenv/versions/3.0.1/lib/ruby/gems/3.0.0/gems/activerecord-7.0.0.alpha2/lib/active_record/suppressor.rb:44
|
||||
#1 block {|format=#<ActionController::MimeResponds::Collec...|} in create at ~/projects/rails-guide-example/app/controllers/posts_controller.rb:28
|
||||
# and 75 frames (use `bt' command for all frames)
|
||||
|
||||
Stop by #0 BP - Method @post.save at /Users/st0012/.rbenv/versions/3.0.1/lib/ruby/gems/3.0.0/gems/activerecord-7.0.0.alpha2/lib/active_record/suppressor.rb:43
|
||||
```
|
||||
|
||||
#### The catch command
|
||||
|
||||
**Stop when an exception is raised - e.g. `catch ActiveRecord::RecordInvalid`**
|
||||
|
||||
```rb
|
||||
[20, 29] in ~/projects/rails-guide-example/app/controllers/posts_controller.rb
|
||||
20| end
|
||||
21|
|
||||
22| # POST /posts or /posts.json
|
||||
23| def create
|
||||
24| @post = Post.new(post_params)
|
||||
=> 25| debugger
|
||||
26|
|
||||
27| respond_to do |format|
|
||||
28| if @post.save!
|
||||
29| format.html { redirect_to @post, notice: "Post was successfully created." }
|
||||
=>#0 PostsController#create at ~/projects/rails-guide-example/app/controllers/posts_controller.rb:25
|
||||
#1 ActionController::BasicImplicitRender#send_action(method="create", args=[]) at ~/.rbenv/versions/3.0.1/lib/ruby/gems/3.0.0/gems/actionpack-7.0.0.alpha2/lib/action_controller/metal/basic_implicit_render.rb:6
|
||||
# and 72 frames (use `bt' command for all frames)
|
||||
(rdbg) catch ActiveRecord::RecordInvalid # command
|
||||
#1 BP - Catch "ActiveRecord::RecordInvalid"
|
||||
```
|
||||
|
||||
```rb
|
||||
(rdbg) c # continue command
|
||||
[75, 84] in ~/.rbenv/versions/3.0.1/lib/ruby/gems/3.0.0/gems/activerecord-7.0.0.alpha2/lib/active_record/validations.rb
|
||||
75| def default_validation_context
|
||||
76| new_record? ? :create : :update
|
||||
77| end
|
||||
78|
|
||||
79| def raise_validation_error
|
||||
=> 80| raise(RecordInvalid.new(self))
|
||||
81| end
|
||||
82|
|
||||
83| def perform_validations(options = {})
|
||||
84| options[:validate] == false || valid?(options[:context])
|
||||
=>#0 ActiveRecord::Validations#raise_validation_error at ~/.rbenv/versions/3.0.1/lib/ruby/gems/3.0.0/gems/activerecord-7.0.0.alpha2/lib/active_record/validations.rb:80
|
||||
#1 ActiveRecord::Validations#save!(options={}) at ~/.rbenv/versions/3.0.1/lib/ruby/gems/3.0.0/gems/activerecord-7.0.0.alpha2/lib/active_record/validations.rb:53
|
||||
# and 88 frames (use `bt' command for all frames)
|
||||
|
||||
Stop by #1 BP - Catch "ActiveRecord::RecordInvalid"
|
||||
```
|
||||
|
||||
#### The watch command
|
||||
|
||||
**Stop when the instance variable is changed - e.g. `watch @_response_body`**
|
||||
|
||||
```rb
|
||||
[20, 29] in ~/projects/rails-guide-example/app/controllers/posts_controller.rb
|
||||
20| end
|
||||
21|
|
||||
22| # POST /posts or /posts.json
|
||||
23| def create
|
||||
24| @post = Post.new(post_params)
|
||||
=> 25| debugger
|
||||
26|
|
||||
27| respond_to do |format|
|
||||
28| if @post.save!
|
||||
29| format.html { redirect_to @post, notice: "Post was successfully created." }
|
||||
=>#0 PostsController#create at ~/projects/rails-guide-example/app/controllers/posts_controller.rb:25
|
||||
#1 ActionController::BasicImplicitRender#send_action(method="create", args=[]) at ~/.rbenv/versions/3.0.1/lib/ruby/gems/3.0.0/gems/actionpack-7.0.0.alpha2/lib/action_controller/metal/basic_implicit_render.rb:6
|
||||
# and 72 frames (use `bt' command for all frames)
|
||||
(rdbg) watch @_response_body # command
|
||||
#0 BP - Watch #<PostsController:0x00007fce69ca5320> @_response_body =
|
||||
```
|
||||
|
||||
```rb
|
||||
(rdbg) c # continue command
|
||||
[173, 182] in ~/.rbenv/versions/3.0.1/lib/ruby/gems/3.0.0/gems/actionpack-7.0.0.alpha2/lib/action_controller/metal.rb
|
||||
173| body = [body] unless body.nil? || body.respond_to?(:each)
|
||||
174| response.reset_body!
|
||||
175| return unless body
|
||||
176| response.body = body
|
||||
177| super
|
||||
=> 178| end
|
||||
179|
|
||||
180| # Tests if render or redirect has already happened.
|
||||
181| def performed?
|
||||
182| response_body || response.committed?
|
||||
=>#0 ActionController::Metal#response_body=(body=["<html><body>You are being <a href=\"ht...) at ~/.rbenv/versions/3.0.1/lib/ruby/gems/3.0.0/gems/actionpack-7.0.0.alpha2/lib/action_controller/metal.rb:178 #=> ["<html><body>You are being <a href=\"ht...
|
||||
#1 ActionController::Redirecting#redirect_to(options=#<Post id: 13, title: "qweqwe", content:..., response_options={:allow_other_host=>false}) at ~/.rbenv/versions/3.0.1/lib/ruby/gems/3.0.0/gems/actionpack-7.0.0.alpha2/lib/action_controller/metal/redirecting.rb:74
|
||||
# and 82 frames (use `bt' command for all frames)
|
||||
|
||||
Stop by #0 BP - Watch #<PostsController:0x00007fce69ca5320> @_response_body = -> ["<html><body>You are being <a href=\"http://localhost:3000/posts/13\">redirected</a>.</body></html>"]
|
||||
(rdbg)
|
||||
```
|
||||
|
||||
#### Breakpoint options
|
||||
|
||||
In addition to different types of breakpoints, you can also specify options to achieve more advanced debugging workflow. Currently, the debugger supports 4 options:
|
||||
|
||||
- `do: <cmd or expr>` - when the breakpoint is triggered, execute the given command/expression and continue the program:
|
||||
- `break Foo#bar do: bt` - when `Foo#bar` is called, print the stack frames
|
||||
- `pre: <cmd or expr>` - when the breakpoint is triggered, execute the given command/expression before stopping:
|
||||
- `break Foo#bar pre: info` - when `Foo#bar` is called, print its surrounding variables before stopping.
|
||||
- `if: <expr>` - the breakpoint only stops if the result of `<expr`> is true:
|
||||
- `break Post#save if: params[:debug]` - stops at `Post#save` if `params[:debug]` is also true
|
||||
- `path: <path_regexp>` - the breakpoint only stops if the event that triggers it (e.g. a method call) happens from the given path:
|
||||
- `break Post#save if: app/services/a_service` - stops at `Post#save` if the method call happens at a method matches Ruby regexp `/app\/services\/a_service/`.
|
||||
|
||||
Please also note that the first 3 options: `do:`, `pre:` and `if:` are also available for the debug statements we mentioned earlier. For example:
|
||||
|
||||
```rb
|
||||
[2, 11] in ~/projects/rails-guide-example/app/controllers/posts_controller.rb
|
||||
2| before_action :set_post, only: %i[ show edit update destroy ]
|
||||
3|
|
||||
4| # GET /posts or /posts.json
|
||||
5| def index
|
||||
6| @posts = Post.all
|
||||
=> 7| debugger(do: "info")
|
||||
8| end
|
||||
9|
|
||||
10| # GET /posts/1 or /posts/1.json
|
||||
11| def show
|
||||
=>#0 PostsController#index at ~/projects/rails-guide-example/app/controllers/posts_controller.rb:7
|
||||
#1 ActionController::BasicImplicitRender#send_action(method="index", args=[]) at ~/.rbenv/versions/3.0.1/lib/ruby/gems/3.0.0/gems/actionpack-7.0.0.alpha2/lib/action_controller/metal/basic_implicit_render.rb:6
|
||||
# and 72 frames (use `bt' command for all frames)
|
||||
(rdbg:binding.break) info
|
||||
%self = #<PostsController:0x00000000017480>
|
||||
@_action_has_layout = true
|
||||
@_action_name = "index"
|
||||
@_config = {}
|
||||
@_lookup_context = #<ActionView::LookupContext:0x00007fce3ad336b8 @details_key=nil, @digest_cache=...
|
||||
@_request = #<ActionDispatch::Request GET "http://localhost:3000/posts" for 127.0.0.1>
|
||||
@_response = #<ActionDispatch::Response:0x00007fce3ad397e8 @mon_data=#<Monitor:0x00007fce3ad396a8>...
|
||||
@_response_body = nil
|
||||
@_routes = nil
|
||||
@marked_for_same_origin_verification = true
|
||||
@posts = #<ActiveRecord::Relation [#<Post id: 2, title: "qweqwe", content: "qweqwe", created_at: "...
|
||||
@rendered_format = nil
|
||||
```
|
||||
|
||||
#### Program your debugging workflow
|
||||
|
||||
With those options, you can script your debugging workflow in one line like:
|
||||
|
||||
```rb
|
||||
def create
|
||||
debugger(do: "catch ActiveRecord::RecordInvalid do: bt 10")
|
||||
# ...
|
||||
end
|
||||
```
|
||||
|
||||
And then the debugger will run the scripted command and insert the catch breakpoint
|
||||
|
||||
```rb
|
||||
(rdbg:binding.break) catch ActiveRecord::RecordInvalid do: bt 10
|
||||
#0 BP - Catch "ActiveRecord::RecordInvalid"
|
||||
[75, 84] in ~/.rbenv/versions/3.0.1/lib/ruby/gems/3.0.0/gems/activerecord-7.0.0.alpha2/lib/active_record/validations.rb
|
||||
75| def default_validation_context
|
||||
76| new_record? ? :create : :update
|
||||
77| end
|
||||
78|
|
||||
79| def raise_validation_error
|
||||
=> 80| raise(RecordInvalid.new(self))
|
||||
81| end
|
||||
82|
|
||||
83| def perform_validations(options = {})
|
||||
84| options[:validate] == false || valid?(options[:context])
|
||||
=>#0 ActiveRecord::Validations#raise_validation_error at ~/.rbenv/versions/3.0.1/lib/ruby/gems/3.0.0/gems/activerecord-7.0.0.alpha2/lib/active_record/validations.rb:80
|
||||
#1 ActiveRecord::Validations#save!(options={}) at ~/.rbenv/versions/3.0.1/lib/ruby/gems/3.0.0/gems/activerecord-7.0.0.alpha2/lib/active_record/validations.rb:53
|
||||
# and 88 frames (use `bt' command for all frames)
|
||||
```
|
||||
|
||||
Once the catch breakpoint is triggered, it'll print the stack frames
|
||||
|
||||
```rb
|
||||
Stop by #0 BP - Catch "ActiveRecord::RecordInvalid"
|
||||
|
||||
(rdbg:catch) bt 10
|
||||
=>#0 ActiveRecord::Validations#raise_validation_error at ~/.rbenv/versions/3.0.1/lib/ruby/gems/3.0.0/gems/activerecord-7.0.0.alpha2/lib/active_record/validations.rb:80
|
||||
#1 ActiveRecord::Validations#save!(options={}) at ~/.rbenv/versions/3.0.1/lib/ruby/gems/3.0.0/gems/activerecord-7.0.0.alpha2/lib/active_record/validations.rb:53
|
||||
#2 block in save! at ~/.rbenv/versions/3.0.1/lib/ruby/gems/3.0.0/gems/activerecord-7.0.0.alpha2/lib/active_record/transactions.rb:302
|
||||
```
|
||||
|
||||
This technique can save you from repeated manual input and make the debugging experience smoother.
|
||||
|
||||
You can find more commands and configuration options from its [documentation](https://github.com/ruby/debug).
|
||||
|
||||
#### Autoloading Caveat
|
||||
|
|
Loading…
Reference in New Issue