appmap 0.28.1 → 0.31.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 +4 -4
- data/CHANGELOG.md +14 -1
- data/README.md +24 -0
- data/Rakefile +1 -1
- data/lib/appmap.rb +7 -6
- data/lib/appmap/class_map.rb +15 -23
- data/lib/appmap/config.rb +91 -0
- data/lib/appmap/hook.rb +92 -128
- data/lib/appmap/metadata.rb +1 -1
- data/lib/appmap/minitest.rb +141 -0
- data/lib/appmap/record.rb +27 -0
- data/lib/appmap/rspec.rb +1 -1
- data/lib/appmap/trace.rb +9 -1
- data/lib/appmap/version.rb +1 -1
- data/spec/config_spec.rb +3 -3
- data/spec/fixtures/hook/compare.rb +7 -0
- data/spec/fixtures/hook/openssl_sign.rb +87 -0
- data/spec/hook_spec.rb +141 -18
- data/spec/util_spec.rb +1 -1
- data/test/fixtures/minitest_recorder/Gemfile +5 -0
- data/test/fixtures/minitest_recorder/appmap.yml +3 -0
- data/test/fixtures/minitest_recorder/lib/hello.rb +5 -0
- data/test/fixtures/minitest_recorder/test/hello_test.rb +12 -0
- data/test/fixtures/process_recorder/appmap.yml +3 -0
- data/test/fixtures/process_recorder/hello.rb +9 -0
- data/test/minitest_test.rb +38 -0
- data/test/record_process_test.rb +35 -0
- metadata +15 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 54b9a05aeb3eea84572b115a6de1106e6743b74046048e3c168f99a75aa9e669
|
4
|
+
data.tar.gz: eb8a690376d833c4bec9f0789c5499f413f7dcb3b1ef111257785bd344861f64
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 453f06041220ecd0a4fc563ae007e7f8247d3dfcccf49657cbf47ddc53d0175e6834d1dbd89a2338a032d6cfae866929216b58a11bb6f0fe3fd2ea69508d7b2a
|
7
|
+
data.tar.gz: 77f372ad255c3c664c690affa2962958cebd1e8f34579809e4d975fdf57404889d8506f9153d44a95afda297ffe7b1654529252a5293487477e803bdf0cc9608
|
data/CHANGELOG.md
CHANGED
@@ -1,7 +1,20 @@
|
|
1
|
+
# v0.31.0
|
2
|
+
|
3
|
+
* Add the ability to hook methods by default, and optionally add labels to them in the
|
4
|
+
classmap. Use it to hook `ActiveSupport::SecurityUtils.secure_compare`.
|
5
|
+
|
6
|
+
# v0.30.0
|
7
|
+
|
8
|
+
* Add support for Minitest.
|
9
|
+
|
10
|
+
# v0.29.0
|
11
|
+
|
12
|
+
* Add `lib/appmap/record.rb`, which can be `require`d to record the rest of the process.
|
13
|
+
|
1
14
|
# v0.28.1
|
15
|
+
|
2
16
|
* Fix the `defined_class` recorded in an appmap for an instance method included in a class
|
3
17
|
at runtime.
|
4
|
-
|
5
18
|
* Only include the `static` attribute on `call` events in an appmap. Determine its value
|
6
19
|
based on the receiver of the method call.
|
7
20
|
|
data/README.md
CHANGED
@@ -3,6 +3,7 @@
|
|
3
3
|
- [Configuration](#configuration)
|
4
4
|
- [Running](#running)
|
5
5
|
- [RSpec](#rspec)
|
6
|
+
- [Minitest](#minitest)
|
6
7
|
- [Cucumber](#cucumber)
|
7
8
|
- [Remote recording](#remote-recording)
|
8
9
|
- [Ruby on Rails](#ruby-on-rails)
|
@@ -125,6 +126,29 @@ If you include the `feature` and `feature_group` metadata, these attributes will
|
|
125
126
|
|
126
127
|
If you don't explicitly declare `feature` and `feature_group`, then they will be inferred from the spec name and example descriptions.
|
127
128
|
|
129
|
+
## Minitest
|
130
|
+
|
131
|
+
To record Minitest tests, follow these additional steps:
|
132
|
+
|
133
|
+
1) Require `appmap/minitest` in `test_helper.rb`
|
134
|
+
|
135
|
+
```ruby
|
136
|
+
require 'appmap/minitest'
|
137
|
+
```
|
138
|
+
|
139
|
+
2) Run the tests with the environment variable `APPMAP=true`:
|
140
|
+
|
141
|
+
```sh-session
|
142
|
+
$ APPMAP=true bundle exec -Ilib -Itest test/*
|
143
|
+
```
|
144
|
+
|
145
|
+
Each Minitest test will output an AppMap file into the directory `tmp/appmap/minitest`. For example:
|
146
|
+
|
147
|
+
```
|
148
|
+
$ find tmp/appmap/minitest
|
149
|
+
Hello_says_hello_when_prompted.appmap.json
|
150
|
+
```
|
151
|
+
|
128
152
|
## Cucumber
|
129
153
|
|
130
154
|
To record Cucumber tests, follow these additional steps:
|
data/Rakefile
CHANGED
data/lib/appmap.rb
CHANGED
@@ -8,6 +8,11 @@ rescue NameError
|
|
8
8
|
end
|
9
9
|
|
10
10
|
require 'appmap/version'
|
11
|
+
require 'appmap/hook'
|
12
|
+
require 'appmap/config'
|
13
|
+
require 'appmap/trace'
|
14
|
+
require 'appmap/class_map'
|
15
|
+
require 'appmap/metadata'
|
11
16
|
|
12
17
|
module AppMap
|
13
18
|
class << self
|
@@ -34,14 +39,12 @@ module AppMap
|
|
34
39
|
# the load events won't be seen and the hooks won't activate.
|
35
40
|
def initialize(config_file_path = 'appmap.yml')
|
36
41
|
warn "Configuring AppMap from path #{config_file_path}"
|
37
|
-
|
38
|
-
|
39
|
-
Hook.hook(configuration)
|
42
|
+
self.configuration = Config.load_from_file(config_file_path)
|
43
|
+
Hook.new(configuration).enable
|
40
44
|
end
|
41
45
|
|
42
46
|
# tracing can be used to start tracing, stop tracing, and record events.
|
43
47
|
def tracing
|
44
|
-
require 'appmap/trace'
|
45
48
|
@tracing ||= Trace::Tracing.new
|
46
49
|
end
|
47
50
|
|
@@ -68,14 +71,12 @@ module AppMap
|
|
68
71
|
|
69
72
|
# class_map builds a class map from a config and a list of Ruby methods.
|
70
73
|
def class_map(methods)
|
71
|
-
require 'appmap/class_map'
|
72
74
|
ClassMap.build_from_methods(configuration, methods)
|
73
75
|
end
|
74
76
|
|
75
77
|
# detect_metadata returns default metadata detected from the Ruby system and from the
|
76
78
|
# filesystem.
|
77
79
|
def detect_metadata
|
78
|
-
require 'appmap/metadata'
|
79
80
|
@metadata ||= Metadata.detect.freeze
|
80
81
|
@metadata.deep_dup
|
81
82
|
end
|
data/lib/appmap/class_map.rb
CHANGED
@@ -1,7 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require 'active_support/core_ext'
|
4
|
-
|
5
3
|
module AppMap
|
6
4
|
class ClassMap
|
7
5
|
module HasChildren
|
@@ -50,7 +48,7 @@ module AppMap
|
|
50
48
|
end
|
51
49
|
end
|
52
50
|
Function = Struct.new(:name) do
|
53
|
-
attr_accessor :static, :location
|
51
|
+
attr_accessor :static, :location, :labels
|
54
52
|
|
55
53
|
def type
|
56
54
|
'function'
|
@@ -61,8 +59,9 @@ module AppMap
|
|
61
59
|
name: name,
|
62
60
|
type: type,
|
63
61
|
location: location,
|
64
|
-
static: static
|
65
|
-
|
62
|
+
static: static,
|
63
|
+
labels: labels
|
64
|
+
}.delete_if {|k,v| v.nil?}
|
66
65
|
end
|
67
66
|
end
|
68
67
|
end
|
@@ -71,27 +70,17 @@ module AppMap
|
|
71
70
|
def build_from_methods(config, methods)
|
72
71
|
root = Types::Root.new
|
73
72
|
methods.each do |method|
|
74
|
-
package = package_for_method(
|
75
|
-
|
73
|
+
package = config.package_for_method(method) \
|
74
|
+
or raise "No package found for method #{method}"
|
75
|
+
add_function root, package, method
|
76
76
|
end
|
77
77
|
root.children.map(&:to_h)
|
78
78
|
end
|
79
79
|
|
80
80
|
protected
|
81
81
|
|
82
|
-
def
|
83
|
-
location = method.
|
84
|
-
location_file, = location
|
85
|
-
location_file = location_file[Dir.pwd.length + 1..-1] if location_file.index(Dir.pwd) == 0
|
86
|
-
|
87
|
-
packages.find do |pkg|
|
88
|
-
(location_file.index(pkg.path) == 0) &&
|
89
|
-
!pkg.exclude.find { |p| location_file.index(p) }
|
90
|
-
end or raise "No package found for method #{method}"
|
91
|
-
end
|
92
|
-
|
93
|
-
def add_function(root, package_name, method)
|
94
|
-
location = method.method.source_location
|
82
|
+
def add_function(root, package, method)
|
83
|
+
location = method.source_location
|
95
84
|
location_file, lineno = location
|
96
85
|
location_file = location_file[Dir.pwd.length + 1..-1] if location_file.index(Dir.pwd) == 0
|
97
86
|
|
@@ -99,7 +88,7 @@ module AppMap
|
|
99
88
|
|
100
89
|
object_infos = [
|
101
90
|
{
|
102
|
-
name:
|
91
|
+
name: package.path,
|
103
92
|
type: 'package'
|
104
93
|
}
|
105
94
|
]
|
@@ -109,12 +98,15 @@ module AppMap
|
|
109
98
|
type: 'class'
|
110
99
|
}
|
111
100
|
end
|
112
|
-
|
113
|
-
name: method.
|
101
|
+
function_info = {
|
102
|
+
name: method.name,
|
114
103
|
type: 'function',
|
115
104
|
location: [ location_file, lineno ].join(':'),
|
116
105
|
static: static
|
117
106
|
}
|
107
|
+
function_info[:labels] = package.labels if package.labels
|
108
|
+
object_infos << function_info
|
109
|
+
|
118
110
|
parent = root
|
119
111
|
object_infos.each do |info|
|
120
112
|
parent = find_or_create parent.children, info do
|
@@ -0,0 +1,91 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module AppMap
|
4
|
+
Package = Struct.new(:path, :exclude, :labels) do
|
5
|
+
def initialize(path, exclude, labels = nil)
|
6
|
+
super
|
7
|
+
end
|
8
|
+
|
9
|
+
def to_h
|
10
|
+
{
|
11
|
+
path: path,
|
12
|
+
exclude: exclude.blank? ? nil : exclude,
|
13
|
+
labels: labels.blank? ? nil : labels
|
14
|
+
}.compact
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
class Config
|
19
|
+
# Methods that should always be hooked, with their containing
|
20
|
+
# package and labels that should be applied to them.
|
21
|
+
HOOKED_METHODS = {
|
22
|
+
'ActiveSupport::SecurityUtils' => {
|
23
|
+
secure_compare: Package.new('active_support', nil, ['security'])
|
24
|
+
},
|
25
|
+
'OpenSSL::X509::Certificate' => {
|
26
|
+
sign: Package.new('openssl', nil, ['security'])
|
27
|
+
}
|
28
|
+
}
|
29
|
+
|
30
|
+
attr_reader :name, :packages
|
31
|
+
def initialize(name, packages = [])
|
32
|
+
@name = name
|
33
|
+
@packages = packages
|
34
|
+
end
|
35
|
+
|
36
|
+
class << self
|
37
|
+
# Loads configuration data from a file, specified by the file name.
|
38
|
+
def load_from_file(config_file_name)
|
39
|
+
require 'yaml'
|
40
|
+
load YAML.safe_load(::File.read(config_file_name))
|
41
|
+
end
|
42
|
+
|
43
|
+
# Loads configuration from a Hash.
|
44
|
+
def load(config_data)
|
45
|
+
packages = (config_data['packages'] || []).map do |package|
|
46
|
+
Package.new(package['path'], package['exclude'] || [])
|
47
|
+
end
|
48
|
+
Config.new config_data['name'], packages
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def to_h
|
53
|
+
{
|
54
|
+
name: name,
|
55
|
+
packages: packages.map(&:to_h)
|
56
|
+
}
|
57
|
+
end
|
58
|
+
|
59
|
+
def package_for_method(method)
|
60
|
+
location = method.source_location
|
61
|
+
location_file, = location
|
62
|
+
return unless location_file
|
63
|
+
|
64
|
+
defined_class,_,method_name = Hook.qualify_method_name(method)
|
65
|
+
hooked_method = find_hooked_method(defined_class, method_name)
|
66
|
+
return hooked_method if hooked_method
|
67
|
+
|
68
|
+
location_file = location_file[Dir.pwd.length + 1..-1] if location_file.index(Dir.pwd) == 0
|
69
|
+
packages.find do |pkg|
|
70
|
+
(location_file.index(pkg.path) == 0) &&
|
71
|
+
!pkg.exclude.find { |p| location_file.index(p) }
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def included_by_location?(method)
|
76
|
+
!!package_for_method(method)
|
77
|
+
end
|
78
|
+
|
79
|
+
def always_hook?(defined_class, method_name)
|
80
|
+
!!find_hooked_method(defined_class, method_name)
|
81
|
+
end
|
82
|
+
|
83
|
+
def find_hooked_method(defined_class, method_name)
|
84
|
+
find_hooked_class(defined_class)[method_name]
|
85
|
+
end
|
86
|
+
|
87
|
+
def find_hooked_class(defined_class)
|
88
|
+
HOOKED_METHODS[defined_class] || {}
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
data/lib/appmap/hook.rb
CHANGED
@@ -6,161 +6,125 @@ module AppMap
|
|
6
6
|
class Hook
|
7
7
|
LOG = false
|
8
8
|
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
9
|
+
HOOK_DISABLE_KEY = 'AppMap::Hook.disable'
|
10
|
+
|
11
|
+
class << self
|
12
|
+
# Return the class, separator ('.' or '#'), and method name for
|
13
|
+
# the given method.
|
14
|
+
def qualify_method_name(method)
|
15
|
+
if method.owner.singleton_class?
|
16
|
+
# Singleton class names can take two forms:
|
17
|
+
# #<Class:Foo> or
|
18
|
+
# #<Class:#<Bar:0x0123ABC>>. Retrieve the name of
|
19
|
+
# the class from the string.
|
20
|
+
#
|
21
|
+
# (There really isn't a better way to do this. The
|
22
|
+
# singleton's reference to the class it was created
|
23
|
+
# from is stored in an instance variable named
|
24
|
+
# '__attached__'. It doesn't have the '@' prefix, so
|
25
|
+
# it's internal only, and not accessible from user
|
26
|
+
# code.)
|
27
|
+
class_name = /#<Class:((#<(?<cls>.*?):)|((?<cls>.*?)>))/.match(method.owner.to_s)['cls']
|
28
|
+
[ class_name, '.', method.name ]
|
29
|
+
else
|
30
|
+
[ method.owner.name, '#', method.name ]
|
31
|
+
end
|
15
32
|
end
|
16
33
|
end
|
17
34
|
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
require 'yaml'
|
23
|
-
load YAML.safe_load(::File.read(config_file_name))
|
24
|
-
end
|
35
|
+
attr_reader :config
|
36
|
+
def initialize(config)
|
37
|
+
@config = config
|
38
|
+
end
|
25
39
|
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
40
|
+
# Observe class loading and hook all methods which match the config.
|
41
|
+
def enable &block
|
42
|
+
before_hook = lambda do |defined_class, method, receiver, args|
|
43
|
+
require 'appmap/event'
|
44
|
+
call_event = AppMap::Event::MethodCall.build_from_invocation(defined_class, method, receiver, args)
|
45
|
+
AppMap.tracing.record_event call_event, defined_class: defined_class, method: method
|
46
|
+
[ call_event, Time.now ]
|
33
47
|
end
|
34
48
|
|
35
|
-
|
36
|
-
|
49
|
+
after_hook = lambda do |call_event, defined_class, method, start_time, return_value, exception|
|
50
|
+
require 'appmap/event'
|
51
|
+
elapsed = Time.now - start_time
|
52
|
+
return_event = AppMap::Event::MethodReturn.build_from_invocation \
|
53
|
+
defined_class, method, call_event.id, elapsed, return_value, exception
|
54
|
+
AppMap.tracing.record_event return_event
|
37
55
|
end
|
38
56
|
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
57
|
+
with_disabled_hook = lambda do |&fn|
|
58
|
+
# Don't record functions, such as to_s and inspect, that might be called
|
59
|
+
# by the fn. Otherwise there can be a stack overflow.
|
60
|
+
Thread.current[HOOK_DISABLE_KEY] = true
|
61
|
+
begin
|
62
|
+
fn.call
|
63
|
+
ensure
|
64
|
+
Thread.current[HOOK_DISABLE_KEY] = false
|
65
|
+
end
|
44
66
|
end
|
45
|
-
end
|
46
67
|
|
47
|
-
|
68
|
+
tp = TracePoint.new(:end) do |tp|
|
69
|
+
hook = self
|
70
|
+
cls = tp.self
|
48
71
|
|
49
|
-
|
50
|
-
|
51
|
-
def hook(config = AppMap.configure)
|
52
|
-
package_include_paths = config.packages.map(&:path)
|
53
|
-
package_exclude_paths = config.packages.map do |pkg|
|
54
|
-
pkg.exclude.map do |exclude|
|
55
|
-
File.join(pkg.path, exclude)
|
56
|
-
end
|
57
|
-
end.flatten
|
72
|
+
instance_methods = cls.public_instance_methods(false)
|
73
|
+
class_methods = cls.singleton_class.public_instance_methods(false) - instance_methods
|
58
74
|
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
AppMap.tracing.record_event call_event, defined_class: defined_class, method: method
|
63
|
-
[ call_event, Time.now ]
|
64
|
-
end
|
75
|
+
hook_method = lambda do |cls|
|
76
|
+
lambda do |method_id|
|
77
|
+
next if method_id.to_s =~ /_hooked_by_appmap$/
|
65
78
|
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
defined_class, method, call_event.id, elapsed, return_value, exception
|
71
|
-
AppMap.tracing.record_event return_event
|
72
|
-
end
|
79
|
+
method = cls.public_instance_method(method_id)
|
80
|
+
disasm = RubyVM::InstructionSequence.disasm(method)
|
81
|
+
# Skip methods that have no instruction sequence, as they are obviously trivial.
|
82
|
+
next unless disasm
|
73
83
|
|
74
|
-
|
75
|
-
|
76
|
-
# by the fn. Otherwise there can be a stack oveflow.
|
77
|
-
Thread.current[HOOK_DISABLE_KEY] = true
|
78
|
-
begin
|
79
|
-
fn.call
|
80
|
-
ensure
|
81
|
-
Thread.current[HOOK_DISABLE_KEY] = false
|
82
|
-
end
|
83
|
-
end
|
84
|
+
defined_class, method_symbol, method_name = Hook.qualify_method_name(method)
|
85
|
+
method_display_name = [defined_class, method_symbol, method_name].join
|
84
86
|
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
instance_methods = cls.public_instance_methods(false)
|
89
|
-
class_methods = cls.singleton_class.public_instance_methods(false) - instance_methods
|
90
|
-
|
91
|
-
hook_method = lambda do |cls|
|
92
|
-
lambda do |method_id|
|
93
|
-
next if method_id.to_s =~ /_hooked_by_appmap$/
|
94
|
-
|
95
|
-
method = cls.public_instance_method(method_id)
|
96
|
-
location = method.source_location
|
97
|
-
location_file, = location
|
98
|
-
next unless location_file
|
99
|
-
|
100
|
-
location_file = location_file[Dir.pwd.length + 1..-1] if location_file.index(Dir.pwd) == 0
|
101
|
-
match = package_include_paths.find { |p| location_file.index(p) == 0 }
|
102
|
-
match &&= !package_exclude_paths.find { |p| location_file.index(p) }
|
103
|
-
next unless match
|
104
|
-
|
105
|
-
disasm = RubyVM::InstructionSequence.disasm(method)
|
106
|
-
# Skip methods that have no instruction sequence, as they are obviously trivial.
|
107
|
-
next unless disasm
|
108
|
-
|
109
|
-
defined_class, method_symbol = if method.owner.singleton_class?
|
110
|
-
# Singleton class names can take two forms:
|
111
|
-
# #<Class:Foo> or
|
112
|
-
# #<Class:#<Bar:0x0123ABC>>. Retrieve the name of
|
113
|
-
# the class from the string.
|
114
|
-
#
|
115
|
-
# (There really isn't a better way to do this. The
|
116
|
-
# singleton's reference to the class it was created
|
117
|
-
# from is stored in an instance variable named
|
118
|
-
# '__attached__'. It doesn't have the '@' prefix, so
|
119
|
-
# it's internal only, and not accessible from user
|
120
|
-
# code.)
|
121
|
-
class_name = /#<Class:((#<(?<cls>.*?):)|((?<cls>.*?)>))/.match(method.owner.to_s)['cls']
|
122
|
-
[ class_name, '.' ]
|
123
|
-
else
|
124
|
-
[ method.owner.name, '#' ]
|
125
|
-
end
|
126
|
-
|
127
|
-
method_display_name = "#{defined_class}#{method_symbol}#{method.name}"
|
128
|
-
# Don't try and trace the tracing method or there will be a stack overflow
|
129
|
-
# in the defined hook method.
|
130
|
-
next if method_display_name == "AppMap.tracing"
|
87
|
+
# Don't try and trace the AppMap methods or there will be
|
88
|
+
# a stack overflow in the defined hook method.
|
89
|
+
next if /\AAppMap[:\.]/.match?(method_display_name)
|
131
90
|
|
132
|
-
|
91
|
+
next unless \
|
92
|
+
config.always_hook?(defined_class, method_name) ||
|
93
|
+
config.included_by_location?(method)
|
133
94
|
|
134
|
-
|
135
|
-
base_method = method.bind(self).to_proc
|
95
|
+
warn "AppMap: Hooking #{method_display_name}" if LOG
|
136
96
|
|
137
|
-
|
138
|
-
|
139
|
-
return base_method.call(*args, &block) unless enabled
|
97
|
+
cls.define_method method_id do |*args, &block|
|
98
|
+
base_method = method.bind(self).to_proc
|
140
99
|
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
100
|
+
hook_disabled = Thread.current[HOOK_DISABLE_KEY]
|
101
|
+
enabled = true if !hook_disabled && AppMap.tracing.enabled?
|
102
|
+
return base_method.call(*args, &block) unless enabled
|
103
|
+
|
104
|
+
call_event, start_time = with_disabled_hook.call do
|
105
|
+
before_hook.call(defined_class, method, self, args)
|
106
|
+
end
|
107
|
+
return_value = nil
|
108
|
+
exception = nil
|
109
|
+
begin
|
110
|
+
return_value = base_method.call(*args, &block)
|
111
|
+
rescue
|
112
|
+
exception = $ERROR_INFO
|
113
|
+
raise
|
114
|
+
ensure
|
115
|
+
with_disabled_hook.call do
|
116
|
+
after_hook.call(call_event, defined_class, method, start_time, return_value, exception)
|
155
117
|
end
|
156
118
|
end
|
157
119
|
end
|
120
|
+
end
|
158
121
|
end
|
159
122
|
|
160
123
|
instance_methods.each(&hook_method.call(cls))
|
161
124
|
class_methods.each(&hook_method.call(cls.singleton_class))
|
162
125
|
end
|
163
|
-
|
126
|
+
|
127
|
+
tp.enable(&block)
|
164
128
|
end
|
165
129
|
end
|
166
130
|
end
|