tracer 0.2.1 → 0.2.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 13d3b55fac8c903711f78ccb14e872f8c312dde28dda5ed7f5fa664d04dd2a3b
4
- data.tar.gz: 71c21f53b34f98870d4e6b49c84432867375c24f56c3d3a4ebd47e4d6bdaeef5
3
+ metadata.gz: 2384a62a824cb7906cd027700d16844a6b666e5dc7b04749fcb2d69ece9a2a85
4
+ data.tar.gz: 762568e03d81d9dafa2ad6735f9901d53507aaeaa350aac53ef3989325136757
5
5
  SHA512:
6
- metadata.gz: 54cb3be80359764539089df166a7ad922dea9ebea0cb8450a815f1956fbd780c3d6ed31bd5ea8e01cbf24689131c6463a5edb156220313b0c12e024f54d99565
7
- data.tar.gz: 683343e41cb7150fdd256c4427a4ca9971a87b592093721e5d3f00a40141136eeaf884a9242e7b20f46a0e47f35f445eede9c2b612731f12a38f5f1c20ad6c3a
6
+ metadata.gz: bbdccfb4cf5054d554a76e22f9bbffd21aa8f3e9a13e366cbfb41d076803c4de4b019697b81d8dd1278655390f7c1949190e7d7c72b2fcdc7ffa83622b89a71d
7
+ data.tar.gz: b0a8142e250b24964d9fbcbc674d052107061725b6673b7d573553faf55afc722559396c0b878f3639c051dcfd1a9794e285116ba38e2f493d4be9b8d17c11ae
data/README.md CHANGED
@@ -1,6 +1,19 @@
1
1
  # Tracer
2
2
 
