appmap 0.40.0 → 0.41.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +5 -0
- data/README.md +25 -6
- data/lib/appmap/config.rb +35 -19
- data/lib/appmap/hook.rb +7 -0
- data/lib/appmap/version.rb +1 -1
- data/spec/abstract_controller_base_spec.rb +2 -2
- data/spec/config_spec.rb +1 -0
- data/spec/fixtures/hook/exclude.rb +15 -0
- data/spec/fixtures/rails5_users_app/appmap.yml +4 -1
- data/spec/fixtures/rails6_users_app/appmap.yml +4 -1
- data/spec/hook_spec.rb +11 -1
- data/spec/record_sql_rails_pg_spec.rb +1 -1
- data/spec/rspec_feature_metadata_spec.rb +1 -1
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d747c0e65dc1efb45769ae9118a7b97015b0378d79e20eec855e74c20b622af2
|
4
|
+
data.tar.gz: dcc3b91f32246aaefd02fbcf47672bfdf13acf6cadb715fbc5ff0040dbcfbf72
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5f82a6a0cf8075537b0835e783779c827c5afed81e83e242bc97cbfd5b6ef0320fe352235436f57009a63db321223b9f7f82e8bcdced558d9b935a2ce4e4cbf3
|
7
|
+
data.tar.gz: aaa4676d24b6bb1bcbd34fb7f0e3a0244d005c36dd89782b774b342b7e30e1c9637795d0d492522b2a5e525de413e3adb9da54bd5ace2699373ffdfadf3a8157
|
data/CHANGELOG.md
CHANGED
data/README.md
CHANGED
@@ -53,15 +53,23 @@ Support for new versions is added frequently, please check back regularly for up
|
|
53
53
|
<a href="https://www.loom.com/share/78ab32a312ff4b85aa8827a37f1cb655"> <p>Quick and easy setup of the AppMap gem for Rails - Watch Video</p> <img style="max-width:300px;" src="https://cdn.loom.com/sessions/thumbnails/78ab32a312ff4b85aa8827a37f1cb655-with-play.gif"> </a>
|
54
54
|
|
55
55
|
|
56
|
-
Add `gem 'appmap'` to your Gemfile
|
56
|
+
Add `gem 'appmap'` to **beginning** of your Gemfile. We recommend that you add the `appmap` gem to the `:development, :test` group. Your Gemfile should look something like this:
|
57
57
|
|
58
58
|
```
|
59
|
+
source 'https://rubygems.org'
|
60
|
+
git_source(:github) { |repo| "https://github.com/#{repo}.git" }
|
61
|
+
|
62
|
+
# Optional rubRuby version
|
63
|
+
# ruby '2.7.2'
|
64
|
+
|
59
65
|
group :development, :test do
|
60
66
|
gem 'appmap'
|
61
67
|
end
|
62
68
|
```
|
63
69
|
|
64
|
-
|
70
|
+
Install with `bundle install`, as usual.
|
71
|
+
|
72
|
+
It's important to add the `appmap` gem before any other gems that you may want to instrument. There is more about this in the section on adding gems to the *appmap.yml*.
|
65
73
|
|
66
74
|
**Railtie**
|
67
75
|
|
@@ -79,7 +87,8 @@ When you run your program, the `appmap` gem reads configuration settings from `a
|
|
79
87
|
file for a typical Rails project:
|
80
88
|
|
81
89
|
```yaml
|
82
|
-
name
|
90
|
+
# 'name' should generally be the same as the code repo name.
|
91
|
+
name: my_project
|
83
92
|
packages:
|
84
93
|
- path: app/controllers
|
85
94
|
- path: app/models
|
@@ -91,10 +100,15 @@ packages:
|
|
91
100
|
- gem: devise
|
92
101
|
- gem: aws-sdk
|
93
102
|
- gem: will_paginate
|
103
|
+
exclude:
|
104
|
+
- MyClass
|
105
|
+
- MyClass#my_instance_method
|
106
|
+
- MyClass.my_class_method
|
94
107
|
```
|
95
108
|
|
96
109
|
* **name** Provides the project name (required)
|
97
|
-
* **packages** A list of source code directories which should be
|
110
|
+
* **packages** A list of source code directories which should be recorded.
|
111
|
+
* **exclude** A list of classes and/or methods to definitively exclude from recording.
|
98
112
|
|
99
113
|
**packages**
|
100
114
|
|
@@ -102,13 +116,18 @@ Each entry in the `packages` list is a YAML object which has the following keys:
|
|
102
116
|
|
103
117
|
* **path** The path to the source code directory. The path may be relative to the current working directory, or it may
|
104
118
|
be an absolute path.
|
105
|
-
* **gem** As an alternative to specifying the path, specify the name of a dependency gem. When using `gem`, don't specify `path`.
|
119
|
+
* **gem** As an alternative to specifying the path, specify the name of a dependency gem. When using `gem`, don't specify `path`. In your `Gemfile`, the `appmap` gem **must** be listed **before** any gem that you specify in your *appmap.yml*.
|
106
120
|
* **exclude** A list of files and directories which will be ignored. By default, all modules, classes and public
|
107
|
-
functions are inspected.
|
121
|
+
functions are inspected. See also: global `exclude` list.
|
108
122
|
* **shallow** When set to `true`, only the first function call entry into a package will be recorded. Subsequent function calls within
|
109
123
|
the same package are not recorded unless code execution leaves the package and re-enters it. Default: `true` when using `gem`,
|
110
124
|
`false` when using `path`.
|
111
125
|
|
126
|
+
**exclude**
|
127
|
+
|
128
|
+
Optional list of fully qualified class and method names. Separate class and method names with period (`.`) for class methods and hash (`#`) for instance methods.
|
129
|
+
|
130
|
+
|
112
131
|
# Labels
|
113
132
|
|
114
133
|
The [AppMap data format](https://github.com/applandinc/appmap) provides for class and function `labels`, which can be used to enhance the AppMap visualizations, and to programatically analyze the data.
|
data/lib/appmap/config.rb
CHANGED
@@ -57,32 +57,32 @@ module AppMap
|
|
57
57
|
# Methods that should always be hooked, with their containing
|
58
58
|
# package and labels that should be applied to them.
|
59
59
|
HOOKED_METHODS = {
|
60
|
-
'ActiveSupport::SecurityUtils' => Hook.new(:secure_compare, Package.build_from_path('active_support', package_name: 'active_support', labels: %w[
|
61
|
-
'ActionView::Renderer' => Hook.new(:render, Package.build_from_path('action_view', package_name: 'action_view', labels: %w[view]))
|
60
|
+
'ActiveSupport::SecurityUtils' => Hook.new(:secure_compare, Package.build_from_path('active_support', package_name: 'active_support', labels: %w[provider.secure_compare])),
|
61
|
+
'ActionView::Renderer' => Hook.new(:render, Package.build_from_path('action_view', package_name: 'action_view', labels: %w[mvc.view]))
|
62
62
|
}.freeze
|
63
63
|
|
64
64
|
BUILTIN_METHODS = {
|
65
65
|
'OpenSSL::PKey::PKey' => Hook.new(:sign, OPENSSL_PACKAGES),
|
66
|
-
'Digest::Instance' => Hook.new(:digest, OPENSSL_PACKAGES),
|
67
66
|
'OpenSSL::X509::Request' => Hook.new(%i[sign verify], OPENSSL_PACKAGES),
|
68
67
|
'OpenSSL::PKCS5' => Hook.new(%i[pbkdf2_hmac_sha1 pbkdf2_hmac], OPENSSL_PACKAGES),
|
69
68
|
'OpenSSL::Cipher' => Hook.new(%i[encrypt decrypt final], OPENSSL_PACKAGES),
|
70
69
|
'OpenSSL::X509::Certificate' => Hook.new(:sign, OPENSSL_PACKAGES),
|
71
|
-
'Net::HTTP' => Hook.new(:request, Package.build_from_path('net/http', package_name: 'net/http', labels: %w[http io])),
|
72
|
-
'Net::SMTP' => Hook.new(:send, Package.build_from_path('net/smtp', package_name: 'net/smtp', labels: %w[smtp email io])),
|
73
|
-
'Net::POP3' => Hook.new(:mails, Package.build_from_path('net/pop3', package_name: 'net/pop', labels: %w[pop
|
74
|
-
'Net::IMAP' => Hook.new(:send_command, Package.build_from_path('net/imap', package_name: 'net/imap', labels: %w[imap email io])),
|
75
|
-
'Marshal' => Hook.new(%i[dump load], Package.build_from_path('marshal', labels: %w[serialization marshal])),
|
76
|
-
'Psych' => Hook.new(%i[dump dump_stream load load_stream parse parse_stream], Package.build_from_path('yaml', package_name: 'psych', labels: %w[serialization
|
77
|
-
'JSON::Ext::Parser' => Hook.new(:parse, Package.build_from_path('json', package_name: 'json', labels: %w[serialization
|
78
|
-
'JSON::Ext::Generator::State' => Hook.new(:generate, Package.build_from_path('json', package_name: 'json', labels: %w[serialization
|
70
|
+
'Net::HTTP' => Hook.new(:request, Package.build_from_path('net/http', package_name: 'net/http', labels: %w[protocol.http io])),
|
71
|
+
'Net::SMTP' => Hook.new(:send, Package.build_from_path('net/smtp', package_name: 'net/smtp', labels: %w[protocol.smtp protocol.email io])),
|
72
|
+
'Net::POP3' => Hook.new(:mails, Package.build_from_path('net/pop3', package_name: 'net/pop', labels: %w[protocol.pop protocol.email io])),
|
73
|
+
'Net::IMAP' => Hook.new(:send_command, Package.build_from_path('net/imap', package_name: 'net/imap', labels: %w[protocol.imap protocol.email io])),
|
74
|
+
'Marshal' => Hook.new(%i[dump load], Package.build_from_path('marshal', labels: %w[format.marshal provider.serialization marshal])),
|
75
|
+
'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])),
|
76
|
+
'JSON::Ext::Parser' => Hook.new(:parse, Package.build_from_path('json', package_name: 'json', labels: %w[format.json provider.serialization])),
|
77
|
+
'JSON::Ext::Generator::State' => Hook.new(:generate, Package.build_from_path('json', package_name: 'json', labels: %w[format.json provider.serialization])),
|
79
78
|
}.freeze
|
80
79
|
|
81
|
-
attr_reader :name, :packages
|
80
|
+
attr_reader :name, :packages, :exclude
|
82
81
|
|
83
|
-
def initialize(name, packages = [])
|
82
|
+
def initialize(name, packages = [], exclude = [])
|
84
83
|
@name = name
|
85
84
|
@packages = packages
|
85
|
+
@exclude = exclude
|
86
86
|
end
|
87
87
|
|
88
88
|
class << self
|
@@ -108,22 +108,30 @@ module AppMap
|
|
108
108
|
[ Package.build_from_path(path, exclude: package['exclude'] || [], shallow: package['shallow']) ]
|
109
109
|
end
|
110
110
|
end.flatten
|
111
|
-
Config.new config_data['name'], packages
|
111
|
+
Config.new config_data['name'], packages, config_data['exclude'] || []
|
112
112
|
end
|
113
113
|
end
|
114
114
|
|
115
115
|
def to_h
|
116
116
|
{
|
117
117
|
name: name,
|
118
|
-
packages: packages.map(&:to_h)
|
118
|
+
packages: packages.map(&:to_h),
|
119
|
+
exclude: exclude
|
119
120
|
}
|
120
121
|
end
|
121
122
|
|
123
|
+
# package_for_method finds the Package, if any, which configures the hook
|
124
|
+
# for a method.
|
122
125
|
def package_for_method(method)
|
126
|
+
package_hooked_by_class(method) || package_hooked_by_source_location(method)
|
127
|
+
end
|
128
|
+
|
129
|
+
def package_hooked_by_class(method)
|
123
130
|
defined_class, _, method_name = ::AppMap::Hook.qualify_method_name(method)
|
124
|
-
|
125
|
-
|
131
|
+
return find_package(defined_class, method_name)
|
132
|
+
end
|
126
133
|
|
134
|
+
def package_hooked_by_source_location(method)
|
127
135
|
location = method.source_location
|
128
136
|
location_file, = location
|
129
137
|
return unless location_file
|
@@ -135,14 +143,22 @@ module AppMap
|
|
135
143
|
end
|
136
144
|
end
|
137
145
|
|
138
|
-
def
|
139
|
-
|
146
|
+
def never_hook?(method)
|
147
|
+
defined_class, separator, method_name = ::AppMap::Hook.qualify_method_name(method)
|
148
|
+
return true if exclude.member?(defined_class) || exclude.member?([ defined_class, separator, method_name ].join)
|
140
149
|
end
|
141
150
|
|
151
|
+
# always_hook? indicates a method that should always be hooked.
|
142
152
|
def always_hook?(defined_class, method_name)
|
143
153
|
!!find_package(defined_class, method_name)
|
144
154
|
end
|
145
155
|
|
156
|
+
# included_by_location? indicates a method whose source location matches a method definition that has been
|
157
|
+
# configured for inclusion.
|
158
|
+
def included_by_location?(method)
|
159
|
+
!!package_for_method(method)
|
160
|
+
end
|
161
|
+
|
146
162
|
def find_package(defined_class, method_name)
|
147
163
|
hook = find_hook(defined_class)
|
148
164
|
return nil unless hook
|
data/lib/appmap/hook.rb
CHANGED
@@ -63,6 +63,8 @@ module AppMap
|
|
63
63
|
# Skip methods that have no instruction sequence, as they are obviously trivial.
|
64
64
|
next unless disasm
|
65
65
|
|
66
|
+
next if config.never_hook?(method)
|
67
|
+
|
66
68
|
next unless \
|
67
69
|
config.always_hook?(hook_cls, method.name) ||
|
68
70
|
config.included_by_location?(method)
|
@@ -84,6 +86,8 @@ module AppMap
|
|
84
86
|
tp.enable(&block)
|
85
87
|
end
|
86
88
|
|
89
|
+
# hook_builtins builds hooks for code that is built in to the Ruby standard library.
|
90
|
+
# No TracePoint events are emitted for builtins, so a separate hooking mechanism is needed.
|
87
91
|
def hook_builtins
|
88
92
|
return unless self.class.lock_builtins
|
89
93
|
|
@@ -97,6 +101,7 @@ module AppMap
|
|
97
101
|
require hook.package.package_name if hook.package.package_name
|
98
102
|
Array(hook.method_names).each do |method_name|
|
99
103
|
method_name = method_name.to_sym
|
104
|
+
|
100
105
|
cls = class_from_string.(class_name)
|
101
106
|
method = \
|
102
107
|
begin
|
@@ -105,6 +110,8 @@ module AppMap
|
|
105
110
|
cls.method(method_name) rescue nil
|
106
111
|
end
|
107
112
|
|
113
|
+
next if config.never_hook?(method)
|
114
|
+
|
108
115
|
if method
|
109
116
|
Hook::Method.new(hook.package, cls, method).activate
|
110
117
|
else
|
data/lib/appmap/version.rb
CHANGED
@@ -8,7 +8,7 @@ describe 'AbstractControllerBase' do
|
|
8
8
|
FileUtils.rm_rf tmpdir
|
9
9
|
FileUtils.mkdir_p tmpdir
|
10
10
|
cmd = <<~CMD.gsub "\n", ' '
|
11
|
-
docker-compose run --rm -e APPMAP=true
|
11
|
+
docker-compose run --rm -e RAILS_ENV=test -e APPMAP=true
|
12
12
|
-v #{File.absolute_path tmpdir}:/app/tmp app ./bin/rspec #{spec_name}
|
13
13
|
CMD
|
14
14
|
run_cmd cmd, chdir: fixture_dir
|
@@ -137,7 +137,7 @@ describe 'AbstractControllerBase' do
|
|
137
137
|
'name' => 'Renderer',
|
138
138
|
'children' => include(hash_including(
|
139
139
|
'name' => 'render',
|
140
|
-
'labels' => ['view']
|
140
|
+
'labels' => ['mvc.view']
|
141
141
|
))
|
142
142
|
))
|
143
143
|
))
|
data/spec/config_spec.rb
CHANGED
data/spec/hook_spec.rb
CHANGED
@@ -61,6 +61,16 @@ describe 'AppMap class Hooking', docker: false do
|
|
61
61
|
AppMap.configuration = nil
|
62
62
|
end
|
63
63
|
|
64
|
+
it 'excludes named classes and methods' do
|
65
|
+
load 'spec/fixtures/hook/exclude.rb'
|
66
|
+
package = AppMap::Config::Package.build_from_path('spec/fixtures/hook/exclude.rb')
|
67
|
+
config = AppMap::Config.new('hook_spec', [ package ], %w[ExcludeTest])
|
68
|
+
AppMap.configuration = config
|
69
|
+
|
70
|
+
expect(config.never_hook?(ExcludeTest.new.method(:instance_method))).to be_truthy
|
71
|
+
expect(config.never_hook?(ExcludeTest.method(:cls_method))).to be_truthy
|
72
|
+
end
|
73
|
+
|
64
74
|
it 'parses labels from comments' do
|
65
75
|
_, tracer = invoke_test_file 'spec/fixtures/hook/labels.rb' do
|
66
76
|
ClassWithLabel.new.fn_with_label
|
@@ -827,7 +837,7 @@ describe 'AppMap class Hooking', docker: false do
|
|
827
837
|
entry = cm[1][:children][0][:children][0][:children][0]
|
828
838
|
# Sanity check, make sure we got the right one
|
829
839
|
expect(entry[:name]).to eq('secure_compare')
|
830
|
-
expect(entry[:labels]).to eq(%w[
|
840
|
+
expect(entry[:labels]).to eq(%w[provider.secure_compare])
|
831
841
|
end
|
832
842
|
end
|
833
843
|
|
@@ -5,7 +5,7 @@ describe 'SQL events' do
|
|
5
5
|
around(:each) do |example|
|
6
6
|
FileUtils.rm_rf tmpdir
|
7
7
|
FileUtils.mkdir_p tmpdir
|
8
|
-
cmd = "docker-compose run --rm -e ORM_MODULE=#{orm_module} -e APPMAP=true -v #{File.absolute_path tmpdir}:/app/tmp app ./bin/rspec spec/controllers/users_controller_api_spec.rb:#{test_line_number}"
|
8
|
+
cmd = "docker-compose run --rm -e ORM_MODULE=#{orm_module} -e RAILS_ENV=test -e APPMAP=true -v #{File.absolute_path tmpdir}:/app/tmp app ./bin/rspec spec/controllers/users_controller_api_spec.rb:#{test_line_number}"
|
9
9
|
run_cmd cmd, chdir: fixture_dir
|
10
10
|
|
11
11
|
example.run
|
@@ -7,7 +7,7 @@ describe 'RSpec feature and feature group metadata' do
|
|
7
7
|
around(:each) do |example|
|
8
8
|
FileUtils.rm_rf tmpdir
|
9
9
|
FileUtils.mkdir_p tmpdir
|
10
|
-
cmd = "docker-compose run --rm -e APPMAP=true -v #{File.absolute_path(tmpdir).shellescape}:/app/tmp app ./bin/rspec spec/models/user_spec.rb"
|
10
|
+
cmd = "docker-compose run --rm -e RAILS_ENV=test -e APPMAP=true -v #{File.absolute_path(tmpdir).shellescape}:/app/tmp app ./bin/rspec spec/models/user_spec.rb"
|
11
11
|
run_cmd cmd, chdir: fixture_dir
|
12
12
|
|
13
13
|
example.run
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: appmap
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.41.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Kevin Gilpin
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-
|
11
|
+
date: 2021-02-09 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|
@@ -386,6 +386,7 @@ files:
|
|
386
386
|
- spec/fixtures/hook/compare.rb
|
387
387
|
- spec/fixtures/hook/constructor.rb
|
388
388
|
- spec/fixtures/hook/exception_method.rb
|
389
|
+
- spec/fixtures/hook/exclude.rb
|
389
390
|
- spec/fixtures/hook/instance_method.rb
|
390
391
|
- spec/fixtures/hook/labels.rb
|
391
392
|
- spec/fixtures/hook/singleton_method.rb
|