appmap 0.41.2 → 0.45.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.releaserc.yml +11 -0
- data/.travis.yml +23 -2
- data/CHANGELOG.md +40 -0
- data/README.md +36 -6
- data/README_CI.md +29 -0
- data/Rakefile +4 -2
- data/appmap.gemspec +5 -3
- data/lib/appmap.rb +4 -2
- data/lib/appmap/class_map.rb +7 -10
- data/lib/appmap/config.rb +98 -28
- data/lib/appmap/cucumber.rb +1 -1
- data/lib/appmap/event.rb +18 -0
- data/lib/appmap/handler/function.rb +19 -0
- data/lib/appmap/handler/net_http.rb +107 -0
- data/lib/appmap/hook.rb +42 -22
- data/lib/appmap/hook/method.rb +5 -7
- data/lib/appmap/minitest.rb +35 -30
- data/lib/appmap/rails/request_handler.rb +30 -17
- data/lib/appmap/record.rb +1 -1
- data/lib/appmap/rspec.rb +32 -96
- data/lib/appmap/trace.rb +2 -1
- data/lib/appmap/util.rb +39 -2
- data/lib/appmap/version.rb +2 -2
- data/release.sh +17 -0
- data/spec/abstract_controller_base_spec.rb +76 -29
- data/spec/class_map_spec.rb +3 -11
- data/spec/config_spec.rb +33 -1
- data/spec/fixtures/hook/custom_instance_method.rb +11 -0
- data/spec/fixtures/hook/method_named_call.rb +11 -0
- data/spec/fixtures/rails5_users_app/Gemfile +7 -3
- data/spec/fixtures/rails5_users_app/app/controllers/api/users_controller.rb +2 -0
- data/spec/fixtures/rails5_users_app/app/controllers/users_controller.rb +9 -1
- data/spec/fixtures/rails5_users_app/config/application.rb +2 -0
- data/spec/fixtures/rails5_users_app/create_app +8 -2
- data/spec/fixtures/rails5_users_app/spec/controllers/users_controller_api_spec.rb +16 -3
- data/spec/fixtures/rails5_users_app/spec/controllers/users_controller_spec.rb +2 -2
- data/spec/fixtures/rails5_users_app/spec/models/user_spec.rb +2 -12
- data/spec/fixtures/rails5_users_app/spec/rails_helper.rb +3 -9
- data/spec/fixtures/rails6_users_app/Gemfile +5 -4
- data/spec/fixtures/rails6_users_app/app/controllers/api/users_controller.rb +1 -0
- data/spec/fixtures/rails6_users_app/app/controllers/users_controller.rb +9 -1
- data/spec/fixtures/rails6_users_app/config/application.rb +2 -0
- data/spec/fixtures/rails6_users_app/create_app +8 -2
- data/spec/fixtures/rails6_users_app/spec/controllers/users_controller_api_spec.rb +16 -3
- data/spec/fixtures/rails6_users_app/spec/controllers/users_controller_spec.rb +2 -2
- data/spec/fixtures/rails6_users_app/spec/models/user_spec.rb +2 -12
- data/spec/fixtures/rails6_users_app/spec/rails_helper.rb +3 -9
- data/spec/hook_spec.rb +135 -18
- data/spec/record_net_http_spec.rb +160 -0
- data/spec/record_sql_rails_pg_spec.rb +1 -1
- data/spec/spec_helper.rb +16 -0
- data/test/expectations/openssl_test_key_sign1.json +2 -4
- data/test/fixtures/rspec_recorder/spec/decorated_hello_spec.rb +3 -3
- data/test/fixtures/rspec_recorder/spec/plain_hello_spec.rb +1 -1
- data/test/gem_test.rb +1 -1
- data/test/minitest_test.rb +1 -2
- data/test/rspec_test.rb +1 -20
- metadata +17 -13
- data/exe/appmap +0 -154
- data/spec/rspec_feature_metadata_spec.rb +0 -31
- data/test/cli_test.rb +0 -116
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0db499c7da4baea3f29fb56214a95e18047a07fc0c132ba559f7f6eb7ef9033a
|
4
|
+
data.tar.gz: 3aabf3bf1a31c39d6844ccf17807a4b7e206783bdf8e373008d3db0d04a854d7
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a38e51f5c932d9a04b566c2664104e08a56af80fd3277c039603eb719336599bc9764781e1d0835c6c7b702e4e0465587fd4537e292df325c0d9efe4a90c1493
|
7
|
+
data.tar.gz: 168e2b8c1605cf7b4f8c4d6a5f373b5add4ce1804c4c494f3cc4e3b29da5a08f4e121889bca36e06e1e29aa4c9943c16b76f7868e9331c3a7e5b33af0817f1a0
|
data/.releaserc.yml
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
plugins:
|
2
|
+
- '@semantic-release/commit-analyzer'
|
3
|
+
- '@semantic-release/release-notes-generator'
|
4
|
+
- '@semantic-release/changelog'
|
5
|
+
- 'semantic-release-rubygem'
|
6
|
+
- - '@semantic-release/git'
|
7
|
+
- assets:
|
8
|
+
- CHANGELOG.md
|
9
|
+
- appmap.gemspec
|
10
|
+
- lib/appmap/version.rb
|
11
|
+
- '@semantic-release/github'
|
data/.travis.yml
CHANGED
@@ -13,11 +13,32 @@ services:
|
|
13
13
|
# necessary.
|
14
14
|
before_script:
|
15
15
|
- unset RAILS_ENV
|
16
|
-
|
16
|
+
|
17
|
+
cache:
|
18
|
+
bundler: true
|
19
|
+
|
20
|
+
|
21
|
+
# GEM_ALTERNATIVE_NAME only needed for deployment
|
17
22
|
jobs:
|
18
23
|
include:
|
19
24
|
- stage: test
|
20
25
|
script:
|
21
26
|
- mkdir tmp
|
22
|
-
- bundle exec rake test
|
27
|
+
- GEM_ALTERNATIVE_NAME='' bundle exec rake test
|
28
|
+
|
23
29
|
|
30
|
+
before_deploy:
|
31
|
+
- |
|
32
|
+
nvm install --lts \
|
33
|
+
&& nvm use --lts \
|
34
|
+
&& npm i -g \
|
35
|
+
semantic-release \
|
36
|
+
@semantic-release/git \
|
37
|
+
@semantic-release/changelog \
|
38
|
+
semantic-release-rubygem
|
39
|
+
|
40
|
+
deploy:
|
41
|
+
- provider: script
|
42
|
+
script: ./release.sh
|
43
|
+
on:
|
44
|
+
branch: master
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,43 @@
|
|
1
|
+
# [0.45.0](https://github.com/applandinc/appmap-ruby/compare/v0.44.0...v0.45.0) (2021-05-03)
|
2
|
+
|
3
|
+
|
4
|
+
### Bug Fixes
|
5
|
+
|
6
|
+
* Properly name status_code in HTTP server response ([556e87c](https://github.com/applandinc/appmap-ruby/commit/556e87c9a7bf214f6b8714add4f77448fd223d33))
|
7
|
+
|
8
|
+
|
9
|
+
### Features
|
10
|
+
|
11
|
+
* Record http_client_request and http_client_response ([1db32ae](https://github.com/applandinc/appmap-ruby/commit/1db32ae0d26a7f1400b6b814d25b13368f06c158))
|
12
|
+
* Update AppMap format version to 1.5.0 ([061705e](https://github.com/applandinc/appmap-ruby/commit/061705e4619cb881e8edd022ef835183e399e127))
|
13
|
+
* **build:** add deployment via `semantic-release` with automatic publication to rubygems ([9f183de](https://github.com/applandinc/appmap-ruby/commit/9f183de13f405900000c3da979c3a8a5b6e34a24))
|
14
|
+
|
15
|
+
# v0.44.0
|
16
|
+
|
17
|
+
* Support recording and labeling of indivudal functions via `functions:` section in *appmap.yml*.
|
18
|
+
* Remove deprecated `exe/appmap`.
|
19
|
+
* Add `test_status` and `exception` fields to AppMap metadata.
|
20
|
+
* Write AppMap file atomically, by writing to a temp file first and then moving it into place.
|
21
|
+
* Remove printing of `Inventory.json` file.
|
22
|
+
* Remove source code from `classMap`.
|
23
|
+
|
24
|
+
# v0.43.0
|
25
|
+
|
26
|
+
* Record `name` and `class` of each entry in Hash-like parameters, messages, and return values.
|
27
|
+
* Record client-sent headers in HTTP server request and response.
|
28
|
+
* Record HTTP server request `mime_type`.
|
29
|
+
* Record HTTP server request `authorization`.
|
30
|
+
|
31
|
+
# v0.42.1
|
32
|
+
|
33
|
+
* Add missing require `set`.
|
34
|
+
* Check `cls.respond_to?(:singleton_class)`, since it oddly, may not.
|
35
|
+
|
36
|
+
# v0.42.0
|
37
|
+
|
38
|
+
* Remove `feature_group` and `feature` metadata from minitest and RSpec AppMaps.
|
39
|
+
* Add `metadata.source_location`.
|
40
|
+
|
1
41
|
# v0.41.2
|
2
42
|
|
3
43
|
* Don't rely on `gemspec.source_paths` to list all the source locations in a gem. Hook any code that's loaded
|
data/README.md
CHANGED
@@ -11,6 +11,7 @@
|
|
11
11
|
- [Remote recording](#remote-recording)
|
12
12
|
- [Server process recording](#server-process-recording)
|
13
13
|
- [AppMap for VSCode](#appmap-for-vscode)
|
14
|
+
- [AppMap Swagger](#appmap-swagger)
|
14
15
|
- [Uploading AppMaps](#uploading-appmaps)
|
15
16
|
- [Development](#development)
|
16
17
|
- [Running tests](#running-tests)
|
@@ -109,6 +110,9 @@ name: my_project
|
|
109
110
|
packages:
|
110
111
|
- path: app/controllers
|
111
112
|
- path: app/models
|
113
|
+
# Exclude sub-paths within the package path
|
114
|
+
exclude:
|
115
|
+
- concerns/accessor
|
112
116
|
- path: app/jobs
|
113
117
|
- path: app/helpers
|
114
118
|
# Include the gems that you want to see in the dependency maps.
|
@@ -117,15 +121,22 @@ packages:
|
|
117
121
|
- gem: devise
|
118
122
|
- gem: aws-sdk
|
119
123
|
- gem: will_paginate
|
124
|
+
# Global exclusion of a class name
|
120
125
|
exclude:
|
121
126
|
- MyClass
|
122
127
|
- MyClass#my_instance_method
|
123
128
|
- MyClass.my_class_method
|
129
|
+
functions:
|
130
|
+
- packages: myapp
|
131
|
+
class: ControllerHelper
|
132
|
+
function: logged_in_user
|
133
|
+
labels: [ authentication ]
|
124
134
|
```
|
125
135
|
|
126
136
|
* **name** Provides the project name (required)
|
127
137
|
* **packages** A list of source code directories which should be recorded.
|
128
138
|
* **exclude** A list of classes and/or methods to definitively exclude from recording.
|
139
|
+
* **functions** A list of specific functions, scoped by package and class, to record.
|
129
140
|
|
130
141
|
**packages**
|
131
142
|
|
@@ -144,6 +155,11 @@ Each entry in the `packages` list is a YAML object which has the following keys:
|
|
144
155
|
|
145
156
|
Optional list of fully qualified class and method names. Separate class and method names with period (`.`) for class methods and hash (`#`) for instance methods.
|
146
157
|
|
158
|
+
**functions**
|
159
|
+
|
160
|
+
Optional list of `class, function` pairs. The `package` name is used to place the function within the class map, and does not have to match
|
161
|
+
the folder or gem name. The primary use of `functions` is to apply specific labels to functions whose source code is not accessible (e.g., it's in a Gem).
|
162
|
+
For functions which are part of the application code, use `@label` or `@labels` in code comments to apply labels.
|
147
163
|
|
148
164
|
# Labels
|
149
165
|
|
@@ -329,9 +345,20 @@ Run your Rails server with `APPMAP_RECORD=true`. When the server exits, an *appm
|
|
329
345
|
|
330
346
|
Be sure and set `WEB_CONCURRENCY=1`, if you are using a webserver that can run multiple processes. You only want there to be one process while you are recording, otherwise they will both try and write *appmap.json* and one of them will clobber the other.
|
331
347
|
|
348
|
+
|
332
349
|
# AppMap for VSCode
|
333
350
|
|
334
|
-
The [AppMap extension for VSCode](https://marketplace.visualstudio.com/items?itemName=appland.appmap)
|
351
|
+
The [AppMap extension for VSCode](https://marketplace.visualstudio.com/items?itemName=appland.appmap) helps you navigate your code more efficiently with interactive, accurate software architecture diagrams right in your IDE. In less than two minutes you can go from installing the AppMap extension to exploring maps of your code's architecture. AppMap helps you:
|
352
|
+
|
353
|
+
* Onboard to code architecture, with no extra work for the team
|
354
|
+
* Conduct code and design reviews using live and accurate data
|
355
|
+
* Troubleshoot hard-to-understand bugs using a "top-down" approach.
|
356
|
+
|
357
|
+
Each interactive diagram links directly to the source code, and the information is easy to share.
|
358
|
+
|
359
|
+
# AppMap Swagger
|
360
|
+
|
361
|
+
[appmap_swagger](https://github.com/applandinc/appmap_swagger-ruby) is a tool to generate Swagger files from AppMap data. With `appmap_swagger`, you can add Swagger to your Ruby or Ruby on Rails project, with no need to write or modify code. Use the Swagger UI to interact with your web services API as you build it, and use diffs of Swagger to perform code review of web service changes.
|
335
362
|
|
336
363
|
# Uploading AppMaps
|
337
364
|
|
@@ -369,7 +396,7 @@ The fixture apps in `test/fixtures` are plain Ruby projects that exercise the ba
|
|
369
396
|
|
370
397
|
### `spec/fixtures`
|
371
398
|
|
372
|
-
The fixture apps in `spec/fixtures` are simple Rack,
|
399
|
+
The fixture apps in `spec/fixtures` are simple Rack, Rails5, and Rails6 apps.
|
373
400
|
You can use them to interactively develop and test the recording features of the `appmap` gem.
|
374
401
|
These fixture apps are more sophisticated than `test/fixtures`, because they include additional
|
375
402
|
resources such as a PostgreSQL database.
|
@@ -395,11 +422,15 @@ $ docker-compose run --rm app ./create_app
|
|
395
422
|
Now you can start a development container.
|
396
423
|
|
397
424
|
```sh-session
|
398
|
-
$ docker-compose run --rm -v $PWD/../../..:/src/appmap-ruby app bash
|
425
|
+
$ docker-compose run --rm -v $PWD:/app -v $PWD/../../..:/src/appmap-ruby app bash
|
399
426
|
Starting rails_users_app_pg_1 ... done
|
400
|
-
root@6fab5f89125f:/app# cd /src/
|
427
|
+
root@6fab5f89125f:/app# cd /src/appmap-ruby
|
428
|
+
root@6fab5f89125f:/src/appmap-ruby# rm ext/appmap/*.so ext/appmap/*.o
|
429
|
+
root@6fab5f89125f:/src/appmap-ruby# bundle
|
430
|
+
root@6fab5f89125f:/src/appmap-ruby# bundle exec rake compile
|
431
|
+
root@6fab5f89125f:/src/appmap-ruby# cd /src/app
|
401
432
|
root@6fab5f89125f:/src/app# bundle config local.appmap /src/appmap-ruby
|
402
|
-
root@6fab5f89125f:/src/app# bundle
|
433
|
+
root@6fab5f89125f:/src/app# bundle
|
403
434
|
```
|
404
435
|
|
405
436
|
At this point, the bundle is built with the `appmap` gem located in `/src/appmap`, which is volume-mounted from the host.
|
@@ -414,4 +445,3 @@ Configuring AppMap from path appmap.yml
|
|
414
445
|
Finished in 0.07357 seconds (files took 2.1 seconds to load)
|
415
446
|
4 examples, 0 failures
|
416
447
|
```
|
417
|
-
|
data/README_CI.md
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
# Configuration variables:
|
2
|
+
|
3
|
+
* `GH_TOKEN`: used by `semantic-release` to push changes to Github and manage releases
|
4
|
+
* `GEM_HOST_API_KEY`: rubygems API key
|
5
|
+
* `GEM_ALTERNATIVE_NAME` (optional): used for testing of CI flows,
|
6
|
+
to avoid publication of test releases under official package name
|
7
|
+
* `DOCKERHUB\_USERNAME`, `DOCKERHUB_PASSWORD`: optional dockerhub credentials,
|
8
|
+
to avoid throttling of dockerhub anonymous pulls
|
9
|
+
|
10
|
+
Note: for security reasons, it's better to use dedicated (not personal)
|
11
|
+
Dockerhub account,
|
12
|
+
and also use [access tokens](https://docs.docker.com/docker-hub/access-tokens/)
|
13
|
+
instead of primary password
|
14
|
+
|
15
|
+
# Release command
|
16
|
+
|
17
|
+
`./release.sh`
|
18
|
+
|
19
|
+
Bash wrapper script is used merely as a launcher of `semantic-release`
|
20
|
+
with extra logic to explicitly determine git url from `TRAVIS_REPO_SLUG` \
|
21
|
+
variable if its defined (otherwise git url is taken from `package.json`,
|
22
|
+
which breaks CI on forked repos).
|
23
|
+
|
24
|
+
# CI flow
|
25
|
+
|
26
|
+
1. Test happens using current version number specified in `lib/appmap/version.rb`, then `release.sh` launches `semantic-release` to do the rest
|
27
|
+
2. The version number is increased (including modicication of `version.rb`)
|
28
|
+
3. Gem is published under new version number
|
29
|
+
4. Github release is created with the new version number
|
data/Rakefile
CHANGED
@@ -37,7 +37,8 @@ end
|
|
37
37
|
|
38
38
|
def build_base_image(ruby_version)
|
39
39
|
run_cmd "docker build" \
|
40
|
-
" --build-arg RUBY_VERSION=#{ruby_version}
|
40
|
+
" --build-arg RUBY_VERSION=#{ruby_version}" \
|
41
|
+
" --build-arg GEM_VERSION=#{GEM_VERSION}" \
|
41
42
|
" -t appmap:#{GEM_VERSION} -f Dockerfile.appmap ."
|
42
43
|
end
|
43
44
|
|
@@ -46,7 +47,7 @@ def build_app_image(app, ruby_version)
|
|
46
47
|
run_cmd( {"RUBY_VERSION" => ruby_version, "GEM_VERSION" => GEM_VERSION},
|
47
48
|
" docker-compose build" \
|
48
49
|
" --build-arg RUBY_VERSION=#{ruby_version}" \
|
49
|
-
" --build-arg GEM_VERSION=#{GEM_VERSION}")
|
50
|
+
" --build-arg GEM_VERSION=#{GEM_VERSION}" )
|
50
51
|
end
|
51
52
|
end
|
52
53
|
|
@@ -138,3 +139,4 @@ task spec: %i[spec:all]
|
|
138
139
|
task test: %i[spec:all minitest]
|
139
140
|
|
140
141
|
task default: :test
|
142
|
+
|
data/appmap.gemspec
CHANGED
@@ -4,8 +4,12 @@ lib = File.expand_path('lib', __dir__)
|
|
4
4
|
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
5
5
|
require 'appmap/version'
|
6
6
|
|
7
|
+
|
8
|
+
|
7
9
|
Gem::Specification.new do |spec|
|
8
|
-
|
10
|
+
# ability to parameterize gem name is added intentionally,
|
11
|
+
# to support the possibility of unofficial releases, e.g. during CI tests
|
12
|
+
spec.name = (ENV['GEM_ALTERNATIVE_NAME'].to_s.empty? ? 'appmap' : ENV["GEM_ALTERNATIVE_NAME"] )
|
9
13
|
spec.version = AppMap::VERSION
|
10
14
|
spec.authors = ['Kevin Gilpin']
|
11
15
|
spec.email = ['kgilpin@gmail.com']
|
@@ -20,8 +24,6 @@ Gem::Specification.new do |spec|
|
|
20
24
|
")
|
21
25
|
spec.extensions << "ext/appmap/extconf.rb"
|
22
26
|
|
23
|
-
spec.bindir = 'exe'
|
24
|
-
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
25
27
|
spec.require_paths = ['lib']
|
26
28
|
|
27
29
|
spec.add_dependency 'activesupport'
|
data/lib/appmap.rb
CHANGED
@@ -9,6 +9,7 @@ end
|
|
9
9
|
|
10
10
|
require 'appmap/version'
|
11
11
|
require 'appmap/hook'
|
12
|
+
require 'appmap/handler/net_http'
|
12
13
|
require 'appmap/config'
|
13
14
|
require 'appmap/trace'
|
14
15
|
require 'appmap/class_map'
|
@@ -43,6 +44,7 @@ module AppMap
|
|
43
44
|
# Call this function before the program code is loaded by the Ruby VM, otherwise
|
44
45
|
# the load events won't be seen and the hooks won't activate.
|
45
46
|
def initialize(config_file_path = 'appmap.yml')
|
47
|
+
raise "AppMap configuration file #{config_file_path} does not exist" unless ::File.exists?(config_file_path)
|
46
48
|
warn "Configuring AppMap from path #{config_file_path}"
|
47
49
|
Config.load_from_file(config_file_path).tap do |configuration|
|
48
50
|
self.configuration = configuration
|
@@ -83,8 +85,8 @@ module AppMap
|
|
83
85
|
end
|
84
86
|
|
85
87
|
# Builds a class map from a config and a list of Ruby methods.
|
86
|
-
def class_map(methods
|
87
|
-
ClassMap.build_from_methods(methods
|
88
|
+
def class_map(methods)
|
89
|
+
ClassMap.build_from_methods(methods)
|
88
90
|
end
|
89
91
|
|
90
92
|
# Returns default metadata detected from the Ruby system and from the
|
data/lib/appmap/class_map.rb
CHANGED
@@ -71,17 +71,17 @@ module AppMap
|
|
71
71
|
end
|
72
72
|
|
73
73
|
class << self
|
74
|
-
def build_from_methods(methods
|
74
|
+
def build_from_methods(methods)
|
75
75
|
root = Types::Root.new
|
76
76
|
methods.each do |method|
|
77
|
-
add_function root, method
|
77
|
+
add_function root, method
|
78
78
|
end
|
79
79
|
root.children.map(&:to_h)
|
80
80
|
end
|
81
81
|
|
82
82
|
protected
|
83
83
|
|
84
|
-
def add_function(root, method
|
84
|
+
def add_function(root, method)
|
85
85
|
package = method.package
|
86
86
|
static = method.static
|
87
87
|
|
@@ -113,16 +113,13 @@ module AppMap
|
|
113
113
|
[ method.defined_class, static ? '.' : '#', method.name ].join
|
114
114
|
end
|
115
115
|
|
116
|
-
|
117
|
-
|
116
|
+
comment = begin
|
117
|
+
method.comment
|
118
118
|
rescue MethodSource::SourceNotFoundError
|
119
|
-
|
119
|
+
nil
|
120
120
|
end
|
121
121
|
|
122
|
-
|
123
|
-
function_info[:source] = source unless source.blank?
|
124
|
-
function_info[:comment] = comment unless comment.blank?
|
125
|
-
end
|
122
|
+
function_info[:comment] = comment unless comment.blank?
|
126
123
|
|
127
124
|
function_info[:labels] = parse_labels(comment) + (package.labels || [])
|
128
125
|
object_infos << function_info
|
data/lib/appmap/config.rb
CHANGED
@@ -3,6 +3,13 @@
|
|
3
3
|
module AppMap
|
4
4
|
class Config
|
5
5
|
Package = Struct.new(:path, :gem, :package_name, :exclude, :labels, :shallow) do
|
6
|
+
attr_writer :handler_class
|
7
|
+
|
8
|
+
def handler_class
|
9
|
+
require 'appmap/handler/function'
|
10
|
+
@handler_class || AppMap::Handler::Function
|
11
|
+
end
|
12
|
+
|
6
13
|
# Indicates that only the entry points to a package will be recorded.
|
7
14
|
# Once the code has entered a package, subsequent calls within the package will not be
|
8
15
|
# recorded unless the code leaves the package and re-enters it.
|
@@ -42,6 +49,7 @@ module AppMap
|
|
42
49
|
path: path,
|
43
50
|
package_name: package_name,
|
44
51
|
gem: gem,
|
52
|
+
handler_class: handler_class.name,
|
45
53
|
exclude: exclude.blank? ? nil : exclude,
|
46
54
|
labels: labels.blank? ? nil : labels,
|
47
55
|
shallow: shallow
|
@@ -49,44 +57,91 @@ module AppMap
|
|
49
57
|
end
|
50
58
|
end
|
51
59
|
|
52
|
-
|
60
|
+
Function = Struct.new(:package, :cls, :labels, :function_names) do
|
61
|
+
def to_h
|
62
|
+
{
|
63
|
+
package: package,
|
64
|
+
class: cls,
|
65
|
+
labels: labels,
|
66
|
+
functions: function_names.map(&:to_sym)
|
67
|
+
}.compact
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
class Hook
|
72
|
+
attr_reader :method_names, :package
|
73
|
+
|
74
|
+
def initialize(method_names, package)
|
75
|
+
@method_names = method_names
|
76
|
+
@package = package
|
77
|
+
end
|
78
|
+
|
79
|
+
def to_h
|
80
|
+
{
|
81
|
+
package: package.name,
|
82
|
+
method_names: method_names
|
83
|
+
}
|
84
|
+
end
|
53
85
|
end
|
54
86
|
|
55
|
-
OPENSSL_PACKAGES = Package.build_from_path('openssl', package_name: 'openssl', labels:
|
87
|
+
OPENSSL_PACKAGES = ->(labels) { Package.build_from_path('openssl', package_name: 'openssl', labels: labels) }
|
56
88
|
|
57
89
|
# Methods that should always be hooked, with their containing
|
58
90
|
# package and labels that should be applied to them.
|
59
91
|
HOOKED_METHODS = {
|
60
|
-
'ActiveSupport::SecurityUtils' => Hook.new(:secure_compare, Package.build_from_path('active_support', labels: %w[
|
92
|
+
'ActiveSupport::SecurityUtils' => Hook.new(:secure_compare, Package.build_from_path('active_support', labels: %w[crypto.secure_compare])),
|
61
93
|
'ActionView::Renderer' => Hook.new(:render, Package.build_from_path('action_view', labels: %w[mvc.view])),
|
62
|
-
'ActionDispatch::
|
63
|
-
'ActionDispatch::Cookies::
|
64
|
-
'
|
65
|
-
'CanCan::
|
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
|
+
]
|
66
103
|
}.freeze
|
67
104
|
|
68
105
|
BUILTIN_METHODS = {
|
69
|
-
'OpenSSL::PKey::PKey' => Hook.new(:sign, OPENSSL_PACKAGES),
|
70
|
-
'OpenSSL::X509::Request' => Hook.new(%i[sign verify], OPENSSL_PACKAGES),
|
71
|
-
'OpenSSL::PKCS5' => Hook.new(%i[pbkdf2_hmac_sha1 pbkdf2_hmac], OPENSSL_PACKAGES),
|
72
|
-
'OpenSSL::Cipher' =>
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
'
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
'
|
81
|
-
'
|
106
|
+
'OpenSSL::PKey::PKey' => Hook.new(:sign, OPENSSL_PACKAGES.(%w[crypto.pkey])),
|
107
|
+
'OpenSSL::X509::Request' => Hook.new(%i[sign verify], OPENSSL_PACKAGES.(%w[crypto.x509])),
|
108
|
+
'OpenSSL::PKCS5' => Hook.new(%i[pbkdf2_hmac_sha1 pbkdf2_hmac], OPENSSL_PACKAGES.(%w[crypto.pkcs5])),
|
109
|
+
'OpenSSL::Cipher' => [
|
110
|
+
Hook.new(%i[encrypt], OPENSSL_PACKAGES.(%w[crypto.encrypt])),
|
111
|
+
Hook.new(%i[decrypt], OPENSSL_PACKAGES.(%w[crypto.decrypt]))
|
112
|
+
],
|
113
|
+
'ActiveSupport::Callbacks::CallbackSequence' => [
|
114
|
+
Hook.new(:invoke_before, Package.build_from_path('active_support', package_name: 'active_support', labels: %w[mvc.before_action])),
|
115
|
+
Hook.new(:invoke_after, Package.build_from_path('active_support', package_name: 'active_support', labels: %w[mvc.after_action])),
|
116
|
+
],
|
117
|
+
'OpenSSL::X509::Certificate' => Hook.new(:sign, OPENSSL_PACKAGES.(%w[crypto.x509])),
|
118
|
+
'Net::HTTP' => Hook.new(:request, Package.build_from_path('net/http', package_name: 'net/http', labels: %w[protocol.http]).tap do |package|
|
119
|
+
package.handler_class = AppMap::Handler::NetHTTP
|
120
|
+
end),
|
121
|
+
'Net::SMTP' => Hook.new(:send, Package.build_from_path('net/smtp', package_name: 'net/smtp', labels: %w[protocol.email.smtp])),
|
122
|
+
'Net::POP3' => Hook.new(:mails, Package.build_from_path('net/pop3', package_name: 'net/pop', labels: %w[protocol.email.pop])),
|
123
|
+
'Net::IMAP' => Hook.new(:send_command, Package.build_from_path('net/imap', package_name: 'net/imap', labels: %w[protocol.email.imap])),
|
124
|
+
'Marshal' => Hook.new(%i[dump load], Package.build_from_path('marshal', labels: %w[format.marshal])),
|
125
|
+
'Psych' => Hook.new(%i[dump dump_stream load load_stream parse parse_stream], Package.build_from_path('yaml', package_name: 'psych', labels: %w[format.yaml])),
|
126
|
+
'JSON::Ext::Parser' => Hook.new(:parse, Package.build_from_path('json', package_name: 'json', labels: %w[format.json])),
|
127
|
+
'JSON::Ext::Generator::State' => Hook.new(:generate, Package.build_from_path('json', package_name: 'json', labels: %w[format.json])),
|
82
128
|
}.freeze
|
83
129
|
|
84
|
-
attr_reader :name, :packages, :exclude
|
130
|
+
attr_reader :name, :packages, :exclude, :builtin_methods
|
85
131
|
|
86
|
-
def initialize(name, packages
|
132
|
+
def initialize(name, packages, exclude: [], functions: [])
|
87
133
|
@name = name
|
88
134
|
@packages = packages
|
89
135
|
@exclude = exclude
|
136
|
+
@builtin_methods = BUILTIN_METHODS
|
137
|
+
@functions = functions
|
138
|
+
@hooked_methods = HOOKED_METHODS.dup
|
139
|
+
functions.each do |func|
|
140
|
+
package_options = {}
|
141
|
+
package_options[:labels] = func.labels if func.labels
|
142
|
+
@hooked_methods[func.cls] ||= []
|
143
|
+
@hooked_methods[func.cls] << Hook.new(func.function_names, Package.build_from_path(func.package, package_options))
|
144
|
+
end
|
90
145
|
end
|
91
146
|
|
92
147
|
class << self
|
@@ -98,6 +153,16 @@ module AppMap
|
|
98
153
|
|
99
154
|
# Loads configuration from a Hash.
|
100
155
|
def load(config_data)
|
156
|
+
functions = (config_data['functions'] || []).map do |function_data|
|
157
|
+
package = function_data['package']
|
158
|
+
cls = function_data['class']
|
159
|
+
functions = function_data['function'] || function_data['functions']
|
160
|
+
raise 'AppMap class configuration should specify package, class and function(s)' unless package && cls && functions
|
161
|
+
functions = Array(functions).map(&:to_sym)
|
162
|
+
labels = function_data['label'] || function_data['labels']
|
163
|
+
labels = Array(labels).map(&:to_s) if labels
|
164
|
+
Function.new(package, cls, labels, functions)
|
165
|
+
end
|
101
166
|
packages = (config_data['packages'] || []).map do |package|
|
102
167
|
gem = package['gem']
|
103
168
|
path = package['path']
|
@@ -112,7 +177,8 @@ module AppMap
|
|
112
177
|
Package.build_from_path(path, exclude: package['exclude'] || [], shallow: package['shallow'])
|
113
178
|
end
|
114
179
|
end.compact
|
115
|
-
|
180
|
+
exclude = config_data['exclude'] || []
|
181
|
+
Config.new config_data['name'], packages, exclude: exclude, functions: functions
|
116
182
|
end
|
117
183
|
end
|
118
184
|
|
@@ -120,6 +186,7 @@ module AppMap
|
|
120
186
|
{
|
121
187
|
name: name,
|
122
188
|
packages: packages.map(&:to_h),
|
189
|
+
functions: @functions.map(&:to_h),
|
123
190
|
exclude: exclude
|
124
191
|
}
|
125
192
|
end
|
@@ -164,14 +231,17 @@ module AppMap
|
|
164
231
|
end
|
165
232
|
|
166
233
|
def find_package(defined_class, method_name)
|
167
|
-
|
168
|
-
return nil unless
|
234
|
+
hooks = find_hooks(defined_class)
|
235
|
+
return nil unless hooks
|
169
236
|
|
170
|
-
Array(
|
237
|
+
hook = Array(hooks).find do |hook|
|
238
|
+
Array(hook.method_names).include?(method_name)
|
239
|
+
end
|
240
|
+
hook ? hook.package : nil
|
171
241
|
end
|
172
242
|
|
173
|
-
def
|
174
|
-
|
243
|
+
def find_hooks(defined_class)
|
244
|
+
Array(@hooked_methods[defined_class] || @builtin_methods[defined_class])
|
175
245
|
end
|
176
246
|
end
|
177
247
|
end
|