3
- Tracer is an extraction of [`ruby/debug`](https://github.com/ruby/debug)'s [powerful tracers](https://github.com/ruby/debug/blob/master/lib/debug/tracer.rb), with user-facing APIs, IRB-integration, and some improvements.
3
+ [![Ruby](https://github.com/ruby/tracer/actions/workflows/main.yml/badge.svg)](https://github.com/ruby/tracer/actions/workflows/main.yml)
4
+ [![Gem Version](https://badge.fury.io/rb/tracer.svg)](https://badge.fury.io/rb/tracer)
5
+
6
+ The `tracer` gem provides helpful tracing utilities to help users observe their program's runtime behaviour.
7
+
8
+ The currently supported tracers are:
9
+
10
+ - [`ObjectTracer`](#objecttracer)
11
+ - [`IvarTracer`](#ivartracer)
12
+ - [`CallTracer`](#calltracer)
13
+ - [`ExceptionTracer`](#exceptiontracer)
14
+ - [`LineTracer`](#linetracer)
15
+
16
+ It also comes with experimental [IRB integration](#irb-integration) to allow quick access from REPL.
4
17
 
5
18
  ## Installation
6
19
 
@@ -8,6 +21,14 @@ Tracer is an extraction of [`ruby/debug`](https://github.com/ruby/debug)'s [powe
8
21
  $ bundle add tracer --group=development,test
9
22
  ```
10
23
 
24
+ Or directly add it to your `Gemfile`
25
+
26
+ ```rb
27
+ group :development, :test do
28
+ gem "tracer"
29
+ end
30
+ ```
31
+
11
32
  If bundler is not being used to manage dependencies, install the gem by executing:
12
33
 
13
34
  ```shell
@@ -54,49 +75,6 @@ trace_call { ... } # trace method calls in the given block
54
75
  trace_exception { ... } # trace exceptions in the given block
55
76
  ```
56
77
 
57
- ### IRB-integration
58
-
59
- Once required, `tracer` registers a few IRB commands to help you trace Ruby expressions:
60
-
61
- ```
62
- trace Trace the target object (or self) in the given expression. Usage: `trace [target,] <expression>`
63
- trace_call Trace method calls in the given expression. Usage: `trace_call <expression>`
64
- trace_exception Trace exceptions in the given expression. Usage: `trace_exception <expression>`
65
- ```
66
-
67
- **Example**
68
-
69
- ```rb
70
- # test.rb
71
- require "tracer"
72
-
73
- obj = Object.new
74
-
75
- def obj.foo
76
- 100
77
- end
78
-
79
- def bar(obj)
80
- obj.foo
81
- end
82
-
83
- binding.irb
84
- ```
85
-
86
-
87
- ```
88
- irb(main):001:0> trace obj, bar(obj)
89
- #depth:23 #<Object:0x0000000107a86648> is used as a parameter obj of Object#bar at (eval):1:in `<main>'
90
- #depth:24 #<Object:0x0000000107a86648> receives .foo at test.rb:10:in `bar'
91
- => 100
92
- irb(main):002:0> trace_call bar(obj)
93
- #depth:23> Object#bar at (eval):1:in `<main>'
94
- #depth:24> #<Object:0x0000000107a86648>.foo at test.rb:10:in `bar'
95
- #depth:24< #<Object:0x0000000107a86648>.foo #=> 100 at test.rb:10:in `bar'
96
- #depth:23< Object#bar #=> 100 at (eval):1:in `<main>'
97
- => 100
98
- ```
99
-
100
78
  ### Tracer Classes
101
79
 
102
80
  If you want to have more control over individual traces, you can use individual tracer classes:
@@ -126,6 +104,30 @@ end
126
104
  #depth:4 #<User:0x000000010696cad8 @name="John"> receives #name (User#name) at test.rb:8:in `authorized?'
127
105
  ```
128
106
 
107
+ #### IvarTracer
108
+
109
+ > **Note**
110
+ > Ruby 3.0 and below's accessor calls don't trigger TracePoint properly so the result may be inaccurate with those versions.
111
+
112
+ ```rb
113
+ require "tracer"
114
+
115
+ class Cat
116
+ attr_accessor :name
117
+ end
118
+
119
+ cat = Cat.new
120
+
121
+ tracer = IvarTracer.new(cat, :@name)
122
+ tracer.start do
123
+ cat.name = "Kitty"
124
+ cat.instance_variable_set(:@name, "Ketty")
125
+ end
126
+
127
+ #depth:3 Cat#name= sets @name = "Kitty" at test.rb:11
128
+ #depth:3 Kernel#instance_variable_set sets @name = "Ketty" at test.rb:12
129
+ ```
130
+
129
131
  #### ExceptionTracer
130
132
 
131
133
  ```rb
@@ -137,7 +139,8 @@ rescue StandardError
137
139
  nil
138
140
  end
139
141
 
140
- #depth:1 #<RuntimeError: boom> at test.rb:4
142
+ #depth:0 #<RuntimeError: boom> raised at test.rb:4
143
+ #depth:1 #<RuntimeError: boom> rescued at test.rb:6
141
144
  ```
142
145
 
143
146
  #### CallTracer
@@ -197,13 +200,56 @@ end
197
200
  #depth:5 at test.rb:8
198
201
  ```
199
202
 
203
+ ### IRB-integration
204
+
205
+ Once required, `tracer` registers a few IRB commands to help you trace Ruby expressions:
206
+
207
+ ```
208
+ trace Trace the target object (or self) in the given expression. Usage: `trace [target,] <expression>`
209
+ trace_call Trace method calls in the given expression. Usage: `trace_call <expression>`
210
+ trace_exception Trace exceptions in the given expression. Usage: `trace_exception <expression>`
211
+ ```
212
+
213
+ **Example**
214
+
215
+ ```rb
216
+ # test.rb
217
+ require "tracer"
218
+
219
+ obj = Object.new
220
+
221
+ def obj.foo
222
+ 100
223
+ end
224
+
225
+ def bar(obj)
226
+ obj.foo
227
+ end
228
+
229
+ binding.irb
230
+ ```
231
+
232
+ ```shell
233
+ irb(main):001:0> trace obj, bar(obj)
234
+ #depth:23 #<Object:0x0000000107a86648> is used as a parameter obj of Object#bar at (eval):1:in `<main>'
235
+ #depth:24 #<Object:0x0000000107a86648> receives .foo at test.rb:10:in `bar'
236
+ => 100
237
+ irb(main):002:0> trace_call bar(obj)
238
+ #depth:23> Object#bar at (eval):1:in `<main>'
239
+ #depth:24> #<Object:0x0000000107a86648>.foo at test.rb:10:in `bar'
240
+ #depth:24< #<Object:0x0000000107a86648>.foo #=> 100 at test.rb:10:in `bar'
241
+ #depth:23< Object#bar #=> 100 at (eval):1:in `<main>'
242
+ => 100
243
+ ```
244
+
200
245
  ## Customization
201
246
 
202
247
  TBD
203
248
 
204
- ## Acknowledgement
249
+ ## Acknowledgements
205
250
 
206
- [@ko1](https://github.com/ko1) (Koichi Sasada) implemented the majority of [`ruby/debug`](https://github.com/ruby/debug), including its tracers. So this project wouldn't exist without his amazing work there.
251
+ A big shout-out to [@ko1](https://github.com/ko1) (Koichi Sasada) for his awesome work on [`ruby/debug`](https://github.com/ruby/debug).
252
+ The [cool tracers in `ruby/debug`](https://github.com/ruby/debug/blob/master/lib/debug/tracer.rb) were an inspiration and laid the groundwork for this project.
207
253
 
208
254
  ## Development
209
255
 
data/lib/tracer/base.rb CHANGED
@@ -10,6 +10,7 @@ module Tracer
10
10
  M_INSPECT = Object.instance_method(:inspect)
11
11
  M_CLASS = Object.instance_method(:class)
12
12
  M_IS_A = Object.instance_method(:is_a?)
13
+ M_INSTANCE_VARIABLE_GET = Object.instance_method(:instance_variable_get)
13
14
  HOME = ENV["HOME"] ? (ENV["HOME"] + "/") : nil
14
15
 
15
16
  include Color
@@ -70,12 +71,21 @@ module Tracer
70
71
  end
71
72
  end
72
73
 
73
- def initialize(output: STDOUT, pattern: nil, colorize: nil, depth_offset: 0)
74
+ attr_reader :header
75
+
76
+ def initialize(
77
+ output: STDOUT,
78
+ pattern: nil,
79
+ colorize: nil,
80
+ depth_offset: 0,
81
+ header: nil
82
+ )
74
83
  @name = self.class.name
75
84
  @type = @name.sub(/Tracer\z/, "")
76
85
  @output = output
77
86
  @depth_offset = depth_offset
78
87
  @colorize = colorize || colorizable?
88
+ @header = header
79
89
 
80
90
  if pattern
81
91
  @pattern = Regexp.compile(pattern)
@@ -90,10 +100,6 @@ module Tracer
90
100
  [@type, @pattern, @into].freeze
91
101
  end
92
102
 
93
- def header
94
- ""
95
- end
96
-
97
103
  def to_s
98
104
  s = "#{@name} #{description}"
99
105
  s += " with pattern #{@pattern.inspect}" if @pattern
@@ -141,10 +147,14 @@ module Tracer
141
147
 
142
148
  def out(tp, msg = nil, depth: caller.size - 1, location: nil)
143
149
  location ||= "#{tp.path}:#{tp.lineno}"
144
- buff =
145
- "#{header} \#depth:#{"%-2d" % depth}#{msg} at #{colorize("#{location}", [:GREEN])}"
150
+ if header
151
+ str = +"#{header} "
152
+ else
153
+ str = +""
154
+ end
155
+ str << "\#depth:#{"%-2d" % depth}#{msg} at #{colorize("#{location}", [:GREEN])}"
146
156
 
147
- puts buff
157
+ puts str
148
158
  end
149
159
 
150
160
  def puts(msg)
@@ -4,16 +4,32 @@ require_relative "base"
4
4
 
5
5
  class ExceptionTracer < Tracer::Base
6
6
  def setup_tp
7
- TracePoint.new(:raise) do |tp|
8
- next if skip?(tp)
7
+ if RUBY_VERSION >= "3.3.0"
8
+ TracePoint.new(:raise, :rescue) do |tp|
9
+ next if skip?(tp)
9
10
 
10
- exc = tp.raised_exception
11
+ exc = tp.raised_exception
11
12
 
12
- out tp,
13
- " #{colorize_magenta(exc.inspect)}",
14
- depth: caller.size - (1 + @depth_offset)
15
- rescue Exception => e
16
- p e
13
+ action = tp.event == :raise ? "raised" : "rescued"
14
+
15
+ out tp,
16
+ " #{colorize_magenta(exc.inspect)} #{action}",
17
+ depth: caller.size - (1 + @depth_offset)
18
+ rescue Exception => e
19
+ p e
20
+ end
21
+ else
22
+ TracePoint.new(:raise) do |tp|
23
+ next if skip?(tp)
24
+
25
+ exc = tp.raised_exception
26
+
27
+ out tp,
28
+ " #{colorize_magenta(exc.inspect)} raised",
29
+ depth: caller.size - (1 + @depth_offset)
30
+ rescue Exception => e
31
+ p e
32
+ end
17
33
  end
18
34
  end
19
35
 
data/lib/tracer/irb.rb CHANGED
@@ -1,7 +1,15 @@
1
- require_relative "../tracer"
2
1
  require "irb/cmd/nop"
3
2
  require "irb"
4
3
 
4
+ if Gem::Version.new(IRB::VERSION) < Gem::Version.new("1.6.0")
5
+ warn <<~MSG
6
+ Your version of IRB is too old so Tracer cannot register its commands.
7
+ Please upgrade IRB by adding `gem "irb", "~> 1.6.0"` to your Gemfile.
8
+ MSG
9
+
10
+ return
11
+ end
12
+
5
13
  module Tracer
6
14
  def self.register_irb_commands
7
15
  ec = IRB::ExtendCommandBundle.instance_variable_get(:@EXTEND_COMMANDS)
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "base"
4
+
5
+ class IvarTracer < Tracer::Base
6
+ def initialize(target, var_name, **kw)
7
+ @target = target
8
+ @var_name = var_name
9
+ @original_value = M_INSTANCE_VARIABLE_GET.bind_call(target, var_name)
10
+ super(**kw)
11
+ end
12
+
13
+ def key
14
+ [@type, @target, @var_name, @pattern, @into].freeze
15
+ end
16
+
17
+ def description
18
+ "for #{@var_name} of #{@target} #{super}"
19
+ end
20
+
21
+ def setup_tp
22
+ TracePoint.new(:a_return) do |tp|
23
+ next if skip?(tp)
24
+
25
+ if tp.self == @target &&
26
+ value = M_INSTANCE_VARIABLE_GET.bind_call(@target, @var_name)
27
+ if tp.event == :c_return
28
+ location = nil
29
+ else
30
+ location = caller_locations(2, 1).first.to_s
31
+ next if location.match?(DIR) || location.match?(/<internal:/)
32
+ end
33
+
34
+ depth = caller.size
35
+ call_identifier_str = (tp.defined_class ? minfo(tp) : "block")
36
+ call_identifier_str = colorize_blue(call_identifier_str)
37
+ depth += 1 if tp.event == :c_return
38
+ value = safe_inspect(value)
39
+
40
+ if value != @original_value
41
+ out tp,
42
+ "#{call_identifier_str} sets #{colorize_cyan(@var_name)} = #{colorize_magenta(value)}",
43
+ depth: depth - 2 - @depth_offset,
44
+ location: location
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Tracer
4
- VERSION = "0.2.1"
4
+ VERSION = "0.2.3"
5
5
  end
data/lib/tracer.rb CHANGED
@@ -5,6 +5,7 @@ require_relative "tracer/line_tracer"
5
5
  require_relative "tracer/call_tracer"
6
6
  require_relative "tracer/exception_tracer"
7
7
  require_relative "tracer/object_tracer"
8
+ require_relative "tracer/ivar_tracer"
8
9
 
9
10
  module Tracer
10
11
  module Helper
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: tracer
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.1
4
+ version: 0.2.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Stan Lo
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: exe
11
11
  cert_chain: []
12
- date: 2023-05-19 00:00:00.000000000 Z
12
+ date: 2024-03-22 00:00:00.000000000 Z
13
13
  dependencies: []
14
14
  description: A Ruby tracer
15
15
  email:
@@ -21,11 +21,8 @@ extra_rdoc_files: []
21
21
  files:
22
22
  - CHANGELOG.md
23
23
  - CODE_OF_CONDUCT.md
24
- - Gemfile
25
- - Gemfile.lock
26
24
  - LICENSE.txt
27
25
  - README.md
28
- - Rakefile
29
26
  - lib/tracer.rb
30
27
  - lib/tracer/base.rb
31
28
  - lib/tracer/call_tracer.rb
@@ -33,10 +30,10 @@ files:
33
30
  - lib/tracer/exception_tracer.rb
34
31
  - lib/tracer/helper.rb
35
32
  - lib/tracer/irb.rb
33
+ - lib/tracer/ivar_tracer.rb
36
34
  - lib/tracer/line_tracer.rb
37
35
  - lib/tracer/object_tracer.rb
38
36
  - lib/tracer/version.rb
39
- - tracer.gemspec
40
37
  homepage: https://github.com/ruby/tracer
41
38
  licenses:
42
39
  - Ruby
@@ -60,7 +57,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
60
57
  - !ruby/object:Gem::Version
61
58
  version: '0'
62
59
  requirements: []
63
- rubygems_version: 3.4.10
60
+ rubygems_version: 3.5.4
64
61
  signing_key:
65
62
  specification_version: 4
66
63
  summary: A Ruby tracer
data/Gemfile DELETED
@@ -1,13 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- source "https://rubygems.org"
4
-
5
- # Specify your gem's dependencies in ruby-tracer.gemspec
6
- gemspec
7
-
8
- gem "rake", "~> 13.0"
9
- gem "irb"
10
-
11
- gem "test-unit", "~> 3.0"
12
-
13
- gem "ruby-lsp"
data/Gemfile.lock DELETED
@@ -1,41 +0,0 @@
1
- PATH
2
- remote: .
3
- specs:
4
- tracer (0.2.1)
5
-
6
- GEM
7
- remote: https://rubygems.org/
8
- specs:
9
- io-console (0.6.0)
10
- irb (1.6.2)
11
- reline (>= 0.3.0)
12
- language_server-protocol (3.17.0.3)
13
- power_assert (2.0.2)
14
- prettier_print (1.2.0)
15
- rake (13.0.6)
16
- reline (0.3.2)
17
- io-console (~> 0.5)
18
- ruby-lsp (0.3.8)
19
- language_server-protocol (~> 3.17.0)
20
- sorbet-runtime
21
- syntax_tree (>= 5.0.0, < 6)
22
- sorbet-runtime (0.5.10648)
23
- syntax_tree (5.3.0)
24
- prettier_print (>= 1.2.0)
25
- test-unit (3.5.5)
26
- power_assert
27
-
28
- PLATFORMS
29
- arm64-darwin-21
30
- arm64-darwin-22
31
- x86_64-linux
32
-
33
- DEPENDENCIES
34
- irb
35
- rake (~> 13.0)
36
- ruby-lsp
37
- test-unit (~> 3.0)
38
- tracer!
39
-
40
- BUNDLED WITH
41
- 2.4.2
data/Rakefile DELETED
@@ -1,12 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "bundler/gem_tasks"
4
- require "rake/testtask"
5
-
6
- Rake::TestTask.new(:test) do |t|
7
- t.libs << "test"
8
- t.libs << "lib"
9
- t.test_files = FileList["test/**/*_test.rb"]
10
- end
11
-
12
- task default: :test
data/tracer.gemspec DELETED
@@ -1,43 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative "lib/tracer/version"
4
-
5
- Gem::Specification.new do |spec|
6
- spec.name = "tracer"
7
- spec.version = Tracer::VERSION
8
- spec.authors = ["Stan Lo", "Keiju ISHITSUKA"]
9
- spec.email = %w[stan001212@gmail.com keiju@ruby-lang.org]
10
-
11
- spec.summary = "A Ruby tracer"
12
- spec.description = "A Ruby tracer"
13
- spec.homepage = "https://github.com/ruby/tracer"
14
- spec.licenses = %w[Ruby BSD-2-Clause]
15
- spec.required_ruby_version = ">= 2.7.0"
16
-
17
- spec.metadata["homepage_uri"] = spec.homepage
18
- spec.metadata["source_code_uri"] = "https://github.com/ruby/tracer"
19
- spec.metadata[
20
- "changelog_uri"
21
- ] = "https://github.com/ruby/tracer/blob/main/CHANGELOG.md"
22
-
23
- # Specify which files should be added to the gem when it is released.
24
- # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
25
- spec.files =
26
- Dir.chdir(__dir__) do
27
- `git ls-files -z`.split("\x0")
28
- .reject do |f|
29
- (f == __FILE__) ||
30
- f.match(
31
- %r{\A(?:(?:bin|test|spec|features)/|\.(?:git|circleci)|appveyor)}
32
- )
33
- end
34
- end
35
- spec.bindir = "exe"
36
- spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
37
- spec.require_paths = ["lib"]
38
-
39
- # Uncomment to register a new dependency of your gem
40
-
41
- # For more information and examples about making a new gem, check out our
42
- # guide at: https://bundler.io/guides/creating_gem.html
43
- end