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 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,5 @@
1
+ module ITrace
2
+ module Defaults
3
+ METHODS_TO_EXCLUDE = ::Kernel.instance_methods + ::Module.instance_methods
4
+ end
5
+ 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,7 @@
1
+ module ITrace
2
+ class NotAModuleError < ::RuntimeError
3
+ def initialize(not_a_module)
4
+ super("Not a module: #{not_a_module.inspect}")
5
+ end
6
+ end
7
+ 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
@@ -0,0 +1,3 @@
1
+ module ITrace
2
+ VERSION = "0.0.0"
3
+ end
@@ -0,0 +1,9 @@
1
+ require 'itrace'
2
+
3
+ class ITraceInstance
4
+ include ITrace
5
+
6
+ def initialize(logger = nil)
7
+ @logger = logger
8
+ end
9
+ 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: []