appmap 0.41.2 → 0.45.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (62) hide show
  1. checksums.yaml +4 -4
  2. data/.releaserc.yml +11 -0
  3. data/.travis.yml +23 -2
  4. data/CHANGELOG.md +40 -0
  5. data/README.md +36 -6
  6. data/README_CI.md +29 -0
  7. data/Rakefile +4 -2
  8. data/appmap.gemspec +5 -3
  9. data/lib/appmap.rb +4 -2
  10. data/lib/appmap/class_map.rb +7 -10
  11. data/lib/appmap/config.rb +98 -28
  12. data/lib/appmap/cucumber.rb +1 -1
  13. data/lib/appmap/event.rb +18 -0
  14. data/lib/appmap/handler/function.rb +19 -0
  15. data/lib/appmap/handler/net_http.rb +107 -0
  16. data/lib/appmap/hook.rb +42 -22
  17. data/lib/appmap/hook/method.rb +5 -7
  18. data/lib/appmap/minitest.rb +35 -30
  19. data/lib/appmap/rails/request_handler.rb +30 -17
  20. data/lib/appmap/record.rb +1 -1
  21. data/lib/appmap/rspec.rb +32 -96
  22. data/lib/appmap/trace.rb +2 -1
  23. data/lib/appmap/util.rb +39 -2
  24. data/lib/appmap/version.rb +2 -2
  25. data/release.sh +17 -0
  26. data/spec/abstract_controller_base_spec.rb +76 -29
  27. data/spec/class_map_spec.rb +3 -11
  28. data/spec/config_spec.rb +33 -1
  29. data/spec/fixtures/hook/custom_instance_method.rb +11 -0
  30. data/spec/fixtures/hook/method_named_call.rb +11 -0
  31. data/spec/fixtures/rails5_users_app/Gemfile +7 -3
  32. data/spec/fixtures/rails5_users_app/app/controllers/api/users_controller.rb +2 -0
  33. data/spec/fixtures/rails5_users_app/app/controllers/users_controller.rb +9 -1
  34. data/spec/fixtures/rails5_users_app/config/application.rb +2 -0
  35. data/spec/fixtures/rails5_users_app/create_app +8 -2
  36. data/spec/fixtures/rails5_users_app/spec/controllers/users_controller_api_spec.rb +16 -3
  37. data/spec/fixtures/rails5_users_app/spec/controllers/users_controller_spec.rb +2 -2
  38. data/spec/fixtures/rails5_users_app/spec/models/user_spec.rb +2 -12
  39. data/spec/fixtures/rails5_users_app/spec/rails_helper.rb +3 -9
  40. data/spec/fixtures/rails6_users_app/Gemfile +5 -4
  41. data/spec/fixtures/rails6_users_app/app/controllers/api/users_controller.rb +1 -0
  42. data/spec/fixtures/rails6_users_app/app/controllers/users_controller.rb +9 -1
  43. data/spec/fixtures/rails6_users_app/config/application.rb +2 -0
  44. data/spec/fixtures/rails6_users_app/create_app +8 -2
  45. data/spec/fixtures/rails6_users_app/spec/controllers/users_controller_api_spec.rb +16 -3
  46. data/spec/fixtures/rails6_users_app/spec/controllers/users_controller_spec.rb +2 -2
  47. data/spec/fixtures/rails6_users_app/spec/models/user_spec.rb +2 -12
  48. data/spec/fixtures/rails6_users_app/spec/rails_helper.rb +3 -9
  49. data/spec/hook_spec.rb +135 -18
  50. data/spec/record_net_http_spec.rb +160 -0
  51. data/spec/record_sql_rails_pg_spec.rb +1 -1
  52. data/spec/spec_helper.rb +16 -0
  53. data/test/expectations/openssl_test_key_sign1.json +2 -4
  54. data/test/fixtures/rspec_recorder/spec/decorated_hello_spec.rb +3 -3
  55. data/test/fixtures/rspec_recorder/spec/plain_hello_spec.rb +1 -1
  56. data/test/gem_test.rb +1 -1
  57. data/test/minitest_test.rb +1 -2
  58. data/test/rspec_test.rb +1 -20
  59. metadata +17 -13
  60. data/exe/appmap +0 -154
  61. data/spec/rspec_feature_metadata_spec.rb +0 -31
  62. data/test/cli_test.rb +0 -116
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: adc97c5504b0171bfe253ae399f75ceabce09325656572f921520263023f3f8c
4
- data.tar.gz: 35e0ca65e34c843ace959017ee2830e74d2f49878b8b0538f42ebde37855f6bb
3
+ metadata.gz: 0db499c7da4baea3f29fb56214a95e18047a07fc0c132ba559f7f6eb7ef9033a
4
+ data.tar.gz: 3aabf3bf1a31c39d6844ccf17807a4b7e206783bdf8e373008d3db0d04a854d7
5
5
  SHA512:
