appmap 0.45.0 → 0.48.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/.travis.yml +10 -0
- data/CHANGELOG.md +36 -0
- data/README.md +39 -27
- data/lib/appmap.rb +1 -2
- data/lib/appmap/class_map.rb +7 -15
- data/lib/appmap/config.rb +194 -96
- data/lib/appmap/event.rb +29 -28
- data/lib/appmap/handler/function.rb +1 -1
- data/lib/appmap/handler/rails/request_handler.rb +124 -0
- data/lib/appmap/handler/rails/sql_handler.rb +152 -0
- data/lib/appmap/handler/rails/template.rb +155 -0
- data/lib/appmap/hook.rb +109 -71
- data/lib/appmap/hook/method.rb +1 -1
- data/lib/appmap/railtie.rb +6 -28
- data/lib/appmap/trace.rb +46 -6
- data/lib/appmap/util.rb +18 -0
- data/lib/appmap/version.rb +2 -2
- data/package-lock.json +3 -3
- data/spec/abstract_controller_base_spec.rb +68 -9
- data/spec/class_map_spec.rb +3 -3
- data/spec/fixtures/rails5_users_app/config/application.rb +0 -2
- data/spec/fixtures/rails6_users_app/config/application.rb +0 -2
- data/spec/hook_spec.rb +11 -58
- data/spec/railtie_spec.rb +7 -11
- data/spec/util_spec.rb +18 -1
- metadata +5 -4
- data/lib/appmap/rails/request_handler.rb +0 -122
- data/lib/appmap/rails/sql_handler.rb +0 -150
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6ce9ca4faa9074d177861610ff47bec4e7b02191e870759a12d1ed9207e9f79a
|
4
|
+
data.tar.gz: 6b0157f28774b1d46fdea527c8076846253780040628f3879ec8c18af8c4a60d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e9d67de222d734c8b650c2c8bb8cef70b371908561dd8df43410f370071cdd1c24b9cc8d4919ccfda59745afefccc29e8af30444f25136aac685384ec079e0ba
|
7
|
+
data.tar.gz: 387a7a074b9c56ae423825e0beea405909066528c40ff6d6ce3789843193c6e0c73cafabefd8e091dc9961dd5e2c112f311538e8afdc5bbaf37ca187be2f1606
|
data/.travis.yml
CHANGED
@@ -17,6 +17,16 @@ before_script:
|
|
17
17
|
cache:
|
18
18
|
bundler: true
|
19
19
|
|
20
|
+
before_install:
|
21
|
+
# see https://blog.travis-ci.com/docker-rate-limits
|
22
|
+
# and also https://www.docker.com/blog/what-you-need-to-know-about-upcoming-docker-hub-rate-limiting/
|
23
|
+
# if we do not use authorized account,
|
24
|
+
# the pulls-per-IP quota is shared with other Travis users
|
25
|
+
- >
|
26
|
+
if [ ! -z "$DOCKERHUB_PASSWORD" ] && [ ! -z "$DOCKERHUB_USERNAME" ]; then
|
27
|
+
echo "$DOCKERHUB_PASSWORD" | docker login -u "$DOCKERHUB_USERNAME" --password-stdin ;
|
28
|
+
fi
|
29
|
+
|
20
30
|
|
21
31
|
# GEM_ALTERNATIVE_NAME only needed for deployment
|
22
32
|
jobs:
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,39 @@
|
|
1
|
+
# [0.48.0](https://github.com/applandinc/appmap-ruby/compare/v0.47.1...v0.48.0) (2021-05-19)
|
2
|
+
|
3
|
+
|
4
|
+
### Features
|
5
|
+
|
6
|
+
* Hook the code only when APPMAP=true ([dd9e383](https://github.com/applandinc/appmap-ruby/commit/dd9e383024d1d9205a617d46bd64b90820035533))
|
7
|
+
* Remove server process recording from doc and tests ([383ba0a](https://github.com/applandinc/appmap-ruby/commit/383ba0ad444922a0a85409477d11bc7ed06a9160))
|
8
|
+
|
9
|
+
## [0.47.1](https://github.com/applandinc/appmap-ruby/compare/v0.47.0...v0.47.1) (2021-05-13)
|
10
|
+
|
11
|
+
|
12
|
+
### Bug Fixes
|
13
|
+
|
14
|
+
* Add the proper template function hooks for Rails 6.0.7 ([175f489](https://github.com/applandinc/appmap-ruby/commit/175f489acbaed77ad52a18d805e4b6eeae1abfdb))
|
15
|
+
|
16
|
+
# [0.47.0](https://github.com/applandinc/appmap-ruby/compare/v0.46.0...v0.47.0) (2021-05-13)
|
17
|
+
|
18
|
+
|
19
|
+
### Features
|
20
|
+
|
21
|
+
* Emit swagger-style normalized paths instead of Rails-style ones ([5a93cd7](https://github.com/applandinc/appmap-ruby/commit/5a93cd7096ca195146a84a6733c7d502dbcd0272))
|
22
|
+
|
23
|
+
# [0.46.0](https://github.com/applandinc/appmap-ruby/compare/v0.45.1...v0.46.0) (2021-05-12)
|
24
|
+
|
25
|
+
|
26
|
+
### Features
|
27
|
+
|
28
|
+
* Record view template rendering events and template paths ([973b258](https://github.com/applandinc/appmap-ruby/commit/973b2581b6e2d4e15a1b93331e4e95a88678faae))
|
29
|
+
|
30
|
+
## [0.45.1](https://github.com/applandinc/appmap-ruby/compare/v0.45.0...v0.45.1) (2021-05-04)
|
31
|
+
|
32
|
+
|
33
|
+
### Bug Fixes
|
34
|
+
|
35
|
+
* Optimize instrumentation and load time ([db4a8ce](https://github.com/applandinc/appmap-ruby/commit/db4a8ceed4103a52caafa46626c66f33fbfeac27))
|
36
|
+
|
1
37
|
# [0.45.0](https://github.com/applandinc/appmap-ruby/compare/v0.44.0...v0.45.0) (2021-05-03)
|
2
38
|
|
3
39
|
|
data/README.md
CHANGED
@@ -9,11 +9,11 @@
|
|
9
9
|
- [Minitest](#minitest)
|
10
10
|
- [Cucumber](#cucumber)
|
11
11
|
- [Remote recording](#remote-recording)
|
12
|
-
- [Server process recording](#server-process-recording)
|
13
12
|
- [AppMap for VSCode](#appmap-for-vscode)
|
14
13
|
- [AppMap Swagger](#appmap-swagger)
|
15
14
|
- [Uploading AppMaps](#uploading-appmaps)
|
16
15
|
- [Development](#development)
|
16
|
+
- [Internal architecture](#internal-architecture)
|
17
17
|
- [Running tests](#running-tests)
|
18
18
|
- [Using fixture apps](#using-fixture-apps)
|
19
19
|
- [`test/fixtures`](#testfixtures)
|
@@ -83,22 +83,6 @@ If you are using Ruby on Rails, require the railtie after Rails is loaded.
|
|
83
83
|
require 'appmap/railtie' if defined?(AppMap).
|
84
84
|
```
|
85
85
|
|
86
|
-
**application.rb**
|
87
|
-
|
88
|
-
Add this line to *application.rb*, to enable server recording with `APPMAP_RECORD=true`:
|
89
|
-
|
90
|
-
```ruby
|
91
|
-
module MyApp
|
92
|
-
class Application < Rails::Application
|
93
|
-
...
|
94
|
-
|
95
|
-
config.appmap.enabled = true if ENV['APPMAP_RECORD']
|
96
|
-
|
97
|
-
...
|
98
|
-
end
|
99
|
-
end
|
100
|
-
```
|
101
|
-
|
102
86
|
# Configuration
|
103
87
|
|
104
88
|
When you run your program, the `appmap` gem reads configuration settings from `appmap.yml`. Here's a sample configuration
|
@@ -325,25 +309,25 @@ if defined?(AppMap)
|
|
325
309
|
end
|
326
310
|
```
|
327
311
|
|
328
|
-
2. Download and unpack the [AppLand browser extension](https://github.com/applandinc/appland-browser-extension). Install into Chrome using `chrome://extensions/`. Turn on "Developer Mode" and then load the extension using the "Load unpacked" button.
|
312
|
+
2. (optional) Download and unpack the [AppLand browser extension](https://github.com/applandinc/appland-browser-extension). Install into Chrome using `chrome://extensions/`. Turn on "Developer Mode" and then load the extension using the "Load unpacked" button.
|
329
313
|
|
330
|
-
3. Start your Rails application server
|
314
|
+
3. Start your Rails application server, with `APPMAP_RECORD=true`. For example:
|
331
315
|
|
332
316
|
```sh-session
|
333
|
-
$ bundle exec rails server
|
317
|
+
$ APPMAP_RECORD=true bundle exec rails server
|
334
318
|
```
|
335
319
|
|
336
|
-
4.
|
320
|
+
4. Start the recording
|
337
321
|
|
338
|
-
|
322
|
+
Option 1: Open the AppLand browser extension and push `Start`.
|
323
|
+
Option 2: `curl -XPOST localhost:3000/_appmap/record` (be sure and get the port number right)
|
339
324
|
|
340
|
-
|
341
|
-
|
342
|
-
## Server process recording
|
325
|
+
5. Use your app. For example, perform a login flow, or run through a manual UI test.
|
343
326
|
|
344
|
-
|
327
|
+
6. Finish the recording.
|
345
328
|
|
346
|
-
|
329
|
+
Option 1: Open the AppLand browser extension and push `Stop`. The recording will be transferred to the AppLand website and opened in your browser.
|
330
|
+
Option 2: `curl -XDELETE localhost:3000/_appmap/record > recording.appmap.json` - Saves the recording as a local file.
|
347
331
|
|
348
332
|
|
349
333
|
# AppMap for VSCode
|
@@ -369,6 +353,34 @@ For instructions on uploading, see the documentation of the [AppLand CLI](https:
|
|
369
353
|
# Development
|
370
354
|
[](https://travis-ci.com/applandinc/appmap-ruby)
|
371
355
|
|
356
|
+
## Internal architecture
|
357
|
+
|
358
|
+
**Configuration**
|
359
|
+
|
360
|
+
*appmap.yml* is loaded into an `AppMap::Config`.
|
361
|
+
|
362
|
+
**Hooking**
|
363
|
+
|
364
|
+
Once configuration is loaded, `AppMap::Hook` is enabled. "Hooking" refers to the process of replacing a method
|
365
|
+
with a "hooked" version of the method. The hooked method checks to see if tracing is enabled. If so, it wraps the original
|
366
|
+
method with calls that record the parameters and return value.
|
367
|
+
|
368
|
+
**Builtins**
|
369
|
+
|
370
|
+
`Hook` begins by iterating over builtin classes and modules defined in the `Config`. Builtins include code
|
371
|
+
like `openssl` and `net/http`. This code is not dependent on any external libraries being present, and
|
372
|
+
`appmap` cannot guarantee that it will be loaded before builtins. Therefore, it's necessary to require it and
|
373
|
+
hook it by looking up the classes and modules as constants in the `Object` namespace.
|
374
|
+
|
375
|
+
**User code and gems**
|
376
|
+
|
377
|
+
After hooking builtins, `Hook` attaches a [TracePoint](https://ruby-doc.org/core-2.6/TracePoint.html) to `:begin` events.
|
378
|
+
This TracePoint is notified each time a new class or module is being evaluated. When this happens, `Hook` uses the `Config`
|
379
|
+
to determine whether any code within the evaluated file is configured for hooking. If so, a `TracePoint` is attached to
|
380
|
+
`:end` events. Each `:end` event is fired when a class or module definition is completed. When this happens, the `Hook` enumerates
|
381
|
+
the public methods of the class or module, hooking the ones that are targeted by the `Config`. Once the `:end` TracePoint leaves
|
382
|
+
the scope of the `:begin`, the `:end` TracePoint is disabled.
|
383
|
+
|
372
384
|
## Running tests
|
373
385
|
|
374
386
|
Before running tests, configure `local.appmap` to point to your local `appmap-ruby` directory.
|
data/lib/appmap.rb
CHANGED
@@ -9,7 +9,6 @@ end
|
|
9
9
|
|
10
10
|
require 'appmap/version'
|
11
11
|
require 'appmap/hook'
|
12
|
-
require 'appmap/handler/net_http'
|
13
12
|
require 'appmap/config'
|
14
13
|
require 'appmap/trace'
|
15
14
|
require 'appmap/class_map'
|
@@ -99,4 +98,4 @@ module AppMap
|
|
99
98
|
end
|
100
99
|
|
101
100
|
require 'appmap/railtie' if defined?(::Rails::Railtie)
|
102
|
-
AppMap.initialize
|
101
|
+
AppMap.initialize if ENV['APPMAP'] == 'true'
|
data/lib/appmap/class_map.rb
CHANGED
@@ -82,16 +82,13 @@ module AppMap
|
|
82
82
|
protected
|
83
83
|
|
84
84
|
def add_function(root, method)
|
85
|
-
package = method.package
|
86
|
-
static = method.static
|
87
|
-
|
88
85
|
object_infos = [
|
89
86
|
{
|
90
|
-
name: package
|
87
|
+
name: method.package,
|
91
88
|
type: 'package'
|
92
89
|
}
|
93
90
|
]
|
94
|
-
object_infos += method.
|
91
|
+
object_infos += method.class_name.split('::').map do |name|
|
95
92
|
{
|
96
93
|
name: name,
|
97
94
|
type: 'class'
|
@@ -100,7 +97,7 @@ module AppMap
|
|
100
97
|
function_info = {
|
101
98
|
name: method.name,
|
102
99
|
type: 'function',
|
103
|
-
static: static
|
100
|
+
static: method.static
|
104
101
|
}
|
105
102
|
location = method.source_location
|
106
103
|
|
@@ -108,20 +105,15 @@ module AppMap
|
|
108
105
|
if location
|
109
106
|
location_file, lineno = location
|
110
107
|
location_file = location_file[Dir.pwd.length + 1..-1] if location_file.index(Dir.pwd) == 0
|
111
|
-
[ location_file, lineno ].join(':')
|
108
|
+
[ location_file, lineno ].compact.join(':')
|
112
109
|
else
|
113
|
-
[ method.
|
110
|
+
[ method.class_name, method.static ? '.' : '#', method.name ].join
|
114
111
|
end
|
115
112
|
|
116
|
-
comment =
|
117
|
-
method.comment
|
118
|
-
rescue MethodSource::SourceNotFoundError
|
119
|
-
nil
|
120
|
-
end
|
121
|
-
|
113
|
+
comment = method.comment
|
122
114
|
function_info[:comment] = comment unless comment.blank?
|
123
115
|
|
124
|
-
function_info[:labels] = parse_labels(comment) + (
|
116
|
+
function_info[:labels] = parse_labels(comment) + (method.labels || [])
|
125
117
|
object_infos << function_info
|
126
118
|
|
127
119
|
parent = root
|
data/lib/appmap/config.rb
CHANGED
@@ -1,8 +1,25 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'appmap/handler/net_http'
|
4
|
+
require 'appmap/handler/rails/template'
|
5
|
+
|
3
6
|
module AppMap
|
4
7
|
class Config
|
8
|
+
# Specifies a code +path+ to be mapped.
|
9
|
+
# Options:
|
10
|
+
#
|
11
|
+
# * +gem+ may indicate a gem name that "owns" the path
|
12
|
+
# * +package_name+ can be used to make sure that the code is required so that it can be loaded. This is generally used with
|
13
|
+
# builtins, or when the path to be required is not automatically required when bundler requires the gem.
|
14
|
+
# * +exclude+ can be used used to exclude sub-paths. Generally not used with +gem+.
|
15
|
+
# * +labels+ is used to apply labels to matching code. This is really only useful when the package will be applied to
|
16
|
+
# specific functions, via TargetMethods.
|
17
|
+
# * +shallow+ indicates shallow mapping, in which only the entrypoint to a gem is recorded.
|
5
18
|
Package = Struct.new(:path, :gem, :package_name, :exclude, :labels, :shallow) do
|
19
|
+
# This is for internal use only.
|
20
|
+
private_methods :gem
|
21
|
+
|
22
|
+
# Specifies the class that will convert code events into event objects.
|
6
23
|
attr_writer :handler_class
|
7
24
|
|
8
25
|
def handler_class
|
@@ -18,25 +35,36 @@ module AppMap
|
|
18
35
|
end
|
19
36
|
|
20
37
|
class << self
|
38
|
+
# Builds a package for a path, such as `app/models` in a Rails app. Generally corresponds to a `path:` entry
|
39
|
+
# in appmap.yml. Also used for mapping specific methods via TargetMethods.
|
21
40
|
def build_from_path(path, shallow: false, package_name: nil, exclude: [], labels: [])
|
22
41
|
Package.new(path, nil, package_name, exclude, labels, shallow)
|
23
42
|
end
|
24
43
|
|
25
|
-
|
26
|
-
|
44
|
+
# Builds a package for gem. Generally corresponds to a `gem:` entry in appmap.yml. Also used when mapping
|
45
|
+
# a builtin.
|
46
|
+
def build_from_gem(gem, shallow: true, package_name: nil, exclude: [], labels: [], optional: false, force: false)
|
47
|
+
if !force && %w[method_source activesupport].member?(gem)
|
27
48
|
warn "WARNING: #{gem} cannot be AppMapped because it is a dependency of the appmap gem"
|
28
49
|
return
|
29
50
|
end
|
30
|
-
|
51
|
+
path = gem_path(gem, optional)
|
52
|
+
if path
|
53
|
+
Package.new(path, gem, package_name, exclude, labels, shallow)
|
54
|
+
else
|
55
|
+
warn "#{gem} is not available in the bundle" if AppMap::Hook::LOG
|
56
|
+
end
|
31
57
|
end
|
32
58
|
|
33
59
|
private_class_method :new
|
34
60
|
|
35
61
|
protected
|
36
62
|
|
37
|
-
def gem_path(gem)
|
38
|
-
gemspec = Gem.loaded_specs[gem]
|
39
|
-
|
63
|
+
def gem_path(gem, optional)
|
64
|
+
gemspec = Gem.loaded_specs[gem]
|
65
|
+
# This exception will notify a user that their appmap.yml contains non-existent gems.
|
66
|
+
raise "Gem #{gem.inspect} not found" unless gemspec || optional
|
67
|
+
gemspec ? gemspec.gem_dir : nil
|
40
68
|
end
|
41
69
|
end
|
42
70
|
|
@@ -57,7 +85,33 @@ module AppMap
|
|
57
85
|
end
|
58
86
|
end
|
59
87
|
|
60
|
-
|
88
|
+
# Identifies specific methods within a package which should be hooked.
|
89
|
+
class TargetMethods # :nodoc:
|
90
|
+
attr_reader :method_names, :package
|
91
|
+
|
92
|
+
def initialize(method_names, package)
|
93
|
+
@method_names = method_names
|
94
|
+
@package = package
|
95
|
+
end
|
96
|
+
|
97
|
+
def include_method?(method_name)
|
98
|
+
Array(method_names).include?(method_name)
|
99
|
+
end
|
100
|
+
|
101
|
+
def to_h
|
102
|
+
{
|
103
|
+
package: package.name,
|
104
|
+
method_names: method_names
|
105
|
+
}
|
106
|
+
end
|
107
|
+
end
|
108
|
+
private_constant :TargetMethods
|
109
|
+
|
110
|
+
# Function represents a specific function configured for hooking by the +functions+
|
111
|
+
# entry in appmap.yml. When the Config is initialized, each Function is converted into
|
112
|
+
# a Package and TargetMethods. It's called a Function rather than a Method, because Function
|
113
|
+
# is the AppMap terminology.
|
114
|
+
Function = Struct.new(:package, :cls, :labels, :function_names) do # :nodoc:
|
61
115
|
def to_h
|
62
116
|
{
|
63
117
|
package: package,
|
@@ -67,80 +121,127 @@ module AppMap
|
|
67
121
|
}.compact
|
68
122
|
end
|
69
123
|
end
|
124
|
+
private_constant :Function
|
70
125
|
|
71
|
-
|
72
|
-
|
126
|
+
ClassTargetMethods = Struct.new(:cls, :target_methods) # :nodoc:
|
127
|
+
private_constant :ClassTargetMethods
|
73
128
|
|
74
|
-
|
75
|
-
|
76
|
-
|
129
|
+
MethodHook = Struct.new(:cls, :method_names, :labels) # :nodoc:
|
130
|
+
private_constant :MethodHook
|
131
|
+
|
132
|
+
class << self
|
133
|
+
def package_hooks(gem_name, methods, handler_class: nil, package_name: nil)
|
134
|
+
Array(methods).map do |method|
|
135
|
+
package = Package.build_from_gem(gem_name, package_name: package_name, labels: method.labels, shallow: false, optional: true)
|
136
|
+
next unless package
|
137
|
+
|
138
|
+
package.handler_class = handler_class if handler_class
|
139
|
+
ClassTargetMethods.new(method.cls, TargetMethods.new(Array(method.method_names), package))
|
140
|
+
end.compact
|
77
141
|
end
|
78
142
|
|
79
|
-
def
|
80
|
-
|
81
|
-
package: package.name,
|
82
|
-
method_names: method_names
|
83
|
-
}
|
143
|
+
def method_hook(cls, method_names, labels)
|
144
|
+
MethodHook.new(cls, method_names, labels)
|
84
145
|
end
|
85
146
|
end
|
86
147
|
|
87
|
-
|
148
|
+
# Hook well-known functions. When a function configured here is available in the bundle, it will be hooked with the
|
149
|
+
# predefined labels specified here. If any of these hooks are not desired, they can be disabled in the +exclude+ section
|
150
|
+
# of appmap.yml.
|
151
|
+
METHOD_HOOKS = [
|
152
|
+
package_hooks('actionview',
|
153
|
+
[
|
154
|
+
method_hook('ActionView::Renderer', :render, %w[mvc.view]),
|
155
|
+
method_hook('ActionView::TemplateRenderer', :render, %w[mvc.view]),
|
156
|
+
method_hook('ActionView::PartialRenderer', :render, %w[mvc.view])
|
157
|
+
],
|
158
|
+
handler_class: AppMap::Handler::Rails::Template::RenderHandler,
|
159
|
+
package_name: 'action_view'
|
160
|
+
),
|
161
|
+
package_hooks('actionview',
|
162
|
+
[
|
163
|
+
method_hook('ActionView::Resolver', %i[find_all find_all_anywhere], %w[mvc.template.resolver])
|
164
|
+
],
|
165
|
+
handler_class: AppMap::Handler::Rails::Template::ResolverHandler,
|
166
|
+
package_name: 'action_view'
|
167
|
+
),
|
168
|
+
package_hooks('actionpack',
|
169
|
+
[
|
170
|
+
method_hook('ActionDispatch::Request::Session', %i[destroy [] dig values []= clear update delete fetch merge], %w[http.session]),
|
171
|
+
method_hook('ActionDispatch::Cookies::CookieJar', %i[[]= clear update delete recycle], %w[http.session]),
|
172
|
+
method_hook('ActionDispatch::Cookies::EncryptedCookieJar', %i[[]= clear update delete recycle], %w[http.cookie crypto.encrypt])
|
173
|
+
],
|
174
|
+
package_name: 'action_dispatch'
|
175
|
+
),
|
176
|
+
package_hooks('cancancan',
|
177
|
+
[
|
178
|
+
method_hook('CanCan::ControllerAdditions', %i[authorize! can? cannot?], %w[security.authorization]),
|
179
|
+
method_hook('CanCan::Ability', %i[authorize?], %w[security.authorization])
|
180
|
+
]
|
181
|
+
),
|
182
|
+
package_hooks('actionpack',
|
183
|
+
[
|
184
|
+
method_hook('ActionController::Instrumentation', %i[process_action send_file send_data redirect_to], %w[mvc.controller])
|
185
|
+
],
|
186
|
+
package_name: 'action_controller'
|
187
|
+
)
|
188
|
+
].flatten.freeze
|
88
189
|
|
89
|
-
|
90
|
-
# package and labels that should be applied to them.
|
91
|
-
HOOKED_METHODS = {
|
92
|
-
'ActiveSupport::SecurityUtils' => Hook.new(:secure_compare, Package.build_from_path('active_support', labels: %w[crypto.secure_compare])),
|
93
|
-
'ActionView::Renderer' => Hook.new(:render, Package.build_from_path('action_view', labels: %w[mvc.view])),
|
94
|
-
'ActionDispatch::Request::Session' => Hook.new(%i[destroy [] dig values []= clear update delete fetch merge], Package.build_from_path('action_pack', labels: %w[http.session])),
|
95
|
-
'ActionDispatch::Cookies::CookieJar' => Hook.new(%i[[]= clear update delete recycle], Package.build_from_path('action_pack', labels: %w[http.cookie])),
|
96
|
-
'ActionDispatch::Cookies::EncryptedCookieJar' => Hook.new(%i[[]=], Package.build_from_path('action_pack', labels: %w[http.cookie crypto.encrypt])),
|
97
|
-
'CanCan::ControllerAdditions' => Hook.new(%i[authorize! can? cannot?], Package.build_from_path('cancancan', labels: %w[security.authorization])),
|
98
|
-
'CanCan::Ability' => Hook.new(%i[authorize!], Package.build_from_path('cancancan', labels: %w[security.authorization])),
|
99
|
-
'ActionController::Instrumentation' => [
|
100
|
-
Hook.new(%i[process_action send_file send_data redirect_to], Package.build_from_path('action_view', labels: %w[mvc.controller])),
|
101
|
-
Hook.new(%i[render], Package.build_from_path('action_view', labels: %w[mvc.view])),
|
102
|
-
]
|
103
|
-
}.freeze
|
190
|
+
OPENSSL_PACKAGES = ->(labels) { Package.build_from_path('openssl', package_name: 'openssl', labels: labels) }
|
104
191
|
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
'OpenSSL::
|
192
|
+
# Hook functions which are builtin to Ruby. Because they are builtins, they may be loaded before appmap.
|
193
|
+
# Therefore, we can't rely on TracePoint to report the loading of this code.
|
194
|
+
BUILTIN_HOOKS = {
|
195
|
+
'OpenSSL::PKey::PKey' => TargetMethods.new(:sign, OPENSSL_PACKAGES.(%w[crypto.pkey])),
|
196
|
+
'OpenSSL::X509::Request' => TargetMethods.new(%i[sign verify], OPENSSL_PACKAGES.(%w[crypto.x509])),
|
197
|
+
'OpenSSL::PKCS5' => TargetMethods.new(%i[pbkdf2_hmac_sha1 pbkdf2_hmac], OPENSSL_PACKAGES.(%w[crypto.pkcs5])),
|
109
198
|
'OpenSSL::Cipher' => [
|
110
|
-
|
111
|
-
|
199
|
+
TargetMethods.new(%i[encrypt], OPENSSL_PACKAGES.(%w[crypto.encrypt])),
|
200
|
+
TargetMethods.new(%i[decrypt], OPENSSL_PACKAGES.(%w[crypto.decrypt]))
|
112
201
|
],
|
113
202
|
'ActiveSupport::Callbacks::CallbackSequence' => [
|
114
|
-
|
115
|
-
|
203
|
+
TargetMethods.new(:invoke_before, Package.build_from_gem('activesupport', force: true, package_name: 'active_support', labels: %w[mvc.before_action])),
|
204
|
+
TargetMethods.new(:invoke_after, Package.build_from_gem('activesupport', force: true, package_name: 'active_support', labels: %w[mvc.after_action])),
|
116
205
|
],
|
117
|
-
'
|
118
|
-
'
|
206
|
+
'ActiveSupport::SecurityUtils' => TargetMethods.new(:secure_compare, Package.build_from_gem('activesupport', force: true, package_name: 'active_support/security_utils', labels: %w[crypto.secure_compare])),
|
207
|
+
'OpenSSL::X509::Certificate' => TargetMethods.new(:sign, OPENSSL_PACKAGES.(%w[crypto.x509])),
|
208
|
+
'Net::HTTP' => TargetMethods.new(:request, Package.build_from_path('net/http', package_name: 'net/http', labels: %w[protocol.http]).tap do |package|
|
119
209
|
package.handler_class = AppMap::Handler::NetHTTP
|
120
210
|
end),
|
121
|
-
'Net::SMTP' =>
|
122
|
-
'Net::POP3' =>
|
123
|
-
|
124
|
-
'
|
125
|
-
'
|
126
|
-
'
|
127
|
-
'JSON::Ext::
|
211
|
+
'Net::SMTP' => TargetMethods.new(:send, Package.build_from_path('net/smtp', package_name: 'net/smtp', labels: %w[protocol.email.smtp])),
|
212
|
+
'Net::POP3' => TargetMethods.new(:mails, Package.build_from_path('net/pop3', package_name: 'net/pop', labels: %w[protocol.email.pop])),
|
213
|
+
# This is happening: Method send_command not found on Net::IMAP
|
214
|
+
# 'Net::IMAP' => TargetMethods.new(:send_command, Package.build_from_path('net/imap', package_name: 'net/imap', labels: %w[protocol.email.imap])),
|
215
|
+
# 'Marshal' => TargetMethods.new(%i[dump load], Package.build_from_path('marshal', labels: %w[format.marshal])),
|
216
|
+
'Psych' => TargetMethods.new(%i[dump dump_stream load load_stream parse parse_stream], Package.build_from_path('yaml', package_name: 'psych', labels: %w[format.yaml])),
|
217
|
+
'JSON::Ext::Parser' => TargetMethods.new(:parse, Package.build_from_path('json', package_name: 'json', labels: %w[format.json])),
|
218
|
+
'JSON::Ext::Generator::State' => TargetMethods.new(:generate, Package.build_from_path('json', package_name: 'json', labels: %w[format.json])),
|
128
219
|
}.freeze
|
129
220
|
|
130
|
-
attr_reader :name, :packages, :exclude, :
|
221
|
+
attr_reader :name, :packages, :exclude, :hooked_methods, :builtin_hooks
|
131
222
|
|
132
223
|
def initialize(name, packages, exclude: [], functions: [])
|
133
224
|
@name = name
|
134
225
|
@packages = packages
|
226
|
+
@hook_paths = Set.new(packages.map(&:path))
|
135
227
|
@exclude = exclude
|
136
|
-
@
|
228
|
+
@builtin_hooks = BUILTIN_HOOKS
|
137
229
|
@functions = functions
|
138
|
-
|
230
|
+
|
231
|
+
@hooked_methods = METHOD_HOOKS.each_with_object(Hash.new { |h,k| h[k] = [] }) do |cls_target_methods, hooked_methods|
|
232
|
+
hooked_methods[cls_target_methods.cls] << cls_target_methods.target_methods
|
233
|
+
end
|
234
|
+
|
139
235
|
functions.each do |func|
|
140
236
|
package_options = {}
|
141
237
|
package_options[:labels] = func.labels if func.labels
|
142
|
-
@hooked_methods[func.cls]
|
143
|
-
|
238
|
+
@hooked_methods[func.cls] << TargetMethods.new(func.function_names, Package.build_from_path(func.package, package_options))
|
239
|
+
end
|
240
|
+
|
241
|
+
@hooked_methods.each_value do |hooks|
|
242
|
+
Array(hooks).each do |hook|
|
243
|
+
@hook_paths << hook.package.path
|
244
|
+
end
|
144
245
|
end
|
145
246
|
end
|
146
247
|
|
@@ -191,57 +292,54 @@ module AppMap
|
|
191
292
|
}
|
192
293
|
end
|
193
294
|
|
194
|
-
#
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
end
|
199
|
-
|
200
|
-
def package_hooked_by_class(method)
|
201
|
-
defined_class, _, method_name = ::AppMap::Hook.qualify_method_name(method)
|
202
|
-
return find_package(defined_class, method_name)
|
295
|
+
# Determines if methods defined in a file path should possibly be hooked.
|
296
|
+
def path_enabled?(path)
|
297
|
+
path = AppMap::Util.normalize_path(path)
|
298
|
+
@hook_paths.find { |hook_path| path.index(hook_path) == 0 }
|
203
299
|
end
|
204
300
|
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
301
|
+
# Looks up a class and method in the config, to find the matching Package configuration.
|
302
|
+
# This class is only used after +path_enabled?+ has returned `true`.
|
303
|
+
LookupPackage = Struct.new(:config, :cls, :method) do
|
304
|
+
def package
|
305
|
+
# Global "excludes" configuration can be used to ignore any class/method.
|
306
|
+
return if config.never_hook?(cls, method)
|
209
307
|
|
210
|
-
|
211
|
-
packages.select { |pkg| pkg.path }.find do |pkg|
|
212
|
-
(location_file.index(pkg.path) == 0) &&
|
213
|
-
!pkg.exclude.find { |p| location_file.index(p) }
|
308
|
+
package_for_code_object || package_for_location
|
214
309
|
end
|
215
|
-
end
|
216
|
-
|
217
|
-
def never_hook?(method)
|
218
|
-
defined_class, separator, method_name = ::AppMap::Hook.qualify_method_name(method)
|
219
|
-
return true if exclude.member?(defined_class) || exclude.member?([ defined_class, separator, method_name ].join)
|
220
|
-
end
|
221
310
|
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
311
|
+
# Hook a method which is specified by class and method name.
|
312
|
+
def package_for_code_object
|
313
|
+
Array(config.hooked_methods[cls.name])
|
314
|
+
.compact
|
315
|
+
.find { |hook| hook.include_method?(method.name) }
|
316
|
+
&.package
|
317
|
+
end
|
226
318
|
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
319
|
+
# Hook a method which is specified by code location (i.e. path).
|
320
|
+
def package_for_location
|
321
|
+
location = method.source_location
|
322
|
+
location_file, = location
|
323
|
+
return unless location_file
|
324
|
+
|
325
|
+
location_file = AppMap::Util.normalize_path(location_file)
|
326
|
+
config
|
327
|
+
.packages
|
328
|
+
.select { |pkg| pkg.path }
|
329
|
+
.find do |pkg|
|
330
|
+
(location_file.index(pkg.path) == 0) &&
|
331
|
+
!pkg.exclude.find { |p| location_file.index(p) }
|
332
|
+
end
|
333
|
+
end
|
231
334
|
end
|
232
335
|
|
233
|
-
def
|
234
|
-
|
235
|
-
return nil unless hooks
|
236
|
-
|
237
|
-
hook = Array(hooks).find do |hook|
|
238
|
-
Array(hook.method_names).include?(method_name)
|
239
|
-
end
|
240
|
-
hook ? hook.package : nil
|
336
|
+
def lookup_package(cls, method)
|
337
|
+
LookupPackage.new(self, cls, method).package
|
241
338
|
end
|
242
339
|
|
243
|
-
def
|
244
|
-
|
340
|
+
def never_hook?(cls, method)
|
341
|
+
_, separator, = ::AppMap::Hook.qualify_method_name(method)
|
342
|
+
return true if exclude.member?(cls.name) || exclude.member?([ cls.name, separator, method.name ].join)
|
245
343
|
end
|
246
344
|
end
|
247
345
|
end
|