method_call_tracer 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +9 -0
- data/.rubocop.yml +2 -0
- data/Gemfile +6 -0
- data/README.md +58 -0
- data/Rakefile +10 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/lib/generators/method_tracer/install_generator.rb +12 -0
- data/lib/generators/method_tracer/templates/initializer.rb +11 -0
- data/lib/method_tracer/tracer.rb +101 -0
- data/lib/method_tracer/version.rb +3 -0
- data/lib/method_tracer.rb +13 -0
- data/method_tracer.gemspec +25 -0
- data/run_tests.sh +9 -0
- metadata +75 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: c0a180a16f9d4798139f03dc6cc487632ec0cfbf
|
4
|
+
data.tar.gz: d7294c63a4bc0e293fa9e883338248aee417c050
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 9f03140a84d526cc297d97c650ae3a55df9e28dbfe2b5d8be5ed832aee97689a73b4eb2402faaca16d5ad1a9b2a2c434fe9f1d4032041b45cabc2e2135e818d8
|
7
|
+
data.tar.gz: 2e36e0b7bcbb327ffa925f3667f23532238d47956ca70ce6a46e852732aca43b700288473bd45fe82eefcb0854ccab39310fe69849ea68b49448d9fd23601f1b
|
data/.gitignore
ADDED
data/.rubocop.yml
ADDED
data/Gemfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,58 @@
|
|
1
|
+
# MethodTracer
|
2
|
+
|
3
|
+
MethodTracer is a tool for detecting lines in an application that call certain methods, somewhat akin to the syscall monitoring functionality of strace(1). The methods to be traced are specified by file pattern, which makes it simple to trace an entire gem<sup>1</sup>. The most common use case is helping developers and testers focus their efforts when upgrading or changing gems in large applications.
|
4
|
+
|
5
|
+
<sup>1</sup> Designating traced methods by file lets you trace methods defined in the "namespace" of a different gem. For example, if a hypothetical gem `activerecord-extension` defines some methods on the class `ActiveRecord`, we still have the ability to trace only the methods from `activerecord-extension` without capturing the methods from `activerecord`.
|
6
|
+
|
7
|
+
## Usage
|
8
|
+
|
9
|
+
To attach tracers to methods, instantiate `MethodTracer::Tracer` objects. For Rails applications, these can be placed in the provided initializer.
|
10
|
+
|
11
|
+
```ruby
|
12
|
+
# Trace all methods defined by the system installation of gibbon:
|
13
|
+
MethodTracer::Tracer.new('/var/lib/gems/2.3.0/gems/gibbon-2.2.4/')
|
14
|
+
|
15
|
+
# Trace all methods defined by rbenv's 2.3.3 installation of activerecord:
|
16
|
+
MethodTracer::Tracer.new('/home/eddie/.rbenv/versions/2.3.3/lib/ruby/gems/2.3.0/gems/activerecord-5.0.7/')
|
17
|
+
```
|
18
|
+
|
19
|
+
With tracers attached, exercise as much of the application as possible. Perhaps run the comprehensive test suite that you of course have.
|
20
|
+
|
21
|
+
## Configuration
|
22
|
+
|
23
|
+
MethodTracer supports the following configuration options:
|
24
|
+
|
25
|
+
`MethodTracer::Config.app_path`: The path of the application to trace calls from. MethodTracer will only report portions of call chains that are inside this path. Set it to `Rails.application.paths.path.to_s` for Rails applications. Set it to `'/'` or `''` to report all calls.
|
26
|
+
|
27
|
+
`MethodTracer::Config.output_file`: A filename or `IO` or `StringIO` object where the report output should be sent. Defaults to `$stdout`.
|
28
|
+
|
29
|
+
## Limitations
|
30
|
+
|
31
|
+
MethodTracer theoretically does not interfere with the original behavior of methods being traced. However, certain gems are known to misbehave when the tracing logic is monkeypatched in, and MethodTracer will refuse to trace those gems. Furthermore, any methods defined after the `MethodTracer::Tracer` is instantiated will remain untraced.
|
32
|
+
|
33
|
+
## Installation
|
34
|
+
|
35
|
+
Add this line to your application's Gemfile:
|
36
|
+
|
37
|
+
```ruby
|
38
|
+
gem 'method_tracer'
|
39
|
+
```
|
40
|
+
|
41
|
+
And then execute:
|
42
|
+
|
43
|
+
$ bundle
|
44
|
+
|
45
|
+
Or install it yourself as:
|
46
|
+
|
47
|
+
$ gem install method_tracer
|
48
|
+
|
49
|
+
If you are using it with Rails, run the installation generator to install an example config initializer:
|
50
|
+
|
51
|
+
$ rails generate method_tracer:install
|
52
|
+
|
53
|
+
## Contributing
|
54
|
+
|
55
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/elebow/method_tracer.
|
56
|
+
|
57
|
+
## License
|
58
|
+
This gem is dedicated to the public domain. In jurisdictions where this is not possible, this gem is licensed to all under the least restrictive terms possible, and the author waives all waivable copyright rights.
|
data/Rakefile
ADDED
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'bundler/setup'
|
4
|
+
require 'method_tracer'
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require 'irb'
|
14
|
+
IRB.start(__FILE__)
|
data/bin/setup
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
module MethodTracer
|
2
|
+
module Generators
|
3
|
+
class InstallGenerator < Rails::Generators::Base
|
4
|
+
source_root File.dirname(__FILE__) + '/templates'
|
5
|
+
desc 'Installs initializer'
|
6
|
+
|
7
|
+
def install
|
8
|
+
template 'initializer.rb', 'config/initializers/method_tracer.rb'
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
# Define the prefix for the application code. Only calls matching this pattern
|
2
|
+
# will be recorded.
|
3
|
+
MethodTracer::Config.app_path = Rails.application.paths.path.to_s
|
4
|
+
|
5
|
+
# Override the default output file. Specify a filename string or an IO or StringIO object
|
6
|
+
# like `$stdout`.
|
7
|
+
# MethodTracer::Config.output_file = 'output_file_2.log'
|
8
|
+
|
9
|
+
# Create a MethodTracer::Spy object for every gem to be spied on
|
10
|
+
# MethodTracer::Tracer.new('/var/lib/gems/2.3.0/gems/gibbon-2.2.4/lib/gibbon/')
|
11
|
+
# MethodTracer::Tracer.new('/var/lib/gems/2.3.0/gems/other_gem-1.0.0/lib/other_gem-1.0.0/')
|
@@ -0,0 +1,101 @@
|
|
1
|
+
module MethodTracer
|
2
|
+
class Tracer
|
3
|
+
UNCOOPERATIVE_NAMES = [
|
4
|
+
# Some classes don't behave well when we try to hook them, at least with
|
5
|
+
# the methods currently used.
|
6
|
+
'CGI', # Waits for input from stdin when opening rails console
|
7
|
+
'RSpec' # RSpec doesn't run
|
8
|
+
].freeze
|
9
|
+
|
10
|
+
def initialize(target)
|
11
|
+
@target_path = target
|
12
|
+
|
13
|
+
find_methods!
|
14
|
+
|
15
|
+
add_hooks_to_class_methods
|
16
|
+
add_hooks_to_instance_methods
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.uncooperative_class?(class_name)
|
20
|
+
!class_name.nil? &&
|
21
|
+
UNCOOPERATIVE_NAMES.any? { |bad_name| class_name.include?(bad_name) }
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.record_and_call_original(unbound_m, receiver, *args, &block)
|
25
|
+
outfile.write "#{receiver}.#{unbound_m.name} called from:\n"
|
26
|
+
caller_locations.select { |loc| loc.path.start_with?(Config.app_path) }
|
27
|
+
.each do |loc|
|
28
|
+
outfile.write "#{loc.path}:#{loc.lineno}\n"
|
29
|
+
end
|
30
|
+
|
31
|
+
unbound_m.bind(receiver).call(*args, &block)
|
32
|
+
end
|
33
|
+
|
34
|
+
def self.outfile
|
35
|
+
@outfile ||= begin
|
36
|
+
output_file = Config.output_file
|
37
|
+
if output_file.instance_of?(IO) || output_file.instance_of?(StringIO)
|
38
|
+
output_file
|
39
|
+
elsif output_file.instance_of?(String)
|
40
|
+
File.open(output_file, 'a')
|
41
|
+
else
|
42
|
+
raise "Unhandled output_file type: #{output_file}"
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
|
49
|
+
def add_hooks_to_class_methods
|
50
|
+
methods_of_interest(@all_class_methods).each do |m|
|
51
|
+
unbound_m = m.unbind
|
52
|
+
receiver = m.receiver
|
53
|
+
|
54
|
+
receiver.send(:define_singleton_method, unbound_m.name) do |*args, &block|
|
55
|
+
Tracer.record_and_call_original(unbound_m, receiver, *args, &block)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def add_hooks_to_instance_methods
|
61
|
+
methods_of_interest(@all_instance_methods).each do |m|
|
62
|
+
unbound_m = m.unbind
|
63
|
+
receiver = m.receiver
|
64
|
+
|
65
|
+
receiver.class.send(:define_method, m.name) do |*args, &block|
|
66
|
+
Tracer.record_and_call_original(unbound_m, receiver, *args, &block)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def find_methods!
|
72
|
+
@all_class_methods = []
|
73
|
+
@all_instance_methods = []
|
74
|
+
ObjectSpace.each_object(Class) do |defined_class|
|
75
|
+
next if Tracer.uncooperative_class?(defined_class.name)
|
76
|
+
|
77
|
+
defined_class.methods(false).each do |method_sym|
|
78
|
+
@all_class_methods << defined_class.method(method_sym)
|
79
|
+
end
|
80
|
+
|
81
|
+
begin
|
82
|
+
instance = defined_class.new
|
83
|
+
defined_class.instance_methods(false).each do |method_sym|
|
84
|
+
@all_instance_methods << instance.method(method_sym)
|
85
|
+
end
|
86
|
+
rescue StandardError, NotImplementedError, LoadError
|
87
|
+
# If the class isn't instantiable, skip it
|
88
|
+
next
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def methods_of_interest(methods)
|
94
|
+
methods.select do |m|
|
95
|
+
m.instance_of?(Method) &&
|
96
|
+
!m.source_location.nil? &&
|
97
|
+
m.source_location[0].start_with?(@target_path)
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
lib = File.expand_path('../lib', __FILE__)
|
2
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
3
|
+
require 'method_tracer/version'
|
4
|
+
|
5
|
+
Gem::Specification.new do |spec|
|
6
|
+
spec.name = 'method_call_tracer'
|
7
|
+
spec.version = MethodTracer::VERSION
|
8
|
+
spec.authors = ['Eddie Lebow']
|
9
|
+
spec.email = ['elebow@users.noreply.github.com']
|
10
|
+
|
11
|
+
spec.summary = 'A tool that finds lines in your application that call a specified method'
|
12
|
+
spec.description = 'This tool wraps every specified method with some logging statements ' \
|
13
|
+
'that record the call stack, allowing you to see exactly which lines in ' \
|
14
|
+
'your application make calls to methods in question. The specified ' \
|
15
|
+
'methods can constitute all methods defined in a certain gem.'
|
16
|
+
spec.homepage = 'https://github.com/elebow/method_tracer'
|
17
|
+
spec.license = 'public domain'
|
18
|
+
|
19
|
+
spec.files = `git ls-files -z`.split("\x0").reject do |f|
|
20
|
+
f.match(%r{^(test|spec|features)/})
|
21
|
+
end
|
22
|
+
spec.require_paths = ['lib']
|
23
|
+
|
24
|
+
spec.add_development_dependency 'minitest', '~> 0'
|
25
|
+
end
|
data/run_tests.sh
ADDED
metadata
ADDED
@@ -0,0 +1,75 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: method_call_tracer
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Eddie Lebow
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2018-09-24 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: minitest
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
description: This tool wraps every specified method with some logging statements that
|
28
|
+
record the call stack, allowing you to see exactly which lines in your application
|
29
|
+
make calls to methods in question. The specified methods can constitute all methods
|
30
|
+
defined in a certain gem.
|
31
|
+
email:
|
32
|
+
- elebow@users.noreply.github.com
|
33
|
+
executables: []
|
34
|
+
extensions: []
|
35
|
+
extra_rdoc_files: []
|
36
|
+
files:
|
37
|
+
- ".gitignore"
|
38
|
+
- ".rubocop.yml"
|
39
|
+
- Gemfile
|
40
|
+
- README.md
|
41
|
+
- Rakefile
|
42
|
+
- bin/console
|
43
|
+
- bin/setup
|
44
|
+
- lib/generators/method_tracer/install_generator.rb
|
45
|
+
- lib/generators/method_tracer/templates/initializer.rb
|
46
|
+
- lib/method_tracer.rb
|
47
|
+
- lib/method_tracer/tracer.rb
|
48
|
+
- lib/method_tracer/version.rb
|
49
|
+
- method_tracer.gemspec
|
50
|
+
- run_tests.sh
|
51
|
+
homepage: https://github.com/elebow/method_tracer
|
52
|
+
licenses:
|
53
|
+
- public domain
|
54
|
+
metadata: {}
|
55
|
+
post_install_message:
|
56
|
+
rdoc_options: []
|
57
|
+
require_paths:
|
58
|
+
- lib
|
59
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
60
|
+
requirements:
|
61
|
+
- - ">="
|
62
|
+
- !ruby/object:Gem::Version
|
63
|
+
version: '0'
|
64
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
requirements: []
|
70
|
+
rubyforge_project:
|
71
|
+
rubygems_version: 2.5.2.1
|
72
|
+
signing_key:
|
73
|
+
specification_version: 4
|
74
|
+
summary: A tool that finds lines in your application that call a specified method
|
75
|
+
test_files: []
|