6
- metadata.gz: 7ee982724e27a8252a7506dda4ea9bf0817bd39f258d9d1ea01bd9b393aa3e523043ff2120e191806a6d41df096bfde110abf4956e4cd56ee1064aa12f4def9f
7
- data.tar.gz: 715e4b8a140e8be61be375500a701ede6c01f0a709aee0cde0b65bd809a04b4c50543900ca9ac7bcbedd30f4252ef66ae9f18246eaa5591d3a9eeb7d5ba28be4
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) is a great way to onboard developers to new code, and troubleshoot hard-to-understand bugs with visuals.
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, Rails4, and Rails5 apps.
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/app
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 update appmap
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} --build-arg GEM_VERSION=#{GEM_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
- spec.name = 'appmap'
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, options = {})
87
- ClassMap.build_from_methods(methods, options)
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
@@ -71,17 +71,17 @@ module AppMap
71
71
  end
72
72
 
73
73
  class << self
74
- def build_from_methods(methods, options = {})
74
+ def build_from_methods(methods)
75
75
  root = Types::Root.new
76
76
  methods.each do |method|
77
- add_function root, method, options
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, include_source: true)
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
- source, comment = begin
117
- [ method.source, method.comment ]
116
+ comment = begin
117
+ method.comment
118
118
  rescue MethodSource::SourceNotFoundError
119
- [ nil, nil, ]
119
+ nil
120
120
  end
121
121
 
122
- if include_source
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
- Hook = Struct.new(:method_names, :package) do
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: %w[security crypto])
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[provider.secure_compare])),
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::Cookies::CookieJar' => Hook.new(%i[[]= clear update delete recycle], Package.build_from_path('action_pack', labels: %w[provider.http.cookie])),
63
- 'ActionDispatch::Cookies::EncryptedCookieJar' => Hook.new(%i[[]=], Package.build_from_path('action_pack', labels: %w[provider.http.cookie crypto])),
64
- 'CanCan::ControllerAdditions' => Hook.new(%i[authorize! can? cannot?], Package.build_from_path('cancancan', labels: %w[provider.authorization])),
65
- 'CanCan::Ability' => Hook.new(%i[authorize!], Package.build_from_path('cancancan', labels: %w[provider.authorization])),
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' => Hook.new(%i[encrypt decrypt final], OPENSSL_PACKAGES),
73
- 'OpenSSL::X509::Certificate' => Hook.new(:sign, OPENSSL_PACKAGES),
74
- 'Net::HTTP' => Hook.new(:request, Package.build_from_path('net/http', package_name: 'net/http', labels: %w[protocol.http io])),
75
- 'Net::SMTP' => Hook.new(:send, Package.build_from_path('net/smtp', package_name: 'net/smtp', labels: %w[protocol.smtp protocol.email io])),
76
- 'Net::POP3' => Hook.new(:mails, Package.build_from_path('net/pop3', package_name: 'net/pop', labels: %w[protocol.pop protocol.email io])),
77
- 'Net::IMAP' => Hook.new(:send_command, Package.build_from_path('net/imap', package_name: 'net/imap', labels: %w[protocol.imap protocol.email io])),
78
- 'Marshal' => Hook.new(%i[dump load], Package.build_from_path('marshal', labels: %w[format.marshal provider.serialization])),
79
- '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 provider.serialization])),
80
- 'JSON::Ext::Parser' => Hook.new(:parse, Package.build_from_path('json', package_name: 'json', labels: %w[format.json provider.serialization])),
81
- 'JSON::Ext::Generator::State' => Hook.new(:generate, Package.build_from_path('json', package_name: 'json', labels: %w[format.json provider.serialization])),
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 = [], exclude = [])
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
- Config.new config_data['name'], packages, config_data['exclude'] || []
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
- hook = find_hook(defined_class)
168
- return nil unless hook
234
+ hooks = find_hooks(defined_class)
235
+ return nil unless hooks
169
236
 
170
- Array(hook.method_names).include?(method_name) ? hook.package : nil
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 find_hook(defined_class)
174
- HOOKED_METHODS[defined_class] || BUILTIN_METHODS[defined_class]
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