itrace 0.0.0
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 +7 -0
- data/lib/itrace.rb +39 -0
- data/lib/itrace/defaults.rb +5 -0
- data/lib/itrace/interceptor.rb +67 -0
- data/lib/itrace/log_switch.rb +52 -0
- data/lib/itrace/not_a_module_error.rb +7 -0
- data/lib/itrace/tracer.rb +129 -0
- data/lib/itrace/version.rb +3 -0
- data/lib/itrace_instance.rb +9 -0
- data/test/test.rb +33 -0
- metadata +81 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: aaee13c8ccc9bafa35ce956930204faa2b7cf854e873fa4d3115a7163882870b
|
4
|
+
data.tar.gz: e0af7f995dca9a058d56a86bbf9e6141e88e76ddad72200a2d92b058f6075397
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 7927e6da51baedecc9b1a1cb671a1f67f4088f70374352fb8bceef75e525d24e35ca18f26c059d14181082278ea60cda8d63dccc9bdcc004311bea19a5842304
|
7
|
+
data.tar.gz: cf535894e672ef424eaa43b2f0806071257bcafe2811833b1920c32eca409bfb267ca5cbf5decef72770cd7e1e49c033e427c97ea11b66c98c494d68e633a44f
|
data/lib/itrace.rb
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
require 'itrace/tracer'
|
2
|
+
require 'logger'
|
3
|
+
|
4
|
+
module ITrace
|
5
|
+
def logger
|
6
|
+
@logger ||= default_logger
|
7
|
+
end
|
8
|
+
|
9
|
+
def tracer
|
10
|
+
@tracer ||= default_tracer
|
11
|
+
end
|
12
|
+
|
13
|
+
def trace(*modules, **opts, &blk)
|
14
|
+
tracer.trace(*modules, **opts, &blk)
|
15
|
+
end
|
16
|
+
|
17
|
+
def enable_logging
|
18
|
+
tracer.enable_logging
|
19
|
+
end
|
20
|
+
|
21
|
+
def disable_logging
|
22
|
+
tracer.disable_logging
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
def default_logger
|
27
|
+
@default_logger ||= begin
|
28
|
+
logger = ::Logger.new($stderr)
|
29
|
+
logger.level = ::Logger::DEBUG
|
30
|
+
logger
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def default_tracer
|
35
|
+
@default_tracer ||= Tracer.new(logger)
|
36
|
+
end
|
37
|
+
|
38
|
+
extend self
|
39
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
module ITrace
|
2
|
+
class Interceptor < ::Module
|
3
|
+
def initialize(log_switch, mod, methods_to_exclude, opts)
|
4
|
+
@log_switch = log_switch
|
5
|
+
intercepted_first_method = false
|
6
|
+
interceptor = self
|
7
|
+
|
8
|
+
mod.public_instance_methods.each do |method_sym|
|
9
|
+
next if methods_to_exclude[method_sym]
|
10
|
+
@module_name ||= produce_module_name(mod)
|
11
|
+
|
12
|
+
unless intercepted_first_method
|
13
|
+
log_module_interception if opts[:log_interception_of_modules]
|
14
|
+
intercepted_first_method = true
|
15
|
+
end
|
16
|
+
|
17
|
+
method_name = method_sym.to_s
|
18
|
+
log_method_interception(method_name) if opts[:log_interception_of_methods]
|
19
|
+
|
20
|
+
define_method(method_sym) do |*args, &blk|
|
21
|
+
interceptor.log_call(self, method_name, args)
|
22
|
+
super(*args, &blk)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
mod.prepend(self)
|
27
|
+
end
|
28
|
+
|
29
|
+
def log_call(instance, method_name, args)
|
30
|
+
@log_switch.debug('itrace') do
|
31
|
+
args = args.map do |a|
|
32
|
+
a = a.inspect
|
33
|
+
a.size > 20 ? a[0, 17] + '...' : a
|
34
|
+
end.join(', ')
|
35
|
+
|
36
|
+
if @module_name == instance.class.name
|
37
|
+
"#{get_instance_name(instance)}.#{method_name}(#{args})"
|
38
|
+
else
|
39
|
+
"#{get_instance_name(instance)}[#{@module_name}].#{method_name}(#{args})"
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
def log_module_interception
|
46
|
+
@log_switch.debug('itrace'){ "Intercepting #{@module_name}." }
|
47
|
+
end
|
48
|
+
|
49
|
+
def log_method_interception(module_name, method_name)
|
50
|
+
@log_switch.debug('itrace'){ "Intercepting #{@module_name}\##{method_name}." }
|
51
|
+
end
|
52
|
+
|
53
|
+
def produce_module_name(mod)
|
54
|
+
mod.singleton_class? ? 'singleton' : (mod.name || mod.object_id.to_s)
|
55
|
+
end
|
56
|
+
|
57
|
+
def get_instance_name(instance)
|
58
|
+
@instance_names ||= {}
|
59
|
+
@instance_names[instance.object_id] ||= produce_instance_name(instance)
|
60
|
+
end
|
61
|
+
|
62
|
+
def produce_instance_name(instance)
|
63
|
+
instance_name = instance.is_a?(::Module) && instance.name
|
64
|
+
instance_name ? "#{instance.class}:(#{instance.name})" : "#{instance.class}:#{instance.object_id}"
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
module ITrace
|
2
|
+
class LogSwitch
|
3
|
+
attr_reader :logger
|
4
|
+
|
5
|
+
def initialize(logger, enable_logging = true)
|
6
|
+
@logger = logger
|
7
|
+
@logger_methods = {}
|
8
|
+
@enable_logging = enable_logging
|
9
|
+
end
|
10
|
+
|
11
|
+
def enable_logging
|
12
|
+
@enable_logging = true
|
13
|
+
self
|
14
|
+
end
|
15
|
+
|
16
|
+
def disable_logging
|
17
|
+
@enable_logging = false
|
18
|
+
self
|
19
|
+
end
|
20
|
+
|
21
|
+
def call_logger(m_id, *args, &blk)
|
22
|
+
get_logger_method(m_id).call(*args, &blk) if @enable_logging
|
23
|
+
end
|
24
|
+
|
25
|
+
def change_logger(logger)
|
26
|
+
raise NotYetImplementedError
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
class NotYetImplementedError < ::StandardError
|
31
|
+
def initialize; super("Not yet implemented"); end
|
32
|
+
end
|
33
|
+
|
34
|
+
def get_logger_method(m_id)
|
35
|
+
@logger_methods[m_id] ||= @logger.method(m_id)
|
36
|
+
end
|
37
|
+
|
38
|
+
def create_wrapper_method(m_id)
|
39
|
+
logger_method = get_logger_method(m_id)
|
40
|
+
|
41
|
+
define_singleton_method(m_id) do |*args, &blk|
|
42
|
+
logger_method.call(*args, &blk) if @enable_logging
|
43
|
+
end
|
44
|
+
|
45
|
+
singleton_method(m_id)
|
46
|
+
end
|
47
|
+
|
48
|
+
def method_missing(m_id, *args, &blk)
|
49
|
+
create_wrapper_method(m_id).call(*args, &blk)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,129 @@
|
|
1
|
+
require 'itrace/defaults'
|
2
|
+
require 'itrace/interceptor'
|
3
|
+
require 'itrace/log_switch'
|
4
|
+
require 'itrace/not_a_module_error'
|
5
|
+
|
6
|
+
module ITrace
|
7
|
+
class Tracer
|
8
|
+
def initialize(logger, enable_logging = true)
|
9
|
+
@logger = logger
|
10
|
+
@log_switch = ::ITrace::LogSwitch.new(@logger, enable_logging = true)
|
11
|
+
@intercepted = {}
|
12
|
+
end
|
13
|
+
|
14
|
+
def trace(*modules, **opts)
|
15
|
+
rause "Not an array: #{opts[:include_methods]}" unless opts[:include_methods].nil? || \
|
16
|
+
opts[:include_methods].is_a?(Array)
|
17
|
+
rause "Not an array: #{opts[:exclude_methods]}" unless opts[:exclude_methods].nil? || \
|
18
|
+
opts[:exclude_methods].is_a?(Array)
|
19
|
+
|
20
|
+
modules_to_exclude = (opts[:exclude_modules] || [])
|
21
|
+
.map{ |e| [get_object_id(e, opts[:use_orig_object_id]), true] }.to_h
|
22
|
+
methods_to_include = (opts[:include_methods] || []).map(&:to_sym)
|
23
|
+
methods_to_exclude = (opts[:exclude_methods] || [])
|
24
|
+
.concat(::ITrace::Defaults::METHODS_TO_EXCLUDE).map(&:to_sym)
|
25
|
+
.reject{ |e| methods_to_include.include?(e) }.map{ |e| [e, true] }.to_h
|
26
|
+
|
27
|
+
modules.flatten.each do |mod|
|
28
|
+
unless mod.is_a?(::Module)
|
29
|
+
if opts[:ignore_non_modules]
|
30
|
+
@log_switch.warn("Ignoring non-module: #{get_object_id(mod, true)}")
|
31
|
+
else
|
32
|
+
raise ::ITrace::NotAModuleError.new(mod)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
intercept(mod, modules_to_exclude, methods_to_exclude, opts)
|
37
|
+
end
|
38
|
+
|
39
|
+
if block_given?
|
40
|
+
begin
|
41
|
+
yield
|
42
|
+
ensure
|
43
|
+
@log_switch.disable_logging
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
self
|
48
|
+
end
|
49
|
+
|
50
|
+
def enable_logging
|
51
|
+
@log_switch.enable_logging
|
52
|
+
self
|
53
|
+
end
|
54
|
+
|
55
|
+
def disable_logging
|
56
|
+
@log_switch.disable_logging
|
57
|
+
self
|
58
|
+
end
|
59
|
+
|
60
|
+
private
|
61
|
+
def intercept(mod, modules_to_exclude, methods_to_exclude, opts, done = {})
|
62
|
+
return if mod.is_a?(::ITrace) || mod.is_a?(::ITrace::Interceptor)
|
63
|
+
object_id = get_object_id(mod, opts[:use_orig_object_id])
|
64
|
+
return if modules_to_exclude[object_id]
|
65
|
+
return if done[object_id]
|
66
|
+
done[object_id] = true
|
67
|
+
|
68
|
+
unless @intercepted[mod.object_id]
|
69
|
+
::ITrace::Interceptor.new(@log_switch, mod, methods_to_exclude, opts)
|
70
|
+
@intercepted[mod.object_id] = true
|
71
|
+
end
|
72
|
+
|
73
|
+
if opts[:include_singleton_classes]
|
74
|
+
singleton_class = mod.singleton_class
|
75
|
+
object_id = get_object_id(singleton_class, opts[:use_orig_object_id])
|
76
|
+
|
77
|
+
unless @intercepted[object_id]
|
78
|
+
::ITrace::Interceptor.new(@log_switch, singleton_class, methods_to_exclude, opts)
|
79
|
+
@intercepted[object_id] = true
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
if opts[:recursive]
|
84
|
+
mod.constants.each do |const|
|
85
|
+
begin
|
86
|
+
submod = get_const_get_method(mod, opts[:use_orig_const_get]).call(const)
|
87
|
+
rescue Exception => e
|
88
|
+
if opts[:ignore_exceptions] && opts[:ignore_exceptions].any?{ |k| e.class == k }
|
89
|
+
@log_switch.warn('itrace'){ "Ignoring caught exceptoin: #{e.class.name}" }
|
90
|
+
next
|
91
|
+
end
|
92
|
+
|
93
|
+
raise e
|
94
|
+
end
|
95
|
+
|
96
|
+
if submod.is_a?(::Module)
|
97
|
+
intercept(mod, modules_to_exclude, methods_to_exclude, opts, done)
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
def get_const_get_method(mod, get_original)
|
104
|
+
method = mod.method(:const_get)
|
105
|
+
|
106
|
+
if get_original
|
107
|
+
until method.owner == ::Module
|
108
|
+
super_method = method.super_method or return method
|
109
|
+
method = super_method
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
return method
|
114
|
+
end
|
115
|
+
|
116
|
+
def get_object_id(obj, get_original)
|
117
|
+
method = obj.method(:object_id)
|
118
|
+
|
119
|
+
if get_original
|
120
|
+
until method.owner == ::Kernel
|
121
|
+
super_method = method.super_method or return method.call
|
122
|
+
method = super_method
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
return method.call
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
data/test/test.rb
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'minitest/autorun'
|
2
|
+
require 'itrace'
|
3
|
+
require 'itrace_instance'
|
4
|
+
|
5
|
+
class Sample
|
6
|
+
class Another
|
7
|
+
def sample(*args)
|
8
|
+
puts "Sample::Another#sample: #{args.join(' ')}"
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def sample(*args)
|
13
|
+
puts "Sample#sample: #{args.join(' ')}"
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
describe ITrace do
|
18
|
+
it "logs method calls using default logger" do
|
19
|
+
ITrace.trace(Sample, recursive: true, include_singleton_classes: true) do
|
20
|
+
Sample.new.sample(1, 2, 3)
|
21
|
+
Sample::Another.new.sample(:a, :b, :c)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
describe ITraceInstance do
|
27
|
+
it "instantiates and logs method calls using default logger" do
|
28
|
+
ITraceInstance.new.trace(Sample, recursive: true, include_singleton_classes: true) do
|
29
|
+
Sample.new.sample(1, 2, 3)
|
30
|
+
Sample::Another.new.sample(:a, :b, :c)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
metadata
ADDED
@@ -0,0 +1,81 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: itrace
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- R.P. Pedraza
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2019-08-27 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rake
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '12.3'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '12.3'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: minitest
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '5.11'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '5.11'
|
41
|
+
description: Prepends anonymous modules that intercept and log method calls
|
42
|
+
email: rp.pedraza@gmail.com
|
43
|
+
executables: []
|
44
|
+
extensions: []
|
45
|
+
extra_rdoc_files: []
|
46
|
+
files:
|
47
|
+
- lib/itrace.rb
|
48
|
+
- lib/itrace/defaults.rb
|
49
|
+
- lib/itrace/interceptor.rb
|
50
|
+
- lib/itrace/log_switch.rb
|
51
|
+
- lib/itrace/not_a_module_error.rb
|
52
|
+
- lib/itrace/tracer.rb
|
53
|
+
- lib/itrace/version.rb
|
54
|
+
- lib/itrace_instance.rb
|
55
|
+
- test/test.rb
|
56
|
+
homepage: https://rubygems.org/gems/itrace
|
57
|
+
licenses:
|
58
|
+
- MIT
|
59
|
+
metadata:
|
60
|
+
source_code_uri: https://github.com/rp-pedraza/itrace
|
61
|
+
post_install_message:
|
62
|
+
rdoc_options: []
|
63
|
+
require_paths:
|
64
|
+
- lib
|
65
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
66
|
+
requirements:
|
67
|
+
- - ">="
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '0'
|
70
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
71
|
+
requirements:
|
72
|
+
- - ">="
|
73
|
+
- !ruby/object:Gem::Version
|
74
|
+
version: '0'
|
75
|
+
requirements: []
|
76
|
+
rubyforge_project:
|
77
|
+
rubygems_version: 2.7.4
|
78
|
+
signing_key:
|
79
|
+
specification_version: 4
|
80
|
+
summary: An intercepting tracer
|
81
|
+
test_files: []
|