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 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
@@ -0,0 +1,9 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /Gemfile.lock
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
data/.rubocop.yml ADDED
@@ -0,0 +1,2 @@
1
+ Metrics/LineLength:
2
+ Max: 100
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ source 'https://rubygems.org'
2
+
3
+ git_source(:github) { |repo_name| "https://github.com/#{repo_name}" }
4
+
5
+ # Specify your gem's dependencies in method_tracer.gemspec
6
+ gemspec
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
@@ -0,0 +1,10 @@
1
+ require 'bundler/gem_tasks'
2
+ require 'rake/testtask'
3
+
4
+ Rake::TestTask.new(:test) do |t|
5
+ t.libs << 'test'
6
+ t.libs << 'lib'
7
+ t.test_files = FileList['test/**/*_test.rb']
8
+ end
9
+
10
+ task default: :test
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,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -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,3 @@
1
+ module MethodTracer
2
+ VERSION = '0.1.0'.freeze
3
+ end
@@ -0,0 +1,13 @@
1
+ require 'method_tracer/tracer'
2
+
3
+ require 'method_tracer/version'
4
+
5
+ module MethodTracer
6
+ class Config
7
+ class << self
8
+ attr_accessor :app_path, :output_file
9
+ end
10
+
11
+ @output_file = $stdout
12
+ end
13
+ 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
@@ -0,0 +1,9 @@
1
+ #!/bin/sh
2
+
3
+ if [ "$#" -ge 1 ]; then
4
+ name_arg=--name "$*"
5
+ else
6
+ name_arg=""
7
+ fi
8
+
9
+ bundle e ruby -Ilib:test "$(find test -name "*_test.rb")" $name_arg
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: []